A game framework written with osu! in mind.

Merge remote-tracking branch 'ppy/master' into dropdown_keyboard_support

+5844 -1501
+1 -1
build/build.cake
··· 1 1 using System.Threading; 2 2 #addin "nuget:?package=CodeFileSanity&version=0.0.21" 3 - #addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.3.4" 3 + #addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.1.1" 4 4 #tool "nuget:?package=NVika.MSBuild&version=1.0.1" 5 5 #tool "nuget:?package=Python&version=3.7.2" 6 6 var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
+3
osu-framework.sln.DotSettings
··· 230 230 <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_CONSTRAINS/@EntryValue">True</s:Boolean> 231 231 <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_LIST/@EntryValue">True</s:Boolean> 232 232 <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String> 233 + <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_BLOCK_STATEMENTS/@EntryValue">1</s:Int64> 234 + <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_CASE/@EntryValue">1</s:Int64> 233 235 <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">NEXT_LINE</s:String> 234 236 <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64> 235 237 <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64> ··· 268 270 <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MD/@EntryIndexedValue">MD5</s:String> 269 271 <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NS/@EntryIndexedValue">NS</s:String> 270 272 <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OS/@EntryIndexedValue">OS</s:String> 273 + <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PM/@EntryIndexedValue">PM</s:String> 271 274 <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RGB/@EntryIndexedValue">RGB</s:String> 272 275 <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RNG/@EntryIndexedValue">RNG</s:String> 273 276 <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SHA/@EntryIndexedValue">SHA</s:String>
+6
osu.Framework.Android/AndroidGameActivity.cs
··· 16 16 17 17 SetContentView(new AndroidGameView(this, CreateGame())); 18 18 } 19 + 20 + protected override void OnPause() { 21 + base.OnPause(); 22 + // Because Android is not playing nice with Background - we just kill it 23 + System.Diagnostics.Process.GetCurrentProcess().Kill(); 24 + } 19 25 } 20 26 }
+2
osu.Framework.Android/AndroidGameWindow.cs
··· 41 41 42 42 public override void Run() 43 43 { 44 + View.Run(); 44 45 } 45 46 46 47 public override void Run(double updateRate) 47 48 { 49 + View.Run(updateRate); 48 50 } 49 51 } 50 52 }
+3 -3
osu.Framework.Android/osu.Framework.Android.csproj
··· 28 28 <Reference Include="System.Core" /> 29 29 </ItemGroup> 30 30 <ItemGroup> 31 - <PackageReference Include="ppy.osuTK.Android" Version="1.0.66" /> 31 + <ProjectReference Include="..\osu.Framework\osu.Framework.csproj" /> 32 32 </ItemGroup> 33 33 <ItemGroup> 34 - <ProjectReference Include="..\osu.Framework\osu.Framework.csproj" /> 34 + <PackageReference Include="ppy.osuTK.Android" Version="1.0.101" /> 35 35 </ItemGroup> 36 - </Project> 36 + </Project>
+3 -30
osu.Framework.Tests.iOS/osu.Framework.Tests.iOS.csproj
··· 37 37 <Compile Include="AppDelegate.cs" /> 38 38 </ItemGroup> 39 39 <ItemGroup> 40 - <Compile Include="..\osu.Framework.Tests\**\Bindables\**\*.cs"> 40 + <Compile Include="..\osu.Framework.Tests\**\*.cs" Exclude="..\osu.Framework.Tests\Program.cs;..\osu.Framework.Tests\obj\**\*;..\osu.Framework.Tests\bin\**\*"> 41 41 <Link>%(RecursiveDir)%(Filename)%(Extension)</Link> 42 42 </Compile> 43 - <Compile Include="..\osu.Framework.Tests\**\Clocks\**\*.cs"> 44 - <Link>%(RecursiveDir)%(Filename)%(Extension)</Link> 45 - </Compile> 46 - <Compile Include="..\osu.Framework.Tests\**\Dependencies\**\*.cs"> 47 - <Link>%(RecursiveDir)%(Filename)%(Extension)</Link> 48 - </Compile> 49 - <Compile Include="..\osu.Framework.Tests\**\Exceptions\**\*.cs"> 50 - <Link>%(RecursiveDir)%(Filename)%(Extension)</Link> 51 - </Compile> 52 - <Compile Include="..\osu.Framework.Tests\**\IO\**\*.cs"> 53 - <Link>%(RecursiveDir)%(Filename)%(Extension)</Link> 54 - </Compile> 55 - <Compile Include="..\osu.Framework.Tests\**\Lists\**\*.cs"> 56 - <Link>%(RecursiveDir)%(Filename)%(Extension)</Link> 57 - </Compile> 58 - <Compile Include="..\osu.Framework.Tests\**\MathUtils\**\*.cs"> 59 - <Link>%(RecursiveDir)%(Filename)%(Extension)</Link> 60 - </Compile> 61 - <Compile Include="..\osu.Framework.Tests\**\Platform\**\*.cs"> 62 - <Link>%(RecursiveDir)%(Filename)%(Extension)</Link> 63 - </Compile> 64 - <Compile Include="..\osu.Framework.Tests\**\Visual\**\*.cs"> 65 - <Link>%(RecursiveDir)%(Filename)%(Extension)</Link> 66 - </Compile> 67 - <EmbeddedResource Include="..\osu.Framework.Tests\**\Resources\**\*.*"> 68 - <Link>%(RecursiveDir)%(Filename)%(Extension)</Link> 43 + <EmbeddedResource Include="..\osu.Framework.Tests\Resources\**\*"> 44 + <Link>Resources\%(RecursiveDir)%(Filename)%(Extension)</Link> 69 45 </EmbeddedResource> 70 - <Compile Include="..\osu.Framework.Tests\*.cs" Exclude="**\Program.cs"> 71 - <Link>%(RecursiveDir)%(Filename)%(Extension)</Link> 72 - </Compile> 73 46 </ItemGroup> 74 47 <ItemGroup> 75 48 <ProjectReference Include="..\osu.Framework\osu.Framework.csproj">
+130
osu.Framework.Tests/Audio/AudioComponentTest.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System.Threading; 5 + using System.Threading.Tasks; 6 + using NUnit.Framework; 7 + using osu.Framework.Audio; 8 + using osu.Framework.Audio.Track; 9 + using osu.Framework.IO.Stores; 10 + using osu.Framework.Platform; 11 + using osu.Framework.Threading; 12 + 13 + namespace osu.Framework.Tests.Audio 14 + { 15 + [TestFixture] 16 + public class AudioComponentTest 17 + { 18 + private AudioThread thread; 19 + private NamespacedResourceStore<byte[]> store; 20 + private AudioManager manager; 21 + 22 + [SetUp] 23 + public void SetUp() 24 + { 25 + Architecture.SetIncludePath(); 26 + 27 + thread = new AudioThread(); 28 + store = new NamespacedResourceStore<byte[]>(new DllResourceStore(@"osu.Framework.dll"), @"Resources"); 29 + 30 + manager = new AudioManager(thread, store, store); 31 + 32 + thread.Start(); 33 + } 34 + 35 + [TearDown] 36 + public void TearDown() 37 + { 38 + Assert.IsFalse(thread.Exited); 39 + 40 + thread.Exit(); 41 + 42 + Thread.Sleep(500); 43 + 44 + Assert.IsTrue(thread.Exited); 45 + } 46 + 47 + [Test] 48 + public void TestNestedStoreAdjustments() 49 + { 50 + var customStore = manager.GetSampleStore(new ResourceStore<byte[]>()); 51 + 52 + checkAggregateVolume(manager.Samples, 1); 53 + checkAggregateVolume(customStore, 1); 54 + 55 + manager.Samples.Volume.Value = 0.5; 56 + 57 + waitAudioFrame(); 58 + 59 + checkAggregateVolume(manager.Samples, 0.5); 60 + checkAggregateVolume(customStore, 0.5); 61 + 62 + customStore.Volume.Value = 0.5; 63 + 64 + waitAudioFrame(); 65 + 66 + checkAggregateVolume(manager.Samples, 0.5); 67 + checkAggregateVolume(customStore, 0.25); 68 + } 69 + 70 + private void checkAggregateVolume(ISampleStore store, double expected) 71 + { 72 + Assert.AreEqual(expected, ((IAggregateAudioAdjustment)store).AggregateVolume.Value); 73 + } 74 + 75 + [Test] 76 + public void TestVirtualTrack() 77 + { 78 + var track = manager.Tracks.GetVirtual(); 79 + 80 + waitAudioFrame(); 81 + 82 + checkTrackCount(1); 83 + 84 + Assert.IsFalse(track.IsRunning); 85 + Assert.AreEqual(0, track.CurrentTime); 86 + 87 + track.Start(); 88 + waitAudioFrame(); 89 + 90 + Assert.Greater(track.CurrentTime, 0); 91 + 92 + track.Stop(); 93 + Assert.IsFalse(track.IsRunning); 94 + 95 + track.Dispose(); 96 + waitAudioFrame(); 97 + 98 + checkTrackCount(0); 99 + } 100 + 101 + private void checkTrackCount(int expected) 102 + => Assert.AreEqual(expected, ((TrackStore)manager.Tracks).Items.Count); 103 + 104 + /// <summary> 105 + /// Block for a specified number of audio thread frames. 106 + /// </summary> 107 + /// <param name="count">The number of frames to wait for. Two frames is generally considered safest.</param> 108 + private void waitAudioFrame(int count = 2) 109 + { 110 + var cts = new TaskCompletionSource<bool>(); 111 + 112 + void runScheduled() 113 + { 114 + thread.Scheduler.Add(() => 115 + { 116 + if (--count > 0) 117 + runScheduled(); 118 + else 119 + { 120 + cts.SetResult(true); 121 + } 122 + }); 123 + } 124 + 125 + runScheduled(); 126 + 127 + Task.WaitAll(cts.Task); 128 + } 129 + } 130 + }
+163
osu.Framework.Tests/Bindables/AggregateBindableTest.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System.Threading; 5 + using NUnit.Framework; 6 + using osu.Framework.Bindables; 7 + 8 + namespace osu.Framework.Tests.Bindables 9 + { 10 + [TestFixture] 11 + public class AggregateBindableTest 12 + { 13 + [Test] 14 + public void TestMultiplicationAggregate() 15 + { 16 + var aggregate = new AggregateBindable<double>((a, b) => a * b, new Bindable<double>(1)); 17 + 18 + Assert.AreEqual(1, aggregate.Result.Value); 19 + 20 + var bindable1 = new BindableDouble(0.5); 21 + aggregate.AddSource(bindable1); 22 + Assert.AreEqual(0.5, aggregate.Result.Value); 23 + 24 + var bindable2 = new BindableDouble(0.25); 25 + aggregate.AddSource(bindable2); 26 + Assert.AreEqual(0.125, aggregate.Result.Value); 27 + } 28 + 29 + [Test] 30 + public void TestResultBounds() 31 + { 32 + var aggregate = new AggregateBindable<double>((a, b) => a * b, new BindableDouble(1) 33 + { 34 + Default = 1, 35 + MinValue = 0, 36 + MaxValue = 2 37 + }); 38 + 39 + Assert.AreEqual(1, aggregate.Result.Value); 40 + 41 + var bindable1 = new BindableDouble(-1); 42 + aggregate.AddSource(bindable1); 43 + Assert.AreEqual(0, aggregate.Result.Value); 44 + 45 + var bindable2 = new BindableDouble(-4); 46 + aggregate.AddSource(bindable2); 47 + Assert.AreEqual(2, aggregate.Result.Value); 48 + } 49 + 50 + [Test] 51 + public void TestClassAggregate() 52 + { 53 + var aggregate = new AggregateBindable<BoxedInt>((a, b) => new BoxedInt((a?.Value ?? 0) + (b?.Value ?? 0))); 54 + 55 + Assert.AreEqual(null, aggregate.Result.Value); 56 + 57 + var bindable1 = new Bindable<BoxedInt>(new BoxedInt(1)); 58 + aggregate.AddSource(bindable1); 59 + Assert.AreEqual(1, aggregate.Result.Value.Value); 60 + 61 + var bindable2 = new Bindable<BoxedInt>(new BoxedInt(2)); 62 + aggregate.AddSource(bindable2); 63 + Assert.AreEqual(3, aggregate.Result.Value.Value); 64 + } 65 + 66 + private class BoxedInt 67 + { 68 + public readonly int Value; 69 + 70 + public BoxedInt(int value) 71 + { 72 + Value = value; 73 + } 74 + } 75 + 76 + [Test] 77 + public void TestSourceChanged() 78 + { 79 + var aggregate = new AggregateBindable<double>((a, b) => a * b, new Bindable<double>(1)); 80 + 81 + var bindable1 = new BindableDouble(0.5); 82 + aggregate.AddSource(bindable1); 83 + 84 + var bindable2 = new BindableDouble(0.5); 85 + aggregate.AddSource(bindable2); 86 + 87 + Assert.AreEqual(0.25, aggregate.Result.Value); 88 + 89 + bindable1.Value = 0.25; 90 + Assert.AreEqual(0.125, aggregate.Result.Value); 91 + 92 + bindable2.Value = 0.25; 93 + Assert.AreEqual(0.0625, aggregate.Result.Value); 94 + } 95 + 96 + [Test] 97 + public void TestSourceRemoved() 98 + { 99 + var aggregate = new AggregateBindable<double>((a, b) => a * b, new Bindable<double>(1)); 100 + 101 + var bindable1 = new BindableDouble(0.5); 102 + aggregate.AddSource(bindable1); 103 + 104 + var bindable2 = new BindableDouble(0.5); 105 + aggregate.AddSource(bindable2); 106 + 107 + Assert.AreEqual(0.25, aggregate.Result.Value); 108 + 109 + aggregate.RemoveSource(bindable1); 110 + Assert.AreEqual(0.5, aggregate.Result.Value); 111 + 112 + aggregate.RemoveSource(bindable2); 113 + Assert.AreEqual(1, aggregate.Result.Value); 114 + } 115 + 116 + [Test] 117 + public void TestValueChangedFirings() 118 + { 119 + int aggregateResultFireCount = 0, bindable1FireCount = 0, bindable2FireCount = 0; 120 + 121 + var aggregate = new AggregateBindable<double>((a, b) => a * b, new Bindable<double>(1)); 122 + aggregate.Result.BindValueChanged(_ => Interlocked.Increment(ref aggregateResultFireCount)); 123 + 124 + Assert.AreEqual(0, aggregateResultFireCount); 125 + 126 + var bindable1 = new BindableDouble(0.5); 127 + bindable1.BindValueChanged(_ => Interlocked.Increment(ref bindable1FireCount)); 128 + aggregate.AddSource(bindable1); 129 + 130 + Assert.AreEqual(1, aggregateResultFireCount); 131 + 132 + var bindable2 = new BindableDouble(0.5); 133 + bindable2.BindValueChanged(_ => Interlocked.Increment(ref bindable2FireCount)); 134 + aggregate.AddSource(bindable2); 135 + 136 + Assert.AreEqual(2, aggregateResultFireCount); 137 + 138 + bindable1.Value = 0.25; 139 + 140 + Assert.AreEqual(3, aggregateResultFireCount); 141 + Assert.AreEqual(1, bindable1FireCount); 142 + Assert.AreEqual(0, bindable2FireCount); 143 + 144 + bindable2.Value = 0.25; 145 + 146 + Assert.AreEqual(4, aggregateResultFireCount); 147 + Assert.AreEqual(1, bindable1FireCount); 148 + Assert.AreEqual(1, bindable2FireCount); 149 + 150 + aggregate.RemoveSource(bindable2); 151 + 152 + Assert.AreEqual(5, aggregateResultFireCount); 153 + Assert.AreEqual(1, bindable1FireCount); 154 + Assert.AreEqual(1, bindable2FireCount); 155 + 156 + bindable2.Value = 0.5; 157 + 158 + Assert.AreEqual(5, aggregateResultFireCount); 159 + Assert.AreEqual(1, bindable1FireCount); 160 + Assert.AreEqual(2, bindable2FireCount); 161 + } 162 + } 163 + }
+24
osu.Framework.Tests/Bindables/BindableEnumTest.cs
··· 31 31 Assert.AreEqual(expected, bindable.Value); 32 32 } 33 33 34 + [TestCase("Value1", TestEnum.Value1)] 35 + [TestCase("Value2", TestEnum.Value2)] 36 + [TestCase("-1", TestEnum.Value1 - 1)] 37 + [TestCase("2", TestEnum.Value2 + 1)] 38 + public void TestParsingStringToNullableType(string value, TestEnum? expected) 39 + { 40 + var bindable = new Bindable<TestEnum?>(); 41 + bindable.Parse(value); 42 + 43 + Assert.AreEqual(expected, bindable.Value); 44 + } 45 + 34 46 [TestCase(TestEnum.Value1)] 35 47 [TestCase(TestEnum.Value2)] 36 48 [TestCase(TestEnum.Value1 - 1)] ··· 38 50 public void TestParsingEnum(TestEnum value) 39 51 { 40 52 var bindable = new Bindable<TestEnum>(); 53 + bindable.Parse(value); 54 + 55 + Assert.AreEqual(value, bindable.Value); 56 + } 57 + 58 + [TestCase(TestEnum.Value1)] 59 + [TestCase(TestEnum.Value2)] 60 + [TestCase(TestEnum.Value1 - 1)] 61 + [TestCase(TestEnum.Value2 + 1)] 62 + public void TestParsingEnumToNullableType(TestEnum value) 63 + { 64 + var bindable = new Bindable<TestEnum?>(); 41 65 bindable.Parse(value); 42 66 43 67 Assert.AreEqual(value, bindable.Value);
+2
osu.Framework.Tests/Clocks/InterpolatingClockTest.cs
··· 34 34 35 35 // test with test clock not elapsing 36 36 double lastValue = interpolating.CurrentTime; 37 + 37 38 for (int i = 0; i < 100; i++) 38 39 { 39 40 interpolating.ProcessFrame(); ··· 48 49 49 50 // test with test clock elapsing 50 51 lastValue = interpolating.CurrentTime; 52 + 51 53 for (int i = 0; i < 100; i++) 52 54 { 53 55 source.CurrentTime += 50;
+1
osu.Framework.Tests/IO/TestDesktopStorage.cs
··· 14 14 public void TestRelativePaths() 15 15 { 16 16 var guid = new Guid().ToString(); 17 + 17 18 using (var storage = new TemporaryNativeStorage(guid)) 18 19 { 19 20 var basePath = storage.GetFullPath(string.Empty);
+4
osu.Framework.Tests/IO/TestWebRequest.cs
··· 96 96 List<long> startTimes = new List<long>(); 97 97 98 98 List<Task> running = new List<Task>(); 99 + 99 100 for (int i = 0; i < request_count; i++) 100 101 { 101 102 var request = new DelayedWebRequest ··· 354 355 Assert.DoesNotThrow(request.Perform); 355 356 356 357 var events = request.GetType().GetEvents(BindingFlags.Instance | BindingFlags.Public); 358 + 357 359 foreach (var e in events) 358 360 { 359 361 var field = request.GetType().GetField(e.Name, BindingFlags.Instance | BindingFlags.Public); ··· 368 370 public void TestUnbindOnDispose([Values(true, false)] bool async) 369 371 { 370 372 WebRequest request; 373 + 371 374 using (request = new JsonWebRequest<HttpBinGetResponse>($"{default_protocol}://{host}/get") 372 375 { 373 376 Method = HttpMethod.Get, ··· 383 386 } 384 387 385 388 var events = request.GetType().GetEvents(BindingFlags.Instance | BindingFlags.Public); 389 + 386 390 foreach (var e in events) 387 391 { 388 392 var field = request.GetType().GetField(e.Name, BindingFlags.Instance | BindingFlags.Public);
+1
osu.Framework.Tests/Lists/TestArrayExtensions.cs
··· 123 123 public void TestNonSquareJaggedWithNullRowsToRectangular() 124 124 { 125 125 var jagged = new int[10][]; 126 + 126 127 for (int i = 1; i < 10; i += 2) 127 128 { 128 129 if (i % 2 == 1)
+3
osu.Framework.Tests/Lists/TestWeakList.cs
··· 48 48 var list = new WeakList<object> { obj, obj2, obj3 }; 49 49 50 50 int count = 0; 51 + 51 52 foreach (var item in list) 52 53 { 53 54 if (count == 1) ··· 68 69 var list = new WeakList<object> { obj, obj2, obj3 }; 69 70 70 71 int count = 0; 72 + 71 73 foreach (var item in list) 72 74 { 73 75 if (count == 0) ··· 90 92 GC.WaitForPendingFinalizers(); 91 93 92 94 int index = 0; 95 + 93 96 foreach (var obj in list) 94 97 { 95 98 if (alive[index] != obj)
+7
osu.Framework.Tests/Localisation/LocalisationTest.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; 5 + using System.Collections.Generic; 5 6 using System.IO; 7 + using System.Linq; 6 8 using System.Threading.Tasks; 7 9 using NUnit.Framework; 8 10 using osu.Framework.Configuration; ··· 191 193 { 192 194 default: 193 195 return LOCALISABLE_STRING_EN; 196 + 194 197 case "ja": 195 198 return LOCALISABLE_STRING_JA; 199 + 196 200 case "ja-JP": 197 201 return LOCALISABLE_STRING_JA_JP; 198 202 } ··· 202 206 { 203 207 default: 204 208 return LOCALISABLE_FORMAT_STRING_EN; 209 + 205 210 case "ja": 206 211 return LOCALISABLE_FORMAT_STRING_JA; 207 212 } ··· 216 221 public void Dispose() 217 222 { 218 223 } 224 + 225 + public IEnumerable<string> GetAvailableResources() => Enumerable.Empty<string>(); 219 226 } 220 227 } 221 228 }
+62 -39
osu.Framework.Tests/Platform/TrackBassTest.cs osu.Framework.Tests/Audio/TrackBassTest.cs
··· 3 3 4 4 using System; 5 5 using System.Threading; 6 - using System.Threading.Tasks; 7 6 using ManagedBass; 8 7 using NUnit.Framework; 9 8 using osu.Framework.Audio.Track; ··· 13 12 14 13 #pragma warning disable 4014 15 14 16 - namespace osu.Framework.Tests.Platform 15 + namespace osu.Framework.Tests.Audio 17 16 { 18 17 [TestFixture] 19 18 public class TrackBassTest ··· 36 35 public void Setup() 37 36 { 38 37 track = new TrackBass(resources.GetStream("Resources.Tracks.sample-track.mp3")); 39 - track.Update(); 38 + updateTrack(); 40 39 } 41 40 42 41 [Test] 43 42 public void TestStart() 44 43 { 45 44 track.StartAsync(); 46 - track.Update(); 45 + updateTrack(); 47 46 48 47 Thread.Sleep(50); 49 48 50 - track.Update(); 49 + updateTrack(); 51 50 52 51 Assert.IsTrue(track.IsRunning); 53 52 Assert.Greater(track.CurrentTime, 0); ··· 58 57 { 59 58 track.StartAsync(); 60 59 track.StopAsync(); 61 - track.Update(); 60 + updateTrack(); 62 61 63 62 Assert.IsFalse(track.IsRunning); 64 63 ··· 75 74 76 75 Thread.Sleep(50); 77 76 78 - track.Update(); 77 + updateTrack(); 79 78 track.StopAsync(); 80 - track.Update(); 79 + updateTrack(); 81 80 82 81 Assert.IsFalse(track.IsRunning); 83 82 Assert.AreEqual(track.Length, track.CurrentTime); ··· 87 86 public void TestSeek() 88 87 { 89 88 track.SeekAsync(1000); 90 - track.Update(); 89 + updateTrack(); 91 90 92 91 Assert.IsFalse(track.IsRunning); 93 92 Assert.AreEqual(1000, track.CurrentTime); ··· 98 97 { 99 98 track.StartAsync(); 100 99 track.SeekAsync(1000); 101 - track.Update(); 100 + updateTrack(); 102 101 103 102 Assert.IsTrue(track.IsRunning); 104 103 Assert.GreaterOrEqual(track.CurrentTime, 1000); ··· 110 109 [Test] 111 110 public void TestSeekToEndFails() 112 111 { 113 - track.SeekAsync(track.Length); 114 - track.Update(); 112 + bool? success = null; 113 + 114 + runOnAudioThread(() => { success = track.Seek(track.Length); }); 115 + updateTrack(); 115 116 116 117 Assert.AreEqual(0, track.CurrentTime); 118 + Assert.IsFalse(success); 117 119 } 118 120 119 121 [Test] ··· 121 123 { 122 124 track.SeekAsync(1000); 123 125 track.SeekAsync(0); 124 - track.Update(); 126 + updateTrack(); 125 127 126 128 Thread.Sleep(50); 127 129 128 - track.Update(); 130 + updateTrack(); 129 131 130 132 Assert.GreaterOrEqual(track.CurrentTime, 0); 131 133 Assert.Less(track.CurrentTime, 1000); ··· 138 140 139 141 Thread.Sleep(50); 140 142 141 - track.Update(); 143 + updateTrack(); 142 144 143 145 Assert.IsFalse(track.IsRunning); 144 146 Assert.AreEqual(track.Length, track.CurrentTime); ··· 155 157 156 158 Thread.Sleep(50); 157 159 158 - track.Update(); 160 + updateTrack(); 159 161 track.StartAsync(); 160 - track.Update(); 162 + updateTrack(); 161 163 162 164 Assert.AreEqual(track.Length, track.CurrentTime); 163 165 } ··· 169 171 170 172 Thread.Sleep(50); 171 173 172 - track.Update(); 174 + updateTrack(); 173 175 restartTrack(); 174 176 175 177 Assert.IsTrue(track.IsRunning); ··· 183 185 184 186 Thread.Sleep(50); 185 187 186 - track.Update(); 188 + updateTrack(); 187 189 restartTrack(); 188 190 189 191 Assert.IsTrue(track.IsRunning); ··· 212 214 213 215 Thread.Sleep(50); 214 216 215 - var resetEvent = new ManualResetEvent(false); 217 + // The first update brings the track to its end time and restarts it 218 + updateTrack(); 216 219 217 - Task.Run(() => 218 - { 219 - // The restart action is invoked during the update and will block if not invoked on the audio thread. 220 - // The update is always run on the audio thread in normal operation such that the restart action is always inlined. 221 - // The audio thread is faked here to simulate this operation and avoid a deadlock. 222 - Thread.CurrentThread.Name = GameThread.PrefixedThreadNameFor("Audio"); 220 + // The second update updates the IsRunning state 221 + updateTrack(); 223 222 224 - track.Update(); 225 - resetEvent.Set(); 226 - }); 223 + // In a perfect world the track will be running after the update above, but during testing it's possible that the track is in 224 + // a stalled state due to updates running on Bass' own thread, so we'll loop until the track starts running again 225 + // Todo: This should be fixed in the future if/when we invoke Bass.Update() ourselves 226 + int loopCount = 0; 227 227 228 - resetEvent.WaitOne(TimeSpan.FromSeconds(10)); 228 + while (++loopCount < 50 && !track.IsRunning) 229 + { 230 + updateTrack(); 231 + Thread.Sleep(10); 232 + } 229 233 230 - track.Update(); 234 + if (loopCount == 50) 235 + throw new TimeoutException("Track failed to start in time."); 231 236 232 - Assert.IsTrue(track.IsRunning); 233 237 Assert.LessOrEqual(track.CurrentTime, 1000); 234 238 } 235 239 ··· 237 241 { 238 242 track.SeekAsync(time); 239 243 track.StartAsync(); 240 - track.Update(); 244 + updateTrack(); 241 245 } 242 246 247 + private void updateTrack() => runOnAudioThread(() => track.Update()); 248 + 243 249 private void restartTrack() 244 250 { 245 - var resetEvent = new ManualResetEventSlim(false); 246 - 247 - Task.Run(() => 251 + runOnAudioThread(() => 248 252 { 249 253 track.Restart(); 250 254 track.Update(); 255 + }); 256 + } 257 + 258 + /// <summary> 259 + /// Certain actions are invoked on the audio thread. 260 + /// Here we simulate this process on a correctly named thread to avoid endless blocking. 261 + /// </summary> 262 + /// <param name="action">The action to perform.</param> 263 + private void runOnAudioThread(Action action) 264 + { 265 + var resetEvent = new ManualResetEvent(false); 266 + 267 + new Thread(() => 268 + { 269 + action(); 270 + 251 271 resetEvent.Set(); 252 - }); 272 + }) 273 + { 274 + Name = GameThread.PrefixedThreadNameFor("Audio") 275 + }.Start(); 253 276 254 - while (!resetEvent.IsSet) 255 - track.Update(); 277 + if (!resetEvent.WaitOne(TimeSpan.FromSeconds(10))) 278 + throw new TimeoutException(); 256 279 } 257 280 } 258 281 }
+230
osu.Framework.Tests/Polygons/ConvexPolygonClippingTest.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using NUnit.Framework; 6 + using osu.Framework.Graphics; 7 + using osu.Framework.Graphics.Primitives; 8 + using osu.Framework.MathUtils.Clipping; 9 + using osuTK; 10 + 11 + namespace osu.Framework.Tests.Polygons 12 + { 13 + [TestFixture] 14 + public class ConvexPolygonClippingTest 15 + { 16 + private static readonly Vector2 origin = Vector2.Zero; 17 + private static readonly Vector2 up_1 = new Vector2(0, 1); 18 + private static readonly Vector2 up_2 = new Vector2(0, 2); 19 + private static readonly Vector2 up_3 = new Vector2(0, 3); 20 + private static readonly Vector2 down_1 = new Vector2(0, -1); 21 + private static readonly Vector2 down_2 = new Vector2(0, -2); 22 + private static readonly Vector2 down_3 = new Vector2(0, -3); 23 + private static readonly Vector2 left_1 = new Vector2(-1, 0); 24 + private static readonly Vector2 left_2 = new Vector2(-2, 0); 25 + private static readonly Vector2 left_3 = new Vector2(-3, 0); 26 + private static readonly Vector2 right_1 = new Vector2(1, 0); 27 + private static readonly Vector2 right_2 = new Vector2(2, 0); 28 + private static readonly Vector2 right_3 = new Vector2(3, 0); 29 + 30 + private static object[] externalTestCases => new object[] 31 + { 32 + // Non-rotated 33 + new object[] { new[] { origin, up_1, up_1 + right_1, right_1 }, new[] { up_2, up_3, up_3 + right_1, up_2 } }, 34 + new object[] { new[] { origin, up_1, up_1 + right_1, right_1 }, new[] { right_2, right_2 + up_1, right_3 + up_1, right_3 } }, 35 + new object[] { new[] { origin, up_1, up_1 + right_1, right_1 }, new[] { down_1, down_1 + right_1, down_2 + right_1, down_2 } }, 36 + new object[] { new[] { origin, up_1, up_1 + right_1, right_1 }, new[] { left_2, left_2 + up_1, left_1 + up_1, left_1 } }, 37 + // Rotated 38 + new object[] { new[] { origin, up_1, up_1 + right_1, right_1 }, new[] { up_1 + right_2, up_2 + right_1, up_3 + right_2, up_2 + right_3 } }, 39 + new object[] { new[] { origin, up_1, up_1 + right_1, right_1 }, new[] { up_1 + right_2, down_1 + right_3, down_2 + right_2, down_1 + right_1 } }, 40 + new object[] { new[] { origin, up_1, up_1 + right_1, right_1 }, new[] { down_1 + right_1, down_2 + right_2, down_3 + right_1, down_2 } }, 41 + new object[] { new[] { origin, up_1, up_1 + right_1, right_1 }, new[] { left_1 + up_1, down_2, down_3 + left_2, left_2 } }, 42 + }; 43 + 44 + [TestCaseSource(nameof(externalTestCases))] 45 + public void TestExternalPolygon(Vector2[] polygonVertices1, Vector2[] polygonVertices2) 46 + { 47 + var poly1 = new SimpleConvexPolygon(polygonVertices1); 48 + var poly2 = new SimpleConvexPolygon(polygonVertices2); 49 + 50 + Assert.That(clip(poly1, poly2).Length, Is.Zero); 51 + Assert.That(clip(poly2, poly1).Length, Is.Zero); 52 + 53 + Array.Reverse(polygonVertices1); 54 + Array.Reverse(polygonVertices2); 55 + 56 + Assert.That(clip(poly1, poly2).Length, Is.Zero); 57 + Assert.That(clip(poly2, poly1).Length, Is.Zero); 58 + } 59 + 60 + private static object[] subjectFullyContainedTestCases => new object[] 61 + { 62 + // Same polygon 63 + new object[] { new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { origin, up_2, up_2 + right_2, right_2 } }, 64 + new object[] { new[] { down_2, left_2, up_2, right_2 }, new[] { down_2, left_2, up_2, right_2 } }, 65 + // Corners 66 + new object[] { new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { origin, up_1, up_1 + right_1, right_1 } }, 67 + new object[] { new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { up_1, up_2, up_2 + right_1, up_1 + right_1 } }, 68 + new object[] { new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { up_1 + right_1, up_2 + right_1, up_2 + right_2, up_1 + right_2 } }, 69 + new object[] { new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { right_1, up_1 + right_1, up_1 + right_2, right_2 } }, 70 + new object[] { new[] { down_2, left_2, up_2, right_2 }, new[] { down_2, down_1, right_1, right_2 } }, 71 + new object[] { new[] { down_2, left_2, up_2, right_2 }, new[] { left_2, left_1, down_1, down_2 } }, 72 + new object[] { new[] { down_2, left_2, up_2, right_2 }, new[] { left_2, up_2, up_1, left_1 } }, 73 + new object[] { new[] { down_2, left_2, up_2, right_2 }, new[] { up_2, right_2, right_1, up_1 } }, 74 + // Padded 75 + new object[] { new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { right_1 * 0.5f + up_1 * 0.5f, up_2 * 0.75f, up_2 * 0.75f + right_2 * 0.75f, right_2 * 0.75f } }, 76 + new object[] { new[] { down_2, left_2, up_2, right_2 }, new[] { down_1, left_1, up_1, right_1 } }, 77 + // Rotated 78 + new object[] { new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { up_1 + right_1 * 0.5f, up_2 * 0.75f + right_1, up_1 + right_2 * 0.5f, up_1 * 0.5f + right_1 } }, 79 + new object[] { new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { up_1, up_2 * 0.75f, up_2 + right_1 * 0.5f, up_2 + right_1 } }, 80 + new object[] { new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { right_1, up_1 + right_2, up_1 * 0.5f + right_2, right_2 * 0.75f } }, 81 + new object[] { new[] { down_2, left_2, up_2, right_2 }, new[] { left_1 + up_1, up_1 + right_1, down_1 + right_1, left_1 + down_1 } }, 82 + }; 83 + 84 + [TestCaseSource(nameof(subjectFullyContainedTestCases))] 85 + public void TestSubjectFullyContained(Vector2[] clipVertices, Vector2[] subjectVertices) 86 + { 87 + var clipPolygon = new SimpleConvexPolygon(clipVertices); 88 + var subjectPolygon = new SimpleConvexPolygon(subjectVertices); 89 + 90 + assertPolygonEquals(subjectPolygon, new SimpleConvexPolygon(clip(clipPolygon, subjectPolygon).ToArray()), false); 91 + 92 + Array.Reverse(clipVertices); 93 + Array.Reverse(subjectVertices); 94 + 95 + assertPolygonEquals(subjectPolygon, new SimpleConvexPolygon(clip(clipPolygon, subjectPolygon).ToArray()), true); 96 + } 97 + 98 + [TestCaseSource(nameof(subjectFullyContainedTestCases))] 99 + public void TestClipFullyContained(Vector2[] subjectVertices, Vector2[] clipVertices) 100 + { 101 + var clipPolygon = new SimpleConvexPolygon(clipVertices); 102 + var subjectPolygon = new SimpleConvexPolygon(subjectVertices); 103 + 104 + assertPolygonEquals(clipPolygon, new SimpleConvexPolygon(clip(clipPolygon, subjectPolygon).ToArray()), false); 105 + 106 + Array.Reverse(clipVertices); 107 + Array.Reverse(subjectVertices); 108 + 109 + assertPolygonEquals(clipPolygon, new SimpleConvexPolygon(clip(clipPolygon, subjectPolygon).ToArray()), true); 110 + } 111 + 112 + private static object[] generalClippingTestCases => new object[] 113 + { 114 + new object[] 115 + { 116 + new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { left_1 + up_1, up_1 + right_1, down_1 + right_1, left_1 + down_1 }, new[] { origin, up_1, up_1 + right_1, right_1 } 117 + }, 118 + new object[] { new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { left_1, up_1, right_1, down_1 }, new[] { origin, up_1, right_1 } }, 119 + new object[] 120 + { 121 + new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { up_1 + right_1, up_3 + right_1, up_3 + right_3, up_1 + right_3 }, 122 + new[] { up_1 + right_1, up_2 + right_1, up_2 + right_2, up_1 + right_2 } 123 + }, 124 + new object[] 125 + { 126 + new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { up_2 + right_1, up_3 + right_2, up_2 + right_3, up_1 + right_2 }, new[] { up_2 + right_1, up_2 + right_2, up_1 + right_2 } 127 + }, 128 + new object[] { new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { left_1 + up_1, up_3 + right_1, up_1 + right_1, origin }, new[] { up_2, up_2 + right_1, up_1 + right_1, origin } }, 129 + new object[] 130 + { 131 + new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { left_1 + up_1, up_1 + right_3, down_1 + right_3, left_1 + down_1 }, new[] { up_1, up_1 + right_2, right_2, origin } 132 + }, 133 + new object[] 134 + { 135 + new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { down_1 + left_1, up_3 + left_1, up_3 + right_1, down_1 + right_1 }, new[] { origin, up_2, up_2 + right_1, right_1 } 136 + }, 137 + new object[] 138 + { 139 + new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { down_1, up_1 + right_1, down_1 + right_2, down_2 + right_1 }, new[] { right_1 * 0.5f, up_1 + right_1, right_2 * 0.75f } 140 + }, 141 + new object[] { new[] { origin, up_2, up_2 + right_2, right_2 }, new[] { origin, up_3 + right_3, right_2 }, new[] { origin, up_2 + right_2, right_2 } }, 142 + new object[] { new[] { up_2, right_2, down_2, left_2 }, new[] { left_1 + down_2, left_2 + down_2, up_2 + left_2, up_2 + left_1 }, new[] { up_1 + left_1, down_1 + left_1, left_2 } }, 143 + new object[] { new[] { up_2, right_2, down_2, left_2 }, new[] { origin, down_2 + left_2, up_2 + left_2 }, new[] { origin, down_1 + left_1, left_2, up_1 + left_1 } }, 144 + new object[] { new[] { up_2, right_2, down_2, left_2 }, new[] { origin, left_3, up_3 }, new[] { origin, left_2, up_2 } }, 145 + new object[] { new[] { up_2, right_2, down_2, left_2 }, new[] { origin, left_3, up_3, right_3 }, new[] { origin, left_2, up_2, right_2 } }, 146 + new object[] 147 + { 148 + new[] { left_1 + up_1, right_1 + up_1, down_1 + right_1, down_1 + left_1 }, new[] { up_2 * 0.75f, right_2 * 0.75f, down_2 * 0.75f, left_2 * 0.75f }, 149 + new[] 150 + { 151 + down_1 + left_1 * 0.5f, 152 + down_1 * 0.5f + left_1, 153 + up_1 * 0.5f + left_1, 154 + up_1 + left_1 * 0.5f, 155 + up_1 + right_1 * 0.5f, 156 + up_1 * 0.5f + right_1, 157 + down_1 * 0.5f + right_1, 158 + down_1 + right_1 * 0.5f 159 + } 160 + }, 161 + new object[] 162 + { 163 + new[] { up_1, right_1, left_1 }, new[] { up_1 + right_1 * 0.5f, down_1 + right_1 * 0.5f, down_1 + left_1 * 0.5f, up_1 + left_1 * 0.5f }, 164 + new[] { up_1 * 0.5f + left_1 * 0.5f, up_1, up_1 * 0.5f + right_1 * 0.5f, right_1 * 0.5f, left_1 * 0.5f, } 165 + }, 166 + new object[] 167 + { 168 + // Inverse of the above 169 + new[] { up_1 + right_1 * 0.5f, down_1 + right_1 * 0.5f, down_1 + left_1 * 0.5f, up_1 + left_1 * 0.5f }, new[] { up_1, right_1, left_1 }, 170 + new[] { up_1 * 0.5f + left_1 * 0.5f, up_1, up_1 * 0.5f + right_1 * 0.5f, right_1 * 0.5f, left_1 * 0.5f, } 171 + }, 172 + new object[] 173 + { 174 + new[] { up_1, right_1, down_1 + right_1, down_1 + left_1, left_1 }, new[] { left_1, up_1, up_1 + right_1, down_1 + right_1, down_1 }, 175 + new[] { up_1, right_1, right_1 + down_1, down_1, left_1 } 176 + } 177 + }; 178 + 179 + [TestCaseSource(nameof(generalClippingTestCases))] 180 + public void TestGeneralClipping(Vector2[] clipVertices, Vector2[] subjectVertices, Vector2[] resultingVertices) 181 + { 182 + var clipPolygon = new SimpleConvexPolygon(clipVertices); 183 + var subjectPolygon = new SimpleConvexPolygon(subjectVertices); 184 + 185 + assertPolygonEquals(new SimpleConvexPolygon(resultingVertices), new SimpleConvexPolygon(clip(clipPolygon, subjectPolygon).ToArray()), false); 186 + 187 + Array.Reverse(clipVertices); 188 + Array.Reverse(subjectVertices); 189 + 190 + // The expected polygon is never reversed 191 + assertPolygonEquals(new SimpleConvexPolygon(resultingVertices), new SimpleConvexPolygon(clip(clipPolygon, subjectPolygon).ToArray()), false); 192 + } 193 + 194 + [Test] 195 + public void TestEmptyClip() 196 + { 197 + var quad = new Quad(0, 0, 1, 1); 198 + 199 + assertPolygonEquals( 200 + new SimpleConvexPolygon(Array.Empty<Vector2>()), 201 + new SimpleConvexPolygon(clip(new SimpleConvexPolygon(Array.Empty<Vector2>()), quad).ToArray()), 202 + false); 203 + } 204 + 205 + [Test] 206 + public void TestEmptySubject() 207 + { 208 + var quad = new Quad(0, 0, 1, 1); 209 + 210 + assertPolygonEquals( 211 + new SimpleConvexPolygon(Array.Empty<Vector2>()), 212 + new SimpleConvexPolygon(clip(quad, new SimpleConvexPolygon(Array.Empty<Vector2>())).ToArray()), 213 + false); 214 + } 215 + 216 + private Span<Vector2> clip(SimpleConvexPolygon clipPolygon, SimpleConvexPolygon subjectPolygon) 217 + => new ConvexPolygonClipper<SimpleConvexPolygon, SimpleConvexPolygon>(ref clipPolygon, ref subjectPolygon).Clip(); 218 + 219 + private Span<Vector2> clip<TClip, TSubject>(TClip clipPolygon, TSubject subjectPolygon) 220 + where TClip : IConvexPolygon 221 + where TSubject : IConvexPolygon 222 + => new ConvexPolygonClipper<TClip, TSubject>(ref clipPolygon, ref subjectPolygon).Clip(); 223 + 224 + private void assertPolygonEquals(IPolygon expected, IPolygon actual, bool reverse) 225 + => Assert.That(Vector2Extensions.GetOrientation(actual.GetVertices()), 226 + reverse 227 + ? Is.EqualTo(-Vector2Extensions.GetOrientation(expected.GetVertices())) 228 + : Is.EqualTo(Vector2Extensions.GetOrientation(expected.GetVertices()))); 229 + } 230 + }
+48
osu.Framework.Tests/Primitives/Vector2ExtensionsTest.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using NUnit.Framework; 6 + using osu.Framework.Graphics; 7 + using osu.Framework.Graphics.Primitives; 8 + using osuTK; 9 + 10 + namespace osu.Framework.Tests.Primitives 11 + { 12 + [TestFixture] 13 + public class Vector2ExtensionsTest 14 + { 15 + [TestCase(true)] 16 + [TestCase(false)] 17 + public void TestArrayOrientation(bool clockwise) 18 + { 19 + var vertices = new[] 20 + { 21 + new Vector2(0, 1), 22 + Vector2.One, 23 + new Vector2(1, 0), 24 + Vector2.Zero 25 + }; 26 + 27 + if (!clockwise) 28 + Array.Reverse(vertices); 29 + 30 + float orientation = Vector2Extensions.GetOrientation(vertices); 31 + 32 + Assert.That(orientation, Is.EqualTo(clockwise ? 2 : -2).Within(0.001)); 33 + } 34 + 35 + [TestCase(true)] 36 + [TestCase(false)] 37 + public void TestQuadOrientation(bool normalised) 38 + { 39 + Quad quad = normalised 40 + ? new Quad(Vector2.Zero, new Vector2(1, 0), new Vector2(0, 1), Vector2.One) 41 + : new Quad(new Vector2(0, 1), Vector2.One, Vector2.Zero, new Vector2(1, 0)); 42 + 43 + float orientation = Vector2Extensions.GetOrientation(quad.GetVertices()); 44 + 45 + Assert.That(orientation, Is.EqualTo(normalised ? 2 : -2).Within(0.001)); 46 + } 47 + } 48 + }
osu.Framework.Tests/Resources/Samples/long.mp3

This is a binary file and will not be displayed.

osu.Framework.Tests/Resources/Samples/tone.wav

This is a binary file and will not be displayed.

+1
osu.Framework.Tests/Threading/AsyncDisposalQueueTest.cs
··· 25 25 objects.ForEach(AsyncDisposalQueue.Enqueue); 26 26 27 27 int attempts = 1000; 28 + 28 29 while (!objects.All(o => o.IsDisposed)) 29 30 { 30 31 if (attempts-- == 0)
+204
osu.Framework.Tests/Visual/Audio/TestSceneSamples.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System.Linq; 5 + using osu.Framework.Allocation; 6 + using osu.Framework.Audio.Track; 7 + using osu.Framework.Extensions.IEnumerableExtensions; 8 + using osu.Framework.Graphics; 9 + using osu.Framework.Graphics.Audio; 10 + using osu.Framework.Graphics.Containers; 11 + using osu.Framework.Graphics.Shapes; 12 + using osu.Framework.Input.Events; 13 + using osuTK; 14 + using osuTK.Graphics; 15 + 16 + namespace osu.Framework.Tests.Visual.Audio 17 + { 18 + public class TestSceneSamples : FrameworkTestScene 19 + { 20 + private readonly AudioContainer samples; 21 + private readonly TrackingLine tracking; 22 + 23 + private const int beats = 8; 24 + private const int notes = 16; 25 + 26 + public TestSceneSamples() 27 + { 28 + Children = new Drawable[] 29 + { 30 + new Container 31 + { 32 + Anchor = Anchor.Centre, 33 + Origin = Anchor.Centre, 34 + RelativeSizeAxes = Axes.Both, 35 + Size = new Vector2(0.95f), 36 + FillMode = FillMode.Fit, 37 + Children = new Drawable[] 38 + { 39 + new Box 40 + { 41 + Colour = Color4.Blue, 42 + RelativeSizeAxes = Axes.Both, 43 + }, 44 + new Grid(beats - 1, notes), 45 + samples = new AudioContainer 46 + { 47 + RelativeSizeAxes = Axes.Both, 48 + RelativeChildSize = new Vector2(beats - 1, notes), 49 + Children = new Drawable[] 50 + { 51 + new DraggableSample(0, 0), 52 + new DraggableSample(1, 2), 53 + new DraggableSample(2, 4), 54 + new DraggableSample(3, 5), 55 + new DraggableSample(4, 8), 56 + new DraggableSample(5, 11), 57 + new DraggableSample(6, 14), 58 + new DraggableSample(7, 16), 59 + tracking = new TrackingLine() 60 + } 61 + }, 62 + } 63 + }, 64 + }; 65 + 66 + AddStep("reduce volume", () => samples.VolumeTo(samples.Volume.Value - 0.5f, 1000, Easing.OutQuint)); 67 + AddStep("increase volume", () => samples.VolumeTo(samples.Volume.Value + 0.5f, 1000, Easing.OutQuint)); 68 + 69 + AddStep("reduce frequency", () => samples.FrequencyTo(samples.Frequency.Value - 0.1f, 1000, Easing.OutQuint)); 70 + AddStep("increase frequency", () => samples.FrequencyTo(samples.Frequency.Value + 0.1f, 1000, Easing.OutQuint)); 71 + 72 + AddStep("left balance", () => samples.BalanceTo(samples.Balance.Value - 1, 1000, Easing.OutQuint)); 73 + AddStep("right balance", () => samples.BalanceTo(samples.Balance.Value + 1, 1000, Easing.OutQuint)); 74 + } 75 + 76 + protected override void Update() 77 + { 78 + base.Update(); 79 + 80 + if (tracking.X > beats) 81 + { 82 + tracking.X = 0; 83 + samples.OfType<DraggableSample>().ForEach(s => s.Reset()); 84 + } 85 + else 86 + { 87 + tracking.X += (float)Clock.ElapsedFrameTime / 500; 88 + samples.OfType<DraggableSample>().Where(s => !s.Played && s.X <= tracking.X).ForEach(s => s.Play()); 89 + } 90 + } 91 + 92 + private class TrackingLine : CompositeDrawable 93 + { 94 + public TrackingLine() 95 + { 96 + RelativePositionAxes = Axes.Both; 97 + RelativeSizeAxes = Axes.Y; 98 + Size = new Vector2(4, notes); 99 + Colour = Color4.SkyBlue; 100 + 101 + Blending = BlendingMode.Additive; 102 + 103 + InternalChildren = new Drawable[] 104 + { 105 + new Box 106 + { 107 + Colour = Color4.White, 108 + RelativeSizeAxes = Axes.Both, 109 + }, 110 + }; 111 + } 112 + } 113 + 114 + public class Grid : CompositeDrawable 115 + { 116 + public Grid(int beats, int notes) 117 + { 118 + RelativeSizeAxes = Axes.Both; 119 + 120 + for (int i = 0; i <= beats; i++) 121 + { 122 + AddInternal(new Box 123 + { 124 + RelativePositionAxes = Axes.Both, 125 + RelativeSizeAxes = Axes.Y, 126 + Width = 1, 127 + Colour = Color4.White, 128 + X = (float)i / beats 129 + }); 130 + } 131 + 132 + for (int i = 0; i <= notes; i++) 133 + { 134 + AddInternal(new Box 135 + { 136 + RelativePositionAxes = Axes.Both, 137 + RelativeSizeAxes = Axes.X, 138 + Height = 1, 139 + Colour = Color4.White, 140 + Y = (float)i / notes 141 + }); 142 + } 143 + } 144 + } 145 + 146 + private class DraggableSample : CompositeDrawable 147 + { 148 + public DraggableSample(int beat, int pitch) 149 + { 150 + RelativePositionAxes = Axes.Both; 151 + 152 + Position = new Vector2(beat, pitch); 153 + Size = new Vector2(16); 154 + 155 + Origin = Anchor.Centre; 156 + 157 + InternalChildren = new Drawable[] 158 + { 159 + circle = new Circle 160 + { 161 + Colour = Color4.Yellow, 162 + RelativeSizeAxes = Axes.Both, 163 + Anchor = Anchor.Centre, 164 + Origin = Anchor.Centre, 165 + }, 166 + }; 167 + } 168 + 169 + public bool Played { get; private set; } 170 + 171 + [BackgroundDependencyLoader] 172 + private void load(ISampleStore samples) 173 + { 174 + AddInternal(sample = new DrawableSample(samples.Get("long.mp3"))); 175 + } 176 + 177 + private DrawableSample sample; 178 + 179 + private readonly Circle circle; 180 + 181 + protected override bool OnDragStart(DragStartEvent e) => true; 182 + 183 + protected override bool OnDrag(DragEvent e) 184 + { 185 + Y = (int)(e.MousePosition.Y / (Parent.DrawHeight / notes)); 186 + return true; 187 + } 188 + 189 + public void Reset() 190 + { 191 + Played = false; 192 + } 193 + 194 + public void Play() 195 + { 196 + Played = true; 197 + circle.ScaleTo(1.8f).ScaleTo(1, 600, Easing.OutQuint); 198 + 199 + sample.Frequency.Value = 1 + Y / notes; 200 + sample.Play(); 201 + } 202 + } 203 + } 204 + }
+209
osu.Framework.Tests/Visual/Audio/TestSceneTrack.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using osu.Framework.Allocation; 5 + using osu.Framework.Audio.Track; 6 + using osu.Framework.Bindables; 7 + using osu.Framework.Graphics; 8 + using osu.Framework.Graphics.Audio; 9 + using osu.Framework.Graphics.Containers; 10 + using osu.Framework.Graphics.Shapes; 11 + using osu.Framework.Graphics.Sprites; 12 + using osu.Framework.Input.Events; 13 + using osuTK; 14 + using osuTK.Graphics; 15 + 16 + namespace osu.Framework.Tests.Visual.Audio 17 + { 18 + public class TestSceneTrack : FrameworkTestScene 19 + { 20 + [BackgroundDependencyLoader] 21 + private void load(ITrackStore tracks) 22 + { 23 + Child = new DraggableAudioContainer 24 + { 25 + FillMode = FillMode.Fit, 26 + Child = new DraggableAudioContainer 27 + { 28 + Child = new DraggableAudioContainer 29 + { 30 + Child = new TrackPlayer(tracks.Get("sample-track.mp3")) 31 + } 32 + } 33 + }; 34 + } 35 + 36 + private class TrackPlayer : CompositeDrawable 37 + { 38 + public TrackPlayer(Track track) 39 + { 40 + Anchor = Anchor.Centre; 41 + Origin = Anchor.Centre; 42 + 43 + DrawableTrack drawableTrack; 44 + 45 + Size = new Vector2(50); 46 + 47 + Masking = true; 48 + CornerRadius = 10; 49 + 50 + InternalChildren = new Drawable[] 51 + { 52 + new Box 53 + { 54 + Colour = Color4.Yellow, 55 + RelativeSizeAxes = Axes.Both, 56 + }, 57 + new SpriteIcon 58 + { 59 + Anchor = Anchor.Centre, 60 + Origin = Anchor.Centre, 61 + Icon = FontAwesome.Solid.VolumeUp, 62 + Colour = Color4.Black, 63 + Size = new Vector2(40) 64 + }, 65 + drawableTrack = new DrawableTrack(track) 66 + }; 67 + 68 + drawableTrack.Looping = true; 69 + drawableTrack.Start(); 70 + } 71 + } 72 + 73 + private class DraggableAudioContainer : Container 74 + { 75 + private readonly Container content; 76 + 77 + private readonly AudioContainer audio; 78 + 79 + private readonly SpriteText textLocal; 80 + private readonly SpriteText textAggregate; 81 + 82 + private readonly Box volFill; 83 + private readonly Container warpContent; 84 + private readonly SpriteIcon spinner; 85 + 86 + protected override Container<Drawable> Content => content; 87 + 88 + public DraggableAudioContainer() 89 + { 90 + Size = new Vector2(0.8f); 91 + RelativeSizeAxes = Axes.Both; 92 + 93 + Anchor = Anchor.Centre; 94 + Origin = Anchor.Centre; 95 + 96 + InternalChildren = new Drawable[] 97 + { 98 + warpContent = new Container 99 + { 100 + RelativeSizeAxes = Axes.Both, 101 + Children = new Drawable[] 102 + { 103 + new Box 104 + { 105 + Colour = Color4.DarkGray, 106 + Alpha = 0.5f, 107 + RelativeSizeAxes = Axes.Both, 108 + }, 109 + volFill = new Box 110 + { 111 + Anchor = Anchor.BottomLeft, 112 + Origin = Anchor.BottomLeft, 113 + Colour = Color4.DarkViolet, 114 + Alpha = 0.2f, 115 + RelativeSizeAxes = Axes.Both, 116 + }, 117 + new Container 118 + { 119 + RelativeSizeAxes = Axes.Both, 120 + Padding = new MarginPadding(10), 121 + Children = new Drawable[] 122 + { 123 + textLocal = new SpriteText 124 + { 125 + Anchor = Anchor.TopLeft, 126 + Origin = Anchor.TopLeft, 127 + }, 128 + textAggregate = new SpriteText 129 + { 130 + Anchor = Anchor.BottomRight, 131 + Origin = Anchor.BottomRight, 132 + }, 133 + } 134 + }, 135 + spinner = new SpriteIcon 136 + { 137 + Anchor = Anchor.BottomLeft, 138 + Origin = Anchor.Centre, 139 + Icon = FontAwesome.Solid.CircleNotch, 140 + Blending = BlendingMode.Additive, 141 + Colour = Color4.White, 142 + Alpha = 0.2f, 143 + Scale = new Vector2(20), 144 + Position = new Vector2(20, -20) 145 + } 146 + } 147 + }, 148 + 149 + audio = new AudioContainer 150 + { 151 + RelativeSizeAxes = Axes.Both, 152 + Child = content = new Container 153 + { 154 + RelativeSizeAxes = Axes.Both, 155 + }, 156 + } 157 + }; 158 + } 159 + 160 + protected override void LoadComplete() 161 + { 162 + base.LoadComplete(); 163 + audio.Volume.BindValueChanged(updateLocal); 164 + audio.Balance.BindValueChanged(updateLocal); 165 + audio.Frequency.BindValueChanged(updateLocal, true); 166 + 167 + audio.AggregateVolume.BindValueChanged(updateAggregate); 168 + audio.AggregateBalance.BindValueChanged(updateAggregate); 169 + audio.AggregateFrequency.BindValueChanged(updateAggregate, true); 170 + } 171 + 172 + private void updateAggregate(ValueChangedEvent<double> obj) 173 + { 174 + textAggregate.Text = $"aggregate: vol {audio.AggregateVolume.Value:F1} freq {audio.AggregateFrequency.Value:F1} bal {audio.AggregateBalance.Value:F1}"; 175 + volFill.Height = (float)audio.AggregateVolume.Value; 176 + 177 + warpContent.Rotation = (float)audio.AggregateBalance.Value * 4; 178 + } 179 + 180 + private void updateLocal(ValueChangedEvent<double> obj) => 181 + textLocal.Text = $"local: vol {audio.Volume.Value:F1} freq {audio.Frequency.Value:F1} bal {audio.Balance.Value:F1}"; 182 + 183 + protected override bool OnDrag(DragEvent e) 184 + { 185 + Position += e.Delta; 186 + 187 + audio.Balance.Value = X / 100f; 188 + audio.Frequency.Value = 1 - Y / 100f; 189 + return true; 190 + } 191 + 192 + protected override bool OnScroll(ScrollEvent e) 193 + { 194 + audio.Volume.Value += e.ScrollDelta.Y / 100f; 195 + return true; 196 + } 197 + 198 + protected override void Update() 199 + { 200 + base.Update(); 201 + spinner.Rotation += (float)(audio.AggregateFrequency.Value * Clock.ElapsedFrameTime); 202 + } 203 + 204 + protected override bool OnDragEnd(DragEndEvent e) => true; 205 + 206 + protected override bool OnDragStart(DragStartEvent e) => true; 207 + } 208 + } 209 + }
+1 -2
osu.Framework.Tests/Visual/Bindables/TestSceneBindableAutoUnbinding.cs
··· 8 8 using osu.Framework.Graphics.Containers; 9 9 using osu.Framework.Graphics.Shapes; 10 10 using osu.Framework.Graphics.Sprites; 11 - using osu.Framework.Testing; 12 11 using osuTK; 13 12 using osuTK.Graphics; 14 13 15 14 namespace osu.Framework.Tests.Visual.Bindables 16 15 { 17 - public class TestSceneBindableAutoUnbinding : TestScene 16 + public class TestSceneBindableAutoUnbinding : FrameworkTestScene 18 17 { 19 18 [Test] 20 19 public void TestBindableAutoUnbindingAssign()
+1 -2
osu.Framework.Tests/Visual/Bindables/TestSceneBindableNumbers.cs
··· 7 7 using osu.Framework.Graphics; 8 8 using osu.Framework.Graphics.Containers; 9 9 using osu.Framework.Graphics.Sprites; 10 - using osu.Framework.Testing; 11 10 12 11 namespace osu.Framework.Tests.Visual.Bindables 13 12 { 14 - public class TestSceneBindableNumbers : TestScene 13 + public class TestSceneBindableNumbers : FrameworkTestScene 15 14 { 16 15 private readonly BindableInt bindableInt = new BindableInt(); 17 16 private readonly BindableLong bindableLong = new BindableLong();
+1 -2
osu.Framework.Tests/Visual/Clocks/TestSceneClock.cs
··· 7 7 using osu.Framework.Graphics.Shapes; 8 8 using osu.Framework.Graphics.Sprites; 9 9 using osu.Framework.Input.Events; 10 - using osu.Framework.Testing; 11 10 using osu.Framework.Timing; 12 11 using osuTK; 13 12 using osuTK.Graphics; 14 13 15 14 namespace osu.Framework.Tests.Visual.Clocks 16 15 { 17 - public abstract class TestSceneClock : TestScene 16 + public abstract class TestSceneClock : FrameworkTestScene 18 17 { 19 18 private readonly FillFlowContainer fill; 20 19
+1 -2
osu.Framework.Tests/Visual/Containers/TestSceneCircularContainer.cs
··· 5 5 using osu.Framework.Graphics.Containers; 6 6 using osu.Framework.Graphics.Shapes; 7 7 using osu.Framework.MathUtils; 8 - using osu.Framework.Testing; 9 8 using osuTK; 10 9 11 10 namespace osu.Framework.Tests.Visual.Containers 12 11 { 13 12 [System.ComponentModel.Description(@"Checking for bugged corner radius")] 14 - public class TestSceneCircularContainer : TestScene 13 + public class TestSceneCircularContainer : FrameworkTestScene 15 14 { 16 15 private SingleUpdateCircularContainer container; 17 16
+1 -2
osu.Framework.Tests/Visual/Containers/TestSceneCircularContainerSizing.cs
··· 6 6 using osu.Framework.Graphics; 7 7 using osu.Framework.Graphics.Containers; 8 8 using osu.Framework.Graphics.Shapes; 9 - using osu.Framework.Testing; 10 9 using osuTK; 11 10 12 11 namespace osu.Framework.Tests.Visual.Containers 13 12 { 14 - public class TestSceneCircularContainerSizing : TestScene 13 + public class TestSceneCircularContainerSizing : FrameworkTestScene 15 14 { 16 15 [Test] 17 16 public void TestLateSizing() => Schedule(() =>
+5 -2
osu.Framework.Tests/Visual/Containers/TestSceneCompositeMutability.cs
··· 8 8 using osu.Framework.Graphics; 9 9 using osu.Framework.Graphics.Containers; 10 10 using osu.Framework.Graphics.Shapes; 11 - using osu.Framework.Testing; 12 11 13 12 namespace osu.Framework.Tests.Visual.Containers 14 13 { 15 - public class TestSceneCompositeMutability : TestScene 14 + public class TestSceneCompositeMutability : FrameworkTestScene 16 15 { 17 16 [TestCase(TestThread.External, false)] 18 17 [TestCase(TestThread.Update, false)] ··· 51 50 case LoadState.Loading when thread != TestThread.Load: 52 51 container.LoadingEvent.Set(); 53 52 break; 53 + 54 54 // Special case for the load thread: possibly active during the ready state, but the ready event is handled in the switch below 55 55 case LoadState.Ready when thread == TestThread.Load: 56 56 container.LoadingEvent.Set(); 57 57 stateToWaitFor = LoadState.Loading; // We'll never reach the ready state before the switch below 58 58 break; 59 + 59 60 case LoadState.Ready: 60 61 container.LoadingEvent.Set(); 61 62 container.ReadyEvent.Set(); 62 63 break; 64 + 63 65 case LoadState.Loaded: 64 66 container.LoadingEvent.Set(); 65 67 container.ReadyEvent.Set(); ··· 96 98 AddStep("bind event", () => container.OnLoading += tryThrow); 97 99 AddStep("set loading", () => container.LoadingEvent.Set()); 98 100 break; 101 + 99 102 case LoadState.Ready: 100 103 AddStep("bind event", () => container.OnReady += tryThrow); 101 104 AddStep("set loading", () => container.ReadyEvent.Set());
+4 -10
osu.Framework.Tests/Visual/Containers/TestSceneContainerState.cs
··· 8 8 using osu.Framework.Graphics; 9 9 using osu.Framework.Graphics.Containers; 10 10 using osu.Framework.Graphics.Sprites; 11 - using osu.Framework.Testing; 12 11 13 12 namespace osu.Framework.Tests.Visual.Containers 14 13 { 15 14 [System.ComponentModel.Description("ensure valid container state in various scenarios")] 16 - public class TestSceneContainerState : TestScene 15 + public class TestSceneContainerState : FrameworkTestScene 17 16 { 18 17 /// <summary> 19 18 /// Tests if a drawable can be added to a container, removed, and then re-added to the same container. ··· 140 139 { 141 140 bool unbound = false; 142 141 143 - var drawableA = new Sprite().With(d => 144 - { 145 - d.OnUnbindAllBindables += () => unbound = true; 146 - }); 142 + var drawableA = new Sprite().With(d => { d.OnUnbindAllBindables += () => unbound = true; }); 147 143 148 144 var container = new Container { Children = new[] { drawableA } }; 149 145 ··· 159 155 { 160 156 bool disposed = false; 161 157 162 - var drawableA = new Sprite().With(d => 163 - { 164 - d.OnDispose += () => disposed = true; 165 - }); 158 + var drawableA = new Sprite().With(d => { d.OnDispose += () => disposed = true; }); 166 159 167 160 var container = new Container { Children = new[] { drawableA } }; 168 161 ··· 174 167 175 168 // Disposal happens asynchronously 176 169 int iterations = 20; 170 + 177 171 while (iterations-- > 0) 178 172 { 179 173 if (disposed)
+6 -2
osu.Framework.Tests/Visual/Containers/TestSceneCoordinateSpaces.cs
··· 6 6 using osu.Framework.Graphics.Containers; 7 7 using osu.Framework.Graphics.Shapes; 8 8 using osu.Framework.Graphics.Sprites; 9 - using osu.Framework.Testing; 10 9 using osuTK; 11 10 using osuTK.Graphics; 12 11 13 12 namespace osu.Framework.Tests.Visual.Containers 14 13 { 15 - public class TestSceneCoordinateSpaces : TestScene 14 + public class TestSceneCoordinateSpaces : FrameworkTestScene 16 15 { 17 16 public TestSceneCoordinateSpaces() 18 17 { ··· 47 46 h.CreateMarkerAt(1f); 48 47 h.CreateMarkerAt(1.1f); 49 48 break; 49 + 50 50 case 1: 51 51 h.RelativeChildSize = new Vector2(150, 1); 52 52 h.CreateMarkerAt(0); ··· 56 56 h.CreateMarkerAt(200); 57 57 h.CreateMarkerAt(250); 58 58 break; 59 + 59 60 case 2: 60 61 h.RelativeChildOffset = new Vector2(50, 0); 61 62 h.RelativeChildSize = new Vector2(150, 1); ··· 66 67 h.CreateMarkerAt(200); 67 68 h.CreateMarkerAt(250); 68 69 break; 70 + 69 71 case 3: 70 72 h.RelativeChildOffset = new Vector2(150, 0); 71 73 h.RelativeChildSize = new Vector2(-200, 1); ··· 76 78 h.CreateMarkerAt(200); 77 79 h.CreateMarkerAt(250); 78 80 break; 81 + 79 82 case 4: 80 83 h.RelativeChildOffset = new Vector2(0, 0); 81 84 h.RelativeChildSize = new Vector2(300, 1); ··· 86 89 h.CreateMarkerAt(200); 87 90 h.CreateMarkerAt(250); 88 91 break; 92 + 89 93 case 5: 90 94 h.RelativeChildOffset = new Vector2(-250, 0); 91 95 h.RelativeChildSize = new Vector2(500, 1);
+1 -2
osu.Framework.Tests/Visual/Containers/TestSceneDrawSizePreservingFillContainer.cs
··· 4 4 using osu.Framework.Graphics; 5 5 using osu.Framework.Graphics.Containers; 6 6 using osu.Framework.Graphics.Shapes; 7 - using osu.Framework.Testing; 8 7 using osuTK; 9 8 using osuTK.Graphics; 10 9 11 10 namespace osu.Framework.Tests.Visual.Containers 12 11 { 13 - public class TestSceneDrawSizePreservingFillContainer : TestScene 12 + public class TestSceneDrawSizePreservingFillContainer : FrameworkTestScene 14 13 { 15 14 public TestSceneDrawSizePreservingFillContainer() 16 15 {
+1 -2
osu.Framework.Tests/Visual/Containers/TestSceneDynamicDepth.cs
··· 5 5 using osu.Framework.Graphics.Containers; 6 6 using osu.Framework.Graphics.Shapes; 7 7 using osu.Framework.Graphics.Sprites; 8 - using osu.Framework.Testing; 9 8 using osuTK; 10 9 using osuTK.Graphics; 11 10 12 11 namespace osu.Framework.Tests.Visual.Containers 13 12 { 14 13 [System.ComponentModel.Description("changing depth of child dynamically")] 15 - public class TestSceneDynamicDepth : TestScene 14 + public class TestSceneDynamicDepth : FrameworkTestScene 16 15 { 17 16 private void addDepthSteps(DepthBox box, Container container) 18 17 {
+160
osu.Framework.Tests/Visual/Containers/TestSceneFrontToBack.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using osu.Framework.Allocation; 6 + using osu.Framework.Configuration; 7 + using osu.Framework.Graphics; 8 + using osu.Framework.Graphics.Containers; 9 + using osu.Framework.Graphics.OpenGL; 10 + using osu.Framework.Graphics.OpenGL.Vertices; 11 + using osu.Framework.Graphics.Shapes; 12 + using osu.Framework.Graphics.Sprites; 13 + using osu.Framework.MathUtils; 14 + using osu.Framework.Testing; 15 + using osuTK; 16 + using osuTK.Graphics; 17 + using osuTK.Graphics.ES30; 18 + 19 + namespace osu.Framework.Tests.Visual.Containers 20 + { 21 + public class TestSceneFrontToBack : GridTestScene 22 + { 23 + private SpriteText labelDrawables; 24 + private QueryingCompositeDrawableDrawNode drawNode; 25 + private SpriteText labelFrag; 26 + private SpriteText labelFrag2; 27 + private float currentScale = 1; 28 + 29 + private const int cell_count = 4; 30 + 31 + protected override DrawNode CreateDrawNode() => drawNode = new QueryingCompositeDrawableDrawNode(this); 32 + 33 + public TestSceneFrontToBack() 34 + : base(cell_count / 2, cell_count / 2) 35 + { 36 + } 37 + 38 + [BackgroundDependencyLoader] 39 + private void load(FrameworkDebugConfigManager debugConfig) 40 + { 41 + AddStep("add more drawables", addMoreDrawables); 42 + AddToggleStep("disable front to back", val => 43 + { 44 + debugConfig.Set(DebugSetting.BypassFrontToBackPass, val); 45 + Invalidate(Invalidation.DrawNode); // reset counts 46 + }); 47 + 48 + Add(new Container 49 + { 50 + AutoSizeAxes = Axes.Both, 51 + Depth = float.NegativeInfinity, 52 + Anchor = Anchor.Centre, 53 + Origin = Anchor.Centre, 54 + 55 + Children = new Drawable[] 56 + { 57 + new Box 58 + { 59 + Colour = Color4.Black, 60 + RelativeSizeAxes = Axes.Both, 61 + Alpha = 0.8f 62 + }, 63 + new FillFlowContainer 64 + { 65 + AutoSizeAxes = Axes.Both, 66 + Padding = new MarginPadding(10), 67 + Direction = FillDirection.Vertical, 68 + Children = new Drawable[] 69 + { 70 + labelDrawables = new SpriteText { Font = new FontUsage("RobotoCondensed", weight: "Regular") }, 71 + labelFrag = new SpriteText { Font = new FontUsage("RobotoCondensed", weight: "Regular") }, 72 + labelFrag2 = new SpriteText { Font = new FontUsage("RobotoCondensed", weight: "Regular") }, 73 + } 74 + }, 75 + } 76 + }); 77 + } 78 + 79 + protected override void Update() 80 + { 81 + base.Update(); 82 + 83 + if (drawNode != null) 84 + { 85 + labelDrawables.Text = $"boxes: {Cell(1).Children.Count * cell_count:N0}"; 86 + labelFrag.Text = $"samples ({nameof(DrawNode.Draw)}): {drawNode.DrawSamples:N0}"; 87 + labelFrag2.Text = $"samples ({nameof(DrawNode.DrawOpaqueInteriorSubTree)}): {drawNode.DrawOpaqueInteriorSubTreeSamples:N0}"; 88 + } 89 + } 90 + 91 + private void addMoreDrawables() 92 + { 93 + for (int i = 0; i < 100; i++) 94 + { 95 + Cell(i % cell_count).Add(new Box 96 + { 97 + Anchor = Anchor.Centre, 98 + Origin = Anchor.Centre, 99 + Colour = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1), 100 + RelativeSizeAxes = Axes.Both, 101 + Scale = new Vector2(currentScale) 102 + }); 103 + 104 + currentScale -= 0.001f; 105 + if (currentScale < 0) 106 + currentScale = 1; 107 + } 108 + } 109 + 110 + private class QueryingCompositeDrawableDrawNode : CompositeDrawableDrawNode 111 + { 112 + private int queryObject = -1; 113 + 114 + public int DrawSamples { get; private set; } 115 + public int DrawOpaqueInteriorSubTreeSamples { get; private set; } 116 + 117 + public QueryingCompositeDrawableDrawNode(CompositeDrawable source) 118 + : base(source) 119 + { 120 + } 121 + 122 + internal override void DrawOpaqueInteriorSubTree(DepthValue depthValue, Action<TexturedVertex2D> vertexAction) 123 + { 124 + startQuery(); 125 + base.DrawOpaqueInteriorSubTree(depthValue, vertexAction); 126 + DrawOpaqueInteriorSubTreeSamples = endQuery(); 127 + } 128 + 129 + public override void ApplyState() 130 + { 131 + DrawSamples = 0; 132 + DrawOpaqueInteriorSubTreeSamples = 0; 133 + base.ApplyState(); 134 + } 135 + 136 + public override void Draw(Action<TexturedVertex2D> vertexAction) 137 + { 138 + startQuery(); 139 + base.Draw(vertexAction); 140 + DrawSamples = endQuery(); 141 + } 142 + 143 + private int endQuery() 144 + { 145 + GL.EndQuery(QueryTarget.SamplesPassed); 146 + GL.GetQueryObject(queryObject, GetQueryObjectParam.QueryResult, out int result); 147 + 148 + return result; 149 + } 150 + 151 + private void startQuery() 152 + { 153 + if (queryObject == -1) 154 + queryObject = GL.GenQuery(); 155 + 156 + GL.BeginQuery(QueryTarget.SamplesPassed, queryObject); 157 + } 158 + } 159 + } 160 + }
+147
osu.Framework.Tests/Visual/Containers/TestSceneFrontToBackBox.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using NUnit.Framework; 6 + using osu.Framework.Allocation; 7 + using osu.Framework.Configuration; 8 + using osu.Framework.Graphics; 9 + using osu.Framework.Graphics.Containers; 10 + using osu.Framework.Graphics.Shapes; 11 + using osuTK; 12 + using osuTK.Graphics; 13 + 14 + namespace osu.Framework.Tests.Visual.Containers 15 + { 16 + public class TestSceneFrontToBackBox : FrameworkTestScene 17 + { 18 + private TestBox blendedBox; 19 + 20 + [BackgroundDependencyLoader] 21 + private void load(FrameworkDebugConfigManager debugConfig) 22 + { 23 + AddToggleStep("disable front to back", val => debugConfig.Set(DebugSetting.BypassFrontToBackPass, val)); 24 + } 25 + 26 + [Test] 27 + public void TestOpaqueBoxWithMixedBlending() 28 + { 29 + createBox(); 30 + AddAssert("renders interior", () => blendedBox.CanDrawOpaqueInterior); 31 + } 32 + 33 + [Test] 34 + public void TestTransparentBoxWithMixedBlending() 35 + { 36 + createBox(b => b.Alpha = 0.5f); 37 + AddAssert("doesn't render interior", () => !blendedBox.CanDrawOpaqueInterior); 38 + } 39 + 40 + [Test] 41 + public void TestOpaqueBoxWithAdditiveBlending() 42 + { 43 + createBox(b => b.Blending = BlendingMode.Additive); 44 + AddAssert("doesn't render interior", () => !blendedBox.CanDrawOpaqueInterior); 45 + } 46 + 47 + [Test] 48 + public void TestTransparentBoxWithAdditiveBlending() 49 + { 50 + createBox(b => 51 + { 52 + b.Blending = BlendingMode.Additive; 53 + b.Alpha = 0.5f; 54 + }); 55 + 56 + AddAssert("doesn't render interior", () => !blendedBox.CanDrawOpaqueInterior); 57 + } 58 + 59 + [TestCase(BlendingEquation.Max)] 60 + [TestCase(BlendingEquation.Min)] 61 + [TestCase(BlendingEquation.Subtract)] 62 + [TestCase(BlendingEquation.ReverseSubtract)] 63 + public void TestOpaqueBoxWithNonAddRGBEquation(BlendingEquation equationMode) 64 + { 65 + createBox(b => 66 + { 67 + b.Blending = new BlendingParameters 68 + { 69 + Mode = BlendingMode.Inherit, 70 + AlphaEquation = BlendingEquation.Inherit, 71 + RGBEquation = equationMode 72 + }; 73 + }); 74 + 75 + AddAssert("doesn't render interior", () => !blendedBox.CanDrawOpaqueInterior); 76 + } 77 + 78 + [TestCase(BlendingEquation.Max)] 79 + [TestCase(BlendingEquation.Min)] 80 + [TestCase(BlendingEquation.Subtract)] 81 + [TestCase(BlendingEquation.ReverseSubtract)] 82 + public void TestOpaqueBoxWithNonAddAlphaEquation(BlendingEquation equationMode) 83 + { 84 + createBox(b => 85 + { 86 + b.Blending = new BlendingParameters 87 + { 88 + Mode = BlendingMode.Inherit, 89 + AlphaEquation = equationMode, 90 + RGBEquation = BlendingEquation.Inherit 91 + }; 92 + }); 93 + 94 + AddAssert("doesn't render interior", () => !blendedBox.CanDrawOpaqueInterior); 95 + } 96 + 97 + private void createBox(Action<Box> setupAction = null) => AddStep("create box", () => 98 + { 99 + Clear(); 100 + 101 + Add(new Container 102 + { 103 + Anchor = Anchor.Centre, 104 + Origin = Anchor.Centre, 105 + Size = new Vector2(200), 106 + Children = new Drawable[] 107 + { 108 + new Box 109 + { 110 + RelativeSizeAxes = Axes.Both, 111 + Colour = new Color4(50, 50, 50, 255) 112 + }, 113 + blendedBox = new TestBox 114 + { 115 + Anchor = Anchor.Centre, 116 + Origin = Anchor.Centre, 117 + RelativeSizeAxes = Axes.Both, 118 + Colour = new Color4(100, 100, 100, 255), 119 + Size = new Vector2(0.5f), 120 + } 121 + } 122 + }); 123 + 124 + setupAction?.Invoke(blendedBox); 125 + }); 126 + 127 + private class TestBox : Box 128 + { 129 + public bool CanDrawOpaqueInterior => currentDrawNode.CanDrawOpaqueInterior; 130 + 131 + private DrawNode currentDrawNode; 132 + 133 + protected override DrawNode CreateDrawNode() => new TestBoxDrawNode(this); 134 + 135 + internal override DrawNode GenerateDrawNodeSubtree(ulong frame, int treeIndex, bool forceNewDrawNode) 136 + => currentDrawNode = base.GenerateDrawNodeSubtree(frame, treeIndex, forceNewDrawNode); 137 + 138 + private class TestBoxDrawNode : BoxDrawNode 139 + { 140 + public TestBoxDrawNode(Box source) 141 + : base(source) 142 + { 143 + } 144 + } 145 + } 146 + } 147 + }
+6 -2
osu.Framework.Tests/Visual/Containers/TestSceneLifetimeManagementContainer.cs
··· 8 8 using osu.Framework.Graphics; 9 9 using osu.Framework.Graphics.Containers; 10 10 using osu.Framework.Graphics.Sprites; 11 - using osu.Framework.Testing; 12 11 using osu.Framework.Timing; 13 12 14 13 namespace osu.Framework.Tests.Visual.Containers 15 14 { 16 - public class TestSceneLifetimeManagementContainer : TestScene 15 + public class TestSceneLifetimeManagementContainer : FrameworkTestScene 17 16 { 18 17 private ManualClock manualClock; 19 18 private TestContainer container; ··· 42 41 AddAssert($"{numAlive} alive children", () => 43 42 { 44 43 int num = 0; 44 + 45 45 foreach (var child in container.InternalChildren) 46 46 { 47 47 num += child.IsAlive ? 1 : 0; ··· 190 190 { 191 191 l = rng.Next(5); 192 192 r = rng.Next(5); 193 + 193 194 if (l > r) 194 195 { 195 196 var l1 = l; ··· 252 253 }); 253 254 254 255 int count = 1; 256 + 255 257 for (int i = 0; i < 1000; i++) 256 258 { 257 259 switch (rng.Next(3)) ··· 269 271 } 270 272 271 273 break; 274 + 272 275 case 1: 273 276 AddStep("Change lifetime", changeLifetime); 274 277 break; 278 + 275 279 case 2: 276 280 AddStep("Change time", changeTime); 277 281 break;
+1 -2
osu.Framework.Tests/Visual/Containers/TestSceneMasking.cs
··· 8 8 using osu.Framework.Graphics.Shapes; 9 9 using osu.Framework.Graphics.Sprites; 10 10 using osu.Framework.Input.Events; 11 - using osu.Framework.Testing; 12 11 using osuTK; 13 12 using osuTK.Graphics; 14 13 15 14 namespace osu.Framework.Tests.Visual.Containers 16 15 { 17 - public class TestSceneMasking : TestScene 16 + public class TestSceneMasking : FrameworkTestScene 18 17 { 19 18 protected Container TestContainer; 20 19
+25 -3
osu.Framework.Tests/Visual/Containers/TestSceneScrollContainer.cs
··· 6 6 using osu.Framework.Graphics; 7 7 using osu.Framework.Graphics.Containers; 8 8 using osu.Framework.Graphics.Shapes; 9 + using osu.Framework.MathUtils; 9 10 using osu.Framework.Testing; 10 11 using osuTK; 11 12 using osuTK.Input; ··· 32 33 /// Create a scroll container, attempt to scroll past its <see cref="ScrollContainer.ClampExtension"/>, and check that it does not. 33 34 /// </summary> 34 35 [Test] 35 - public void ScrollToTest() 36 + public void TestScrollTo() 36 37 { 37 38 AddStep("Create scroll container with specified clamp extension", () => createScrollContainer(clampExtension)); 38 39 AddStep("Scroll past extent", () => scrollContainer.ScrollTo(200)); ··· 45 46 /// Attempt to drag a scrollcontainer past its <see cref="ScrollContainer.ClampExtension"/> and check that it does not. 46 47 /// </summary> 47 48 [Test] 48 - public void DraggingScrollTest() 49 + public void TestDraggingScroll() 49 50 { 50 51 AddStep("Create scroll container with specified clamp extension", () => createScrollContainer(clampExtension)); 51 52 AddStep("Click and drag scrollcontainer", () => ··· 63 64 checkScrollWithinBounds(); 64 65 } 65 66 67 + [Test] 68 + public void TestContentAnchor() 69 + { 70 + AddStep("Create scroll container with centre-left content", () => 71 + { 72 + createScrollContainer(clampExtension).With(d => 73 + { 74 + d.RelativeSizeAxes = Axes.None; 75 + d.Size = new Vector2(300); 76 + d.ScrollContent.Anchor = Anchor.CentreLeft; 77 + d.ScrollContent.Origin = Anchor.CentreLeft; 78 + d.ScrollContent.Child.Height = 400; 79 + }); 80 + }); 81 + 82 + AddStep("Scroll to 0", () => scrollContainer.ScrollTo(0, false)); 83 + AddAssert("Content position at top", () => Precision.AlmostEquals(scrollContainer.ScreenSpaceDrawQuad.TopLeft, scrollContainer.ScrollContent.ScreenSpaceDrawQuad.TopLeft)); 84 + } 85 + 66 86 private void checkScrollWithinBounds() 67 87 { 68 88 AddAssert("Scroll amount is within ClampExtension bounds", () => Math.Abs(scrollContainer.Current) <= scrollContainer.ClampExtension); 69 89 } 70 90 71 - private void createScrollContainer(float clampExtension = 0) 91 + private ScrollContainer createScrollContainer(float clampExtension = 0) 72 92 { 73 93 if (scrollContainer != null) 74 94 InputManager.Remove(scrollContainer); ··· 86 106 } 87 107 } 88 108 }); 109 + 110 + return scrollContainer; 89 111 } 90 112 } 91 113 }
+14 -2
osu.Framework.Tests/Visual/Containers/TestSceneSizing.cs
··· 9 9 using osu.Framework.Graphics.Sprites; 10 10 using osu.Framework.Input.Events; 11 11 using osu.Framework.MathUtils; 12 - using osu.Framework.Testing; 13 12 using osuTK; 14 13 using osuTK.Graphics; 15 14 16 15 namespace osu.Framework.Tests.Visual.Containers 17 16 { 18 17 [System.ComponentModel.Description("potentially challenging size calculations")] 19 - public class TestSceneSizing : TestScene 18 + public class TestSceneSizing : FrameworkTestScene 20 19 { 21 20 private Container testContainer; 22 21 ··· 193 192 194 193 box.OnUpdate += delegate { box.Rotation += 0.05f; }; 195 194 break; 195 + 196 196 case 1: 197 197 testContainer.Add(box = new InfofulBoxAutoSize 198 198 { ··· 224 224 Colour = Color4.Blue, 225 225 }); 226 226 break; 227 + 227 228 case 2: 228 229 testContainer.Add(box = new InfofulBoxAutoSize 229 230 { ··· 255 256 Colour = Color4.SeaGreen, 256 257 }); 257 258 break; 259 + 258 260 case 3: 259 261 testContainer.Add(box = new InfofulBox 260 262 { ··· 288 290 Colour = Color4.SeaGreen, 289 291 }); 290 292 break; 293 + 291 294 case 4: 292 295 testContainer.Add(box = new InfofulBoxAutoSize 293 296 { ··· 320 323 Anchor = Anchor.CentreLeft 321 324 }); 322 325 break; 326 + 323 327 case 5: 324 328 testContainer.Add(box = new InfofulBoxAutoSize 325 329 { ··· 353 357 Anchor = Anchor.CentreLeft 354 358 }); 355 359 break; 360 + 356 361 case 6: 357 362 testContainer.Add(box = new InfofulBoxAutoSize 358 363 { ··· 377 382 Colour = Color4.OrangeRed, 378 383 }); 379 384 break; 385 + 380 386 case 7: 381 387 Container shrinkContainer; 382 388 Container<Drawable> boxes; ··· 1183 1189 if (Chameleon && (int)Time.Current / 1000 != lastSwitch) 1184 1190 { 1185 1191 lastSwitch = (int)Time.Current / 1000; 1192 + 1186 1193 switch (lastSwitch % 6) 1187 1194 { 1188 1195 case 0: 1189 1196 Anchor = (Anchor)((int)Anchor + 1); 1190 1197 Origin = (Anchor)((int)Origin + 1); 1191 1198 break; 1199 + 1192 1200 case 1: 1193 1201 this.MoveTo(new Vector2(0, 0), 800, Easing.Out); 1194 1202 break; 1203 + 1195 1204 case 2: 1196 1205 this.MoveTo(new Vector2(200, 0), 800, Easing.Out); 1197 1206 break; 1207 + 1198 1208 case 3: 1199 1209 this.MoveTo(new Vector2(200, 200), 800, Easing.Out); 1200 1210 break; 1211 + 1201 1212 case 4: 1202 1213 this.MoveTo(new Vector2(0, 200), 800, Easing.Out); 1203 1214 break; 1215 + 1204 1216 case 5: 1205 1217 this.MoveTo(new Vector2(0, 0), 800, Easing.Out); 1206 1218 break;
+1 -2
osu.Framework.Tests/Visual/Containers/TestSceneTextFlowContainer.cs
··· 8 8 using osu.Framework.Graphics.Containers; 9 9 using osu.Framework.Graphics.Shapes; 10 10 using osu.Framework.Graphics.Sprites; 11 - using osu.Framework.Testing; 12 11 using osuTK.Graphics; 13 12 14 13 namespace osu.Framework.Tests.Visual.Containers 15 14 { 16 - public class TestSceneTextFlowContainer : TestScene 15 + public class TestSceneTextFlowContainer : FrameworkTestScene 17 16 { 18 17 private const string default_text = "Default text\n\nnewline"; 19 18
+109
osu.Framework.Tests/Visual/Containers/TestSceneVisibilityContainer.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using NUnit.Framework; 5 + using osu.Framework.Graphics; 6 + using osu.Framework.Graphics.Containers; 7 + using osu.Framework.Graphics.Shapes; 8 + using osuTK; 9 + using osuTK.Graphics; 10 + 11 + namespace osu.Framework.Tests.Visual.Containers 12 + { 13 + public class TestSceneVisibilityContainer : FrameworkTestScene 14 + { 15 + private TestVisibilityContainer testContainer; 16 + 17 + [Test] 18 + public void TestShowHide() 19 + { 20 + AddStep("create container", () => Child = testContainer = new TestVisibilityContainer()); 21 + 22 + checkHidden(true); 23 + 24 + AddStep("show", () => testContainer.Show()); 25 + checkVisible(); 26 + 27 + AddStep("hide", () => testContainer.Hide()); 28 + checkHidden(); 29 + 30 + AddAssert("fire count is 2", () => testContainer.FireCount == 2); 31 + } 32 + 33 + [TestCase(true)] 34 + [TestCase(false)] 35 + public void TestStartHidden(bool startHidden) 36 + { 37 + AddStep("create container", () => Child = testContainer = 38 + new TestVisibilityContainer(startHidden) { State = { Value = Visibility.Visible } }); 39 + 40 + checkVisible(!startHidden); 41 + 42 + AddStep("hide", () => testContainer.Hide()); 43 + checkHidden(); 44 + 45 + AddAssert("fire count is 2", () => testContainer.FireCount == 2); 46 + } 47 + 48 + private void checkHidden(bool instant = false) 49 + { 50 + AddAssert("is hidden", () => testContainer.State.Value == Visibility.Hidden); 51 + if (instant) 52 + AddAssert("alpha zero", () => testContainer.Alpha == 0); 53 + else 54 + AddUntilStep("alpha zero", () => testContainer.Alpha == 0); 55 + } 56 + 57 + private void checkVisible(bool instant = false) 58 + { 59 + AddAssert("is visible", () => testContainer.State.Value == Visibility.Visible); 60 + if (instant) 61 + AddAssert("alpha one", () => testContainer.Alpha == 1); 62 + else 63 + AddUntilStep("alpha one", () => testContainer.Alpha == 1); 64 + } 65 + 66 + private class TestVisibilityContainer : VisibilityContainer 67 + { 68 + private readonly bool startHidden; 69 + 70 + protected override bool StartHidden => startHidden; 71 + 72 + public TestVisibilityContainer(bool startHidden = true) 73 + { 74 + this.startHidden = startHidden; 75 + 76 + Size = new Vector2(0.5f); 77 + RelativeSizeAxes = Axes.Both; 78 + 79 + Anchor = Anchor.Centre; 80 + Origin = Anchor.Centre; 81 + 82 + Children = new Drawable[] 83 + { 84 + new Box 85 + { 86 + Colour = Color4.Cyan, 87 + RelativeSizeAxes = Axes.Both, 88 + }, 89 + }; 90 + 91 + State.ValueChanged += e => FireCount++; 92 + } 93 + 94 + public int FireCount { get; private set; } 95 + 96 + protected override void PopIn() 97 + { 98 + this.FadeIn(1000, Easing.OutQuint); 99 + this.ScaleTo(1, 1000, Easing.OutElastic); 100 + } 101 + 102 + protected override void PopOut() 103 + { 104 + this.FadeOut(1000, Easing.OutQuint); 105 + this.ScaleTo(0.4f, 1000, Easing.OutQuint); 106 + } 107 + } 108 + } 109 + }
+1 -2
osu.Framework.Tests/Visual/Drawables/TestSceneBlending.cs
··· 8 8 using osu.Framework.Graphics.Shapes; 9 9 using osu.Framework.Graphics.Sprites; 10 10 using osu.Framework.Graphics.UserInterface; 11 - using osu.Framework.Testing; 12 11 using osuTK; 13 12 using osuTK.Graphics; 14 13 15 14 namespace osu.Framework.Tests.Visual.Drawables 16 15 { 17 - public class TestSceneBlending : TestScene 16 + public class TestSceneBlending : FrameworkTestScene 18 17 { 19 18 private readonly Dropdown<BlendingMode> colourModeDropdown; 20 19 private readonly Dropdown<BlendingEquation> colourEquation;
+1 -2
osu.Framework.Tests/Visual/Drawables/TestSceneConcurrentLoad.cs
··· 9 9 using osu.Framework.Graphics; 10 10 using osu.Framework.Graphics.Containers; 11 11 using osu.Framework.Graphics.Shapes; 12 - using osu.Framework.Testing; 13 12 using osuTK; 14 13 using osuTK.Graphics; 15 14 16 15 namespace osu.Framework.Tests.Visual.Drawables 17 16 { 18 - public class TestSceneConcurrentLoad : TestScene 17 + public class TestSceneConcurrentLoad : FrameworkTestScene 19 18 { 20 19 private const int panel_count = 6; 21 20
+1 -2
osu.Framework.Tests/Visual/Drawables/TestSceneDelayedLoad.cs
··· 9 9 using osu.Framework.Graphics; 10 10 using osu.Framework.Graphics.Containers; 11 11 using osu.Framework.Graphics.Sprites; 12 - using osu.Framework.Testing; 13 12 using osuTK; 14 13 using osuTK.Graphics; 15 14 16 15 namespace osu.Framework.Tests.Visual.Drawables 17 16 { 18 - public class TestSceneDelayedLoad : TestScene 17 + public class TestSceneDelayedLoad : FrameworkTestScene 19 18 { 20 19 private const int panel_count = 2048; 21 20
+1 -2
osu.Framework.Tests/Visual/Drawables/TestSceneDelayedUnload.cs
··· 6 6 using osu.Framework.Graphics; 7 7 using osu.Framework.Graphics.Containers; 8 8 using osu.Framework.Graphics.Sprites; 9 - using osu.Framework.Testing; 10 9 using osuTK; 11 10 using osuTK.Graphics; 12 11 13 12 namespace osu.Framework.Tests.Visual.Drawables 14 13 { 15 - public class TestSceneDelayedUnload : TestScene 14 + public class TestSceneDelayedUnload : FrameworkTestScene 16 15 { 17 16 private const int panel_count = 1024; 18 17
+1 -2
osu.Framework.Tests/Visual/Drawables/TestSceneDrawableLoadCancellation.cs
··· 13 13 using osu.Framework.Graphics.Shapes; 14 14 using osu.Framework.Graphics.Sprites; 15 15 using osu.Framework.Logging; 16 - using osu.Framework.Testing; 17 16 using osuTK; 18 17 using osuTK.Graphics; 19 18 20 19 namespace osu.Framework.Tests.Visual.Drawables 21 20 { 22 - public class TestSceneDrawableLoadCancellation : TestScene 21 + public class TestSceneDrawableLoadCancellation : FrameworkTestScene 23 22 { 24 23 private readonly List<SlowLoader> loaders = new List<SlowLoader>(); 25 24
+1 -2
osu.Framework.Tests/Visual/Drawables/TestSceneEffects.cs
··· 7 7 using osu.Framework.Graphics.Effects; 8 8 using osu.Framework.Graphics.Shapes; 9 9 using osu.Framework.Graphics.Sprites; 10 - using osu.Framework.Testing; 11 10 using osuTK; 12 11 using osuTK.Graphics; 13 12 14 13 namespace osu.Framework.Tests.Visual.Drawables 15 14 { 16 15 [System.ComponentModel.Description("implementing the IEffect interface")] 17 - public class TestSceneEffects : TestScene 16 + public class TestSceneEffects : FrameworkTestScene 18 17 { 19 18 private readonly SpriteText changeColourText; 20 19
+3 -3
osu.Framework.Tests/Visual/Drawables/TestSceneFocus.cs
··· 69 69 [Test] 70 70 public void FocusedOverlayTakesFocusOnShow() 71 71 { 72 - AddAssert("overlay not visible", () => overlay.State == Visibility.Hidden); 72 + AddAssert("overlay not visible", () => overlay.State.Value == Visibility.Hidden); 73 73 checkNotFocused(() => overlay); 74 74 75 75 AddStep("show overlay", () => overlay.Show()); ··· 82 82 [Test] 83 83 public void FocusedOverlayLosesFocusOnClickAway() 84 84 { 85 - AddAssert("overlay not visible", () => overlay.State == Visibility.Hidden); 85 + AddAssert("overlay not visible", () => overlay.State.Value == Visibility.Hidden); 86 86 checkNotFocused(() => overlay); 87 87 88 88 AddStep("show overlay", () => overlay.Show()); ··· 251 251 { 252 252 if (!box.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) 253 253 { 254 - State = Visibility.Hidden; 254 + Hide(); 255 255 return true; 256 256 } 257 257
+1 -2
osu.Framework.Tests/Visual/Drawables/TestSceneIsMaskedAway.cs
··· 5 5 using osu.Framework.Graphics; 6 6 using osu.Framework.Graphics.Containers; 7 7 using osu.Framework.Graphics.Shapes; 8 - using osu.Framework.Testing; 9 8 using osuTK; 10 9 11 10 namespace osu.Framework.Tests.Visual.Drawables 12 11 { 13 - public class TestSceneIsMaskedAway : TestScene 12 + public class TestSceneIsMaskedAway : FrameworkTestScene 14 13 { 15 14 /// <summary> 16 15 /// Tests that a box which is within the bounds of a parent is never masked away, regardless of whether the parent is masking or not.
+114 -190
osu.Framework.Tests/Visual/Drawables/TestSceneModelBackedDrawable.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; 5 - using System.Collections.Generic; 6 5 using System.Threading; 7 6 using NUnit.Framework; 8 7 using osu.Framework.Allocation; ··· 10 9 using osu.Framework.Graphics.Containers; 11 10 using osu.Framework.Graphics.Shapes; 12 11 using osu.Framework.Graphics.Sprites; 13 - using osu.Framework.Testing; 14 12 using osuTK; 15 13 using osuTK.Graphics; 16 14 17 15 namespace osu.Framework.Tests.Visual.Drawables 18 16 { 19 - public class TestSceneModelBackedDrawable : TestScene 17 + public class TestSceneModelBackedDrawable : FrameworkTestScene 20 18 { 21 19 private TestModelBackedDrawable backedDrawable; 22 20 23 - private void createModelBackedDrawable(bool withPlaceholder, bool fadeOutImmediately) => 21 + private void createModelBackedDrawable(bool hasIntermediate, bool showNullModel = false) => 24 22 Child = backedDrawable = new TestModelBackedDrawable 25 23 { 26 24 Anchor = Anchor.Centre, 27 25 Origin = Anchor.Centre, 28 26 Size = new Vector2(200), 29 - InternalFadeOutImmediately = fadeOutImmediately, 30 - HasPlaceholder = withPlaceholder 27 + HasIntermediate = hasIntermediate, 28 + ShowNullModel = showNullModel 31 29 }; 32 30 33 - [TestCase(false, false)] 34 - [TestCase(false, true)] 35 - [TestCase(true, false)] 36 - [TestCase(true, true)] 37 - public void TestDefaultState(bool withPlaceholder, bool fadeOutImmediately) 31 + [Test] 32 + public void TestEmptyDefaultState() 38 33 { 39 - AddStep("setup", () => createModelBackedDrawable(withPlaceholder, fadeOutImmediately)); 40 - 41 - if (withPlaceholder) 42 - AddAssert("placeholder displayed", () => backedDrawable.DisplayedDrawable is TestPlaceholder); 43 - else 44 - AddAssert("no drawable displayed", () => backedDrawable.DisplayedDrawable == null); 34 + AddStep("setup", () => createModelBackedDrawable(false)); 35 + AddAssert("nothing shown", () => backedDrawable.DisplayedDrawable == null); 45 36 } 46 37 47 - [TestCase(false, false)] 48 - [TestCase(false, true)] 49 - [TestCase(true, false)] 50 - [TestCase(true, true)] 51 - public void TestSingleDelayedLoad(bool withPlaceholder, bool fadeOutImmediately) 38 + [Test] 39 + public void TestModelDefaultState() 52 40 { 53 41 TestDrawableModel drawableModel = null; 54 42 55 - AddStep("setup", () => createModelBackedDrawable(withPlaceholder, fadeOutImmediately)); 56 - 57 - AddStep("set model", () => backedDrawable.Model = new TestModel(drawableModel = new TestDrawableModel(1))); 58 - 59 - if (withPlaceholder) 60 - AddAssert("placeholder displayed", () => backedDrawable.DisplayedDrawable is TestPlaceholder); 61 - else 62 - AddAssert("no drawable displayed", () => backedDrawable.DisplayedDrawable == null); 43 + AddStep("setup", () => 44 + { 45 + createModelBackedDrawable(false); 46 + backedDrawable.Model = new TestModel(drawableModel = new TestDrawableModel(1).With(d => d.AllowLoad.Set())); 47 + }); 63 48 64 - AddStep("allow load", () => drawableModel.AllowLoad.Set()); 65 - AddUntilStep("wait for model to be loaded", () => drawableModel.IsLoaded); 66 - 67 - AddAssert("model displayed", () => backedDrawable.DisplayedDrawable == drawableModel); 49 + assertDrawableVisibility(1, () => drawableModel); 68 50 } 69 51 70 - [TestCase(false, false)] 71 - [TestCase(false, true)] 72 - [TestCase(true, false)] 73 - [TestCase(true, true)] 74 - public void TestMultipleLoadDisplaysSinglePlaceholder(bool withPlaceholder, bool fadeOutImmediately) 52 + [TestCase(false)] 53 + [TestCase(true)] 54 + public void TestChangeModel(bool hasIntermediate) 75 55 { 76 56 TestDrawableModel firstModel = null; 77 57 TestDrawableModel secondModel = null; 78 - Drawable placeholder = null; 79 58 80 - AddStep("setup", () => createModelBackedDrawable(withPlaceholder, fadeOutImmediately)); 81 - 82 - AddStep("set first model", () => 59 + AddStep("setup", () => 83 60 { 84 - backedDrawable.Model = new TestModel(firstModel = new TestDrawableModel(1)); 85 - placeholder = backedDrawable.DisplayedDrawable; 61 + createModelBackedDrawable(hasIntermediate); 62 + backedDrawable.Model = new TestModel(firstModel = new TestDrawableModel(1).With(d => d.AllowLoad.Set())); 86 63 }); 87 64 65 + assertDrawableVisibility(1, () => firstModel); 66 + 88 67 AddStep("set second model", () => backedDrawable.Model = new TestModel(secondModel = new TestDrawableModel(2))); 89 - AddAssert("first placeholder still displayed", () => backedDrawable.DisplayedDrawable == placeholder); 90 - 91 - AddStep("allow first model to load", () => firstModel.AllowLoad.Set()); 92 - AddAssert("first placeholder still displayed", () => backedDrawable.DisplayedDrawable == placeholder); 68 + assertIntermediateVisibility(hasIntermediate, () => firstModel); 93 69 94 70 AddStep("allow second model to load", () => secondModel.AllowLoad.Set()); 95 - AddAssert("placeholder is not displayed", () => backedDrawable.DisplayedDrawable != placeholder); 71 + assertDrawableVisibility(2, () => secondModel); 96 72 } 97 73 98 - /// <summary> 99 - /// Covers <see cref="ModelBackedDrawable{T}.FadeOutImmediately"/> usage. 100 - /// </summary> 101 - [TestCase(false, false)] 102 - [TestCase(false, true)] 103 - [TestCase(true, false)] 104 - [TestCase(true, true)] 105 - public void TestIntermediaryPlaceholder(bool withPlaceholder, bool fadeOutImmediately) 74 + [TestCase(false)] 75 + [TestCase(true)] 76 + public void TestChangeModelDuringLoad(bool hasIntermediate) 106 77 { 107 78 TestDrawableModel firstModel = null; 108 79 TestDrawableModel secondModel = null; 109 - 110 - AddStep("setup", () => createModelBackedDrawable(withPlaceholder, fadeOutImmediately)); 111 - 112 - AddStep("set first model", () => backedDrawable.Model = new TestModel(firstModel = new TestDrawableModel(1))); 113 - 114 - if (withPlaceholder) 115 - AddAssert("placeholder is displayed", () => backedDrawable.DisplayedDrawable is TestPlaceholder); 116 - else 117 - AddAssert("nothing displayed", () => backedDrawable.DisplayedDrawable == null); 118 - 119 - AddStep("allow first model to load", () => firstModel.AllowLoad.Set()); 120 - AddUntilStep("wait for first model to be loaded", () => firstModel.IsLoaded); 121 - AddAssert("first model's drawable is displayed", () => backedDrawable.DisplayedDrawable == firstModel); 122 - 123 - AddStep("set second model", () => backedDrawable.Model = new TestModel(secondModel = new TestDrawableModel(2))); 124 - 125 - if (fadeOutImmediately) 126 - { 127 - if (withPlaceholder) 128 - AddAssert("placeholder is displayed", () => backedDrawable.DisplayedDrawable is TestPlaceholder); 129 - else 130 - AddAssert("nothing displayed", () => backedDrawable.DisplayedDrawable == null); 131 - } 132 - else 133 - AddAssert("first model's drawable is displayed", () => backedDrawable.DisplayedDrawable == firstModel); 134 - 135 - AddStep("allow second model to load", () => secondModel.AllowLoad.Set()); 136 - AddUntilStep("wait for second model to be loaded", () => secondModel.IsLoaded); 137 - AddAssert("second model's drawable is displayed", () => backedDrawable.DisplayedDrawable == secondModel); 138 - } 139 - 140 - [TestCase(false, false)] 141 - [TestCase(false, true)] 142 - [TestCase(true, false)] 143 - [TestCase(true, true)] 144 - public void TestSequentialDelayedLoad(bool withPlaceholder, bool fadeOutImmediately) 145 - { 146 - const int model_count = 3; 147 - 148 - var drawableModels = new List<TestDrawableModel>(model_count); 80 + TestDrawableModel thirdModel = null; 149 81 150 82 AddStep("setup", () => 151 83 { 152 - drawableModels.Clear(); 153 - createModelBackedDrawable(withPlaceholder, fadeOutImmediately); 84 + createModelBackedDrawable(hasIntermediate); 85 + backedDrawable.Model = new TestModel(firstModel = new TestDrawableModel(1).With(d => d.AllowLoad.Set())); 154 86 }); 155 87 156 - for (int i = 0; i < model_count; i++) 157 - { 158 - int localI = i; 159 - AddStep($"set model {i + 1}", () => 160 - { 161 - var model = new TestDrawableModel(localI + 1); 162 - drawableModels.Add(model); 88 + assertDrawableVisibility(1, () => firstModel); 163 89 164 - backedDrawable.Model = new TestModel(model); 165 - }); 166 - } 90 + AddStep("set second model", () => backedDrawable.Model = new TestModel(secondModel = new TestDrawableModel(2))); 91 + assertIntermediateVisibility(hasIntermediate, () => firstModel); 167 92 168 - // Due to potential left-over threading from elsewhere, we may have to wait for all models to get into a loading state 169 - AddUntilStep("all loading", () => drawableModels.TrueForAll(d => d.LoadState == LoadState.Loading)); 93 + AddStep("set third model", () => backedDrawable.Model = new TestModel(thirdModel = new TestDrawableModel(3))); 94 + assertIntermediateVisibility(hasIntermediate, () => firstModel); 170 95 171 - for (int i = 0; i < model_count - 1; i++) 172 - { 173 - int localI = i; 174 - AddStep($"allow model {i + 1} to load", () => drawableModels[localI].AllowLoad.Set()); 175 - if (withPlaceholder) 176 - AddAssert("placeholder displayed", () => backedDrawable.DisplayedDrawable is TestPlaceholder); 177 - else 178 - AddAssert("no model displayed", () => backedDrawable.DisplayedDrawable == null); 179 - AddWaitStep("wait for potential load", 5); 180 - AddAssert($"model {i + 1} not loaded", () => !drawableModels[localI].IsLoaded); 181 - } 96 + AddStep("allow second model to load", () => secondModel.AllowLoad.Set()); 97 + assertIntermediateVisibility(hasIntermediate, () => firstModel); 182 98 183 - AddStep($"allow model {model_count} to load", () => drawableModels[model_count - 1].AllowLoad.Set()); 184 - AddUntilStep($"model {model_count} loaded", () => drawableModels[model_count - 1].IsLoaded); 185 - AddAssert($"model {model_count} displayed", () => backedDrawable.DisplayedDrawable == drawableModels[model_count - 1]); 99 + AddStep("allow third model to load", () => thirdModel.AllowLoad.Set()); 100 + assertDrawableVisibility(3, () => thirdModel); 186 101 } 187 102 188 - [TestCase(false, false)] 189 - [TestCase(false, true)] 190 - [TestCase(true, false)] 191 - [TestCase(true, true)] 192 - public void TestOutOfOrderDelayedLoad(bool withPlaceholder, bool fadeOutImmediately) 103 + [TestCase(false)] 104 + [TestCase(true)] 105 + public void TestOutOfOrderLoad(bool hasIntermediate) 193 106 { 194 - const int model_count = 3; 195 - 196 - var drawableModels = new List<TestDrawableModel>(model_count); 107 + TestDrawableModel firstModel = null; 108 + TestDrawableModel secondModel = null; 197 109 198 110 AddStep("setup", () => 199 111 { 200 - drawableModels.Clear(); 201 - createModelBackedDrawable(withPlaceholder, fadeOutImmediately); 112 + createModelBackedDrawable(hasIntermediate); 113 + backedDrawable.Model = new TestModel(firstModel = new TestDrawableModel(1).With(d => d.AllowLoad.Set())); 202 114 }); 203 115 204 - for (int i = 0; i < model_count; i++) 205 - { 206 - int localI = i; 207 - AddStep($"set model {i + 1}", () => 208 - { 209 - var model = new TestDrawableModel(localI + 1); 210 - drawableModels.Add(model); 116 + assertDrawableVisibility(1, () => firstModel); 211 117 212 - backedDrawable.Model = new TestModel(model); 213 - }); 214 - } 118 + AddStep("set second model", () => backedDrawable.Model = new TestModel(secondModel = new TestDrawableModel(2))); 119 + assertIntermediateVisibility(hasIntermediate, () => firstModel); 215 120 216 - // Due to potential left-over threading from elsewhere, we may have to wait for all models to get into a loading state 217 - AddUntilStep("all loading", () => drawableModels.TrueForAll(d => d.LoadState == LoadState.Loading)); 121 + AddStep("allow second model to load", () => secondModel.AllowLoad.Set()); 122 + assertDrawableVisibility(2, () => secondModel); 218 123 219 - AddStep($"allow model {model_count} to load", () => drawableModels[model_count - 1].AllowLoad.Set()); 220 - AddUntilStep($"model {model_count} loaded", () => drawableModels[model_count - 1].IsLoaded); 221 - AddAssert($"model {model_count} displayed", () => backedDrawable.DisplayedDrawable == drawableModels[model_count - 1]); 222 - 223 - for (int i = model_count - 2; i >= 0; i--) 224 - { 225 - int localI = i; 226 - AddStep($"allow model {i + 1} to load", () => drawableModels[localI].AllowLoad.Set()); 227 - AddWaitStep("wait for potential load", 5); 228 - AddAssert($"model {model_count} still displayed", () => backedDrawable.DisplayedDrawable == drawableModels[model_count - 1]); 229 - } 124 + AddStep("allow first model to load", () => firstModel.AllowLoad.Set()); 125 + assertDrawableVisibility(2, () => secondModel); 230 126 } 231 127 232 - [TestCase(false, false)] 233 - [TestCase(false, true)] 234 - [TestCase(true, false)] 235 - [TestCase(true, true)] 236 - public void TestSetNullModel(bool withPlaceholder, bool fadeOutImmediately) 128 + [Test] 129 + public void TestSetNullModel() 237 130 { 238 131 TestDrawableModel drawableModel = null; 239 132 240 - AddStep("setup", () => createModelBackedDrawable(withPlaceholder, fadeOutImmediately)); 241 - 242 - AddStep("set model", () => 133 + AddStep("setup", () => 243 134 { 244 - backedDrawable.Model = new TestModel(drawableModel = new TestDrawableModel(1)); 245 - drawableModel.AllowLoad.Set(); 135 + createModelBackedDrawable(false, true); 136 + backedDrawable.Model = new TestModel(drawableModel = new TestDrawableModel(1).With(d => d.AllowLoad.Set())); 246 137 }); 247 138 248 - AddUntilStep("model is displayed", () => backedDrawable.DisplayedDrawable == drawableModel); 139 + assertDrawableVisibility(1, () => drawableModel); 249 140 250 141 AddStep("set null model", () => backedDrawable.Model = null); 142 + AddUntilStep("null model shown", () => backedDrawable.DisplayedDrawable is TestNullDrawableModel); 143 + } 251 144 252 - if (withPlaceholder) 253 - AddAssert("placeholder displayed", () => backedDrawable.DisplayedDrawable is TestPlaceholder); 145 + private void assertIntermediateVisibility(bool hasIntermediate, Func<Drawable> getLastFunc) 146 + { 147 + if (hasIntermediate) 148 + AddAssert("no drawable visible", () => backedDrawable.DisplayedDrawable == null); 254 149 else 255 - AddAssert("no drawable displayed", () => backedDrawable.DisplayedDrawable == null); 150 + AddUntilStep("last drawable visible", () => backedDrawable.DisplayedDrawable == getLastFunc()); 151 + } 152 + 153 + private void assertDrawableVisibility(int id, Func<Drawable> getFunc) 154 + { 155 + AddUntilStep($"model {id} visible", () => backedDrawable.DisplayedDrawable == getFunc()); 256 156 } 257 157 258 158 private class TestModel ··· 267 167 268 168 private class TestDrawableModel : CompositeDrawable 269 169 { 170 + private readonly int id; 171 + 270 172 public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(false); 271 173 272 - public TestDrawableModel(int id) 273 - : this($"Model {id}") 174 + protected virtual Color4 BackgroundColour 274 175 { 176 + get 177 + { 178 + switch (id % 5) 179 + { 180 + default: 181 + return Color4.SkyBlue; 182 + 183 + case 1: 184 + return Color4.Tomato; 185 + 186 + case 2: 187 + return Color4.DarkGreen; 188 + 189 + case 3: 190 + return Color4.MediumPurple; 191 + 192 + case 4: 193 + return Color4.DarkOrchid; 194 + } 195 + } 275 196 } 276 197 277 - protected TestDrawableModel(string text) 198 + public TestDrawableModel(int id) 278 199 { 200 + this.id = id; 201 + 279 202 RelativeSizeAxes = Axes.Both; 280 203 281 204 InternalChildren = new Drawable[] ··· 283 206 new Box 284 207 { 285 208 RelativeSizeAxes = Axes.Both, 286 - Colour = Color4.SkyBlue 209 + Colour = BackgroundColour 287 210 }, 288 211 new SpriteText 289 212 { 290 213 Anchor = Anchor.Centre, 291 214 Origin = Anchor.Centre, 292 - Text = text 215 + Text = id > 0 ? $"model {id}" : "null" 293 216 } 294 217 }; 295 218 } ··· 303 226 } 304 227 } 305 228 306 - private class TestPlaceholder : TestDrawableModel 229 + private class TestNullDrawableModel : TestDrawableModel 307 230 { 308 - public TestPlaceholder() 309 - : base("Placeholder") 231 + protected override Color4 BackgroundColour => Color4.SlateGray; 232 + 233 + public TestNullDrawableModel() 234 + : base(0) 310 235 { 311 236 AllowLoad.Set(); 312 237 } ··· 314 239 315 240 private class TestModelBackedDrawable : ModelBackedDrawable<TestModel> 316 241 { 242 + public bool ShowNullModel; 243 + 244 + public bool HasIntermediate; 245 + 317 246 protected override Drawable CreateDrawable(TestModel model) 318 247 { 319 - if (model == null) 320 - return HasPlaceholder ? new TestPlaceholder() : null; 248 + if (model == null && ShowNullModel) 249 + return new TestNullDrawableModel(); 321 250 322 - return model.DrawableModel; 251 + return model?.DrawableModel; 323 252 } 324 253 325 254 public new Drawable DisplayedDrawable => base.DisplayedDrawable; 326 255 327 256 public new TestModel Model 328 257 { 329 - get => base.Model; 330 258 set => base.Model = value; 331 259 } 332 260 333 - public bool InternalFadeOutImmediately; 334 - 335 - public bool HasPlaceholder; 336 - 337 - protected override bool FadeOutImmediately => InternalFadeOutImmediately; 261 + protected override bool TransformImmediately => HasIntermediate; 338 262 } 339 263 } 340 264 }
+204
osu.Framework.Tests/Visual/Drawables/TestSceneModelBackedDrawableWithLoading.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System.Threading; 5 + using NUnit.Framework; 6 + using osu.Framework.Allocation; 7 + using osu.Framework.Graphics; 8 + using osu.Framework.Graphics.Containers; 9 + using osu.Framework.Graphics.Shapes; 10 + using osu.Framework.Graphics.Sprites; 11 + using osuTK; 12 + using osuTK.Graphics; 13 + 14 + namespace osu.Framework.Tests.Visual.Drawables 15 + { 16 + public class TestSceneModelBackedDrawableWithLoading : FrameworkTestScene 17 + { 18 + private TestModelBackedDrawable backedDrawable; 19 + 20 + private void createModelBackedDrawable(bool immediate) => 21 + Child = backedDrawable = new TestModelBackedDrawable(immediate) 22 + { 23 + Anchor = Anchor.Centre, 24 + Origin = Anchor.Centre, 25 + Size = new Vector2(200), 26 + }; 27 + 28 + [Test] 29 + public void TestNonImmediateTransform() 30 + { 31 + AddStep("setup", () => createModelBackedDrawable(false)); 32 + addUsageSteps(); 33 + } 34 + 35 + [Test] 36 + public void TestTransformImmediately() 37 + { 38 + AddStep("setup", () => createModelBackedDrawable(true)); 39 + addUsageSteps(); 40 + } 41 + 42 + private void addUsageSteps() 43 + { 44 + TestDrawableModel drawableModel = null; 45 + 46 + AddStep("load first model", () => backedDrawable.Model = new TestModel(drawableModel = new TestDrawableModel())); 47 + AddWaitStep("wait a bit", 5); 48 + AddStep("finish load", () => drawableModel.AllowLoad.Set()); 49 + AddWaitStep("wait a bit", 5); 50 + AddStep("load second model", () => backedDrawable.Model = new TestModel(drawableModel = new TestDrawableModel())); 51 + AddWaitStep("wait a bit", 5); 52 + AddStep("finish load", () => drawableModel.AllowLoad.Set()); 53 + } 54 + 55 + private class TestModelBackedDrawable : ModelBackedDrawable<TestModel> 56 + { 57 + public new TestModel Model 58 + { 59 + set => base.Model = value; 60 + } 61 + 62 + private readonly bool immediate; 63 + private readonly Drawable spinner; 64 + 65 + public TestModelBackedDrawable(bool immediate) 66 + { 67 + this.immediate = immediate; 68 + 69 + CornerRadius = 5; 70 + Masking = true; 71 + 72 + AddRangeInternal(new[] 73 + { 74 + new Box 75 + { 76 + Colour = new Color4(0.1f, 0.1f, 0.1f, 1), 77 + RelativeSizeAxes = Axes.Both, 78 + Depth = float.MaxValue 79 + }, 80 + spinner = new LoadingSpinner 81 + { 82 + Anchor = Anchor.Centre, 83 + Origin = Anchor.Centre, 84 + Size = new Vector2(20), 85 + Alpha = 0, 86 + Depth = float.MinValue 87 + } 88 + }); 89 + } 90 + 91 + protected override bool TransformImmediately => immediate; 92 + 93 + protected override double TransformDuration => 500; 94 + 95 + protected override Drawable CreateDrawable(TestModel model) => model?.Drawable; 96 + 97 + protected override void OnLoadStarted() 98 + { 99 + base.OnLoadStarted(); 100 + 101 + if (!immediate) 102 + DisplayedDrawable?.FadeTo(0, 300, Easing.OutQuint); 103 + 104 + spinner.FadeIn(300, Easing.OutQuint); 105 + } 106 + 107 + protected override void OnLoadFinished() 108 + { 109 + base.OnLoadFinished(); 110 + 111 + spinner.FadeOut(250, Easing.OutQuint); 112 + } 113 + } 114 + 115 + private class TestModel 116 + { 117 + public readonly Drawable Drawable; 118 + 119 + public TestModel(TestDrawableModel drawable) 120 + { 121 + Drawable = drawable; 122 + } 123 + } 124 + 125 + private class TestDrawableModel : CompositeDrawable 126 + { 127 + private static int id = 1; 128 + 129 + public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(); 130 + 131 + protected Color4 BackgroundColour 132 + { 133 + get 134 + { 135 + switch (id % 5) 136 + { 137 + default: 138 + return Color4.SkyBlue; 139 + 140 + case 1: 141 + return Color4.Tomato; 142 + 143 + case 2: 144 + return Color4.DarkGreen; 145 + 146 + case 3: 147 + return Color4.MediumPurple; 148 + 149 + case 4: 150 + return Color4.DarkOrchid; 151 + } 152 + } 153 + } 154 + 155 + public TestDrawableModel() 156 + { 157 + RelativeSizeAxes = Axes.Both; 158 + 159 + InternalChildren = new Drawable[] 160 + { 161 + new Box 162 + { 163 + RelativeSizeAxes = Axes.Both, 164 + Colour = BackgroundColour 165 + }, 166 + new SpriteText 167 + { 168 + Anchor = Anchor.Centre, 169 + Origin = Anchor.Centre, 170 + Text = $"Model {id++}" 171 + } 172 + }; 173 + } 174 + 175 + [BackgroundDependencyLoader] 176 + private void load() 177 + { 178 + AllowLoad.Wait(); 179 + } 180 + } 181 + 182 + private class LoadingSpinner : CompositeDrawable 183 + { 184 + private readonly SpriteIcon icon; 185 + 186 + public LoadingSpinner() 187 + { 188 + InternalChild = icon = new SpriteIcon 189 + { 190 + Anchor = Anchor.Centre, 191 + Origin = Anchor.Centre, 192 + RelativeSizeAxes = Axes.Both, 193 + Icon = FontAwesome.Solid.Spinner 194 + }; 195 + } 196 + 197 + protected override void LoadComplete() 198 + { 199 + base.LoadComplete(); 200 + icon.Spin(2000, RotationDirection.Clockwise); 201 + } 202 + } 203 + } 204 + }
+1 -2
osu.Framework.Tests/Visual/Drawables/TestScenePropertyBoundaries.cs
··· 4 4 using osu.Framework.Allocation; 5 5 using osu.Framework.Graphics; 6 6 using osu.Framework.Graphics.Shapes; 7 - using osu.Framework.Testing; 8 7 using osuTK; 9 8 10 9 namespace osu.Framework.Tests.Visual.Drawables 11 10 { 12 11 [System.ComponentModel.Description("ensure validity of drawables when receiving certain values")] 13 - public class TestScenePropertyBoundaries : TestScene 12 + public class TestScenePropertyBoundaries : FrameworkTestScene 14 13 { 15 14 [BackgroundDependencyLoader] 16 15 private void load()
+27 -4
osu.Framework.Tests/Visual/Drawables/TestSceneProxyDrawables.cs
··· 6 6 using osu.Framework.Graphics.Containers; 7 7 using osu.Framework.Graphics.Shapes; 8 8 using osu.Framework.Graphics.Sprites; 9 - using osu.Framework.Testing; 10 9 using osuTK; 11 10 using osuTK.Graphics; 12 11 13 12 namespace osu.Framework.Tests.Visual.Drawables 14 13 { 15 - public class TestSceneProxyDrawables : TestScene 14 + public class TestSceneProxyDrawables : FrameworkTestScene 16 15 { 17 16 public TestSceneProxyDrawables() 18 17 { ··· 45 44 generateProxyBelowParentOriginalIndirectlyMaskedAway(1), 46 45 generateProxyAboveParentOriginalIndirectlyMaskedAway(6), 47 46 generateProxyBelowParentOriginalIndirectlyMaskedAway(6), 47 + generateOpaqueProxyAboveOpaqueBox(), 48 48 } 49 49 } 50 50 }; ··· 340 340 }; 341 341 } 342 342 343 + private Drawable generateOpaqueProxyAboveOpaqueBox() 344 + { 345 + var box = new Box 346 + { 347 + Anchor = Anchor.Centre, 348 + Origin = Anchor.Centre, 349 + Size = new Vector2(50), 350 + }; 351 + 352 + var proxy = box.CreateProxy(); 353 + 354 + return new Visualiser("proxy above opaque box") 355 + { 356 + Children = new Drawable[] 357 + { 358 + box, 359 + new ProxyVisualiser(proxy, false, 1.0f) 360 + } 361 + }; 362 + } 363 + 343 364 private class NonPresentContainer : Container 344 365 { 345 366 private bool isPresent = true; ··· 407 428 private readonly Drawable original; 408 429 private readonly Drawable overlay; 409 430 410 - public ProxyVisualiser(Drawable proxy, bool proxyIsBelow) 431 + public ProxyVisualiser(Drawable proxy, bool proxyIsBelow, float boxAlpha = 0.5f) 411 432 { 412 433 RelativeSizeAxes = Axes.Both; 413 434 414 435 original = proxy.Original; 436 + 415 437 while (original != (original = original.Original)) 416 438 { 417 439 } ··· 427 449 new Box 428 450 { 429 451 RelativeSizeAxes = Axes.Both, 430 - Alpha = 0.5f, 452 + Alpha = boxAlpha, 431 453 }, 432 454 new SpriteText 433 455 { 434 456 Anchor = Anchor.BottomCentre, 435 457 Origin = Anchor.BottomCentre, 458 + Colour = Color4.Black, 436 459 Text = "proxy" 437 460 } 438 461 }
+2 -2
osu.Framework.Tests/Visual/Drawables/TestSceneTransformRewinding.cs
··· 12 12 using osu.Framework.Graphics.Sprites; 13 13 using osu.Framework.Graphics.Transforms; 14 14 using osu.Framework.MathUtils; 15 - using osu.Framework.Testing; 16 15 using osu.Framework.Timing; 17 16 using osuTK; 18 17 using osuTK.Graphics; 19 18 20 19 namespace osu.Framework.Tests.Visual.Drawables 21 20 { 22 - public class TestSceneTransformRewinding : TestScene 21 + public class TestSceneTransformRewinding : FrameworkTestScene 23 22 { 24 23 private const double interval = 250; 25 24 private const int interval_count = 4; ··· 406 405 maxTimeText.Text = wrapping.MaxTime.ToString("n0"); 407 406 maxTimeText.Colour = time > wrapping.MaxTime ? Color4.Gray : wrapping.Time.Elapsed > 0 ? Color4.Blue : Color4.Red; 408 407 minTimeText.Colour = time < wrapping.MinTime ? Color4.Gray : content.Time.Elapsed > 0 ? Color4.Blue : Color4.Red; 408 + 409 409 if (displayedTransforms == null || !ExaminableDrawable.Transforms.SequenceEqual(displayedTransforms)) 410 410 { 411 411 transforms.Clear();
+1 -2
osu.Framework.Tests/Visual/Drawables/TestSceneUpdateBeforeDraw.cs
··· 7 7 using osu.Framework.Graphics.Containers; 8 8 using osu.Framework.Graphics.Shapes; 9 9 using osu.Framework.Graphics.Sprites; 10 - using osu.Framework.Testing; 11 10 using osuTK; 12 11 using osuTK.Graphics; 13 12 14 13 namespace osu.Framework.Tests.Visual.Drawables 15 14 { 16 15 [Description("Tests whether drawable updates occur before drawing.")] 17 - public class TestSceneUpdateBeforeDraw : TestScene 16 + public class TestSceneUpdateBeforeDraw : FrameworkTestScene 18 17 { 19 18 /// <summary> 20 19 /// Tests whether a <see cref="Drawable"/> is updated before being drawn when it is added to a parent
+6 -4
osu.Framework.Tests/Visual/Drawables/TestSceneWaveform.cs
··· 31 31 }; 32 32 33 33 private Button button; 34 - private TrackBass track; 34 + private Track track; 35 35 private Waveform waveform; 36 36 private Container<Drawable> waveformContainer; 37 37 private readonly Bindable<float> zoom = new BindableFloat(1) { MinValue = 0.1f, MaxValue = 20 }; ··· 39 39 [BackgroundDependencyLoader] 40 40 private void load(Game game, AudioManager audio) 41 41 { 42 - track = new TrackBass(game.Resources.GetStream("Tracks/sample-track.mp3")); 43 - audio.Track.AddItem(track); 42 + var store = audio.GetTrackStore(game.Resources); 44 43 45 - waveform = new Waveform(game.Resources.GetStream("Tracks/sample-track.mp3")); 44 + const string track_name = "Tracks/sample-track.mp3"; 45 + 46 + track = store.Get(track_name); 47 + waveform = new Waveform(store.GetStream(track_name)); 46 48 47 49 const float track_width = 1366; // required because RelativeSizeAxes.X doesn't seem to work with horizontal scroll 48 50
+1 -2
osu.Framework.Tests/Visual/Input/TestSceneInputManager.cs
··· 11 11 using osu.Framework.Input; 12 12 using osu.Framework.Input.Events; 13 13 using osu.Framework.Input.Handlers.Mouse; 14 - using osu.Framework.Testing; 15 14 using osuTK; 16 15 using osuTK.Graphics; 17 16 18 17 namespace osu.Framework.Tests.Visual.Input 19 18 { 20 - public class TestSceneInputManager : TestScene 19 + public class TestSceneInputManager : FrameworkTestScene 21 20 { 22 21 public TestSceneInputManager() 23 22 {
+1
osu.Framework.Tests/Visual/Input/TestSceneInputResampler.cs
··· 145 145 { 146 146 NumRaw++; 147 147 bool foundOne = false; 148 + 148 149 foreach (Vector2 relevant in InputResampler.AddPosition(pos)) 149 150 { 150 151 AddVertex(relevant);
+1 -2
osu.Framework.Tests/Visual/Input/TestSceneJoystick.cs
··· 7 7 using osu.Framework.Graphics.Sprites; 8 8 using osu.Framework.Input; 9 9 using osu.Framework.Input.Events; 10 - using osu.Framework.Testing; 11 10 using osuTK; 12 11 using osuTK.Graphics; 13 12 14 13 namespace osu.Framework.Tests.Visual.Input 15 14 { 16 - public class TestSceneJoystick : TestScene 15 + public class TestSceneJoystick : FrameworkTestScene 17 16 { 18 17 public TestSceneJoystick() 19 18 {
+2
osu.Framework.Tests/Visual/Input/TestSceneKeyBindings.cs
··· 103 103 104 104 Assert.AreEqual(count.OnPressedCount, testButton.OnPressedCount, $"{testButton.Concurrency} {testButton.Action} OnPressedCount"); 105 105 Assert.AreEqual(count.OnReleasedCount, testButton.OnReleasedCount, $"{testButton.Concurrency} {testButton.Action} OnReleasedCount"); 106 + 106 107 if (testButton is ScrollTestButton scrollTestButton && scrollEntry != null) 107 108 { 108 109 Assert.AreEqual(count.OnScrollCount, scrollTestButton.OnScrollCount, $"{testButton.Concurrency} {testButton.Action} OnScrollCount"); ··· 155 156 toggleKey(key); 156 157 foreach (var button in pressedMouseButtons.ToArray()) 157 158 toggleMouseButton(button); 159 + 158 160 foreach (var mode in new[] { none, noneExact, noneModifiers, unique, all }) 159 161 { 160 162 foreach (var action in Enum.GetValues(typeof(TestAction)).Cast<TestAction>())
+2
osu.Framework.Tests/Visual/Input/TestSceneMouseStates.cs
··· 460 460 case MouseMoveEvent mouseMove: 461 461 LastDelta = mouseMove.ScreenSpaceMousePosition - mouseMove.ScreenSpaceLastMousePosition; 462 462 break; 463 + 463 464 case ScrollEvent scroll: 464 465 LastScrollDelta = scroll.ScrollDelta; 465 466 break; ··· 489 490 base.Update(); 490 491 491 492 var inputManager = GetContainingInputManager(); 493 + 492 494 if (inputManager != null) 493 495 { 494 496 var state = inputManager.CurrentState;
+1 -2
osu.Framework.Tests/Visual/Input/TestSceneNestedHover.cs
··· 5 5 using osu.Framework.Graphics.Containers; 6 6 using osu.Framework.Graphics.Shapes; 7 7 using osu.Framework.Input.Events; 8 - using osu.Framework.Testing; 9 8 using osuTK; 10 9 using osuTK.Graphics; 11 10 12 11 namespace osu.Framework.Tests.Visual.Input 13 12 { 14 - public class TestSceneNestedHover : TestScene 13 + public class TestSceneNestedHover : FrameworkTestScene 15 14 { 16 15 public TestSceneNestedHover() 17 16 {
+1 -2
osu.Framework.Tests/Visual/Input/TestScenePathInput.cs
··· 8 8 using osu.Framework.Graphics.Shapes; 9 9 using osu.Framework.Graphics.Sprites; 10 10 using osu.Framework.Input.Events; 11 - using osu.Framework.Testing; 12 11 using osuTK; 13 12 using osuTK.Graphics; 14 13 15 14 namespace osu.Framework.Tests.Visual.Input 16 15 { 17 - public class TestScenePathInput : TestScene 16 + public class TestScenePathInput : FrameworkTestScene 18 17 { 19 18 private const float path_width = 50; 20 19 private const float path_radius = path_width / 2;
+9 -2
osu.Framework.Tests/Visual/Layout/TestSceneFillFlowContainer.cs
··· 10 10 using osu.Framework.Graphics.Sprites; 11 11 using osu.Framework.Graphics.UserInterface; 12 12 using osu.Framework.MathUtils; 13 - using osu.Framework.Testing; 14 13 using osu.Framework.Threading; 15 14 using osuTK; 16 15 using osuTK.Graphics; 17 16 18 17 namespace osu.Framework.Tests.Visual.Layout 19 18 { 20 - public class TestSceneFillFlowContainer : TestScene 19 + public class TestSceneFillFlowContainer : FrameworkTestScene 21 20 { 22 21 private FillDirectionDropdown selectionDropdown; 23 22 ··· 266 265 case 0: 267 266 child.Origin = Anchor.TopLeft; 268 267 break; 268 + 269 269 case 1: 270 270 child.Origin = Anchor.TopCentre; 271 271 break; 272 + 272 273 case 2: 273 274 child.Origin = Anchor.TopRight; 274 275 break; 276 + 275 277 case 3: 276 278 child.Origin = Anchor.CentreLeft; 277 279 break; 280 + 278 281 case 4: 279 282 child.Origin = Anchor.Centre; 280 283 break; 284 + 281 285 case 5: 282 286 child.Origin = Anchor.CentreRight; 283 287 break; 288 + 284 289 case 6: 285 290 child.Origin = Anchor.BottomLeft; 286 291 break; 292 + 287 293 case 7: 288 294 child.Origin = Anchor.BottomCentre; 289 295 break; 296 + 290 297 case 8: 291 298 child.Origin = Anchor.BottomRight; 292 299 break;
+1 -2
osu.Framework.Tests/Visual/Layout/TestSceneFitInsideFlow.cs
··· 5 5 using osu.Framework.Graphics; 6 6 using osu.Framework.Graphics.Containers; 7 7 using osu.Framework.Graphics.Shapes; 8 - using osu.Framework.Testing; 9 8 using osuTK; 10 9 11 10 namespace osu.Framework.Tests.Visual.Layout 12 11 { 13 12 [TestFixture] 14 - public class TestSceneFitInsideFlow : TestScene 13 + public class TestSceneFitInsideFlow : FrameworkTestScene 15 14 { 16 15 private const float container_width = 60; 17 16
+1 -2
osu.Framework.Tests/Visual/Layout/TestSceneGridContainer.cs
··· 10 10 using osu.Framework.Graphics.Containers; 11 11 using osu.Framework.Graphics.Shapes; 12 12 using osu.Framework.MathUtils; 13 - using osu.Framework.Testing; 14 13 using osuTK; 15 14 using osuTK.Graphics; 16 15 17 16 namespace osu.Framework.Tests.Visual.Layout 18 17 { 19 - public class TestSceneGridContainer : TestScene 18 + public class TestSceneGridContainer : FrameworkTestScene 20 19 { 21 20 private Container gridParent; 22 21 private GridContainer grid;
+1 -2
osu.Framework.Tests/Visual/Layout/TestSceneLayoutDurations.cs
··· 7 7 using osu.Framework.Graphics.Shapes; 8 8 using osu.Framework.Input.Events; 9 9 using osu.Framework.MathUtils; 10 - using osu.Framework.Testing; 11 10 using osu.Framework.Timing; 12 11 using osuTK; 13 12 using osuTK.Graphics; 14 13 15 14 namespace osu.Framework.Tests.Visual.Layout 16 15 { 17 - public class TestSceneLayoutDurations : TestScene 16 + public class TestSceneLayoutDurations : FrameworkTestScene 18 17 { 19 18 private ManualClock manualClock; 20 19 private Container autoSizeContainer;
+1 -2
osu.Framework.Tests/Visual/Layout/TestSceneLayoutTransformRewinding.cs
··· 6 6 using osu.Framework.Graphics.Containers; 7 7 using osu.Framework.Graphics.Shapes; 8 8 using osu.Framework.MathUtils; 9 - using osu.Framework.Testing; 10 9 using osuTK; 11 10 12 11 namespace osu.Framework.Tests.Visual.Layout 13 12 { 14 13 [System.ComponentModel.Description("Rewinding of transforms that are important to layout.")] 15 - public class TestSceneLayoutTransformRewinding : TestScene 14 + public class TestSceneLayoutTransformRewinding : FrameworkTestScene 16 15 { 17 16 private readonly ManualUpdateSubTreeContainer manualContainer; 18 17
+1 -2
osu.Framework.Tests/Visual/Layout/TestSceneScrollableFlow.cs
··· 5 5 using osu.Framework.Graphics.Containers; 6 6 using osu.Framework.Graphics.Shapes; 7 7 using osu.Framework.MathUtils; 8 - using osu.Framework.Testing; 9 8 using osu.Framework.Threading; 10 9 using osuTK; 11 10 using osuTK.Graphics; 12 11 13 12 namespace osu.Framework.Tests.Visual.Layout 14 13 { 15 - public class TestSceneScrollableFlow : TestScene 14 + public class TestSceneScrollableFlow : FrameworkTestScene 16 15 { 17 16 private readonly ScheduledDelegate boxCreator; 18 17
+1 -2
osu.Framework.Tests/Visual/Layout/TestSceneTableContainer.cs
··· 8 8 using osu.Framework.Graphics.Shapes; 9 9 using osu.Framework.Graphics.Sprites; 10 10 using osu.Framework.MathUtils; 11 - using osu.Framework.Testing; 12 11 using osuTK; 13 12 using osuTK.Graphics; 14 13 15 14 namespace osu.Framework.Tests.Visual.Layout 16 15 { 17 - public class TestSceneTableContainer : TestScene 16 + public class TestSceneTableContainer : FrameworkTestScene 18 17 { 19 18 private TableContainer table; 20 19
+1 -2
osu.Framework.Tests/Visual/Platform/TestSceneBorderless.cs
··· 12 12 using osu.Framework.Graphics.Shapes; 13 13 using osu.Framework.Graphics.Sprites; 14 14 using osu.Framework.Platform; 15 - using osu.Framework.Testing; 16 15 using osuTK; 17 16 using osuTK.Graphics; 18 17 19 18 namespace osu.Framework.Tests.Visual.Platform 20 19 { 21 - public class TestSceneBorderless : TestScene 20 + public class TestSceneBorderless : FrameworkTestScene 22 21 { 23 22 private readonly SpriteText currentActualSize = new SpriteText(); 24 23 private readonly SpriteText currentClientSize = new SpriteText();
+3 -3
osu.Framework.Tests/Visual/Platform/TestSceneFullscreen.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System.Drawing; 5 + using System.Linq; 5 6 using osu.Framework.Allocation; 6 7 using osu.Framework.Bindables; 7 8 using osu.Framework.Configuration; 8 9 using osu.Framework.Graphics.Containers; 9 10 using osu.Framework.Graphics.Sprites; 10 11 using osu.Framework.Platform; 11 - using osu.Framework.Testing; 12 12 13 13 namespace osu.Framework.Tests.Visual.Platform 14 14 { 15 - public class TestSceneFullscreen : TestScene 15 + public class TestSceneFullscreen : FrameworkTestScene 16 16 { 17 17 private readonly SpriteText currentActualSize = new SpriteText(); 18 18 private readonly SpriteText currentWindowMode = new SpriteText(); 19 19 private readonly SpriteText currentDisplay = new SpriteText(); 20 20 private readonly SpriteText supportedWindowModes = new SpriteText(); 21 21 22 - private GameWindow window; 22 + private IWindow window; 23 23 private readonly BindableSize sizeFullscreen = new BindableSize(); 24 24 private readonly Bindable<WindowMode> windowMode = new Bindable<WindowMode>(); 25 25
+134
osu.Framework.Tests/Visual/Platform/TestSceneResourceStores.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using System.Collections.Generic; 6 + using System.Linq; 7 + using NUnit.Framework; 8 + using osu.Framework.Allocation; 9 + using osu.Framework.Audio; 10 + using osu.Framework.Graphics; 11 + using osu.Framework.Graphics.Containers; 12 + using osu.Framework.Graphics.Shapes; 13 + using osu.Framework.Graphics.Sprites; 14 + using osu.Framework.Graphics.Textures; 15 + using osu.Framework.IO.Stores; 16 + using osu.Framework.Platform; 17 + using osuTK; 18 + using osuTK.Graphics; 19 + 20 + namespace osu.Framework.Tests.Visual.Platform 21 + { 22 + public class TestSceneResourceStores : FrameworkTestScene 23 + { 24 + private FillFlowContainer<ResourceDisplay> flow; 25 + 26 + private FontStore fontStore; 27 + private TextureStore textureStore; 28 + private Storage storage; 29 + private AudioManager audioManager; 30 + 31 + [BackgroundDependencyLoader] 32 + private void load(FontStore fontStore, Storage storage, TextureStore textureStore, AudioManager audioManager) 33 + { 34 + Child = new ScrollContainer 35 + { 36 + RelativeSizeAxes = Axes.Both, 37 + Child = flow = new FillFlowContainer<ResourceDisplay> 38 + { 39 + Margin = new MarginPadding(10), 40 + Spacing = new Vector2(3), 41 + RelativeSizeAxes = Axes.X, 42 + AutoSizeAxes = Axes.Y, 43 + Direction = FillDirection.Vertical, 44 + }, 45 + }; 46 + 47 + this.fontStore = fontStore; 48 + this.textureStore = textureStore; 49 + this.audioManager = audioManager; 50 + this.storage = storage; 51 + } 52 + 53 + [Test] 54 + public void TestFontStore() => showResources(fontStore.GetAvailableResources(), fontStore.Get); 55 + 56 + [Test] 57 + public void TestStorageBackedResourceStore() => showResources(new StorageBackedResourceStore(storage)); 58 + 59 + [Test] 60 + public void TestGetTextureStore() => showResources(textureStore.GetAvailableResources(), textureStore.Get); 61 + 62 + [Test] 63 + public void TestGetTrackManager() => showResources(audioManager.Tracks); 64 + 65 + private void showResources<T>(IResourceStore<T> store) => showResources(store.GetAvailableResources(), store.Get); 66 + 67 + private void showResources<T>(IEnumerable<string> resources, Func<string, T> fetch) 68 + { 69 + AddStep("list resources", () => 70 + { 71 + flow.Clear(); 72 + 73 + foreach (var resourceName in resources) 74 + flow.Add(new ResourceDisplay(resourceName, fetch(resourceName))); 75 + }); 76 + 77 + AddAssert("ensure all loaded", () => flow.Children.All(rd => rd.Resource != null)); 78 + } 79 + 80 + private class ResourceDisplay : Container 81 + { 82 + public readonly object Resource; 83 + 84 + public ResourceDisplay(string name, object resource) 85 + { 86 + Resource = resource; 87 + 88 + AutoSizeAxes = Axes.Y; 89 + RelativeSizeAxes = Axes.X; 90 + 91 + Child = new FillFlowContainer 92 + { 93 + AutoSizeAxes = Axes.Both, 94 + Direction = FillDirection.Horizontal, 95 + Spacing = new Vector2(10), 96 + Children = new Drawable[] 97 + { 98 + new Container 99 + { 100 + AutoSizeAxes = Axes.X, 101 + RelativeSizeAxes = Axes.Y, 102 + Children = new[] 103 + { 104 + new Box 105 + { 106 + Colour = Color4.Navy, 107 + RelativeSizeAxes = Axes.Both, 108 + }, 109 + createDisplay(resource), 110 + } 111 + }, 112 + new SpriteText { Text = name }, 113 + } 114 + }; 115 + } 116 + 117 + private Drawable createDisplay(object resource) 118 + { 119 + switch (resource) 120 + { 121 + case Texture tex: 122 + return new Sprite 123 + { 124 + Size = new Vector2(20), 125 + Texture = tex 126 + }; 127 + 128 + default: 129 + return new SpriteText { Text = resource.ToString() }; 130 + } 131 + } 132 + } 133 + } 134 + }
+3 -5
osu.Framework.Tests/Visual/Platform/TestSceneSafeArea.cs
··· 9 9 using osu.Framework.Graphics.Shapes; 10 10 using osu.Framework.Graphics.Sprites; 11 11 using osu.Framework.Platform; 12 - using osu.Framework.Testing; 13 12 using osuTK; 14 13 using osuTK.Graphics; 15 - using GameWindow = osu.Framework.Platform.GameWindow; 16 14 17 15 namespace osu.Framework.Tests.Visual.Platform 18 16 { 19 - public class TestSceneSafeArea : TestScene 17 + public class TestSceneSafeArea : FrameworkTestScene 20 18 { 21 - private readonly BindableMarginPadding safeAreaPadding = new BindableMarginPadding(); 19 + private readonly IBindable<MarginPadding> safeAreaPadding = new BindableMarginPadding(); 22 20 private readonly Container container; 23 21 private readonly Box box; 24 22 private readonly SpriteText textbox; 25 23 26 - private GameWindow window; 24 + private IWindow window; 27 25 28 26 public TestSceneSafeArea() 29 27 {
+1 -2
osu.Framework.Tests/Visual/Sprites/TestSceneAnimation.cs
··· 7 7 using osu.Framework.Graphics.Containers; 8 8 using osu.Framework.Graphics.Shapes; 9 9 using osu.Framework.Graphics.Textures; 10 - using osu.Framework.Testing; 11 10 using osuTK; 12 11 using osuTK.Graphics; 13 12 14 13 namespace osu.Framework.Tests.Visual.Sprites 15 14 { 16 15 [System.ComponentModel.Description("frame-based animations")] 17 - public class TestSceneAnimation : TestScene 16 + public class TestSceneAnimation : FrameworkTestScene 18 17 { 19 18 public TestSceneAnimation() 20 19 {
+137
osu.Framework.Tests/Visual/Sprites/TestSceneBufferedContainerView.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using NUnit.Framework; 5 + using osu.Framework.Graphics; 6 + using osu.Framework.Graphics.Containers; 7 + using osu.Framework.Graphics.Shapes; 8 + using osu.Framework.Graphics.Sprites; 9 + using osu.Framework.Input.Events; 10 + using osu.Framework.Tests.Visual.Containers; 11 + using osuTK; 12 + using osuTK.Graphics; 13 + 14 + namespace osu.Framework.Tests.Visual.Sprites 15 + { 16 + public class TestSceneBufferedContainerView : FrameworkTestScene 17 + { 18 + [TestCase(false)] 19 + [TestCase(true)] 20 + public void TestNoEffects(bool originalEffects) => createTest(0, 0, originalEffects); 21 + 22 + [Test] 23 + public void TestSubtractOriginalBlur() => createTest(10, 0, false); 24 + 25 + [Test] 26 + public void TestCopyOriginalBlur() => createTest(10, 0, true); 27 + 28 + [TestCase(false)] 29 + [TestCase(true)] 30 + public void TestBlurViewOnly(bool originalEffects) => createTest(0, 10, originalEffects); 31 + 32 + [TestCase(false)] 33 + [TestCase(true)] 34 + public void TestBlurBoth(bool originalEffects) => createTest(10, 20, originalEffects); 35 + 36 + [Test] 37 + public void TestNonSynchronisedQuad() => createTest(10, 0, false, false); 38 + 39 + private void createTest(float originalBlur, float viewBlur, bool originalEffects, bool synchronisedQuad = true) 40 + { 41 + AddStep("create container", () => 42 + { 43 + BufferedContainer container = new BufferedContainer 44 + { 45 + Anchor = Anchor.Centre, 46 + Origin = Anchor.Centre, 47 + RelativeSizeAxes = Axes.Both, 48 + Scale = new Vector2(0.75f), 49 + BlurSigma = new Vector2(originalBlur), 50 + Child = new TestSceneCachedBufferedContainer() 51 + }; 52 + 53 + Children = new Drawable[] 54 + { 55 + container, 56 + new BlurView(container, viewBlur, originalEffects, synchronisedQuad) 57 + { 58 + Position = new Vector2(100, 100) 59 + } 60 + }; 61 + }); 62 + } 63 + 64 + private class BlurView : CompositeDrawable 65 + { 66 + public BlurView(BufferedContainer buffer, float blur, bool displayEffects, bool synchronisedQuad) 67 + { 68 + Size = new Vector2(200); 69 + Masking = true; 70 + CornerRadius = 20; 71 + BorderColour = Color4.Magenta; 72 + BorderThickness = 2; 73 + 74 + InternalChildren = new Drawable[] 75 + { 76 + new GridContainer 77 + { 78 + RelativeSizeAxes = Axes.Both, 79 + Content = new[] 80 + { 81 + new Drawable[] 82 + { 83 + new Container 84 + { 85 + RelativeSizeAxes = Axes.Both, 86 + Children = new Drawable[] 87 + { 88 + new Box 89 + { 90 + RelativeSizeAxes = Axes.Both, 91 + Colour = Color4.Magenta 92 + }, 93 + new SpriteText 94 + { 95 + Anchor = Anchor.Centre, 96 + Origin = Anchor.Centre, 97 + Text = "You can drag this view.", 98 + Font = new FontUsage(size: 16), 99 + } 100 + } 101 + } 102 + }, 103 + new Drawable[] 104 + { 105 + new BufferedContainer 106 + { 107 + RelativeSizeAxes = Axes.Both, 108 + BackgroundColour = Color4.Black, 109 + BlurSigma = new Vector2(blur), 110 + Child = buffer.CreateView().With(d => 111 + { 112 + d.RelativeSizeAxes = Axes.Both; 113 + d.SynchronisedDrawQuad = synchronisedQuad; 114 + d.DisplayOriginalEffects = displayEffects; 115 + }) 116 + } 117 + }, 118 + }, 119 + RowDimensions = new[] 120 + { 121 + new Dimension(GridSizeMode.Absolute, 20), 122 + } 123 + } 124 + }; 125 + } 126 + 127 + protected override bool OnDrag(DragEvent e) 128 + { 129 + Position += e.Delta; 130 + return true; 131 + } 132 + 133 + protected override bool OnDragEnd(DragEndEvent e) => true; 134 + protected override bool OnDragStart(DragStartEvent e) => true; 135 + } 136 + } 137 + }
+1 -2
osu.Framework.Tests/Visual/Sprites/TestSceneScreenshot.cs
··· 7 7 using osu.Framework.Graphics.Sprites; 8 8 using osu.Framework.Graphics.Textures; 9 9 using osu.Framework.Platform; 10 - using osu.Framework.Testing; 11 10 using osuTK; 12 11 using osuTK.Graphics; 13 12 14 13 namespace osu.Framework.Tests.Visual.Sprites 15 14 { 16 - public class TestSceneScreenshot : TestScene 15 + public class TestSceneScreenshot : FrameworkTestScene 17 16 { 18 17 [Resolved] 19 18 private GameHost host { get; set; }
+6 -6
osu.Framework.Tests/Visual/Sprites/TestSceneSmoothedEdges.cs
··· 45 45 } 46 46 } 47 47 48 - private readonly Box[] boxes = new Box[4]; 49 - 50 - protected override void Update() 48 + protected override void LoadComplete() 51 49 { 52 - base.Update(); 50 + base.LoadComplete(); 53 51 54 - foreach (Box box in boxes) 55 - box.Rotation += 0.01f; 52 + for (int i = 0; i < Rows * Cols; ++i) 53 + boxes[i].Spin(10000, RotationDirection.Clockwise); 56 54 } 55 + 56 + private readonly Box[] boxes = new Box[4]; 57 57 } 58 58 }
+2 -2
osu.Framework.Tests/Visual/Sprites/TestSceneSpriteIcon.cs
··· 10 10 using osu.Framework.Graphics.Cursor; 11 11 using osu.Framework.Graphics.Shapes; 12 12 using osu.Framework.Graphics.Sprites; 13 - using osu.Framework.Testing; 14 13 using osuTK; 15 14 using osuTK.Graphics; 16 15 17 16 namespace osu.Framework.Tests.Visual.Sprites 18 17 { 19 18 [TestFixture] 20 - public class TestSceneSpriteIcon : TestScene 19 + public class TestSceneSpriteIcon : FrameworkTestScene 21 20 { 22 21 public TestSceneSpriteIcon() 23 22 { ··· 49 48 }); 50 49 51 50 var weights = typeof(FontAwesome).GetNestedTypes(); 51 + 52 52 foreach (var w in weights) 53 53 { 54 54 flow.Add(new SpriteText
+1 -2
osu.Framework.Tests/Visual/Sprites/TestSceneSpriteText.cs
··· 4 4 using osu.Framework.Graphics; 5 5 using osu.Framework.Graphics.Containers; 6 6 using osu.Framework.Graphics.Sprites; 7 - using osu.Framework.Testing; 8 7 9 8 namespace osu.Framework.Tests.Visual.Sprites 10 9 { 11 - public class TestSceneSpriteText : TestScene 10 + public class TestSceneSpriteText : FrameworkTestScene 12 11 { 13 12 public TestSceneSpriteText() 14 13 {
+5
osu.Framework.Tests/Visual/Sprites/TestSceneSpriteTextScenarios.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; 5 + using System.Collections.Generic; 5 6 using System.IO; 7 + using System.Linq; 6 8 using System.Threading.Tasks; 7 9 using osu.Framework.Allocation; 8 10 using osu.Framework.Bindables; ··· 295 297 { 296 298 default: 297 299 return LOCALISABLE_STRING_EN; 300 + 298 301 case "ja": 299 302 return LOCALISABLE_STRING_JA; 300 303 } ··· 309 312 public void Dispose() 310 313 { 311 314 } 315 + 316 + public IEnumerable<string> GetAvailableResources() => Enumerable.Empty<string>(); 312 317 } 313 318 } 314 319 }
+1 -2
osu.Framework.Tests/Visual/Sprites/TestSceneSpriteTextTruncate.cs
··· 5 5 using osu.Framework.Graphics.Containers; 6 6 using osu.Framework.Graphics.Shapes; 7 7 using osu.Framework.Graphics.Sprites; 8 - using osu.Framework.Testing; 9 8 using osuTK; 10 9 using osuTK.Graphics; 11 10 12 11 namespace osu.Framework.Tests.Visual.Sprites 13 12 { 14 - public class TestSceneSpriteTextTruncate : TestScene 13 + public class TestSceneSpriteTextTruncate : FrameworkTestScene 15 14 { 16 15 private readonly FillFlowContainer flow; 17 16
+1 -2
osu.Framework.Tests/Visual/Sprites/TestSceneTextFlow.cs
··· 6 6 using osu.Framework.Graphics.Effects; 7 7 using osu.Framework.Graphics.Shapes; 8 8 using osu.Framework.Graphics.Sprites; 9 - using osu.Framework.Testing; 10 9 using osuTK; 11 10 using osuTK.Graphics; 12 11 13 12 namespace osu.Framework.Tests.Visual.Sprites 14 13 { 15 14 [System.ComponentModel.Description("word-wrap and paragraphs")] 16 - public class TestSceneTextFlow : TestScene 15 + public class TestSceneTextFlow : FrameworkTestScene 17 16 { 18 17 public TestSceneTextFlow() 19 18 {
+7 -3
osu.Framework.Tests/Visual/Sprites/TestSceneTextures.cs
··· 8 8 using osu.Framework.Graphics.Textures; 9 9 using osu.Framework.IO.Stores; 10 10 using osu.Framework.Platform; 11 - using osu.Framework.Testing; 12 11 13 12 namespace osu.Framework.Tests.Visual.Sprites 14 13 { 15 - public class TestSceneTextures : TestScene 14 + public class TestSceneTextures : FrameworkTestScene 16 15 { 17 16 [Cached] 18 17 private TextureStore normalStore; ··· 48 47 AddAssert("textures share gl texture", () => avatar1.Texture.TextureGL == avatar2.Texture.TextureGL); 49 48 AddAssert("textures have different refcount textures", () => avatar1.Texture != avatar2.Texture); 50 49 51 - AddStep("remove delayed from children", Clear); 50 + AddStep("dispose children", () => 51 + { 52 + Clear(); 53 + avatar1.Dispose(); 54 + avatar2.Dispose(); 55 + }); 52 56 53 57 AddUntilStep("gl textures disposed", () => texture.ReferenceCount == 0); 54 58 }
+1 -2
osu.Framework.Tests/Visual/Sprites/TestSceneTriangles.cs
··· 5 5 using osu.Framework.Graphics.Containers; 6 6 using osu.Framework.Graphics.Shapes; 7 7 using osu.Framework.Input.Events; 8 - using osu.Framework.Testing; 9 8 using osu.Framework.Tests.Visual.Containers; 10 9 using osuTK; 11 10 using osuTK.Graphics; 12 11 13 12 namespace osu.Framework.Tests.Visual.Sprites 14 13 { 15 - public class TestSceneTriangles : TestScene 14 + public class TestSceneTriangles : FrameworkTestScene 16 15 { 17 16 private readonly Container testContainer; 18 17
+2 -2
osu.Framework.Tests/Visual/Sprites/TestSceneVideoSprite.cs
··· 5 5 using osu.Framework.Graphics.Sprites; 6 6 using osu.Framework.Graphics.Video; 7 7 using osu.Framework.IO.Network; 8 - using osu.Framework.Testing; 9 8 using osu.Framework.Timing; 10 9 11 10 namespace osu.Framework.Tests.Visual.Sprites 12 11 { 13 - public class TestSceneVideoSprite : TestScene 12 + public class TestSceneVideoSprite : FrameworkTestScene 14 13 { 15 14 private ManualClock clock; 16 15 private VideoSprite videoSprite; ··· 67 66 if (videoSprite != null) 68 67 { 69 68 var newSecond = (int)(videoSprite.PlaybackPosition / 1000.0); 69 + 70 70 if (newSecond != currentSecond) 71 71 { 72 72 currentSecond = newSecond;
+1 -2
osu.Framework.Tests/Visual/Testing/TestSceneDrawVisualiser.cs
··· 4 4 using osu.Framework.Allocation; 5 5 using osu.Framework.Graphics; 6 6 using osu.Framework.Graphics.Visualisation; 7 - using osu.Framework.Testing; 8 7 using osu.Framework.Tests.Visual.Containers; 9 8 using osuTK; 10 9 11 10 namespace osu.Framework.Tests.Visual.Testing 12 11 { 13 - public class TestSceneDrawVisualiser : TestScene 12 + public class TestSceneDrawVisualiser : FrameworkTestScene 14 13 { 15 14 [BackgroundDependencyLoader] 16 15 private void load()
+1 -1
osu.Framework.Tests/Visual/Testing/TestSceneTest.cs
··· 7 7 8 8 namespace osu.Framework.Tests.Visual.Testing 9 9 { 10 - public class TestSceneTest : TestScene 10 + public class TestSceneTest : FrameworkTestScene 11 11 { 12 12 private int setupRun; 13 13 private int setupStepsRun;
+1 -2
osu.Framework.Tests/Visual/UserInterface/TestSceneCheckboxes.cs
··· 6 6 using osu.Framework.Graphics; 7 7 using osu.Framework.Graphics.Containers; 8 8 using osu.Framework.Graphics.UserInterface; 9 - using osu.Framework.Testing; 10 9 using osuTK; 11 10 12 11 namespace osu.Framework.Tests.Visual.UserInterface 13 12 { 14 - public class TestSceneCheckboxes : TestScene 13 + public class TestSceneCheckboxes : FrameworkTestScene 15 14 { 16 15 public override IReadOnlyList<Type> RequiredTypes => new[] 17 16 {
+16 -2
osu.Framework.Tests/Visual/UserInterface/TestSceneCircularProgress.cs
··· 7 7 using osu.Framework.Graphics.Colour; 8 8 using osu.Framework.Graphics.Textures; 9 9 using osu.Framework.Graphics.UserInterface; 10 - using osu.Framework.Testing; 11 10 using osuTK.Graphics; 12 11 using SixLabors.ImageSharp; 13 12 using SixLabors.ImageSharp.PixelFormats; 14 13 15 14 namespace osu.Framework.Tests.Visual.UserInterface 16 15 { 17 - public class TestSceneCircularProgress : TestScene 16 + public class TestSceneCircularProgress : FrameworkTestScene 18 17 { 19 18 public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(CircularProgress), typeof(CircularProgressDrawNode) }; 20 19 ··· 35 34 var image = new Image<Rgba32>(width, 1); 36 35 37 36 gradientTextureHorizontal = new Texture(width, 1, true); 37 + 38 38 for (int i = 0; i < width; ++i) 39 39 { 40 40 float brightness = (float)i / (width - 1); ··· 46 46 image = new Image<Rgba32>(width, 1); 47 47 48 48 gradientTextureVertical = new Texture(1, width, true); 49 + 49 50 for (int i = 0; i < width; ++i) 50 51 { 51 52 float brightness = (float)i / (width - 1); ··· 57 58 image = new Image<Rgba32>(width, width); 58 59 59 60 gradientTextureBoth = new Texture(width, width, true); 61 + 60 62 for (int i = 0; i < width; ++i) 61 63 { 62 64 for (int j = 0; j < width; ++j) ··· 108 110 protected override void Update() 109 111 { 110 112 base.Update(); 113 + 111 114 switch (rotateMode) 112 115 { 113 116 case 0: 114 117 clock.Current.Value = Time.Current % (period * 2) / period - 1; 115 118 break; 119 + 116 120 case 1: 117 121 clock.Current.Value = Time.Current % period / period; 118 122 break; 123 + 119 124 case 2: 120 125 clock.Current.Value = Time.Current % period / period - 1; 121 126 break; 127 + 122 128 case 3: 123 129 clock.Current.Value = Time.Current % transition_period / transition_period / 5 - 0.1f; 124 130 break; 131 + 125 132 case 4: 126 133 clock.Current.Value = (Time.Current % transition_period / transition_period / 5 - 0.1f + 2) % 2 - 1; 127 134 break; ··· 135 142 case 0: 136 143 clock.Texture = Texture.WhitePixel; 137 144 break; 145 + 138 146 case 1: 139 147 clock.Texture = gradientTextureHorizontal; 140 148 break; 149 + 141 150 case 2: 142 151 clock.Texture = gradientTextureVertical; 143 152 break; 153 + 144 154 case 3: 145 155 clock.Texture = gradientTextureBoth; 146 156 break; ··· 154 164 case 0: 155 165 clock.Colour = new Color4(255, 255, 255, 255); 156 166 break; 167 + 157 168 case 1: 158 169 clock.Colour = new Color4(255, 128, 128, 255); 159 170 break; 171 + 160 172 case 2: 161 173 clock.Colour = new ColourInfo 162 174 { ··· 166 178 BottomRight = new Color4(128, 255, 128, 255), 167 179 }; 168 180 break; 181 + 169 182 case 3: 170 183 clock.Colour = new ColourInfo 171 184 { ··· 175 188 BottomRight = new Color4(128, 255, 128, 255), 176 189 }; 177 190 break; 191 + 178 192 case 4: 179 193 clock.Colour = new ColourInfo 180 194 {
+1 -2
osu.Framework.Tests/Visual/UserInterface/TestSceneContextMenu.cs
··· 8 8 using osu.Framework.Graphics.Cursor; 9 9 using osu.Framework.Graphics.Shapes; 10 10 using osu.Framework.Graphics.UserInterface; 11 - using osu.Framework.Testing; 12 11 using osuTK; 13 12 using osuTK.Graphics; 14 13 15 14 namespace osu.Framework.Tests.Visual.UserInterface 16 15 { 17 - public class TestSceneContextMenu : TestScene 16 + public class TestSceneContextMenu : FrameworkTestScene 18 17 { 19 18 private const int start_time = 0; 20 19 private const int duration = 1000;
+5 -2
osu.Framework.Tests/Visual/UserInterface/TestSceneCountingText.cs
··· 7 7 using osu.Framework.Graphics; 8 8 using osu.Framework.Graphics.Sprites; 9 9 using osu.Framework.Graphics.UserInterface; 10 - using osu.Framework.Testing; 11 10 using osuTK; 12 11 13 12 namespace osu.Framework.Tests.Visual.UserInterface 14 13 { 15 - public class TestSceneCountingText : TestScene 14 + public class TestSceneCountingText : FrameworkTestScene 16 15 { 17 16 private readonly Bindable<CountType> countType = new Bindable<CountType>(); 18 17 ··· 62 61 default: 63 62 case CountType.AsDouble: 64 63 return value.ToString(CultureInfo.InvariantCulture); 64 + 65 65 case CountType.AsInteger: 66 66 return ((int)value).ToString(); 67 + 67 68 case CountType.AsIntegerCeiling: 68 69 return ((int)Math.Ceiling(value)).ToString(); 70 + 69 71 case CountType.AsDouble2: 70 72 return Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); 73 + 71 74 case CountType.AsDouble4: 72 75 return Math.Round(value, 4).ToString(CultureInfo.InvariantCulture); 73 76 }
+1
osu.Framework.Tests/Visual/UserInterface/TestSceneDrawablePath.cs
··· 147 147 protected override bool OnDrag(DragEvent e) 148 148 { 149 149 Vector2 pos = e.MousePosition; 150 + 150 151 if ((pos - oldPos).Length > 10) 151 152 { 152 153 AddVertex(pos);
+1 -2
osu.Framework.Tests/Visual/UserInterface/TestSceneMarkdown.cs
··· 7 7 using osu.Framework.Graphics.Containers; 8 8 using osu.Framework.Graphics.Containers.Markdown; 9 9 using osu.Framework.IO.Network; 10 - using osu.Framework.Testing; 11 10 12 11 namespace osu.Framework.Tests.Visual.UserInterface 13 12 { 14 13 [Description("markdown reader")] 15 - public class TestSceneMarkdown : TestScene 14 + public class TestSceneMarkdown : FrameworkTestScene 16 15 { 17 16 public TestSceneMarkdown() 18 17 {
+3
osu.Framework.Tests/Visual/UserInterface/TestSceneNestedMenus.cs
··· 181 181 AddAssert("Check closed", () => 182 182 { 183 183 int currentSubMenu = 3; 184 + 184 185 while (true) 185 186 { 186 187 var subMenu = menus.GetSubMenu(currentSubMenu); ··· 217 218 AddAssert("Check closed", () => 218 219 { 219 220 int currentSubMenu = 3; 221 + 220 222 while (true) 221 223 { 222 224 var subMenu = menus.GetSubMenu(currentSubMenu); ··· 464 466 public Menu GetSubMenu(int index) 465 467 { 466 468 var currentMenu = menu; 469 + 467 470 for (int i = 0; i < index; i++) 468 471 { 469 472 if (currentMenu == null)
+2 -2
osu.Framework.Tests/Visual/UserInterface/TestSceneRigidBody.cs
··· 7 7 using osu.Framework.Graphics.UserInterface; 8 8 using osu.Framework.MathUtils; 9 9 using osu.Framework.Physics; 10 - using osu.Framework.Testing; 11 10 using osuTK; 12 11 using osuTK.Graphics; 13 12 14 13 namespace osu.Framework.Tests.Visual.UserInterface 15 14 { 16 - public class TestSceneRigidBody : TestScene 15 + public class TestSceneRigidBody : FrameworkTestScene 17 16 { 18 17 private readonly TestRigidBodySimulation sim; 19 18 ··· 80 79 for (int i = 0; i < n; i++) 81 80 { 82 81 RigidBodyContainer<Drawable> d; 82 + 83 83 do 84 84 { 85 85 d = generate();
+2 -2
osu.Framework.Tests/Visual/UserInterface/TestSceneScreenStack.cs
··· 15 15 using osu.Framework.Input.Events; 16 16 using osu.Framework.MathUtils; 17 17 using osu.Framework.Screens; 18 - using osu.Framework.Testing; 19 18 using osu.Framework.Testing.Input; 20 19 using osuTK; 21 20 using osuTK.Graphics; ··· 23 22 24 23 namespace osu.Framework.Tests.Visual.UserInterface 25 24 { 26 - public class TestSceneScreenStack : TestScene 25 + public class TestSceneScreenStack : FrameworkTestScene 27 26 { 28 27 private TestScreen baseScreen; 29 28 private ScreenStack stack; ··· 444 443 AddStep("Setup screens", () => 445 444 { 446 445 screens = new List<TestScreen>(); 446 + 447 447 for (int i = 0; i < 5; i++) 448 448 { 449 449 var screen = new TestScreen();
+1 -2
osu.Framework.Tests/Visual/UserInterface/TestSceneSearchContainer.cs
··· 8 8 using osu.Framework.Graphics.Containers; 9 9 using osu.Framework.Graphics.Sprites; 10 10 using osu.Framework.Graphics.UserInterface; 11 - using osu.Framework.Testing; 12 11 using osuTK; 13 12 14 13 namespace osu.Framework.Tests.Visual.UserInterface 15 14 { 16 - public class TestSceneSearchContainer : TestScene 15 + public class TestSceneSearchContainer : FrameworkTestScene 17 16 { 18 17 private SearchContainer search; 19 18 private BasicTextBox textBox;
+1 -2
osu.Framework.Tests/Visual/UserInterface/TestSceneTabControl.cs
··· 11 11 using osu.Framework.Graphics.Shapes; 12 12 using osu.Framework.Graphics.UserInterface; 13 13 using osu.Framework.Input; 14 - using osu.Framework.Testing; 15 14 using osuTK; 16 15 17 16 namespace osu.Framework.Tests.Visual.UserInterface 18 17 { 19 - public class TestSceneTabControl : TestScene 18 + public class TestSceneTabControl : FrameworkTestScene 20 19 { 21 20 private readonly IEnumerable<TestEnum> items; 22 21
+1 -2
osu.Framework.Tests/Visual/UserInterface/TestSceneTextBox.cs
··· 6 6 using osu.Framework.Graphics; 7 7 using osu.Framework.Graphics.Containers; 8 8 using osu.Framework.Graphics.UserInterface; 9 - using osu.Framework.Testing; 10 9 using osuTK; 11 10 12 11 namespace osu.Framework.Tests.Visual.UserInterface 13 12 { 14 - public class TestSceneTextBox : TestScene 13 + public class TestSceneTextBox : FrameworkTestScene 15 14 { 16 15 public override IReadOnlyList<Type> RequiredTypes => new[] 17 16 {
+2 -2
osu.Framework.Tests/Visual/UserInterface/TestSceneTooltip.cs
··· 7 7 using osu.Framework.Graphics.Shapes; 8 8 using osu.Framework.Graphics.Sprites; 9 9 using osu.Framework.Graphics.UserInterface; 10 - using osu.Framework.Testing; 11 10 using osuTK; 12 11 using osuTK.Graphics; 13 12 14 13 namespace osu.Framework.Tests.Visual.UserInterface 15 14 { 16 - public class TestSceneTooltip : TestScene 15 + public class TestSceneTooltip : FrameworkTestScene 17 16 { 18 17 private readonly Container testContainer; 19 18 ··· 44 43 testContainer.Clear(); 45 44 46 45 CursorContainer cursor = null; 46 + 47 47 if (!cursorlessTooltip) 48 48 { 49 49 cursor = new RectangleCursorContainer();
+8 -8
osu.Framework.iOS.props
··· 98 98 <Reference Include="mscorlib" /> 99 99 </ItemGroup> 100 100 <ItemGroup Label="Package References"> 101 - <PackageReference Include="Markdig" Version="0.15.7" /> 102 - <PackageReference Include="FFmpeg.AutoGen" Version="4.1.0.2" /> 101 + <PackageReference Include="Markdig" Version="0.17.0" /> 102 + <PackageReference Include="FFmpeg.AutoGen" Version="4.1.0.4" /> 103 103 <PackageReference Include="SharpFNT" Version="1.1.0" /> 104 104 <PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0006" /> 105 105 <PackageReference Include="System.Drawing.Common" Version="4.5.1" /> 106 - <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.10.0" /> 107 - <PackageReference Include="ppy.osuTK.NS20" Version="1.0.66" /> 106 + <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.0.0" /> 107 + <PackageReference Include="ppy.osuTK.NS20" Version="1.0.101" /> 108 108 <PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" /> 109 109 <PackageReference Include="ppy.Microsoft.Diagnostics.Runtime" Version="0.9.180305.1" /> 110 - <PackageReference Include="NUnit" Version="3.11.0" /> 110 + <PackageReference Include="NUnit" Version="3.12.0" /> 111 111 <PackageReference Include="System.Net.Http" Version="4.3.4" /> 112 112 <PackageReference Include="ManagedBass" Version="2.0.4" /> 113 113 <PackageReference Include="ManagedBass.Fx" Version="2.0.1" /> 114 - <PackageReference Include="Newtonsoft.Json" Version="12.0.1" /> 114 + <PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> 115 115 <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" /> 116 116 <PackageReference Include="System.Reflection.Emit.ILGeneration" Version="4.3.0" /> 117 - <PackageReference Include="JetBrains.Annotations" Version="2018.3.0" /> 117 + <PackageReference Include="JetBrains.Annotations" Version="2019.1.1" /> 118 118 </ItemGroup> 119 - </Project> 119 + </Project>
+8
osu.Framework.iOS/IOSGameHost.cs
··· 3 3 4 4 using System; 5 5 using System.Collections.Generic; 6 + using osu.Framework.Configuration; 6 7 using osu.Framework.Graphics.Textures; 7 8 using osu.Framework.Input; 8 9 using osu.Framework.Input.Handlers; ··· 27 28 base.SetupForRun(); 28 29 IOSGameWindow.GameView = gameView; 29 30 Window = new IOSGameWindow(); 31 + } 32 + 33 + protected override void SetupConfig(IDictionary<FrameworkSetting, object> gameDefaults) 34 + { 35 + base.SetupConfig(gameDefaults); 36 + 37 + DebugConfig.Set(DebugSetting.BypassFrontToBackPass, true); 30 38 } 31 39 32 40 protected override void PerformExit(bool immediately)
+6 -1
osu.Framework.iOS/IOSGameWindow.cs
··· 7 7 using osu.Framework.Graphics; 8 8 using System; 9 9 using System.Collections.Generic; 10 + using osu.Framework.Bindables; 10 11 11 12 namespace osu.Framework.iOS 12 13 { 13 14 public class IOSGameWindow : GameWindow 14 15 { 15 16 internal static IOSGameView GameView; 17 + 18 + private readonly BindableMarginPadding safeAreaPadding = new BindableMarginPadding(); 19 + 20 + public override IBindable<MarginPadding> SafeAreaPadding => safeAreaPadding; 16 21 17 22 public IOSGameWindow() 18 23 : base(GameView) ··· 53 58 54 59 private void onResize(object sender, EventArgs e) 55 60 { 56 - SafeAreaPadding.Value = new MarginPadding 61 + safeAreaPadding.Value = new MarginPadding 57 62 { 58 63 Top = (float)GameView.SafeArea.Top * GameView.Scale, 59 64 Left = (float)GameView.SafeArea.Left * GameView.Scale,
+1 -1
osu.Framework.iOS/osu.Framework.iOS.csproj
··· 32 32 <ProjectReference Include="..\osu.Framework\osu.Framework.csproj" /> 33 33 </ItemGroup> 34 34 <ItemGroup> 35 - <PackageReference Include="ppy.osuTK.iOS" Version="1.0.66" /> 35 + <PackageReference Include="ppy.osuTK.iOS" Version="1.0.101" /> 36 36 <PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0006" /> 37 37 </ItemGroup> 38 38 </Project>
+3
osu.Framework/Allocation/BackgroundDependencyLoaderAttribute.cs
··· 45 45 { 46 46 case 0: 47 47 return (_, __) => { }; 48 + 48 49 case 1: 49 50 var method = loaderMethods[0]; 50 51 ··· 68 69 case OperationCanceledException _: 69 70 // This activator is cancelled - propagate the cancellation as-is (it will be handled silently) 70 71 throw exc.InnerException; 72 + 71 73 case DependencyInjectionException die: 72 74 // A nested activator has failed (multiple Invoke() calls) - propagate the original error 73 75 throw die; ··· 77 79 throw new DependencyInjectionException { DispatchInfo = ExceptionDispatchInfo.Capture(exc.InnerException) }; 78 80 } 79 81 }; 82 + 80 83 default: 81 84 throw new MultipleDependencyLoaderMethodsException(type); 82 85 }
+1
osu.Framework/Allocation/CachedAttribute.cs
··· 103 103 throw new AccessModifierNotAllowedForCachedValueException(AccessModifier.None, pi); 104 104 105 105 var setMethod = pi.SetMethod; 106 + 106 107 if (setMethod != null) 107 108 { 108 109 var modifier = setMethod.GetAccessModifier();
+1
osu.Framework/Allocation/CachedModelDependencyContainer.cs
··· 108 108 case PropertyInfo pi: 109 109 action(((IBindable)pi.GetValue(targetShadowModel), (IBindable)pi.GetValue(target))); 110 110 break; 111 + 111 112 case FieldInfo fi: 112 113 action(((IBindable)fi.GetValue(targetShadowModel), (IBindable)fi.GetValue(target))); 113 114 break;
+1
osu.Framework/Allocation/ObjectHandle.cs
··· 67 67 try 68 68 { 69 69 var value = handle.Target; 70 + 70 71 if (value is T) 71 72 { 72 73 target = (T)value;
+2
osu.Framework/Allocation/ResolvedAttribute.cs
··· 73 73 var activators = new List<Action<object, IReadOnlyDependencyContainer>>(); 74 74 75 75 var properties = type.GetProperties(activator_flags).Where(f => f.GetCustomAttribute<ResolvedAttribute>() != null); 76 + 76 77 foreach (var property in properties) 77 78 { 78 79 if (!property.CanWrite) ··· 85 86 var attribute = property.GetCustomAttribute<ResolvedAttribute>(); 86 87 87 88 var cacheInfo = new CacheInfo(attribute.Name); 89 + 88 90 if (attribute.Parent != null) 89 91 { 90 92 // When a parent type exists, infer the property name if one is not provided
+2
osu.Framework/Allocation/TripleBuffer.cs
··· 55 55 56 56 buffers[write].FrameId = Interlocked.Increment(ref currentFrame); 57 57 return buffers[write]; 58 + 58 59 case UsageType.Read: 59 60 if (lastWrite < 0) return null; 60 61 ··· 78 79 lock (buffers) 79 80 buffers[read].Usage = UsageType.None; 80 81 break; 82 + 81 83 case UsageType.Write: 82 84 lock (buffers) 83 85 {
+36 -98
osu.Framework/Audio/AdjustableAudioComponent.cs
··· 1 1 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 - using System; 5 - using System.Collections.Generic; 6 - using System.Linq; 7 4 using osu.Framework.Bindables; 8 5 9 6 namespace osu.Framework.Audio 10 7 { 11 - public class AdjustableAudioComponent : AudioComponent 8 + /// <summary> 9 + /// An audio component which allows for basic bindable adjustments to be applied. 10 + /// </summary> 11 + public class AdjustableAudioComponent : AudioComponent, IAggregateAudioAdjustment, IAdjustableAudioComponent 12 12 { 13 - private readonly HashSet<BindableDouble> volumeAdjustments = new HashSet<BindableDouble>(); 14 - private readonly HashSet<BindableDouble> balanceAdjustments = new HashSet<BindableDouble>(); 15 - private readonly HashSet<BindableDouble> frequencyAdjustments = new HashSet<BindableDouble>(); 13 + private readonly AudioAdjustments adjustments = new AudioAdjustments(); 16 14 17 15 /// <summary> 18 - /// Global volume of this component. 16 + /// The volume of this component. 19 17 /// </summary> 20 - public readonly BindableDouble Volume = new BindableDouble(1) 21 - { 22 - MinValue = 0, 23 - MaxValue = 1 24 - }; 25 - 26 - protected readonly BindableDouble VolumeCalculated = new BindableDouble(1) 27 - { 28 - MinValue = 0, 29 - MaxValue = 1 30 - }; 18 + public BindableDouble Volume => adjustments.Volume; 31 19 32 20 /// <summary> 33 - /// Playback balance of this sample (-1 .. 1 where 0 is centered) 21 + /// The playback balance of this sample (-1 .. 1 where 0 is centered) 34 22 /// </summary> 35 - public readonly BindableDouble Balance = new BindableDouble 36 - { 37 - MinValue = -1, 38 - MaxValue = 1 39 - }; 40 - 41 - protected readonly BindableDouble BalanceCalculated = new BindableDouble 42 - { 43 - MinValue = -1, 44 - MaxValue = 1 45 - }; 23 + public BindableDouble Balance => adjustments.Balance; 46 24 47 25 /// <summary> 48 26 /// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency. 49 27 /// </summary> 50 - public readonly BindableDouble Frequency = new BindableDouble(1); 51 - 52 - protected readonly BindableDouble FrequencyCalculated = new BindableDouble(1); 28 + public BindableDouble Frequency => adjustments.Frequency; 53 29 54 30 protected AdjustableAudioComponent() 55 31 { 56 - Volume.ValueChanged += e => InvalidateState(e.NewValue); 57 - Balance.ValueChanged += e => InvalidateState(e.NewValue); 58 - Frequency.ValueChanged += e => InvalidateState(e.NewValue); 32 + AggregateVolume.ValueChanged += InvalidateState; 33 + AggregateBalance.ValueChanged += InvalidateState; 34 + AggregateFrequency.ValueChanged += InvalidateState; 59 35 } 60 36 61 - internal void InvalidateState(double newValue = 0) => EnqueueAction(OnStateChanged); 37 + public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => 38 + adjustments.AddAdjustment(type, adjustBindable); 62 39 63 - internal virtual void OnStateChanged() 64 - { 65 - VolumeCalculated.Value = volumeAdjustments.Aggregate(Volume.Value, (current, adj) => current * adj.Value); 66 - BalanceCalculated.Value = balanceAdjustments.Aggregate(Balance.Value, (current, adj) => current + adj.Value); 67 - FrequencyCalculated.Value = frequencyAdjustments.Aggregate(Frequency.Value, (current, adj) => current * adj.Value); 68 - } 40 + public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => 41 + adjustments.RemoveAdjustment(type, adjustBindable); 69 42 70 - public void AddAdjustmentDependency(AdjustableAudioComponent component) 71 - { 72 - AddAdjustment(AdjustableProperty.Balance, component.BalanceCalculated); 73 - AddAdjustment(AdjustableProperty.Frequency, component.FrequencyCalculated); 74 - AddAdjustment(AdjustableProperty.Volume, component.VolumeCalculated); 75 - } 43 + internal void InvalidateState(ValueChangedEvent<double> valueChangedEvent = null) => EnqueueAction(OnStateChanged); 76 44 77 - public void RemoveAdjustmentDependency(AdjustableAudioComponent component) 45 + internal virtual void OnStateChanged() 78 46 { 79 - RemoveAdjustment(AdjustableProperty.Balance, component.BalanceCalculated); 80 - RemoveAdjustment(AdjustableProperty.Frequency, component.FrequencyCalculated); 81 - RemoveAdjustment(AdjustableProperty.Volume, component.VolumeCalculated); 82 47 } 83 48 84 - public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => EnqueueAction(() => 85 - { 86 - switch (type) 87 - { 88 - case AdjustableProperty.Balance: 89 - if (balanceAdjustments.Contains(adjustBindable)) 90 - throw new ArgumentException("An adjustable binding may only be registered once."); 49 + /// <summary> 50 + /// Bind all adjustments to another component's aggregated results. 51 + /// </summary> 52 + /// <param name="component">The other component (generally a direct parent).</param> 53 + internal void BindAdjustments(IAggregateAudioAdjustment component) => adjustments.BindAdjustments(component); 91 54 92 - balanceAdjustments.Add(adjustBindable); 93 - break; 94 - case AdjustableProperty.Frequency: 95 - if (frequencyAdjustments.Contains(adjustBindable)) 96 - throw new ArgumentException("An adjustable binding may only be registered once."); 55 + /// <summary> 56 + /// Unbind all adjustments from another component's aggregated results. 57 + /// </summary> 58 + /// <param name="component">The other component (generally a direct parent).</param> 59 + internal void UnbindAdjustments(IAggregateAudioAdjustment component) => adjustments.UnbindAdjustments(component); 97 60 98 - frequencyAdjustments.Add(adjustBindable); 99 - break; 100 - case AdjustableProperty.Volume: 101 - if (volumeAdjustments.Contains(adjustBindable)) 102 - throw new ArgumentException("An adjustable binding may only be registered once."); 61 + public IBindable<double> AggregateVolume => adjustments.AggregateVolume; 103 62 104 - volumeAdjustments.Add(adjustBindable); 105 - break; 106 - } 63 + public IBindable<double> AggregateBalance => adjustments.AggregateBalance; 107 64 108 - InvalidateState(); 109 - }); 110 - 111 - public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => EnqueueAction(() => 112 - { 113 - switch (type) 114 - { 115 - case AdjustableProperty.Balance: 116 - balanceAdjustments.Remove(adjustBindable); 117 - break; 118 - case AdjustableProperty.Frequency: 119 - frequencyAdjustments.Remove(adjustBindable); 120 - break; 121 - case AdjustableProperty.Volume: 122 - volumeAdjustments.Remove(adjustBindable); 123 - break; 124 - } 125 - 126 - InvalidateState(); 127 - }); 65 + public IBindable<double> AggregateFrequency => adjustments.AggregateFrequency; 128 66 129 67 protected override void Dispose(bool disposing) 130 68 { 131 - volumeAdjustments.Clear(); 132 - balanceAdjustments.Clear(); 133 - frequencyAdjustments.Clear(); 134 - 135 69 base.Dispose(disposing); 70 + 71 + AggregateVolume.UnbindAll(); 72 + AggregateBalance.UnbindAll(); 73 + AggregateFrequency.UnbindAll(); 136 74 } 137 75 } 138 76
+115
osu.Framework/Audio/AudioAdjustments.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using osu.Framework.Bindables; 5 + 6 + namespace osu.Framework.Audio 7 + { 8 + /// <summary> 9 + /// Provides adjustable and bindable attributes for an audio component. 10 + /// Aggregates results as a <see cref="IAggregateAudioAdjustment"/>. 11 + /// </summary> 12 + public class AudioAdjustments : IAggregateAudioAdjustment, IAdjustableAudioComponent 13 + { 14 + /// <summary> 15 + /// The volume of this component. 16 + /// </summary> 17 + public BindableDouble Volume { get; } = new BindableDouble(1) 18 + { 19 + MinValue = 0, 20 + MaxValue = 1 21 + }; 22 + 23 + /// <summary> 24 + /// The playback balance of this sample (-1 .. 1 where 0 is centered) 25 + /// </summary> 26 + public BindableDouble Balance { get; } = new BindableDouble 27 + { 28 + MinValue = -1, 29 + MaxValue = 1 30 + }; 31 + 32 + /// <summary> 33 + /// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency. 34 + /// </summary> 35 + public BindableDouble Frequency { get; } = new BindableDouble(1); 36 + 37 + public IBindable<double> AggregateVolume => volumeAggregate.Result; 38 + public IBindable<double> AggregateBalance => balanceAggregate.Result; 39 + public IBindable<double> AggregateFrequency => frequencyAggregate.Result; 40 + 41 + private readonly AggregateBindable<double> volumeAggregate; 42 + private readonly AggregateBindable<double> balanceAggregate; 43 + private readonly AggregateBindable<double> frequencyAggregate; 44 + 45 + public AudioAdjustments() 46 + { 47 + volumeAggregate = new AggregateBindable<double>((a, b) => a * b, Volume.GetUnboundCopy()); 48 + volumeAggregate.AddSource(Volume); 49 + 50 + balanceAggregate = new AggregateBindable<double>((a, b) => a + b, Balance.GetUnboundCopy()); 51 + balanceAggregate.AddSource(Balance); 52 + 53 + frequencyAggregate = new AggregateBindable<double>((a, b) => a * b, Frequency.GetUnboundCopy()); 54 + frequencyAggregate.AddSource(Frequency); 55 + } 56 + 57 + public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) 58 + { 59 + switch (type) 60 + { 61 + case AdjustableProperty.Balance: 62 + balanceAggregate.AddSource(adjustBindable); 63 + break; 64 + 65 + case AdjustableProperty.Frequency: 66 + frequencyAggregate.AddSource(adjustBindable); 67 + break; 68 + 69 + case AdjustableProperty.Volume: 70 + volumeAggregate.AddSource(adjustBindable); 71 + break; 72 + } 73 + } 74 + 75 + public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) 76 + { 77 + switch (type) 78 + { 79 + case AdjustableProperty.Balance: 80 + balanceAggregate.RemoveSource(adjustBindable); 81 + break; 82 + 83 + case AdjustableProperty.Frequency: 84 + frequencyAggregate.RemoveSource(adjustBindable); 85 + break; 86 + 87 + case AdjustableProperty.Volume: 88 + volumeAggregate.RemoveSource(adjustBindable); 89 + break; 90 + } 91 + } 92 + 93 + /// <summary> 94 + /// Bind all adjustments from an <see cref="IAggregateAudioAdjustment"/>. 95 + /// </summary> 96 + /// <param name="component">The adjustment source.</param> 97 + internal void BindAdjustments(IAggregateAudioAdjustment component) 98 + { 99 + volumeAggregate.AddSource(component.AggregateVolume); 100 + balanceAggregate.AddSource(component.AggregateBalance); 101 + frequencyAggregate.AddSource(component.AggregateFrequency); 102 + } 103 + 104 + /// <summary> 105 + /// Unbind all adjustments from an <see cref="IAggregateAudioAdjustment"/>. 106 + /// </summary> 107 + /// <param name="component">The adjustment source.</param> 108 + internal void UnbindAdjustments(IAggregateAudioAdjustment component) 109 + { 110 + volumeAggregate.RemoveSource(component.AggregateVolume); 111 + balanceAggregate.RemoveSource(component.AggregateBalance); 112 + frequencyAggregate.RemoveSource(component.AggregateFrequency); 113 + } 114 + } 115 + }
+3 -25
osu.Framework/Audio/AudioCollectionManager.cs
··· 9 9 /// <summary> 10 10 /// A collection of audio components which need central property control. 11 11 /// </summary> 12 - public class AudioCollectionManager<T> : AdjustableAudioComponent 12 + public class AudioCollectionManager<T> : AdjustableAudioComponent, IBassAudio 13 13 where T : AdjustableAudioComponent 14 14 { 15 - protected List<T> Items = new List<T>(); 15 + internal List<T> Items = new List<T>(); 16 16 17 17 public void AddItem(T item) 18 18 { 19 - RegisterItem(item); 20 - AddItemToList(item); 21 - } 22 - 23 - public void AddItemToList(T item) 24 - { 25 19 EnqueueAction(delegate 26 20 { 27 21 if (Items.Contains(item)) return; 28 22 23 + item.BindAdjustments(this); 29 24 Items.Add(item); 30 25 }); 31 - } 32 - 33 - public void RegisterItem(T item) 34 - { 35 - EnqueueAction(() => item.AddAdjustmentDependency(this)); 36 - } 37 - 38 - public void UnregisterItem(T item) 39 - { 40 - EnqueueAction(() => item.RemoveAdjustmentDependency(this)); 41 - } 42 - 43 - internal override void OnStateChanged() 44 - { 45 - base.OnStateChanged(); 46 - foreach (var item in Items) 47 - item.OnStateChanged(); 48 26 } 49 27 50 28 public virtual void UpdateDevice(int deviceIndex)
+26 -12
osu.Framework/Audio/AudioComponent.cs
··· 9 9 10 10 namespace osu.Framework.Audio 11 11 { 12 - public class AudioComponent : IDisposable, IUpdateable 12 + /// <summary> 13 + /// A base class for audio components which offers audio thread deferring, disposal and basic update logic. 14 + /// </summary> 15 + public abstract class AudioComponent : IDisposable, IUpdateable 13 16 { 14 17 /// <summary> 15 18 /// Audio operations will be run on a separate dedicated thread, so we need to schedule any audio API calls using this queue. 16 19 /// </summary> 17 20 protected ConcurrentQueue<Task> PendingActions = new ConcurrentQueue<Task>(); 18 21 22 + private bool acceptingActions = true; 23 + 24 + /// <summary> 25 + /// Enqueues an action to be performed on the audio thread. 26 + /// </summary> 27 + /// <param name="action">The action to perform.</param> 28 + /// <returns>A task which can be used for continuation logic. May return a <see cref="Task.CompletedTask"/> if called while already on the audio thread.</returns> 19 29 protected Task EnqueueAction(Action action) 20 30 { 21 31 if (ThreadSafety.IsAudioThread) ··· 31 41 var task = new Task(action); 32 42 PendingActions.Enqueue(task); 33 43 return task; 34 - } 35 - 36 - private bool acceptingActions = true; 37 - 38 - ~AudioComponent() 39 - { 40 - Dispose(false); 41 44 } 42 45 43 46 /// <summary> 44 - /// Run each loop of the audio thread after queued actions to allow components to update anything they need to. 47 + /// Run each loop of the audio thread's execution after queued actions are completed to allow components to perform any additional operations. 45 48 /// </summary> 46 49 protected virtual void UpdateState() 47 50 { 48 51 } 49 52 53 + /// <summary> 54 + /// Run each loop of the audio thread's execution, after <see cref="UpdateState"/> as a way to update any child components. 55 + /// </summary> 50 56 protected virtual void UpdateChildren() 51 57 { 52 58 } ··· 73 79 } 74 80 75 81 /// <summary> 76 - /// This component has completed playback and is now in a stopped state. 82 + /// Whether this component has completed playback and is in a stopped state. 77 83 /// </summary> 78 84 public virtual bool HasCompleted => !IsAlive; 79 85 80 86 /// <summary> 81 - /// This component has completed all processing and is ready to be removed from its parent. 87 + /// When false, this component has completed all processing and is ready to be removed from its parent. 82 88 /// </summary> 83 89 public virtual bool IsAlive => !IsDisposed; 84 90 91 + /// <summary> 92 + /// Whether this component has finished loading its resources. 93 + /// </summary> 85 94 public virtual bool IsLoaded => true; 86 95 87 96 #region IDisposable Support 88 97 89 - protected volatile bool IsDisposed; // To detect redundant calls 98 + ~AudioComponent() 99 + { 100 + Dispose(false); 101 + } 102 + 103 + protected volatile bool IsDisposed; 90 104 91 105 protected virtual void Dispose(bool disposing) 92 106 {
+38 -33
osu.Framework/Audio/AudioManager.cs
··· 21 21 /// <summary> 22 22 /// The manager component responsible for audio tracks (e.g. songs). 23 23 /// </summary> 24 - public TrackManager Track => GetTrackManager(); 24 + public ITrackStore Tracks => globalTrackStore.Value; 25 25 26 26 /// <summary> 27 27 /// The manager component responsible for audio samples (e.g. sound effects). 28 28 /// </summary> 29 - public SampleManager Sample => GetSampleManager(); 29 + public ISampleStore Samples => globalSampleStore.Value; 30 30 31 31 /// <summary> 32 32 /// The thread audio operations (mainly Bass calls) are ran on. ··· 86 86 /// </summary> 87 87 public Scheduler EventScheduler; 88 88 89 - private readonly Lazy<TrackManager> globalTrackManager; 90 - private readonly Lazy<SampleManager> globalSampleManager; 89 + private readonly Lazy<TrackStore> globalTrackStore; 90 + private readonly Lazy<SampleStore> globalSampleStore; 91 91 92 92 /// <summary> 93 - /// Constructs an AudioManager given a track resource store, and a sample resource store. 93 + /// Constructs an AudioStore given a track resource store, and a sample resource store. 94 94 /// </summary> 95 95 /// <param name="audioThread">The host's audio thread.</param> 96 96 /// <param name="trackStore">The resource store containing all audio tracks to be used in the future.</param> ··· 108 108 sampleStore.AddExtension(@"wav"); 109 109 sampleStore.AddExtension(@"mp3"); 110 110 111 - globalTrackManager = new Lazy<TrackManager>(() => GetTrackManager(trackStore)); 112 - globalSampleManager = new Lazy<SampleManager>(() => GetSampleManager(sampleStore)); 111 + globalTrackStore = new Lazy<TrackStore>(() => 112 + { 113 + var store = new TrackStore(trackStore); 114 + AddItem(store); 115 + store.AddAdjustment(AdjustableProperty.Volume, VolumeTrack); 116 + return store; 117 + }); 118 + 119 + globalSampleStore = new Lazy<SampleStore>(() => 120 + { 121 + var store = new SampleStore(sampleStore); 122 + AddItem(store); 123 + store.AddAdjustment(AdjustableProperty.Volume, VolumeSample); 124 + return store; 125 + }); 113 126 114 127 scheduler.Add(() => 115 128 { ··· 155 168 private IEnumerable<string> getDeviceNames(List<DeviceInfo> devices) => devices.Skip(1).Select(d => d.Name); 156 169 157 170 /// <summary> 158 - /// Obtains the <see cref="TrackManager"/> corresponding to a given resource store. 159 - /// Returns the global <see cref="TrackManager"/> if no resource store is passed. 171 + /// Obtains the <see cref="TrackStore"/> corresponding to a given resource store. 172 + /// Returns the global <see cref="TrackStore"/> if no resource store is passed. 160 173 /// </summary> 161 - /// <param name="store">The <see cref="IResourceStore{T}"/> of which to retrieve the <see cref="TrackManager"/>.</param> 162 - public TrackManager GetTrackManager(IResourceStore<byte[]> store = null) 174 + /// <param name="store">The <see cref="IResourceStore{T}"/> of which to retrieve the <see cref="TrackStore"/>.</param> 175 + public ITrackStore GetTrackStore(IResourceStore<byte[]> store = null) 163 176 { 164 - if (store == null) return globalTrackManager.Value; 177 + if (store == null) return globalTrackStore.Value; 165 178 166 - TrackManager tm = new TrackManager(store); 167 - AddItem(tm); 168 - tm.AddAdjustment(AdjustableProperty.Volume, VolumeTrack); 169 - VolumeTrack.ValueChanged += e => tm.InvalidateState(e.NewValue); 170 - 179 + TrackStore tm = new TrackStore(store); 180 + globalTrackStore.Value.AddItem(tm); 171 181 return tm; 172 182 } 173 183 174 184 /// <summary> 175 - /// Obtains the <see cref="SampleManager"/> corresponding to a given resource store. 176 - /// Returns the global <see cref="SampleManager"/> if no resource store is passed. 185 + /// Obtains the <see cref="SampleStore"/> corresponding to a given resource store. 186 + /// Returns the global <see cref="SampleStore"/> if no resource store is passed. 177 187 /// </summary> 178 - /// <param name="store">The <see cref="IResourceStore{T}"/> of which to retrieve the <see cref="SampleManager"/>.</param> 179 - public SampleManager GetSampleManager(IResourceStore<byte[]> store = null) 188 + /// <param name="store">The <see cref="IResourceStore{T}"/> of which to retrieve the <see cref="SampleStore"/>.</param> 189 + public ISampleStore GetSampleStore(IResourceStore<byte[]> store = null) 180 190 { 181 - if (store == null) return globalSampleManager.Value; 191 + if (store == null) return globalSampleStore.Value; 182 192 183 - SampleManager sm = new SampleManager(store); 184 - AddItem(sm); 185 - sm.AddAdjustment(AdjustableProperty.Volume, VolumeSample); 186 - VolumeSample.ValueChanged += e => sm.InvalidateState(e.NewValue); 187 - 193 + SampleStore sm = new SampleStore(store); 194 + globalSampleStore.Value.AddItem(sm); 188 195 return sm; 189 196 } 190 197 ··· 209 216 newDevice = audioDevices.Find(df => df.IsDefault).Name; 210 217 211 218 bool oldDeviceValid = Bass.CurrentDevice >= 0; 219 + 212 220 if (oldDeviceValid) 213 221 { 214 222 DeviceInfo oldDeviceInfo = Bass.GetDeviceInfo(Bass.CurrentDevice); ··· 289 297 return true; 290 298 } 291 299 292 - public override void UpdateDevice(int deviceIndex) 293 - { 294 - Sample.UpdateDevice(deviceIndex); 295 - Track.UpdateDevice(deviceIndex); 296 - } 297 - 298 300 private void updateAvailableAudioDevices() 299 301 { 300 302 var currentDeviceList = getAllDevices().Where(d => d.IsEnabled).ToList(); ··· 326 328 { 327 329 // use default device 328 330 var device = Bass.GetDeviceInfo(Bass.CurrentDevice); 331 + 329 332 if (!device.IsDefault && !setAudioDevice()) 330 333 { 331 334 if (!device.IsEnabled || !setAudioDevice(device.Name)) ··· 345 348 { 346 349 // use whatever is the preferred device 347 350 var device = Bass.GetDeviceInfo(Bass.CurrentDevice); 351 + 348 352 if (device.Name == AudioDevice.Value) 349 353 { 350 354 if (!device.IsEnabled && !setAudioDevice()) ··· 362 366 else 363 367 { 364 368 var preferredDevice = getAllDevices().SingleOrDefault(d => d.Name == AudioDevice.Value); 369 + 365 370 if (preferredDevice.Name == AudioDevice.Value && preferredDevice.IsEnabled) 366 371 setAudioDevice(preferredDevice.Name); 367 372 else if (!device.IsEnabled && !setAudioDevice())
+39
osu.Framework/Audio/IAdjustableAudioComponent.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using osu.Framework.Bindables; 5 + 6 + namespace osu.Framework.Audio 7 + { 8 + public interface IAdjustableAudioComponent 9 + { 10 + /// <summary> 11 + /// The volume of this component. 12 + /// </summary> 13 + BindableDouble Volume { get; } 14 + 15 + /// <summary> 16 + /// The playback balance of this sample (-1 .. 1 where 0 is centered) 17 + /// </summary> 18 + BindableDouble Balance { get; } 19 + 20 + /// <summary> 21 + /// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency. 22 + /// </summary> 23 + BindableDouble Frequency { get; } 24 + 25 + /// <summary> 26 + /// Add a bindable adjustment source. 27 + /// </summary> 28 + /// <param name="type">The target type for this adjustment.</param> 29 + /// <param name="adjustBindable">The bindable adjustment.</param> 30 + void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable); 31 + 32 + /// <summary> 33 + /// Remove a bindable adjustment source. 34 + /// </summary> 35 + /// <param name="type">The target type for this adjustment.</param> 36 + /// <param name="adjustBindable">The bindable adjustment.</param> 37 + void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable); 38 + } 39 + }
+12
osu.Framework/Audio/IAdjustableResourceStore.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using osu.Framework.IO.Stores; 5 + 6 + namespace osu.Framework.Audio 7 + { 8 + public interface IAdjustableResourceStore<T> : IResourceStore<T>, IAdjustableAudioComponent 9 + where T : AudioComponent 10 + { 11 + } 12 + }
+28
osu.Framework/Audio/IAggregateAudioAdjustment.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using osu.Framework.Bindables; 5 + 6 + namespace osu.Framework.Audio 7 + { 8 + /// <summary> 9 + /// Provides aggregated adjustments for an audio component. 10 + /// </summary> 11 + public interface IAggregateAudioAdjustment 12 + { 13 + /// <summary> 14 + /// The aggregate volume of this component (0..1). 15 + /// </summary> 16 + IBindable<double> AggregateVolume { get; } 17 + 18 + /// <summary> 19 + /// The aggregate playback balance of this sample (-1 .. 1 where 0 is centered) 20 + /// </summary> 21 + IBindable<double> AggregateBalance { get; } 22 + 23 + /// <summary> 24 + /// The aggregate rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency. 25 + /// </summary> 26 + IBindable<double> AggregateFrequency { get; } 27 + } 28 + }
+32
osu.Framework/Audio/Sample/ISampleChannel.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + namespace osu.Framework.Audio.Sample 5 + { 6 + /// <summary> 7 + /// A channel playing back an audio sample. 8 + /// </summary> 9 + public interface ISampleChannel 10 + { 11 + /// <summary> 12 + /// Start playback. 13 + /// </summary> 14 + /// <param name="restart">Whether to restart the sample from the beginning.</param> 15 + void Play(bool restart = true); 16 + 17 + /// <summary> 18 + /// Stop playback. 19 + /// </summary> 20 + void Stop(); 21 + 22 + /// <summary> 23 + /// Whether the sample is playing. 24 + /// </summary> 25 + bool Playing { get; } 26 + 27 + /// <summary> 28 + /// Whether the sample has finished playback. 29 + /// </summary> 30 + bool Played { get; } 31 + } 32 + }
+2 -2
osu.Framework/Audio/Sample/SampleBass.cs
··· 9 9 10 10 namespace osu.Framework.Audio.Sample 11 11 { 12 - internal class SampleBass : Sample, IBassAudio 12 + internal sealed class SampleBass : Sample, IBassAudio 13 13 { 14 14 private volatile int sampleId; 15 15 16 16 public override bool IsLoaded => sampleId != 0; 17 17 18 - public SampleBass(byte[] data, ConcurrentQueue<Task> customPendingActions = null, int concurrency = DEFAULT_CONCURRENCY) 18 + internal SampleBass(byte[] data, ConcurrentQueue<Task> customPendingActions = null, int concurrency = DEFAULT_CONCURRENCY) 19 19 : base(concurrency) 20 20 { 21 21 if (customPendingActions != null)
+1 -1
osu.Framework/Audio/Sample/SampleChannel.cs
··· 6 6 7 7 namespace osu.Framework.Audio.Sample 8 8 { 9 - public abstract class SampleChannel : AdjustableAudioComponent 9 + public abstract class SampleChannel : AdjustableAudioComponent, ISampleChannel 10 10 { 11 11 protected bool WasStarted; 12 12
+4 -4
osu.Framework/Audio/Sample/SampleChannelBass.cs
··· 6 6 7 7 namespace osu.Framework.Audio.Sample 8 8 { 9 - public class SampleChannelBass : SampleChannel, IBassAudio 9 + public sealed class SampleChannelBass : SampleChannel, IBassAudio 10 10 { 11 11 private volatile int channel; 12 12 private volatile bool playing; ··· 35 35 36 36 if (channel != 0) 37 37 { 38 - Bass.ChannelSetAttribute(channel, ChannelAttribute.Volume, VolumeCalculated.Value); 39 - Bass.ChannelSetAttribute(channel, ChannelAttribute.Pan, BalanceCalculated.Value); 40 - Bass.ChannelSetAttribute(channel, ChannelAttribute.Frequency, initialFrequency * FrequencyCalculated.Value); 38 + Bass.ChannelSetAttribute(channel, ChannelAttribute.Volume, AggregateVolume.Value); 39 + Bass.ChannelSetAttribute(channel, ChannelAttribute.Pan, AggregateBalance.Value); 40 + Bass.ChannelSetAttribute(channel, ChannelAttribute.Frequency, initialFrequency * AggregateFrequency.Value); 41 41 } 42 42 } 43 43
+11 -4
osu.Framework/Audio/Sample/SampleManager.cs osu.Framework/Audio/Sample/SampleStore.cs
··· 1 1 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 + using System; 4 5 using System.Collections.Concurrent; 6 + using System.Collections.Generic; 5 7 using System.IO; 6 8 using osu.Framework.IO.Stores; 7 9 using osu.Framework.Statistics; 8 10 using System.Linq; 9 11 using System.Threading.Tasks; 12 + using osu.Framework.Audio.Track; 10 13 11 14 namespace osu.Framework.Audio.Sample 12 15 { 13 - public class SampleManager : AudioCollectionManager<SampleChannel>, IResourceStore<SampleChannel> 16 + internal class SampleStore : AudioCollectionManager<AdjustableAudioComponent>, ISampleStore 14 17 { 15 18 private readonly IResourceStore<byte[]> store; 16 19 ··· 21 24 /// </summary> 22 25 public int PlaybackConcurrency { get; set; } = Sample.DEFAULT_CONCURRENCY; 23 26 24 - public SampleManager(IResourceStore<byte[]> store) 27 + internal SampleStore(IResourceStore<byte[]> store) 25 28 { 26 29 this.store = store; 27 30 } 28 31 29 32 public SampleChannel Get(string name) 30 33 { 34 + if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(SampleStore)}"); 35 + 31 36 if (string.IsNullOrEmpty(name)) return null; 32 37 33 38 lock (sampleCache) 34 39 { 35 40 SampleChannel channel = null; 41 + 36 42 if (!sampleCache.TryGetValue(name, out Sample sample)) 37 43 { 38 44 byte[] data = store.Get(name); ··· 41 47 42 48 if (sample != null) 43 49 { 44 - channel = new SampleChannelBass(sample, AddItemToList); 45 - RegisterItem(channel); 50 + channel = new SampleChannelBass(sample, AddItem); 46 51 } 47 52 48 53 return channel; ··· 66 71 } 67 72 68 73 public Stream GetStream(string name) => store.GetStream(name); 74 + 75 + public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources(); 69 76 } 70 77 }
+11
osu.Framework/Audio/Track/ISampleStore.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using osu.Framework.Audio.Sample; 5 + 6 + namespace osu.Framework.Audio.Track 7 + { 8 + public interface ISampleStore : IAdjustableResourceStore<SampleChannel> 9 + { 10 + } 11 + }
+62
osu.Framework/Audio/Track/ITrack.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + namespace osu.Framework.Audio.Track 5 + { 6 + /// <summary> 7 + /// An audio track. 8 + /// </summary> 9 + public interface ITrack 10 + { 11 + /// <summary> 12 + /// States if this track should repeat. 13 + /// </summary> 14 + bool Looping { get; set; } 15 + 16 + /// <summary> 17 + /// Point in time in milliseconds to restart the track to on loop or <see cref="Restart"/>. 18 + /// </summary> 19 + double RestartPoint { get; set; } 20 + 21 + /// <summary> 22 + /// Current position in milliseconds. 23 + /// </summary> 24 + double CurrentTime { get; } 25 + 26 + /// <summary> 27 + /// Length of the track in milliseconds. 28 + /// </summary> 29 + double Length { get; set; } 30 + 31 + bool IsRunning { get; } 32 + 33 + /// <summary> 34 + /// Reset this track to a logical default state. 35 + /// </summary> 36 + void Reset(); 37 + 38 + /// <summary> 39 + /// Restarts this track from the <see cref="Track.RestartPoint"/> while retaining adjustments. 40 + /// </summary> 41 + void Restart(); 42 + 43 + void ResetSpeedAdjustments(); 44 + 45 + /// <summary> 46 + /// Seek to a new position. 47 + /// </summary> 48 + /// <param name="seek">New position in milliseconds</param> 49 + /// <returns>Whether the seek was successful.</returns> 50 + bool Seek(double seek); 51 + 52 + /// <summary> 53 + /// Start playback. 54 + /// </summary> 55 + void Start(); 56 + 57 + /// <summary> 58 + /// Stop playback. 59 + /// </summary> 60 + void Stop(); 61 + } 62 + }
+17
osu.Framework/Audio/Track/ITrackStore.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + 6 + namespace osu.Framework.Audio.Track 7 + { 8 + public interface ITrackStore : IAdjustableResourceStore<Track> 9 + { 10 + /// <summary> 11 + /// Retrieve a <see cref="TrackVirtual"/> with no audio device backing. 12 + /// </summary> 13 + /// <param name="length">The length of the virtual track.</param> 14 + /// <returns>A new virtual track.</returns> 15 + Track GetVirtual(double length = Double.PositiveInfinity); 16 + } 17 + }
+2 -2
osu.Framework/Audio/Track/Track.cs
··· 8 8 9 9 namespace osu.Framework.Audio.Track 10 10 { 11 - public abstract class Track : AdjustableAudioComponent, IAdjustableClock, IHasTempoAdjust 11 + public abstract class Track : AdjustableAudioComponent, IAdjustableClock, IHasTempoAdjust, ITrack 12 12 { 13 13 public event Action Completed; 14 14 public event Action Failed; ··· 38 38 39 39 protected Track() 40 40 { 41 - Tempo.ValueChanged += e => InvalidateState(e.NewValue); 41 + Tempo.ValueChanged += InvalidateState; 42 42 } 43 43 44 44 /// <summary>
+16 -6
osu.Framework/Audio/Track/TrackBass.cs
··· 14 14 15 15 namespace osu.Framework.Audio.Track 16 16 { 17 - public class TrackBass : Track, IBassAudio, IHasPitchAdjust 17 + public sealed class TrackBass : Track, IBassAudio, IHasPitchAdjust 18 18 { 19 + public const int BYTES_PER_SAMPLE = 4; 20 + 19 21 private AsyncBufferStream dataStream; 20 22 21 23 /// <summary> ··· 39 41 private bool isPlayed; 40 42 41 43 private long byteLength; 44 + 45 + /// <summary> 46 + /// The last position that a seek will succeed for. 47 + /// </summary> 48 + private double lastSeekablePosition; 42 49 43 50 private FileCallbacks fileCallbacks; 44 51 private SyncCallback stopCallback; ··· 96 103 if (success) 97 104 { 98 105 Length = seconds * 1000; 106 + 107 + // Bass does not allow seeking to the end of the track, so the last available position is 1 sample before. 108 + lastSeekablePosition = Bass.ChannelBytes2Seconds(activeStream, byteLength - BYTES_PER_SAMPLE) * 1000; 99 109 100 110 Bass.ChannelGetAttribute(activeStream, ChannelAttribute.Frequency, out float frequency); 101 111 initialFrequency = frequency; ··· 227 237 { 228 238 // At this point the track may not yet be loaded which is indicated by a 0 length. 229 239 // In that case we still want to return true, hence the conservative length. 230 - double conservativeLength = Length == 0 ? double.MaxValue : Length; 240 + double conservativeLength = Length == 0 ? double.MaxValue : lastSeekablePosition; 231 241 double conservativeClamped = MathHelper.Clamp(seek, 0, conservativeLength); 232 242 233 243 await EnqueueAction(() => ··· 255 265 { 256 266 base.OnStateChanged(); 257 267 258 - setDirection(FrequencyCalculated.Value < 0); 268 + setDirection(AggregateFrequency.Value < 0); 259 269 260 - Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Volume, VolumeCalculated.Value); 261 - Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Pan, BalanceCalculated.Value); 270 + Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Volume, AggregateVolume.Value); 271 + Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Pan, AggregateBalance.Value); 262 272 Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Frequency, bassFreq); 263 273 Bass.ChannelSetAttribute(tempoAdjustStream, ChannelAttribute.Tempo, (Math.Abs(Tempo.Value) - 1) * 100); 264 274 } 265 275 266 276 private volatile float initialFrequency; 267 277 268 - private int bassFreq => (int)MathHelper.Clamp(Math.Abs(initialFrequency * FrequencyCalculated.Value), 100, 100000); 278 + private int bassFreq => (int)MathHelper.Clamp(Math.Abs(initialFrequency * AggregateFrequency.Value), 100, 100000); 269 279 270 280 private volatile int bitrate; 271 281
-31
osu.Framework/Audio/Track/TrackManager.cs
··· 1 - // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 - // See the LICENCE file in the repository root for full licence text. 3 - 4 - using osu.Framework.IO.Stores; 5 - 6 - namespace osu.Framework.Audio.Track 7 - { 8 - public class TrackManager : AudioCollectionManager<Track> 9 - { 10 - private readonly IResourceStore<byte[]> store; 11 - 12 - public TrackManager(IResourceStore<byte[]> store) 13 - { 14 - this.store = store; 15 - } 16 - 17 - public Track Get(string name) 18 - { 19 - if (string.IsNullOrEmpty(name)) return null; 20 - 21 - var dataStream = store.GetStream(name); 22 - 23 - if (dataStream == null) 24 - return null; 25 - 26 - Track track = new TrackBass(dataStream); 27 - AddItem(track); 28 - return track; 29 - } 30 - } 31 - }
+52
osu.Framework/Audio/Track/TrackStore.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using System.Collections.Generic; 6 + using System.IO; 7 + using System.Threading.Tasks; 8 + using osu.Framework.IO.Stores; 9 + 10 + namespace osu.Framework.Audio.Track 11 + { 12 + internal class TrackStore : AudioCollectionManager<AdjustableAudioComponent>, ITrackStore 13 + { 14 + private readonly IResourceStore<byte[]> store; 15 + 16 + internal TrackStore(IResourceStore<byte[]> store) 17 + { 18 + this.store = store; 19 + } 20 + 21 + public Track GetVirtual(double length = double.PositiveInfinity) 22 + { 23 + if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(TrackStore)}"); 24 + 25 + var track = new TrackVirtual(length); 26 + AddItem(track); 27 + return track; 28 + } 29 + 30 + public Track Get(string name) 31 + { 32 + if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(TrackStore)}"); 33 + 34 + if (string.IsNullOrEmpty(name)) return null; 35 + 36 + var dataStream = store.GetStream(name); 37 + 38 + if (dataStream == null) 39 + return null; 40 + 41 + Track track = new TrackBass(dataStream); 42 + AddItem(track); 43 + return track; 44 + } 45 + 46 + public Task<Track> GetAsync(string name) => Task.Run(() => Get(name)); 47 + 48 + public Stream GetStream(string name) => store.GetStream(name); 49 + 50 + public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources(); 51 + } 52 + }
+4 -4
osu.Framework/Audio/Track/TrackVirtual.cs
··· 6 6 7 7 namespace osu.Framework.Audio.Track 8 8 { 9 - public class TrackVirtual : Track 9 + public sealed class TrackVirtual : Track 10 10 { 11 11 private readonly StopwatchClock clock = new StopwatchClock(); 12 12 13 13 private double seekOffset; 14 14 15 - public TrackVirtual() 15 + public TrackVirtual(double length) 16 16 { 17 - Length = double.PositiveInfinity; 17 + Length = length; 18 18 } 19 19 20 20 public override bool Seek(double seek) ··· 89 89 base.OnStateChanged(); 90 90 91 91 lock (clock) 92 - clock.Rate = Tempo.Value; 92 + clock.Rate = Tempo.Value * AggregateFrequency.Value; 93 93 } 94 94 } 95 95 }
+42 -41
osu.Framework/Audio/Track/Waveform.cs
··· 19 19 public class Waveform : IDisposable 20 20 { 21 21 /// <summary> 22 - /// <see cref="WaveformPoint"/>s are initially generated to a 1ms resolution to cover most use cases. 22 + /// <see cref="Point"/>s are initially generated to a 1ms resolution to cover most use cases. 23 23 /// </summary> 24 24 private const float resolution = 0.001f; 25 25 ··· 27 27 /// The data stream is iteratively decoded to provide this many points per iteration so as to not exceed BASS's internal buffer size. 28 28 /// </summary> 29 29 private const int points_per_iteration = 100000; 30 - 31 - private const int bytes_per_sample = 4; 32 30 33 31 /// <summary> 34 32 /// FFT1024 gives ~40hz accuracy. ··· 61 59 private const double high_max = 12000; 62 60 63 61 private int channels; 64 - private List<WaveformPoint> points = new List<WaveformPoint>(); 62 + private List<Point> points = new List<Point>(); 65 63 66 64 private readonly CancellationTokenSource cancelSource = new CancellationTokenSource(); 67 65 private readonly Task readTask; ··· 93 91 // Each "point" is generated from a number of samples, each sample contains a number of channels 94 92 int samplesPerPoint = (int)(info.Frequency * resolution * info.Channels); 95 93 96 - int bytesPerPoint = samplesPerPoint * bytes_per_sample; 94 + int bytesPerPoint = samplesPerPoint * TrackBass.BYTES_PER_SAMPLE; 97 95 98 96 points.Capacity = (int)(length / bytesPerPoint); 99 97 100 98 // Each iteration pulls in several samples 101 99 int bytesPerIteration = bytesPerPoint * points_per_iteration; 102 - var sampleBuffer = new float[bytesPerIteration / bytes_per_sample]; 100 + var sampleBuffer = new float[bytesPerIteration / TrackBass.BYTES_PER_SAMPLE]; 103 101 104 102 // Read sample data 105 103 while (length > 0) 106 104 { 107 105 length = Bass.ChannelGetData(decodeStream, sampleBuffer, bytesPerIteration); 108 - int samplesRead = (int)(length / bytes_per_sample); 106 + int samplesRead = (int)(length / TrackBass.BYTES_PER_SAMPLE); 109 107 110 108 // Each point is composed of multiple samples 111 109 for (int i = 0; i < samplesRead; i += samplesPerPoint) 112 110 { 113 111 // Channels are interleaved in the sample data (data[0] -> channel0, data[1] -> channel1, data[2] -> channel0, etc) 114 112 // samplesPerPoint assumes this interleaving behaviour 115 - var point = new WaveformPoint(info.Channels); 113 + var point = new Point(info.Channels); 114 + 116 115 for (int j = i; j < i + samplesPerPoint; j += info.Channels) 117 116 { 118 117 // Find the maximum amplitude for each channel in the point ··· 135 134 float[] bins = new float[fft_bins]; 136 135 int currentPoint = 0; 137 136 long currentByte = 0; 137 + 138 138 while (length > 0) 139 139 { 140 140 length = Bass.ChannelGetData(decodeStream, bins, (int)fft_samples); ··· 190 190 191 191 return await Task.Run(() => 192 192 { 193 - var generatedPoints = new List<WaveformPoint>(); 193 + var generatedPoints = new List<Point>(); 194 194 float pointsPerGeneratedPoint = (float)points.Count / pointCount; 195 195 196 196 // Determines at which width (relative to the resolution) our smoothing filter is truncated. ··· 215 215 int startIndex = (int)i - kernelWidth; 216 216 int endIndex = (int)i + kernelWidth; 217 217 218 - var point = new WaveformPoint(channels); 218 + var point = new Point(channels); 219 219 float totalWeight = 0; 220 + 220 221 for (int j = startIndex; j < endIndex; j++) 221 222 { 222 223 if (j < 0 || j >= points.Count) continue; ··· 252 253 /// <summary> 253 254 /// Gets all the points represented by this <see cref="Waveform"/>. 254 255 /// </summary> 255 - public List<WaveformPoint> GetPoints() => GetPointsAsync().Result; 256 + public List<Point> GetPoints() => GetPointsAsync().Result; 256 257 257 258 /// <summary> 258 259 /// Gets all the points represented by this <see cref="Waveform"/>. 259 260 /// </summary> 260 - public async Task<List<WaveformPoint>> GetPointsAsync() 261 + public async Task<List<Point>> GetPointsAsync() 261 262 { 262 263 if (readTask == null) 263 264 return points; ··· 267 268 } 268 269 269 270 /// <summary> 270 - /// Gets the number of channels represented by each <see cref="WaveformPoint"/>. 271 + /// Gets the number of channels represented by each <see cref="Point"/>. 271 272 /// </summary> 272 273 public int GetChannels() => GetChannelsAsync().Result; 273 274 274 275 /// <summary> 275 - /// Gets the number of channels represented by each <see cref="WaveformPoint"/>. 276 + /// Gets the number of channels represented by each <see cref="Point"/>. 276 277 /// </summary> 277 278 public async Task<int> GetChannelsAsync() 278 279 { ··· 314 315 } 315 316 316 317 #endregion 317 - } 318 318 319 - /// <summary> 320 - /// Represents a singular point of data in a <see cref="Waveform"/>. 321 - /// </summary> 322 - public class WaveformPoint 323 - { 324 319 /// <summary> 325 - /// An array of amplitudes, one for each channel. 320 + /// Represents a singular point of data in a <see cref="Waveform"/>. 326 321 /// </summary> 327 - public readonly float[] Amplitude; 322 + public class Point 323 + { 324 + /// <summary> 325 + /// An array of amplitudes, one for each channel. 326 + /// </summary> 327 + public readonly float[] Amplitude; 328 328 329 - /// <summary> 330 - /// Unnormalised total intensity of the low-range (bass) frequencies. 331 - /// </summary> 332 - public double LowIntensity; 329 + /// <summary> 330 + /// Unnormalised total intensity of the low-range (bass) frequencies. 331 + /// </summary> 332 + public double LowIntensity; 333 333 334 - /// <summary> 335 - /// Unnormalised total intensity of the mid-range frequencies. 336 - /// </summary> 337 - public double MidIntensity; 334 + /// <summary> 335 + /// Unnormalised total intensity of the mid-range frequencies. 336 + /// </summary> 337 + public double MidIntensity; 338 338 339 - /// <summary> 340 - /// Unnormalised total intensity of the high-range (treble) frequencies. 341 - /// </summary> 342 - public double HighIntensity; 339 + /// <summary> 340 + /// Unnormalised total intensity of the high-range (treble) frequencies. 341 + /// </summary> 342 + public double HighIntensity; 343 343 344 - /// <summary> 345 - /// Cconstructs a <see cref="WaveformPoint"/>. 346 - /// </summary> 347 - /// <param name="channels">The number of channels that contain data.</param> 348 - public WaveformPoint(int channels) 349 - { 350 - Amplitude = new float[channels]; 344 + /// <summary> 345 + /// Cconstructs a <see cref="Point"/>. 346 + /// </summary> 347 + /// <param name="channels">The number of channels that contain data.</param> 348 + public Point(int channels) 349 + { 350 + Amplitude = new float[channels]; 351 + } 351 352 } 352 353 } 353 354 }
+96
osu.Framework/Bindables/AggregateBindable.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using System.Collections.Generic; 6 + using System.Linq; 7 + 8 + namespace osu.Framework.Bindables 9 + { 10 + /// <summary> 11 + /// Combines multiple bindables into one aggregate bindable result. 12 + /// </summary> 13 + /// <typeparam name="T">The type of values.</typeparam> 14 + public class AggregateBindable<T> 15 + { 16 + private readonly Func<T, T, T> aggregateFunction; 17 + 18 + /// <summary> 19 + /// The final result after aggregating all added sources. 20 + /// </summary> 21 + public IBindable<T> Result => result; 22 + 23 + private readonly Bindable<T> result; 24 + 25 + private readonly T initialValue; 26 + 27 + /// <summary> 28 + /// Create a new aggregate bindable. 29 + /// </summary> 30 + /// <param name="aggregateFunction">The function to be used for aggregation, taking two input <see cref="T"/> values and returning one output.</param> 31 + /// <param name="resultBindable">An optional newly constructed bindable to use for <see cref="Result"/>. The initial value of this bindable is used as the initial value for the aggregate.</param> 32 + public AggregateBindable(Func<T, T, T> aggregateFunction, Bindable<T> resultBindable = null) 33 + { 34 + this.aggregateFunction = aggregateFunction; 35 + result = resultBindable ?? new Bindable<T>(); 36 + initialValue = result.Value; 37 + } 38 + 39 + private readonly Dictionary<WeakReference, IBindable<T>> sourceMapping = new Dictionary<WeakReference, IBindable<T>>(); 40 + 41 + /// <summary> 42 + /// Add a new source to be included in aggregation. 43 + /// </summary> 44 + /// <param name="bindable">The bindable to add.</param> 45 + public void AddSource(IBindable<T> bindable) 46 + { 47 + lock (sourceMapping) 48 + { 49 + if (findExistingWeak(bindable) != null) 50 + return; 51 + 52 + var boundCopy = bindable.GetBoundCopy(); 53 + sourceMapping.Add(new WeakReference(bindable), boundCopy); 54 + boundCopy.BindValueChanged(recalculateAggregate, true); 55 + } 56 + } 57 + 58 + /// <summary> 59 + /// Remove a source from being included in aggregation. 60 + /// </summary> 61 + /// <param name="bindable">The bindable to remove.</param> 62 + public void RemoveSource(IBindable<T> bindable) 63 + { 64 + lock (sourceMapping) 65 + { 66 + var weak = findExistingWeak(bindable); 67 + 68 + if (weak != null) 69 + { 70 + sourceMapping[weak].UnbindAll(); 71 + sourceMapping.Remove(weak); 72 + } 73 + 74 + recalculateAggregate(); 75 + } 76 + } 77 + 78 + private WeakReference findExistingWeak(IBindable<T> bindable) => sourceMapping.Keys.FirstOrDefault(k => k.Target == bindable); 79 + 80 + private void recalculateAggregate(ValueChangedEvent<T> obj = null) 81 + { 82 + T calculated = initialValue; 83 + 84 + lock (sourceMapping) 85 + { 86 + foreach (var dead in sourceMapping.Keys.Where(k => !k.IsAlive).ToArray()) 87 + sourceMapping.Remove(dead); 88 + 89 + foreach (var s in sourceMapping.Values) 90 + calculated = aggregateFunction(calculated, s.Value); 91 + } 92 + 93 + result.Value = calculated; 94 + } 95 + } 96 + }
+8 -3
osu.Framework/Bindables/Bindable.cs
··· 209 209 case T t: 210 210 Value = t; 211 211 break; 212 + 212 213 case string s: 213 - Value = typeof(T).IsEnum 214 - ? (T)Enum.Parse(typeof(T), s) 215 - : (T)Convert.ChangeType(s, typeof(T), CultureInfo.InvariantCulture); 214 + var underlyingType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); 215 + 216 + if (underlyingType.IsEnum) 217 + Value = (T)Enum.Parse(underlyingType, s); 218 + else 219 + Value = (T)Convert.ChangeType(s, underlyingType, CultureInfo.InvariantCulture); 216 220 break; 221 + 217 222 default: 218 223 throw new ArgumentException($@"Could not parse provided {input.GetType()} ({input}) to {typeof(T)}."); 219 224 }
+2
osu.Framework/Bindables/BindableList.cs
··· 350 350 case null: 351 351 Clear(); 352 352 break; 353 + 353 354 case IEnumerable<T> enumerable: 354 355 Clear(); 355 356 AddRange(enumerable); 356 357 break; 358 + 357 359 default: 358 360 throw new ArgumentException($@"Could not parse provided {input.GetType()} ({input}) to {typeof(T)}."); 359 361 }
+1
osu.Framework/Bindables/BindableMarginPadding.cs
··· 80 80 Right = float.Parse(split[3], CultureInfo.InvariantCulture), 81 81 }; 82 82 break; 83 + 83 84 default: 84 85 base.Parse(input); 85 86 break;
+19
osu.Framework/Bindables/BindableNumber.cs
··· 228 228 case TypeCode.UInt64: 229 229 case TypeCode.Int64: 230 230 return true; 231 + 231 232 default: 232 233 return false; 233 234 } ··· 245 246 246 247 byteBindable.Value = Convert.ToByte(val); 247 248 break; 249 + 248 250 case TypeCode.SByte: 249 251 var sbyteBindable = this as BindableNumber<sbyte>; 250 252 if (sbyteBindable == null) throw new ArgumentNullException(nameof(sbyteBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 251 253 252 254 sbyteBindable.Value = Convert.ToSByte(val); 253 255 break; 256 + 254 257 case TypeCode.UInt16: 255 258 var ushortBindable = this as BindableNumber<ushort>; 256 259 if (ushortBindable == null) throw new ArgumentNullException(nameof(ushortBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 257 260 258 261 ushortBindable.Value = Convert.ToUInt16(val); 259 262 break; 263 + 260 264 case TypeCode.Int16: 261 265 var shortBindable = this as BindableNumber<short>; 262 266 if (shortBindable == null) throw new ArgumentNullException(nameof(shortBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 263 267 264 268 shortBindable.Value = Convert.ToInt16(val); 265 269 break; 270 + 266 271 case TypeCode.UInt32: 267 272 var uintBindable = this as BindableNumber<uint>; 268 273 if (uintBindable == null) throw new ArgumentNullException(nameof(uintBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 269 274 270 275 uintBindable.Value = Convert.ToUInt32(val); 271 276 break; 277 + 272 278 case TypeCode.Int32: 273 279 var intBindable = this as BindableNumber<int>; 274 280 if (intBindable == null) throw new ArgumentNullException(nameof(intBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 275 281 276 282 intBindable.Value = Convert.ToInt32(val); 277 283 break; 284 + 278 285 case TypeCode.UInt64: 279 286 var ulongBindable = this as BindableNumber<ulong>; 280 287 if (ulongBindable == null) throw new ArgumentNullException(nameof(ulongBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 281 288 282 289 ulongBindable.Value = Convert.ToUInt64(val); 283 290 break; 291 + 284 292 case TypeCode.Int64: 285 293 var longBindable = this as BindableNumber<long>; 286 294 if (longBindable == null) throw new ArgumentNullException(nameof(longBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 287 295 288 296 longBindable.Value = Convert.ToInt64(val); 289 297 break; 298 + 290 299 case TypeCode.Single: 291 300 var floatBindable = this as BindableNumber<float>; 292 301 if (floatBindable == null) throw new ArgumentNullException(nameof(floatBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 293 302 294 303 floatBindable.Value = Convert.ToSingle(val); 295 304 break; 305 + 296 306 case TypeCode.Double: 297 307 var doubleBindable = this as BindableNumber<double>; 298 308 if (doubleBindable == null) throw new ArgumentNullException(nameof(doubleBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); ··· 313 323 314 324 byteBindable.Value += Convert.ToByte(val); 315 325 break; 326 + 316 327 case TypeCode.SByte: 317 328 var sbyteBindable = this as BindableNumber<sbyte>; 318 329 if (sbyteBindable == null) throw new ArgumentNullException(nameof(sbyteBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 319 330 320 331 sbyteBindable.Value += Convert.ToSByte(val); 321 332 break; 333 + 322 334 case TypeCode.UInt16: 323 335 var ushortBindable = this as BindableNumber<ushort>; 324 336 if (ushortBindable == null) throw new ArgumentNullException(nameof(ushortBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 325 337 326 338 ushortBindable.Value += Convert.ToUInt16(val); 327 339 break; 340 + 328 341 case TypeCode.Int16: 329 342 var shortBindable = this as BindableNumber<short>; 330 343 if (shortBindable == null) throw new ArgumentNullException(nameof(shortBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 331 344 332 345 shortBindable.Value += Convert.ToInt16(val); 333 346 break; 347 + 334 348 case TypeCode.UInt32: 335 349 var uintBindable = this as BindableNumber<uint>; 336 350 if (uintBindable == null) throw new ArgumentNullException(nameof(uintBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 337 351 338 352 uintBindable.Value += Convert.ToUInt32(val); 339 353 break; 354 + 340 355 case TypeCode.Int32: 341 356 var intBindable = this as BindableNumber<int>; 342 357 if (intBindable == null) throw new ArgumentNullException(nameof(intBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 343 358 344 359 intBindable.Value += Convert.ToInt32(val); 345 360 break; 361 + 346 362 case TypeCode.UInt64: 347 363 var ulongBindable = this as BindableNumber<ulong>; 348 364 if (ulongBindable == null) throw new ArgumentNullException(nameof(ulongBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 349 365 350 366 ulongBindable.Value += Convert.ToUInt64(val); 351 367 break; 368 + 352 369 case TypeCode.Int64: 353 370 var longBindable = this as BindableNumber<long>; 354 371 if (longBindable == null) throw new ArgumentNullException(nameof(longBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 355 372 356 373 longBindable.Value += Convert.ToInt64(val); 357 374 break; 375 + 358 376 case TypeCode.Single: 359 377 var floatBindable = this as BindableNumber<float>; 360 378 if (floatBindable == null) throw new ArgumentNullException(nameof(floatBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}."); 361 379 362 380 floatBindable.Value += Convert.ToSingle(val); 363 381 break; 382 + 364 383 case TypeCode.Double: 365 384 var doubleBindable = this as BindableNumber<double>; 366 385 if (doubleBindable == null) throw new ArgumentNullException(nameof(doubleBindable), $"Generic type {typeof(T)} does not match actual bindable type {GetType()}.");
+1
osu.Framework/Bindables/BindableSize.cs
··· 60 60 61 61 Value = new Size(int.Parse(split[0]), int.Parse(split[1])); 62 62 break; 63 + 63 64 default: 64 65 base.Parse(input); 65 66 break;
+3 -1
osu.Framework/Configuration/FrameworkDebugConfig.cs
··· 21 21 22 22 Set(DebugSetting.ActiveGCMode, GCLatencyMode.SustainedLowLatency); 23 23 Set(DebugSetting.BypassCaching, false).ValueChanged += delegate { StaticCached.BypassCache = Get<bool>(DebugSetting.BypassCaching); }; 24 + Set(DebugSetting.BypassFrontToBackPass, false); 24 25 } 25 26 } 26 27 27 28 public enum DebugSetting 28 29 { 29 30 ActiveGCMode, 30 - BypassCaching 31 + BypassCaching, 32 + BypassFrontToBackPass 31 33 } 32 34 }
+2
osu.Framework/Extensions/ExtensionMethods.cs
··· 103 103 return null; 104 104 105 105 var jagged = new T[rectangular.GetLength(0)][]; 106 + 106 107 for (int r = 0; r < rectangular.GetLength(0); r++) 107 108 { 108 109 jagged[r] = new T[rectangular.GetLength(1)]; ··· 130 131 var cols = rows == 0 ? 0 : jagged.Max(c => c?.Length ?? 0); 131 132 132 133 var rectangular = new T[rows, cols]; 134 + 133 135 for (int r = 0; r < rows; r++) 134 136 for (int c = 0; c < cols; c++) 135 137 {
+7 -5
osu.Framework/Game.cs
··· 1 - // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; ··· 20 20 using osu.Framework.IO.Stores; 21 21 using osu.Framework.Localisation; 22 22 using osu.Framework.Platform; 23 - using GameWindow = osu.Framework.Platform.GameWindow; 24 23 25 24 namespace osu.Framework 26 25 { 27 26 public abstract class Game : Container, IKeyBindingHandler<FrameworkAction> 28 27 { 29 - public GameWindow Window => Host?.Window; 28 + public IWindow Window => Host?.Window; 30 29 31 30 public ResourceStore<byte[]> Resources { get; private set; } 32 31 ··· 124 123 Textures.AddStore(Host.CreateTextureLoaderStore(new OnlineStore())); 125 124 dependencies.Cache(Textures); 126 125 127 - var tracks = new ResourceStore<byte[]>(Resources); 126 + var tracks = new ResourceStore<byte[]>(); 128 127 tracks.AddStore(new NamespacedResourceStore<byte[]>(Resources, @"Tracks")); 129 128 tracks.AddStore(new OnlineStore()); 130 129 131 - var samples = new ResourceStore<byte[]>(Resources); 130 + var samples = new ResourceStore<byte[]>(); 132 131 samples.AddStore(new NamespacedResourceStore<byte[]>(Resources, @"Samples")); 133 132 samples.AddStore(new OnlineStore()); 134 133 135 134 Audio = new AudioManager(Host.AudioThread, tracks, samples) { EventScheduler = Scheduler }; 136 135 dependencies.Cache(Audio); 136 + 137 + dependencies.CacheAs(Audio.Tracks); 138 + dependencies.CacheAs(Audio.Samples); 137 139 138 140 // attach our bindables to the audio subsystem. 139 141 config.BindWith(FrameworkSetting.AudioDevice, Audio.AudioDevice);
+2
osu.Framework/Graphics/Animations/Animation.cs
··· 129 129 if (IsPlaying && frameData.Count > 0) 130 130 { 131 131 currentFrameTime += Time.Elapsed; 132 + 132 133 while (currentFrameTime > frameData[currentFrameIndex].Duration) 133 134 { 134 135 currentFrameTime -= frameData[currentFrameIndex].Duration; 135 136 ++currentFrameIndex; 137 + 136 138 if (currentFrameIndex >= frameData.Count) 137 139 { 138 140 if (Repeat)
+107
osu.Framework/Graphics/Audio/DrawableAudioWrapper.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using JetBrains.Annotations; 6 + using osu.Framework.Allocation; 7 + using osu.Framework.Audio; 8 + using osu.Framework.Bindables; 9 + using osu.Framework.Graphics.Containers; 10 + using osu.Framework.Graphics.Transforms; 11 + 12 + namespace osu.Framework.Graphics.Audio 13 + { 14 + /// <summary> 15 + /// A wrapper which allows audio components (or adjustments) to exist in the draw hierarchy. 16 + /// </summary> 17 + [Cached(typeof(IAggregateAudioAdjustment))] 18 + public abstract class DrawableAudioWrapper : CompositeDrawable, IAggregateAudioAdjustment 19 + { 20 + /// <summary> 21 + /// The volume of this component. 22 + /// </summary> 23 + public BindableDouble Volume => adjustments.Volume; 24 + 25 + /// <summary> 26 + /// The playback balance of this sample (-1 .. 1 where 0 is centered) 27 + /// </summary> 28 + public BindableDouble Balance => adjustments.Balance; 29 + 30 + /// <summary> 31 + /// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency. 32 + /// </summary> 33 + public BindableDouble Frequency => adjustments.Frequency; 34 + 35 + private readonly AdjustableAudioComponent component; 36 + 37 + private readonly bool disposeUnderlyingComponentOnDispose; 38 + 39 + private readonly AudioAdjustments adjustments = new AudioAdjustments(); 40 + 41 + /// <summary> 42 + /// Creates a <see cref="DrawableAudioWrapper"/> that will contain a drawable child. 43 + /// Generally used to add adjustments to a hierarchy without adding an audio component. 44 + /// </summary> 45 + /// <param name="content">The <see cref="Drawable"/> to be wrapped.</param> 46 + protected DrawableAudioWrapper(Drawable content) 47 + { 48 + AddInternal(content); 49 + } 50 + 51 + /// <summary> 52 + /// Creates a <see cref="DrawableAudioWrapper"/> that will wrap an audio component (and contain no drawable content). 53 + /// </summary> 54 + /// <param name="component">The audio component to wrap.</param> 55 + /// <param name="disposeUnderlyingComponentOnDispose">Whether the component should be automatically disposed on drawable disposal/expiry.</param> 56 + protected DrawableAudioWrapper([NotNull] AdjustableAudioComponent component, bool disposeUnderlyingComponentOnDispose = true) 57 + { 58 + this.component = component ?? throw new ArgumentNullException(nameof(component)); 59 + this.disposeUnderlyingComponentOnDispose = disposeUnderlyingComponentOnDispose; 60 + 61 + component.BindAdjustments(adjustments); 62 + } 63 + 64 + [BackgroundDependencyLoader(true)] 65 + private void load(IAggregateAudioAdjustment parentAdjustment) 66 + { 67 + if (parentAdjustment != null) 68 + adjustments.BindAdjustments(parentAdjustment); 69 + } 70 + 71 + protected override void Dispose(bool isDisposing) 72 + { 73 + base.Dispose(isDisposing); 74 + component?.UnbindAdjustments(adjustments); 75 + 76 + if (disposeUnderlyingComponentOnDispose) 77 + component?.Dispose(); 78 + } 79 + 80 + public IBindable<double> AggregateVolume => adjustments.AggregateVolume; 81 + 82 + public IBindable<double> AggregateBalance => adjustments.AggregateBalance; 83 + 84 + public IBindable<double> AggregateFrequency => adjustments.AggregateFrequency; 85 + 86 + /// <summary> 87 + /// Smoothly adjusts <see cref="Volume"/> over time. 88 + /// </summary> 89 + /// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns> 90 + public TransformSequence<DrawableAudioWrapper> VolumeTo(double newVolume, double duration = 0, Easing easing = Easing.None) => 91 + this.TransformBindableTo(Volume, newVolume, duration, easing); 92 + 93 + /// <summary> 94 + /// Smoothly adjusts <see cref="Balance"/> over time. 95 + /// </summary> 96 + /// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns> 97 + public TransformSequence<DrawableAudioWrapper> BalanceTo(double newBalance, double duration = 0, Easing easing = Easing.None) => 98 + this.TransformBindableTo(Balance, newBalance, duration, easing); 99 + 100 + /// <summary> 101 + /// Smoothly adjusts <see cref="Frequency"/> over time. 102 + /// </summary> 103 + /// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns> 104 + public TransformSequence<DrawableAudioWrapper> FrequencyTo(double newFrequency, double duration = 0, Easing easing = Easing.None) => 105 + this.TransformBindableTo(Frequency, newFrequency, duration, easing); 106 + } 107 + }
+34
osu.Framework/Graphics/Audio/DrawableSample.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using osu.Framework.Audio.Sample; 5 + 6 + namespace osu.Framework.Graphics.Audio 7 + { 8 + /// <summary> 9 + /// A <see cref="SampleChannel"/> wrapper to allow insertion in the draw hierarchy to allow transforms, lifetime management etc. 10 + /// </summary> 11 + public class DrawableSample : DrawableAudioWrapper, ISampleChannel 12 + { 13 + private readonly SampleChannel channel; 14 + 15 + /// <summary> 16 + /// Construct a new drawable sample instance. 17 + /// </summary> 18 + /// <param name="channel">The audio sample to wrap.</param> 19 + /// <param name="disposeChannelOnDisposal">Whether the sample should be automatically disposed on drawable disposal/expiry.</param> 20 + public DrawableSample(SampleChannel channel, bool disposeChannelOnDisposal = true) 21 + : base(channel, disposeChannelOnDisposal) 22 + { 23 + this.channel = channel; 24 + } 25 + 26 + public void Play(bool restart = true) => channel.Play(restart); 27 + 28 + public void Stop() => channel.Stop(); 29 + 30 + public bool Playing => channel.Playing; 31 + 32 + public bool Played => channel.Played; 33 + } 34 + }
+60
osu.Framework/Graphics/Audio/DrawableTrack.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using osu.Framework.Audio.Track; 5 + 6 + namespace osu.Framework.Graphics.Audio 7 + { 8 + /// <summary> 9 + /// A <see cref="Track"/> wrapper to allow insertion in the draw hierarchy to allow transforms, lifetime management etc. 10 + /// </summary> 11 + public class DrawableTrack : DrawableAudioWrapper, ITrack 12 + { 13 + private readonly Track track; 14 + 15 + /// <summary> 16 + /// Construct a new drawable track instance. 17 + /// </summary> 18 + /// <param name="track">The audio track to wrap.</param> 19 + /// <param name="disposeTrackOnDisposal">Whether the track should be automatically disposed on drawable disposal/expiry.</param> 20 + public DrawableTrack(Track track, bool disposeTrackOnDisposal = true) 21 + : base(track, disposeTrackOnDisposal) 22 + { 23 + this.track = track; 24 + } 25 + 26 + public bool Looping 27 + { 28 + get => track.Looping; 29 + set => track.Looping = value; 30 + } 31 + 32 + public double RestartPoint 33 + { 34 + get => track.RestartPoint; 35 + set => track.RestartPoint = value; 36 + } 37 + 38 + public double CurrentTime => track.CurrentTime; 39 + 40 + public double Length 41 + { 42 + get => track.Length; 43 + set => track.Length = value; 44 + } 45 + 46 + public bool IsRunning => track.IsRunning; 47 + 48 + public void Reset() => track.Reset(); 49 + 50 + public void Restart() => track.Restart(); 51 + 52 + public void ResetSpeedAdjustments() => track.ResetSpeedAdjustments(); 53 + 54 + public bool Seek(double seek) => track.Seek(seek); 55 + 56 + public void Start() => track.Start(); 57 + 58 + public void Stop() => track.Stop(); 59 + } 60 + }
+4 -3
osu.Framework/Graphics/Audio/WaveformGraph.cs
··· 43 43 private float resolution = 1; 44 44 45 45 /// <summary> 46 - /// Gets or sets the amount of <see cref="WaveformPoint"/>'s displayed relative to <see cref="WaveformGraph.DrawWidth"/>. 46 + /// Gets or sets the amount of <see cref="Framework.Audio.Track.Waveform.Point"/>'s displayed relative to <see cref="WaveformGraph.DrawWidth"/>. 47 47 /// </summary> 48 48 public float Resolution 49 49 { ··· 195 195 private IShader shader; 196 196 private Texture texture; 197 197 198 - private IReadOnlyList<WaveformPoint> points; 198 + private IReadOnlyList<Waveform.Point> points; 199 199 200 200 private Vector2 drawSize; 201 201 private int channels; ··· 293 293 ); 294 294 } 295 295 break; 296 + 296 297 case 1: 297 298 { 298 299 quadToDraw = new Quad( ··· 306 307 } 307 308 308 309 quadToDraw *= DrawInfo.Matrix; 309 - texture.DrawQuad(quadToDraw, colour, null, vertexBatch.AddAction, Vector2.Divide(localInflationAmount, quadToDraw.Size)); 310 + DrawQuad(texture, quadToDraw, colour, null, vertexBatch.AddAction, Vector2.Divide(localInflationAmount, quadToDraw.Size)); 310 311 } 311 312 312 313 shader.Unbind();
+2 -3
osu.Framework/Graphics/Batches/VertexBatch.cs
··· 88 88 89 89 VertexBuffer<T> vertexBuffer = currentVertexBuffer; 90 90 91 - if (!vertexBuffer.Vertices[currentVertex].Equals(v)) 91 + if (vertexBuffer.SetVertex(currentVertex, v)) 92 92 { 93 93 if (changeBeginIndex == -1) 94 94 changeBeginIndex = currentVertex; ··· 96 96 changeEndIndex = currentVertex + 1; 97 97 } 98 98 99 - vertexBuffer.Vertices[currentVertex] = v; 100 99 ++currentVertex; 101 100 102 - if (currentVertex >= vertexBuffer.Vertices.Length) 101 + if (currentVertex >= vertexBuffer.Size) 103 102 { 104 103 Draw(); 105 104 FrameStatistics.Increment(StatisticsCounterType.VBufOverflow);
+6
osu.Framework/Graphics/BlendingInfo.cs
··· 27 27 SourceAlpha = BlendingFactorSrc.One; 28 28 DestinationAlpha = BlendingFactorDest.One; 29 29 break; 30 + 30 31 case BlendingMode.Additive: 31 32 Source = BlendingFactorSrc.SrcAlpha; 32 33 Destination = BlendingFactorDest.One; 33 34 SourceAlpha = BlendingFactorSrc.One; 34 35 DestinationAlpha = BlendingFactorDest.One; 35 36 break; 37 + 36 38 default: 37 39 Source = BlendingFactorSrc.One; 38 40 Destination = BlendingFactorDest.Zero; ··· 53 55 case BlendingEquation.Inherit: 54 56 case BlendingEquation.Add: 55 57 return BlendEquationMode.FuncAdd; 58 + 56 59 case BlendingEquation.Min: 57 60 return BlendEquationMode.Min; 61 + 58 62 case BlendingEquation.Max: 59 63 return BlendEquationMode.Max; 64 + 60 65 case BlendingEquation.Subtract: 61 66 return BlendEquationMode.FuncSubtract; 67 + 62 68 case BlendingEquation.ReverseSubtract: 63 69 return BlendEquationMode.FuncReverseSubtract; 64 70 }
+191
osu.Framework/Graphics/BufferedDrawNode.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using osu.Framework.Allocation; 6 + using osu.Framework.Graphics.OpenGL; 7 + using osu.Framework.Graphics.OpenGL.Buffers; 8 + using osu.Framework.Graphics.OpenGL.Vertices; 9 + using osu.Framework.Graphics.Primitives; 10 + using osuTK; 11 + using osuTK.Graphics; 12 + using osuTK.Graphics.ES30; 13 + 14 + namespace osu.Framework.Graphics 15 + { 16 + public class BufferedDrawNode : TexturedShaderDrawNode 17 + { 18 + protected new IBufferedDrawable Source => (IBufferedDrawable)base.Source; 19 + 20 + /// <summary> 21 + /// The child <see cref="DrawNode"/> which is used to populate the <see cref="FrameBuffer"/>s with. 22 + /// </summary> 23 + protected readonly DrawNode Child; 24 + 25 + /// <summary> 26 + /// Data shared amongst all <see cref="BufferedDrawNode"/>s, providing storage for <see cref="FrameBuffer"/>s. 27 + /// </summary> 28 + protected readonly BufferedDrawNodeSharedData SharedData; 29 + 30 + /// <summary> 31 + /// Contains the colour and blending information of this <see cref="DrawNode"/>. 32 + /// </summary> 33 + protected new DrawColourInfo DrawColourInfo { get; private set; } 34 + 35 + protected RectangleF DrawRectangle { get; private set; } 36 + 37 + private Color4 backgroundColour; 38 + private RectangleF screenSpaceDrawRectangle; 39 + private Vector2 frameBufferSize; 40 + 41 + private readonly All filteringMode; 42 + private readonly RenderbufferInternalFormat[] formats; 43 + 44 + public BufferedDrawNode(IBufferedDrawable source, DrawNode child, BufferedDrawNodeSharedData sharedData, RenderbufferInternalFormat[] formats = null, bool pixelSnapping = false) 45 + : base(source) 46 + { 47 + this.formats = formats; 48 + 49 + Child = child; 50 + SharedData = sharedData; 51 + 52 + filteringMode = pixelSnapping ? All.Nearest : All.Linear; 53 + } 54 + 55 + public override void ApplyState() 56 + { 57 + base.ApplyState(); 58 + 59 + backgroundColour = Source.BackgroundColour; 60 + screenSpaceDrawRectangle = Source.ScreenSpaceDrawQuad.AABBFloat; 61 + DrawColourInfo = Source.FrameBufferDrawColour ?? new DrawColourInfo(Color4.White, base.DrawColourInfo.Blending); 62 + 63 + frameBufferSize = new Vector2((float)Math.Ceiling(screenSpaceDrawRectangle.Width), (float)Math.Ceiling(screenSpaceDrawRectangle.Height)); 64 + DrawRectangle = filteringMode == All.Nearest 65 + ? new RectangleF(screenSpaceDrawRectangle.X, screenSpaceDrawRectangle.Y, frameBufferSize.X, frameBufferSize.Y) 66 + : screenSpaceDrawRectangle; 67 + 68 + Child.ApplyState(); 69 + } 70 + 71 + /// <summary> 72 + /// Whether this <see cref="BufferedDrawNode"/> should be redrawn. 73 + /// </summary> 74 + protected bool RequiresRedraw => GetDrawVersion() > SharedData.DrawVersion; 75 + 76 + /// <summary> 77 + /// Retrieves the version of the state of this <see cref="DrawNode"/>. 78 + /// The <see cref="BufferedDrawNode"/> will only re-render if this version is greater than that of the rendered <see cref="FrameBuffer"/>s. 79 + /// </summary> 80 + /// <remarks> 81 + /// By default, the <see cref="BufferedDrawNode"/> is re-rendered with every <see cref="DrawNode"/> invalidation. 82 + /// </remarks> 83 + /// <returns>A version representing this <see cref="DrawNode"/>'s state.</returns> 84 + protected virtual long GetDrawVersion() => InvalidationID; 85 + 86 + public sealed override void Draw(Action<TexturedVertex2D> vertexAction) 87 + { 88 + if (RequiresRedraw) 89 + { 90 + SharedData.ResetCurrentEffectBuffer(); 91 + 92 + using (establishFrameBufferViewport()) 93 + { 94 + // Fill the frame buffer with drawn children 95 + using (BindFrameBuffer(SharedData.MainBuffer)) 96 + { 97 + // We need to draw children as if they were zero-based to the top-left of the texture. 98 + // We can do this by adding a translation component to our (orthogonal) projection matrix. 99 + GLWrapper.PushOrtho(screenSpaceDrawRectangle); 100 + GLWrapper.Clear(new ClearInfo(backgroundColour)); 101 + 102 + Child.Draw(vertexAction); 103 + 104 + GLWrapper.PopOrtho(); 105 + } 106 + 107 + PopulateContents(); 108 + } 109 + 110 + SharedData.DrawVersion = GetDrawVersion(); 111 + } 112 + 113 + Shader.Bind(); 114 + 115 + DrawContents(); 116 + 117 + Shader.Unbind(); 118 + } 119 + 120 + /// <summary> 121 + /// Populates the contents of the effect buffers of <see cref="SharedData"/>. 122 + /// This is invoked after <see cref="Child"/> has been rendered to the main buffer. 123 + /// </summary> 124 + protected virtual void PopulateContents() 125 + { 126 + } 127 + 128 + /// <summary> 129 + /// Draws the applicable effect buffers of <see cref="SharedData"/> to the back buffer. 130 + /// </summary> 131 + protected virtual void DrawContents() 132 + { 133 + GLWrapper.SetBlend(DrawColourInfo.Blending); 134 + DrawFrameBuffer(SharedData.MainBuffer, DrawRectangle, DrawColourInfo.Colour); 135 + } 136 + 137 + /// <summary> 138 + /// Binds and initialises a <see cref="FrameBuffer"/> if required. 139 + /// </summary> 140 + /// <param name="frameBuffer">The <see cref="FrameBuffer"/> to bind.</param> 141 + /// <returns>A token that must be disposed upon finishing use of <paramref name="frameBuffer"/>.</returns> 142 + protected ValueInvokeOnDisposal BindFrameBuffer(FrameBuffer frameBuffer) 143 + { 144 + if (!frameBuffer.IsInitialized) 145 + frameBuffer.Initialize(true, filteringMode); 146 + 147 + if (formats != null) 148 + { 149 + // These additional render buffers are only required if e.g. depth 150 + // or stencil information needs to also be stored somewhere. 151 + foreach (var f in formats) 152 + frameBuffer.Attach(f); 153 + } 154 + 155 + // This setter will also take care of allocating a texture of appropriate size within the frame buffer. 156 + frameBuffer.Size = frameBufferSize; 157 + 158 + frameBuffer.Bind(); 159 + 160 + return new ValueInvokeOnDisposal(frameBuffer.Unbind); 161 + } 162 + 163 + private ValueInvokeOnDisposal establishFrameBufferViewport() 164 + { 165 + // Disable masking for generating the frame buffer since masking will be re-applied 166 + // when actually drawing later on anyways. This allows more information to be captured 167 + // in the frame buffer and helps with cached buffers being re-used. 168 + RectangleI screenSpaceMaskingRect = new RectangleI((int)Math.Floor(screenSpaceDrawRectangle.X), (int)Math.Floor(screenSpaceDrawRectangle.Y), (int)frameBufferSize.X + 1, (int)frameBufferSize.Y + 1); 169 + 170 + GLWrapper.PushMaskingInfo(new MaskingInfo 171 + { 172 + ScreenSpaceAABB = screenSpaceMaskingRect, 173 + MaskingRect = screenSpaceDrawRectangle, 174 + ToMaskingSpace = Matrix3.Identity, 175 + BlendRange = 1, 176 + AlphaExponent = 1, 177 + }, true); 178 + 179 + // Match viewport to FrameBuffer such that we don't draw unnecessary pixels. 180 + GLWrapper.PushViewport(new RectangleI(0, 0, (int)frameBufferSize.X, (int)frameBufferSize.Y)); 181 + 182 + return new ValueInvokeOnDisposal(returnViewport); 183 + } 184 + 185 + private void returnViewport() 186 + { 187 + GLWrapper.PopViewport(); 188 + GLWrapper.PopMaskingInfo(); 189 + } 190 + } 191 + }
+104
osu.Framework/Graphics/BufferedDrawNodeSharedData.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using osu.Framework.Graphics.OpenGL.Buffers; 6 + 7 + namespace osu.Framework.Graphics 8 + { 9 + /// <summary> 10 + /// Contains data which is shared between all <see cref="BufferedDrawNode"/>s of a <see cref="Drawable"/>. 11 + /// </summary> 12 + /// <remarks> 13 + /// This should be constructed _once_ per <see cref="Drawable"/>, and given to the constructor of <see cref="BufferedDrawNode"/>. 14 + /// </remarks> 15 + public class BufferedDrawNodeSharedData : IDisposable 16 + { 17 + /// <summary> 18 + /// The version of drawn contents currently present in <see cref="MainBuffer"/> and <see cref="effectBuffers"/>. 19 + /// This should only be modified by <see cref="BufferedDrawNode"/>. 20 + /// </summary> 21 + internal long DrawVersion = -1; 22 + 23 + /// <summary> 24 + /// The <see cref="FrameBuffer"/> which contains the original version of the rendered <see cref="Drawable"/>. 25 + /// </summary> 26 + public FrameBuffer MainBuffer { get; } 27 + 28 + /// <summary> 29 + /// A set of <see cref="FrameBuffer"/>s which are used in a ping-pong manner to render effects to. 30 + /// </summary> 31 + private readonly FrameBuffer[] effectBuffers; 32 + 33 + /// <summary> 34 + /// Creates a new <see cref="BufferedDrawNodeSharedData"/> with no effect buffers. 35 + /// </summary> 36 + public BufferedDrawNodeSharedData() 37 + : this(0) 38 + { 39 + } 40 + 41 + /// <summary> 42 + /// Creates a new <see cref="BufferedDrawNodeSharedData"/> with a specific amount of effect buffers. 43 + /// </summary> 44 + /// <param name="effectBufferCount">The number of effect buffers.</param> 45 + /// <exception cref="ArgumentOutOfRangeException">If <paramref name="effectBufferCount"/> is less than 0.</exception> 46 + public BufferedDrawNodeSharedData(int effectBufferCount) 47 + { 48 + if (effectBufferCount < 0) 49 + throw new ArgumentOutOfRangeException(nameof(effectBufferCount), "Must be positive."); 50 + 51 + MainBuffer = new FrameBuffer(); 52 + effectBuffers = new FrameBuffer[effectBufferCount]; 53 + 54 + for (int i = 0; i < effectBufferCount; i++) 55 + effectBuffers[i] = new FrameBuffer(); 56 + } 57 + 58 + private int currentEffectBuffer = -1; 59 + 60 + /// <summary> 61 + /// The <see cref="FrameBuffer"/> which contains the most up-to-date drawn effect. 62 + /// </summary> 63 + public FrameBuffer CurrentEffectBuffer => currentEffectBuffer == -1 ? MainBuffer : effectBuffers[currentEffectBuffer]; 64 + 65 + /// <summary> 66 + /// Retrieves the next <see cref="FrameBuffer"/> which effects can be rendered to. 67 + /// </summary> 68 + /// <exception cref="InvalidOperationException">If there are no available effect buffers.</exception> 69 + public FrameBuffer GetNextEffectBuffer() 70 + { 71 + if (effectBuffers.Length == 0) 72 + throw new InvalidOperationException($"The {nameof(BufferedDrawNode)} requested an effect buffer, but none were available."); 73 + 74 + if (++currentEffectBuffer >= effectBuffers.Length) 75 + currentEffectBuffer = 0; 76 + return effectBuffers[currentEffectBuffer]; 77 + } 78 + 79 + /// <summary> 80 + /// Resets <see cref="CurrentEffectBuffer"/>. 81 + /// This should only be called by <see cref="BufferedDrawNode"/>. 82 + /// </summary> 83 + internal void ResetCurrentEffectBuffer() => currentEffectBuffer = -1; 84 + 85 + ~BufferedDrawNodeSharedData() 86 + { 87 + Dispose(false); 88 + } 89 + 90 + public void Dispose() 91 + { 92 + Dispose(true); 93 + GC.SuppressFinalize(this); 94 + } 95 + 96 + protected virtual void Dispose(bool isDisposing) 97 + { 98 + MainBuffer.Dispose(); 99 + 100 + for (int i = 0; i < effectBuffers.Length; i++) 101 + effectBuffers[i].Dispose(); 102 + } 103 + } 104 + }
+159
osu.Framework/Graphics/Containers/AudioContainer.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using System.Collections; 6 + using System.Collections.Generic; 7 + using osu.Framework.Audio; 8 + using osu.Framework.Graphics.Audio; 9 + using osu.Framework.Graphics.Effects; 10 + using osuTK; 11 + 12 + namespace osu.Framework.Graphics.Containers 13 + { 14 + /// <summary> 15 + /// A container which exposes audio adjustments via <see cref="IAggregateAudioAdjustment"/>. 16 + /// </summary> 17 + /// <remarks> 18 + /// This is a bare-minimal implementation of a container, so it may be required to be nested inside a <see cref="Container"/> for some use cases. 19 + /// </remarks> 20 + /// <typeparam name="T">The tyoe of <see cref="Drawable"/>.</typeparam> 21 + public class AudioContainer<T> : DrawableAudioWrapper, IContainerEnumerable<T>, IContainerCollection<T>, ICollection<T>, IReadOnlyList<T> 22 + where T : Drawable 23 + { 24 + private readonly Container<T> container; 25 + 26 + public AudioContainer() 27 + : this(new Container<T>()) 28 + { 29 + } 30 + 31 + private AudioContainer(Container<T> container) 32 + : base(container) 33 + { 34 + this.container = container; 35 + } 36 + 37 + public override Vector2 Size 38 + { 39 + get => container.Size; 40 + set 41 + { 42 + base.Size = new Vector2( 43 + RelativeSizeAxes.HasFlag(Axes.X) ? 1 : value.X, 44 + RelativeSizeAxes.HasFlag(Axes.Y) ? 1 : value.Y); 45 + 46 + container.Size = value; 47 + } 48 + } 49 + 50 + public override Axes RelativeSizeAxes 51 + { 52 + get => container.RelativeSizeAxes; 53 + set 54 + { 55 + base.RelativeSizeAxes = value; 56 + container.RelativeSizeAxes = value; 57 + } 58 + } 59 + 60 + public new Axes AutoSizeAxes 61 + { 62 + get => container.AutoSizeAxes; 63 + set 64 + { 65 + base.AutoSizeAxes = value; 66 + container.AutoSizeAxes = value; 67 + } 68 + } 69 + 70 + public new EdgeEffectParameters EdgeEffect 71 + { 72 + get => base.EdgeEffect; 73 + set => base.EdgeEffect = value; 74 + } 75 + 76 + public new Vector2 RelativeChildSize 77 + { 78 + get => container.RelativeChildSize; 79 + set => container.RelativeChildSize = value; 80 + } 81 + 82 + public new Vector2 RelativeChildOffset 83 + { 84 + get => container.RelativeChildOffset; 85 + set => container.RelativeChildOffset = value; 86 + } 87 + 88 + public Container<T>.Enumerator GetEnumerator() => container.GetEnumerator(); 89 + 90 + IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); 91 + 92 + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 93 + 94 + public IReadOnlyList<T> Children 95 + { 96 + get => container.Children; 97 + set => container.Children = value; 98 + } 99 + 100 + public int RemoveAll(Predicate<T> match) => container.RemoveAll(match); 101 + 102 + public T Child 103 + { 104 + get => container.Child; 105 + set => container.Child = value; 106 + } 107 + 108 + public IEnumerable<T> ChildrenEnumerable 109 + { 110 + set => container.ChildrenEnumerable = value; 111 + } 112 + 113 + public void Add(T drawable) 114 + { 115 + container.Add(drawable); 116 + } 117 + 118 + public void Clear() 119 + { 120 + container.Clear(); 121 + } 122 + 123 + public bool Contains(T item) => container.Contains(item); 124 + 125 + public void CopyTo(T[] array, int arrayIndex) 126 + { 127 + container.CopyTo(array, arrayIndex); 128 + } 129 + 130 + public void AddRange(IEnumerable<T> collection) 131 + { 132 + container.AddRange(collection); 133 + } 134 + 135 + public bool Remove(T drawable) => container.Remove(drawable); 136 + int ICollection<T>.Count => container.Count; 137 + 138 + public bool IsReadOnly => container.IsReadOnly; 139 + 140 + public void RemoveRange(IEnumerable<T> range) 141 + { 142 + container.RemoveRange(range); 143 + } 144 + 145 + int IReadOnlyCollection<T>.Count => container.Count; 146 + 147 + public T this[int index] => container[index]; 148 + } 149 + 150 + /// <summary> 151 + /// A container which exposes audio adjustments via <see cref="IAggregateAudioAdjustment"/>. 152 + /// </summary> 153 + /// <remarks> 154 + /// This is a bare-minimal implementation of a container, so it may be required to be nested inside a <see cref="Container"/> for some use cases. 155 + /// </remarks> 156 + public class AudioContainer : AudioContainer<Drawable> 157 + { 158 + } 159 + }
+36 -20
osu.Framework/Graphics/Containers/BufferedContainer.cs
··· 12 12 using System; 13 13 using System.Collections.Generic; 14 14 using osu.Framework.Caching; 15 + using osu.Framework.Graphics.Sprites; 15 16 16 17 namespace osu.Framework.Graphics.Containers 17 18 { ··· 35 36 /// appearance of the container at the cost of performance. Such effects include 36 37 /// uniform fading of children, blur, and other post-processing effects. 37 38 /// </summary> 38 - public partial class BufferedContainer<T> : Container<T>, IBufferedContainer 39 + public partial class BufferedContainer<T> : Container<T>, IBufferedContainer, IBufferedDrawable 39 40 where T : Drawable 40 41 { 41 42 private bool drawOriginal; ··· 108 109 get => pixelSnapping; 109 110 set 110 111 { 111 - if (sharedData != null) 112 - { 113 - for (int i = 0; i < sharedData.FrameBuffers.Length; i++) 114 - { 115 - if (sharedData.FrameBuffers[i].IsInitialized) 116 - throw new InvalidOperationException("May only set PixelSnapping before FrameBuffers are initialized (i.e. before the first draw)."); 117 - } 118 - } 112 + if (sharedData?.MainBuffer.IsInitialized == true) 113 + throw new InvalidOperationException("May only set PixelSnapping before FrameBuffers are initialized (i.e. before the first draw)."); 119 114 120 115 pixelSnapping = value; 121 116 } ··· 222 217 223 218 protected override bool CanBeFlattened => false; 224 219 220 + public IShader TextureShader { get; private set; } 221 + 222 + public IShader RoundedTextureShader { get; private set; } 223 + 225 224 private IShader blurShader; 226 225 227 226 /// <summary> ··· 237 236 [BackgroundDependencyLoader] 238 237 private void load(ShaderManager shaders) 239 238 { 239 + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); 240 + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); 240 241 blurShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR); 241 242 } 242 243 243 244 private readonly BufferedContainerDrawNodeSharedData sharedData = new BufferedContainerDrawNodeSharedData(); 244 245 245 - protected override DrawNode CreateDrawNode() => new BufferedContainerDrawNode(this, sharedData); 246 + protected override DrawNode CreateDrawNode() => new BufferedContainerDrawNode(this, sharedData, attachedFormats.ToArray(), PixelSnapping); 246 247 247 248 private readonly List<RenderbufferInternalFormat> attachedFormats = new List<RenderbufferInternalFormat>(); 248 249 ··· 313 314 childrenUpdateVersion = updateVersion; 314 315 } 315 316 316 - private DrawColourInfo baseDrawColourInfo => base.DrawColourInfo; 317 - 318 - public override DrawColourInfo DrawColourInfo 317 + /// <summary> 318 + /// The blending which <see cref="BufferedContainerDrawNode"/> uses for the effect. 319 + /// </summary> 320 + public BlendingParameters DrawEffectBlending 319 321 { 320 322 get 321 323 { 322 - DrawColourInfo result = base.DrawColourInfo; 324 + BlendingParameters blending = EffectBlending; 325 + if (blending.Mode == BlendingMode.Inherit) 326 + blending.Mode = Blending.Mode; 323 327 324 - // When drawing our children to the frame buffer we do not 325 - // want their colour to be polluted by their parent (us!) 326 - // since our own color will be applied on top when we render 327 - // from the frame buffer to the back buffer later on. 328 - result.Colour = ColourInfo.SingleColour(Color4.White); 329 - return result; 328 + if (blending.RGBEquation == BlendingEquation.Inherit) 329 + blending.RGBEquation = Blending.RGBEquation; 330 + 331 + if (blending.AlphaEquation == BlendingEquation.Inherit) 332 + blending.AlphaEquation = Blending.AlphaEquation; 333 + 334 + return blending; 330 335 } 331 336 } 337 + 338 + /// <summary> 339 + /// Creates a view which can be added to a container to display the content of this <see cref="BufferedContainer{T}"/>. 340 + /// </summary> 341 + /// <returns>The view.</returns> 342 + public BufferedContainerView<T> CreateView() => new BufferedContainerView<T>(this, sharedData); 343 + 344 + public DrawColourInfo? FrameBufferDrawColour => base.DrawColourInfo; 345 + 346 + // Children should not receive the true colour to avoid colour doubling when the frame-buffers are rendered to the back-buffer. 347 + public override DrawColourInfo DrawColourInfo => new DrawColourInfo(Color4.White, base.DrawColourInfo.Blending); 332 348 333 349 protected override void Dispose(bool isDisposing) 334 350 {
+36 -218
osu.Framework/Graphics/Containers/BufferedContainer_DrawNode.cs
··· 8 8 using osuTK.Graphics.ES30; 9 9 using osuTK.Graphics; 10 10 using osu.Framework.Graphics.Primitives; 11 - using osu.Framework.Allocation; 12 11 using osu.Framework.Graphics.Shaders; 13 12 using System; 14 13 using osu.Framework.Graphics.Colour; 15 - using osu.Framework.Graphics.OpenGL.Vertices; 16 - using System.Diagnostics; 17 14 using osu.Framework.MathUtils; 18 15 19 16 namespace osu.Framework.Graphics.Containers 20 17 { 21 18 public partial class BufferedContainer<T> 22 19 { 23 - private class BufferedContainerDrawNode : CompositeDrawableDrawNode 20 + private class BufferedContainerDrawNode : BufferedDrawNode, ICompositeDrawNode 24 21 { 25 22 protected new BufferedContainer<T> Source => (BufferedContainer<T>)base.Source; 26 23 24 + protected new CompositeDrawableDrawNode Child => (CompositeDrawableDrawNode)base.Child; 25 + 27 26 private bool drawOriginal; 28 - private Color4 backgroundColour; 29 27 private ColourInfo effectColour; 30 28 private BlendingParameters effectBlending; 31 29 private EffectPlacement effectPlacement; ··· 36 34 37 35 private long updateVersion; 38 36 39 - private RectangleF screenSpaceDrawRectangle; 40 - private All filteringMode; 41 - 42 - private readonly List<RenderbufferInternalFormat> formats = new List<RenderbufferInternalFormat>(); 43 - 44 37 private IShader blurShader; 45 38 46 - private readonly BufferedContainerDrawNodeSharedData sharedData; 47 - 48 - public BufferedContainerDrawNode(BufferedContainer<T> source, BufferedContainerDrawNodeSharedData sharedData) 49 - : base(source) 39 + public BufferedContainerDrawNode(BufferedContainer<T> source, BufferedContainerDrawNodeSharedData sharedData, RenderbufferInternalFormat[] formats = null, 40 + bool pixelSnapping = false) 41 + : base(source, new CompositeDrawableDrawNode(source), sharedData, formats, pixelSnapping) 50 42 { 51 - this.sharedData = sharedData; 52 43 } 53 44 54 45 public override void ApplyState() 55 46 { 56 47 base.ApplyState(); 57 - 58 - screenSpaceDrawRectangle = Source.ScreenSpaceDrawQuad.AABBFloat; 59 - filteringMode = Source.PixelSnapping ? All.Nearest : All.Linear; 60 48 61 49 updateVersion = Source.updateVersion; 62 - backgroundColour = Source.BackgroundColour; 63 - 64 - BlendingParameters localEffectBlending = Source.EffectBlending; 65 - if (localEffectBlending.Mode == BlendingMode.Inherit) 66 - localEffectBlending.Mode = Source.Blending.Mode; 67 - 68 - if (localEffectBlending.RGBEquation == BlendingEquation.Inherit) 69 - localEffectBlending.RGBEquation = Source.Blending.RGBEquation; 70 - 71 - if (localEffectBlending.AlphaEquation == BlendingEquation.Inherit) 72 - localEffectBlending.AlphaEquation = Source.Blending.AlphaEquation; 73 50 74 51 effectColour = Source.EffectColour; 75 - effectBlending = localEffectBlending; 52 + effectBlending = Source.DrawEffectBlending; 76 53 effectPlacement = Source.EffectPlacement; 77 54 78 55 drawOriginal = Source.DrawOriginal; 79 56 blurSigma = Source.BlurSigma; 80 57 blurRadius = new Vector2I(Blur.KernelSize(blurSigma.X), Blur.KernelSize(blurSigma.Y)); 81 58 blurRotation = Source.BlurRotation; 82 - 83 - formats.Clear(); 84 - formats.AddRange(Source.attachedFormats); 85 59 86 60 blurShader = Source.blurShader; 87 - 88 - // BufferedContainer overrides DrawColourInfo for children, but needs to be reset to draw ourselves 89 - DrawColourInfo = Source.baseDrawColourInfo; 90 61 } 91 62 92 - public override bool AddChildDrawNodes => RequiresRedraw; 93 - 94 - /// <summary> 95 - /// Whether this <see cref="BufferedContainerDrawNode"/> should have its children re-drawn. 96 - /// </summary> 97 - public bool RequiresRedraw => updateVersion > sharedData.DrawVersion; 63 + protected override long GetDrawVersion() => updateVersion; 98 64 99 - private ValueInvokeOnDisposal establishFrameBufferViewport(Vector2 roundedSize) 65 + protected override void PopulateContents() 100 66 { 101 - // Disable masking for generating the frame buffer since masking will be re-applied 102 - // when actually drawing later on anyways. This allows more information to be captured 103 - // in the frame buffer and helps with cached buffers being re-used. 104 - RectangleI screenSpaceMaskingRect = new RectangleI((int)Math.Floor(screenSpaceDrawRectangle.X), (int)Math.Floor(screenSpaceDrawRectangle.Y), (int)roundedSize.X + 1, 105 - (int)roundedSize.Y + 1); 67 + base.PopulateContents(); 106 68 107 - GLWrapper.PushMaskingInfo(new MaskingInfo 69 + if (blurRadius.X > 0 || blurRadius.Y > 0) 108 70 { 109 - ScreenSpaceAABB = screenSpaceMaskingRect, 110 - MaskingRect = screenSpaceDrawRectangle, 111 - ToMaskingSpace = Matrix3.Identity, 112 - BlendRange = 1, 113 - AlphaExponent = 1, 114 - }, true); 71 + GL.Disable(EnableCap.ScissorTest); 115 72 116 - // Match viewport to FrameBuffer such that we don't draw unnecessary pixels. 117 - GLWrapper.PushViewport(new RectangleI(0, 0, (int)roundedSize.X, (int)roundedSize.Y)); 73 + if (blurRadius.X > 0) drawBlurredFrameBuffer(blurRadius.X, blurSigma.X, blurRotation); 74 + if (blurRadius.Y > 0) drawBlurredFrameBuffer(blurRadius.Y, blurSigma.Y, blurRotation + 90); 118 75 119 - return new ValueInvokeOnDisposal(returnViewport); 76 + GL.Enable(EnableCap.ScissorTest); 77 + } 120 78 } 121 79 122 - private void returnViewport() 80 + protected override void DrawContents() 123 81 { 124 - GLWrapper.PopViewport(); 125 - GLWrapper.PopMaskingInfo(); 126 - } 82 + if (drawOriginal && effectPlacement == EffectPlacement.InFront) 83 + base.DrawContents(); 127 84 128 - private ValueInvokeOnDisposal bindFrameBuffer(FrameBuffer frameBuffer, Vector2 requestedSize) 129 - { 130 - if (!frameBuffer.IsInitialized) 131 - frameBuffer.Initialize(true, filteringMode); 85 + GLWrapper.SetBlend(new BlendingInfo(effectBlending)); 132 86 133 - // These additional render buffers are only required if e.g. depth 134 - // or stencil information needs to also be stored somewhere. 135 - foreach (var f in formats) 136 - frameBuffer.Attach(f); 87 + ColourInfo finalEffectColour = DrawColourInfo.Colour; 88 + finalEffectColour.ApplyChild(effectColour); 137 89 138 - // This setter will also take care of allocating a texture of appropriate size within the framebuffer. 139 - frameBuffer.Size = requestedSize; 90 + DrawFrameBuffer(SharedData.CurrentEffectBuffer, DrawRectangle, finalEffectColour); 140 91 141 - frameBuffer.Bind(); 142 - 143 - return new ValueInvokeOnDisposal(frameBuffer.Unbind); 144 - } 145 - 146 - private void drawFrameBufferToBackBuffer(FrameBuffer frameBuffer, RectangleF drawRectangle, ColourInfo colourInfo) 147 - { 148 - // The strange Y coordinate and Height are a result of OpenGL coordinate systems having Y grow upwards and not downwards. 149 - RectangleF textureRect = new RectangleF(0, frameBuffer.Texture.Height, frameBuffer.Texture.Width, -frameBuffer.Texture.Height); 150 - if (frameBuffer.Texture.Bind()) 151 - // Color was already applied by base.Draw(); no need to re-apply. Thus we use White here. 152 - frameBuffer.Texture.DrawQuad(drawRectangle, textureRect, colourInfo); 153 - } 154 - 155 - private void drawChildren(Action<TexturedVertex2D> vertexAction, Vector2 frameBufferSize) 156 - { 157 - // Fill the frame buffer with drawn children 158 - using (bindFrameBuffer(currentFrameBuffer, frameBufferSize)) 159 - { 160 - // We need to draw children as if they were zero-based to the top-left of the texture. 161 - // We can do this by adding a translation component to our (orthogonal) projection matrix. 162 - GLWrapper.PushOrtho(screenSpaceDrawRectangle); 163 - 164 - GLWrapper.Clear(new ClearInfo(backgroundColour)); 165 - base.Draw(vertexAction); 166 - 167 - GLWrapper.PopOrtho(); 168 - } 92 + if (drawOriginal && effectPlacement == EffectPlacement.Behind) 93 + base.DrawContents(); 169 94 } 170 95 171 96 private void drawBlurredFrameBuffer(int kernelRadius, float sigma, float blurRotation) 172 97 { 173 - FrameBuffer source = currentFrameBuffer; 174 - FrameBuffer target = advanceFrameBuffer(); 98 + FrameBuffer current = SharedData.CurrentEffectBuffer; 99 + FrameBuffer target = SharedData.GetNextEffectBuffer(); 175 100 176 101 GLWrapper.SetBlend(new BlendingInfo(BlendingMode.None)); 177 102 178 - using (bindFrameBuffer(target, source.Size)) 103 + using (BindFrameBuffer(target)) 179 104 { 180 105 blurShader.GetUniform<int>(@"g_Radius").UpdateValue(ref kernelRadius); 181 106 blurShader.GetUniform<float>(@"g_Sigma").UpdateValue(ref sigma); 182 107 183 - Vector2 size = source.Size; 108 + Vector2 size = current.Size; 184 109 blurShader.GetUniform<Vector2>(@"g_TexSize").UpdateValue(ref size); 185 110 186 111 float radians = -MathHelper.DegreesToRadians(blurRotation); ··· 188 113 blurShader.GetUniform<Vector2>(@"g_BlurDirection").UpdateValue(ref blur); 189 114 190 115 blurShader.Bind(); 191 - drawFrameBufferToBackBuffer(source, new RectangleF(0, 0, source.Texture.Width, source.Texture.Height), ColourInfo.SingleColour(Color4.White)); 116 + DrawFrameBuffer(current, new RectangleF(0, 0, current.Texture.Width, current.Texture.Height), ColourInfo.SingleColour(Color4.White)); 192 117 blurShader.Unbind(); 193 118 } 194 119 } 195 120 196 - private int currentFrameBufferIndex; 197 - private FrameBuffer currentFrameBuffer => sharedData.FrameBuffers[currentFrameBufferIndex]; 198 - private FrameBuffer advanceFrameBuffer() => sharedData.FrameBuffers[currentFrameBufferIndex = (currentFrameBufferIndex + 1) % 2]; 199 - 200 - /// <summary> 201 - /// Makes sure the first frame buffer is always the one we want to draw from. 202 - /// This saves us the need to sync the draw indices across draw node trees 203 - /// since the SharedData.FrameBuffers array is already shared. 204 - /// </summary> 205 - private void finalizeFrameBuffer() 121 + public List<DrawNode> Children 206 122 { 207 - if (currentFrameBufferIndex != 0) 208 - { 209 - Trace.Assert(currentFrameBufferIndex == 1, 210 - $"Only the first two framebuffers should be the last to be written to at the end of {nameof(Draw)}."); 211 - 212 - FrameBuffer temp = sharedData.FrameBuffers[0]; 213 - sharedData.FrameBuffers[0] = sharedData.FrameBuffers[1]; 214 - sharedData.FrameBuffers[1] = temp; 215 - 216 - currentFrameBufferIndex = 0; 217 - } 123 + get => Child.Children; 124 + set => Child.Children = value; 218 125 } 219 126 220 - // Our effects will be drawn into framebuffers 0 and 1. If we want to preserve the originally 221 - // drawn children we need to put them in a separate buffer; in this case buffer 2. Otherwise, 222 - // we do not want to allocate a third buffer for nothing and hence we start with 0. 223 - private int originalIndex => drawOriginal && (blurRadius.X > 0 || blurRadius.Y > 0) ? 2 : 0; 224 - 225 - public override void Draw(Action<TexturedVertex2D> vertexAction) 226 - { 227 - currentFrameBufferIndex = originalIndex; 228 - 229 - Vector2 frameBufferSize = new Vector2((float)Math.Ceiling(screenSpaceDrawRectangle.Width), (float)Math.Ceiling(screenSpaceDrawRectangle.Height)); 230 - if (RequiresRedraw) 231 - { 232 - sharedData.DrawVersion = updateVersion; 233 - 234 - using (establishFrameBufferViewport(frameBufferSize)) 235 - { 236 - drawChildren(vertexAction, frameBufferSize); 237 - 238 - // Blur post-processing in case a blur radius is defined. 239 - if (blurRadius.X > 0 || blurRadius.Y > 0) 240 - { 241 - GL.Disable(EnableCap.ScissorTest); 242 - 243 - if (blurRadius.X > 0) drawBlurredFrameBuffer(blurRadius.X, blurSigma.X, blurRotation); 244 - if (blurRadius.Y > 0) drawBlurredFrameBuffer(blurRadius.Y, blurSigma.Y, blurRotation + 90); 245 - 246 - GL.Enable(EnableCap.ScissorTest); 247 - } 248 - } 249 - 250 - finalizeFrameBuffer(); 251 - } 252 - 253 - RectangleF drawRectangle = filteringMode == All.Nearest 254 - ? new RectangleF(screenSpaceDrawRectangle.X, screenSpaceDrawRectangle.Y, frameBufferSize.X, frameBufferSize.Y) 255 - : screenSpaceDrawRectangle; 256 - 257 - Shader.Bind(); 258 - 259 - if (drawOriginal && effectPlacement == EffectPlacement.InFront) 260 - { 261 - GLWrapper.SetBlend(DrawColourInfo.Blending); 262 - drawFrameBufferToBackBuffer(sharedData.FrameBuffers[originalIndex], drawRectangle, DrawColourInfo.Colour); 263 - } 264 - 265 - // Blit the final framebuffer to screen. 266 - GLWrapper.SetBlend(new BlendingInfo(effectBlending)); 267 - 268 - ColourInfo finalEffectColour = DrawColourInfo.Colour; 269 - finalEffectColour.ApplyChild(effectColour); 270 - drawFrameBufferToBackBuffer(sharedData.FrameBuffers[0], drawRectangle, finalEffectColour); 271 - 272 - if (drawOriginal && effectPlacement == EffectPlacement.Behind) 273 - { 274 - GLWrapper.SetBlend(DrawColourInfo.Blending); 275 - drawFrameBufferToBackBuffer(sharedData.FrameBuffers[originalIndex], drawRectangle, DrawColourInfo.Colour); 276 - } 277 - 278 - Shader.Unbind(); 279 - } 127 + public bool AddChildDrawNodes => RequiresRedraw; 280 128 } 281 129 282 - private class BufferedContainerDrawNodeSharedData : IDisposable 130 + private class BufferedContainerDrawNodeSharedData : BufferedDrawNodeSharedData 283 131 { 284 - /// <summary> 285 - /// The <see cref="FrameBuffer"/>s to render to. 286 - /// These are used in a ping-pong manner to render effects <see cref="BufferedContainerDrawNode"/>. 287 - /// </summary> 288 - public readonly FrameBuffer[] FrameBuffers = new FrameBuffer[3]; 289 - 290 - /// <summary> 291 - /// The version of drawn contents currently present in <see cref="FrameBuffers"/>. 292 - /// This should only be modified by <see cref="BufferedContainerDrawNode"/>. 293 - /// </summary> 294 - public long DrawVersion = -1; 295 - 296 132 public BufferedContainerDrawNodeSharedData() 133 + : base(2) 297 134 { 298 - for (int i = 0; i < FrameBuffers.Length; i++) 299 - FrameBuffers[i] = new FrameBuffer(); 300 - } 301 - 302 - ~BufferedContainerDrawNodeSharedData() 303 - { 304 - dispose(); 305 - } 306 - 307 - public void Dispose() 308 - { 309 - dispose(); 310 - GC.SuppressFinalize(this); 311 - } 312 - 313 - private void dispose() 314 - { 315 - for (int i = 0; i < FrameBuffers.Length; i++) 316 - FrameBuffers[i].Dispose(); 317 135 } 318 136 } 319 137 }
+10 -4
osu.Framework/Graphics/Containers/CompositeDrawable.cs
··· 76 76 77 77 private CancellationTokenSource disposalCancellationSource; 78 78 79 - private static readonly ThreadedTaskScheduler threaded_scheduler = new ThreadedTaskScheduler(4); 79 + private static readonly ThreadedTaskScheduler threaded_scheduler = new ThreadedTaskScheduler(4, nameof(LoadComponentsAsync)); 80 80 81 81 /// <summary> 82 82 /// Loads a future child or grand-child of this <see cref="CompositeDrawable"/> asynchronously. <see cref="Dependencies"/> ··· 418 418 return false; 419 419 420 420 internalChildren.RemoveAt(index); 421 + 421 422 if (drawable.IsAlive) 422 423 { 423 424 aliveInternalChildren.Remove(drawable); ··· 563 564 { 564 565 case LoadState.NotLoaded: 565 566 break; 567 + 566 568 case LoadState.Loading: 567 569 if (Thread.CurrentThread != LoadThread) 568 570 throw new InvalidThreadForChildMutationException(LoadState, "not on the load thread"); 569 571 570 572 break; 573 + 571 574 case LoadState.Ready: 572 575 // Allow mutating from the load thread since parenting containers may still be in the loading state 573 576 if (Thread.CurrentThread != LoadThread && !ThreadSafety.IsUpdateThread) 574 577 throw new InvalidThreadForChildMutationException(LoadState, "not on the load or update threads"); 575 578 576 579 break; 580 + 577 581 case LoadState.Loaded: 578 582 if (!ThreadSafety.IsUpdateThread) 579 583 throw new InvalidThreadForChildMutationException(LoadState, "not on the update thread"); ··· 985 989 private static void addFromComposite(ulong frame, int treeIndex, bool forceNewDrawNode, ref int j, CompositeDrawable parentComposite, List<DrawNode> target) 986 990 { 987 991 SortedList<Drawable> children = parentComposite.aliveInternalChildren; 992 + 988 993 for (int i = 0; i < children.Count; ++i) 989 994 { 990 995 Drawable drawable = children[i]; ··· 995 1000 continue; 996 1001 997 1002 CompositeDrawable composite = drawable as CompositeDrawable; 1003 + 998 1004 if (composite?.CanBeFlattened == true) 999 1005 { 1000 1006 if (!composite.IsMaskedAway) ··· 1550 1556 set 1551 1557 { 1552 1558 if ((AutoSizeAxes & Axes.X) != 0) 1553 - throw new InvalidOperationException($"The width of a {nameof(CompositeDrawable)} with {nameof(AutoSizeAxes)} should only be manually set if it is relative to its parent."); 1559 + throw new InvalidOperationException($"The width of a {nameof(CompositeDrawable)} with {nameof(AutoSizeAxes)} can not be set manually."); 1554 1560 1555 1561 base.Width = value; 1556 1562 } ··· 1568 1574 set 1569 1575 { 1570 1576 if ((AutoSizeAxes & Axes.Y) != 0) 1571 - throw new InvalidOperationException($"The height of a {nameof(CompositeDrawable)} with {nameof(AutoSizeAxes)} should only be manually set if it is relative to its parent."); 1577 + throw new InvalidOperationException($"The height of a {nameof(CompositeDrawable)} with {nameof(AutoSizeAxes)} can not be set manually."); 1572 1578 1573 1579 base.Height = value; 1574 1580 } ··· 1588 1594 set 1589 1595 { 1590 1596 if ((AutoSizeAxes & Axes.Both) != 0) 1591 - throw new InvalidOperationException($"The Size of a {nameof(CompositeDrawable)} with {nameof(AutoSizeAxes)} should only be manually set if it is relative to its parent."); 1597 + throw new InvalidOperationException($"The Size of a {nameof(CompositeDrawable)} with {nameof(AutoSizeAxes)} can not be set manually."); 1592 1598 1593 1599 base.Size = value; 1594 1600 }
+89 -12
osu.Framework/Graphics/Containers/CompositeDrawable_DrawNode.cs
··· 10 10 using osu.Framework.Graphics.Textures; 11 11 using osu.Framework.Graphics.Colour; 12 12 using System; 13 + using System.Runtime.CompilerServices; 13 14 using osu.Framework.Graphics.Effects; 14 15 using osu.Framework.Graphics.OpenGL.Vertices; 16 + using osuTK.Graphics.ES30; 15 17 16 18 namespace osu.Framework.Graphics.Containers 17 19 { ··· 22 24 /// </summary> 23 25 protected class CompositeDrawableDrawNode : DrawNode, ICompositeDrawNode 24 26 { 27 + private static readonly float cos_45 = (float)Math.Cos(Math.PI / 4); 28 + 25 29 protected new CompositeDrawable Source => (CompositeDrawable)base.Source; 26 30 27 31 /// <summary> ··· 57 61 private bool forceLocalVertexBatch; 58 62 59 63 /// <summary> 60 - /// The vertex batch used for rendering. 64 + /// The vertex batch used for child quads during the back-to-front pass. 65 + /// </summary> 66 + private QuadBatch<TexturedVertex2D> quadBatch; 67 + 68 + /// <summary> 69 + /// The vertex batch used for child triangles during the front-to-back pass. 61 70 /// </summary> 62 - private QuadBatch<TexturedVertex2D> vertexBatch; 71 + private LinearBatch<TexturedVertex2D> triangleBatch; 63 72 64 73 public CompositeDrawableDrawNode(CompositeDrawable source) 65 74 : base(source) ··· 74 83 throw new InvalidOperationException("Can not have border effects/edge effects if masking is disabled."); 75 84 76 85 Vector3 scale = DrawInfo.MatrixInverse.ExtractScale(); 86 + float blendRange = Source.MaskingSmoothness * (scale.X + scale.Y) / 2; 87 + 88 + // Calculate a shrunk rectangle which is free from corner radius/smoothing/border effects 89 + float shrinkage = Source.CornerRadius - Source.CornerRadius * cos_45 + blendRange + Source.borderThickness; 90 + RectangleF shrunkDrawRectangle = Source.DrawRectangle.Shrink(shrinkage); 77 91 78 92 maskingInfo = !Source.Masking 79 93 ? (MaskingInfo?)null ··· 81 95 { 82 96 ScreenSpaceAABB = Source.ScreenSpaceDrawQuad.AABB, 83 97 MaskingRect = Source.DrawRectangle, 98 + ConservativeScreenSpaceQuad = Quad.FromRectangle(shrunkDrawRectangle) * DrawInfo.Matrix, 84 99 ToMaskingSpace = DrawInfo.MatrixInverse, 85 100 CornerRadius = Source.CornerRadius, 86 101 BorderThickness = Source.BorderThickness, ··· 88 103 // We are setting the linear blend range to the approximate size of a _pixel_ here. 89 104 // This results in the optimal trade-off between crispness and smoothness of the 90 105 // edges of the masked region according to sampling theory. 91 - BlendRange = Source.MaskingSmoothness * (scale.X + scale.Y) / 2, 106 + BlendRange = blendRange, 92 107 AlphaExponent = 1, 93 108 }; 94 109 ··· 102 117 103 118 private void drawEdgeEffect() 104 119 { 105 - if (maskingInfo == null || edgeEffect.Type == EdgeEffectType.None || edgeEffect.Radius <= 0.0f || edgeEffect.Colour.Linear.A <= 0.0f) 120 + if (maskingInfo == null || edgeEffect.Type == EdgeEffectType.None || edgeEffect.Radius <= 0.0f || edgeEffect.Colour.Linear.A <= 0) 106 121 return; 107 122 108 123 RectangleF effectRect = maskingInfo.Value.MaskingRect.Inflate(edgeEffect.Radius).Offset(edgeEffect.Offset); ··· 135 150 colour.TopRight.MultiplyAlpha(DrawColourInfo.Colour.TopRight.Linear.A); 136 151 colour.BottomRight.MultiplyAlpha(DrawColourInfo.Colour.BottomRight.Linear.A); 137 152 138 - Texture.WhitePixel.DrawQuad( 153 + DrawQuad( 154 + Texture.WhitePixel, 139 155 screenSpaceMaskingQuad.Value, 140 156 colour, null, null, null, 141 157 // HACK HACK HACK. We re-use the unused vertex blend range to store the original ··· 152 168 153 169 private bool mayHaveOwnVertexBatch(int amountChildren) => forceLocalVertexBatch || amountChildren >= min_amount_children_to_warrant_batch; 154 170 155 - private void updateVertexBatch() 171 + private void updateQuadBatch() 172 + { 173 + if (Children == null) 174 + return; 175 + 176 + // This logic got roughly copied from the old osu! code base. These constants seem to have worked well so far. 177 + int clampedAmountChildren = MathHelper.Clamp(Children.Count, 1, 1000); 178 + if (mayHaveOwnVertexBatch(clampedAmountChildren) && (quadBatch == null || quadBatch.Size < clampedAmountChildren)) 179 + quadBatch = new QuadBatch<TexturedVertex2D>(clampedAmountChildren * 2, 500); 180 + } 181 + 182 + private void updateTriangleBatch() 156 183 { 157 184 if (Children == null) 158 185 return; 159 186 160 187 // This logic got roughly copied from the old osu! code base. These constants seem to have worked well so far. 161 188 int clampedAmountChildren = MathHelper.Clamp(Children.Count, 1, 1000); 162 - if (mayHaveOwnVertexBatch(clampedAmountChildren) && (vertexBatch == null || vertexBatch.Size < clampedAmountChildren)) 163 - vertexBatch = new QuadBatch<TexturedVertex2D>(clampedAmountChildren * 2, 500); 189 + 190 + if (mayHaveOwnVertexBatch(clampedAmountChildren) && (triangleBatch == null || triangleBatch.Size < clampedAmountChildren)) 191 + { 192 + // The same general idea as updateQuadBatch(), except that each child draws up to 3 vertices * 6 triangles after quad-quad intersection 193 + triangleBatch = new LinearBatch<TexturedVertex2D>(clampedAmountChildren * 2 * 3, 500, PrimitiveType.Triangles); 194 + } 164 195 } 165 196 166 197 public override void Draw(Action<TexturedVertex2D> vertexAction) 167 198 { 168 - updateVertexBatch(); 199 + updateQuadBatch(); 169 200 170 201 // Prefer to use own vertex batch instead of the parent-owned one. 171 - if (vertexBatch != null) 172 - vertexAction = vertexBatch.AddAction; 202 + if (quadBatch != null) 203 + vertexAction = quadBatch.AddAction; 173 204 174 205 base.Draw(vertexAction); 175 206 176 207 drawEdgeEffect(); 208 + 177 209 if (maskingInfo != null) 178 210 { 179 211 MaskingInfo info = maskingInfo.Value; ··· 191 223 GLWrapper.PopMaskingInfo(); 192 224 } 193 225 226 + internal override void DrawOpaqueInteriorSubTree(DepthValue depthValue, Action<TexturedVertex2D> vertexAction) 227 + { 228 + DrawChildrenOpaqueInteriors(depthValue, vertexAction); 229 + base.DrawOpaqueInteriorSubTree(depthValue, vertexAction); 230 + } 231 + 232 + /// <summary> 233 + /// Performs <see cref="DrawOpaqueInteriorSubTree"/> on all children of this <see cref="CompositeDrawableDrawNode"/>. 234 + /// </summary> 235 + /// <param name="depthValue">The previous depth value.</param> 236 + /// <param name="vertexAction">The action to be performed on each vertex of the draw node in order to draw it if required. This is primarily used by textured sprites.</param> 237 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 238 + protected virtual void DrawChildrenOpaqueInteriors(DepthValue depthValue, Action<TexturedVertex2D> vertexAction) 239 + { 240 + bool canIncrement = depthValue.CanIncrement; 241 + 242 + // Assume that if we can't increment the depth value, no child can, thus nothing will be drawn. 243 + if (canIncrement) 244 + { 245 + updateTriangleBatch(); 246 + 247 + // Prefer to use own vertex batch instead of the parent-owned one. 248 + if (triangleBatch != null) 249 + vertexAction = triangleBatch.AddAction; 250 + 251 + if (maskingInfo != null) 252 + GLWrapper.PushMaskingInfo(maskingInfo.Value); 253 + } 254 + 255 + // We still need to invoke this method recursively for all children so their depth value is updated 256 + if (Children != null) 257 + { 258 + for (int i = Children.Count - 1; i >= 0; i--) 259 + Children[i].DrawOpaqueInteriorSubTree(depthValue, vertexAction); 260 + } 261 + 262 + // Assume that if we can't increment the depth value, no child can, thus nothing will be drawn. 263 + if (canIncrement) 264 + { 265 + if (maskingInfo != null) 266 + GLWrapper.PopMaskingInfo(); 267 + } 268 + } 269 + 194 270 protected override void Dispose(bool isDisposing) 195 271 { 196 272 base.Dispose(isDisposing); 197 273 198 - vertexBatch?.Dispose(); 274 + quadBatch?.Dispose(); 275 + triangleBatch?.Dispose(); 199 276 } 200 277 } 201 278 }
+5
osu.Framework/Graphics/Containers/CustomizableTextContainer.cs
··· 87 87 var sprites = new List<Drawable>(); 88 88 int index = 0; 89 89 string str = line.Text; 90 + 90 91 while (index < str.Length) 91 92 { 92 93 Drawable placeholderDrawable = null; ··· 96 97 nextPlaceholderIndex = str.IndexOf(unescaped_left, nextPlaceholderIndex + 2, StringComparison.Ordinal); 97 98 98 99 string strPiece = null; 100 + 99 101 if (nextPlaceholderIndex != -1) 100 102 { 101 103 int placeholderEnd = str.IndexOf(unescaped_right, nextPlaceholderIndex, StringComparison.Ordinal); ··· 110 112 string placeholderName = placeholderStr; 111 113 string paramStr = ""; 112 114 int parensOpen = placeholderStr.IndexOf('('); 115 + 113 116 if (parensOpen != -1) 114 117 { 115 118 placeholderName = placeholderStr.Substring(0, parensOpen).Trim(); ··· 132 135 else 133 136 { 134 137 object[] args; 138 + 135 139 if (string.IsNullOrWhiteSpace(paramStr)) 136 140 { 137 141 args = Array.Empty<object>(); ··· 140 144 { 141 145 string[] argStrs = paramStr.Split(','); 142 146 args = new object[argStrs.Length]; 147 + 143 148 for (int i = 0; i < argStrs.Length; ++i) 144 149 { 145 150 if (!int.TryParse(argStrs[i], out int argVal))
+17 -3
osu.Framework/Graphics/Containers/DelayedLoadWrapper.cs
··· 32 32 AutoSizeAxes = (content as CompositeDrawable)?.AutoSizeAxes ?? AutoSizeAxes; 33 33 } 34 34 35 - public override double LifetimeStart => Content.LifetimeStart; 35 + public override double LifetimeStart 36 + { 37 + get => Content.LifetimeStart; 38 + set => Content.LifetimeStart = value; 39 + } 36 40 37 - public override double LifetimeEnd => Content.LifetimeEnd; 41 + public override double LifetimeEnd 42 + { 43 + get => Content.LifetimeEnd; 44 + set => Content.LifetimeEnd = value; 45 + } 38 46 39 47 public virtual Drawable Content { get; protected set; } 40 48 ··· 75 83 { 76 84 if (loadTask != null) throw new InvalidOperationException("Load is already started!"); 77 85 86 + DelayedLoadStarted?.Invoke(Content); 78 87 loadTask = LoadComponentAsync(Content, EndDelayedLoad); 79 88 } 80 89 ··· 87 96 } 88 97 89 98 /// <summary> 99 + /// Fired when delayed async load has started. 100 + /// </summary> 101 + public event Action<Drawable> DelayedLoadStarted; 102 + 103 + /// <summary> 90 104 /// Fired when delayed async load completes. Should be used to perform transitions. 91 105 /// </summary> 92 - public Action<Drawable> DelayedLoadComplete; 106 + public event Action<Drawable> DelayedLoadComplete; 93 107 94 108 /// <summary> 95 109 /// True if the load task for our content has been started.
+8
osu.Framework/Graphics/Containers/FillFlowContainer.cs
··· 97 97 protected override IEnumerable<Vector2> ComputeLayoutPositions() 98 98 { 99 99 var max = MaximumSize; 100 + 100 101 if (max == Vector2.Zero) 101 102 { 102 103 var s = ChildSize; ··· 128 129 129 130 // First pass, computing initial flow positions 130 131 Vector2 size = Vector2.Zero; 132 + 131 133 for (int i = 0; i < children.Length; ++i) 132 134 { 133 135 Drawable c = children[i]; ··· 138 140 { 139 141 case FillDirection.Full: 140 142 return Axes.Both; 143 + 141 144 case FillDirection.Horizontal: 142 145 return Axes.X; 146 + 143 147 case FillDirection.Vertical: 144 148 return Axes.Y; 149 + 145 150 default: 146 151 throw new ArgumentException($"{direction.ToString()} is not defined"); 147 152 } ··· 192 197 rowIndices[i] = rowOffsetsToMiddle.Count - 1; 193 198 194 199 Vector2 stride = Vector2.Zero; 200 + 195 201 if (i < children.Length - 1) 196 202 { 197 203 // Compute stride. Note, that the stride depends on the origins of the drawables ··· 230 236 + $"Consider using multiple instances of {nameof(FillFlowContainer)} if this is intentional."); 231 237 232 238 break; 239 + 233 240 case FillDirection.Horizontal: 234 241 if (c.RelativeAnchorPosition.X != ourRelativeAnchor.X) 235 242 throw new InvalidOperationException( ··· 237 244 + $"Consider using multiple instances of {nameof(FillFlowContainer)} if this is intentional."); 238 245 239 246 break; 247 + 240 248 default: 241 249 if (c.RelativeAnchorPosition != ourRelativeAnchor) 242 250 throw new InvalidOperationException(
+1
osu.Framework/Graphics/Containers/FlowContainer.cs
··· 164 164 var positions = ComputeLayoutPositions().ToArray(); 165 165 166 166 int i = 0; 167 + 167 168 foreach (var d in FlowingChildren) 168 169 { 169 170 if (i > positions.Length)
+2 -2
osu.Framework/Graphics/Containers/FocusedOverlayContainer.cs
··· 8 8 /// </summary> 9 9 public abstract class FocusedOverlayContainer : OverlayContainer 10 10 { 11 - public override bool RequestsFocus => State == Visibility.Visible; 11 + public override bool RequestsFocus => State.Value == Visibility.Visible; 12 12 13 - public override bool AcceptsFocus => State == Visibility.Visible; 13 + public override bool AcceptsFocus => State.Value == Visibility.Visible; 14 14 15 15 protected override void PopIn() 16 16 {
+5
osu.Framework/Graphics/Containers/GridContainer.cs
··· 153 153 154 154 // Create the new cell containers and add content 155 155 cells = new CellContainer[requiredRows, requiredColumns]; 156 + 156 157 for (int r = 0; r < cellRows; r++) 157 158 for (int c = 0; c < cellColumns; c++) 158 159 { ··· 234 235 { 235 236 default: 236 237 throw new InvalidOperationException($"Unsupported dimension: {dimension.Mode}."); 238 + 237 239 case GridSizeMode.Distributed: 238 240 break; 241 + 239 242 case GridSizeMode.Relative: 240 243 sizes[i] = dimension.Size * spanLength; 241 244 break; 245 + 242 246 case GridSizeMode.Absolute: 243 247 sizes[i] = dimension.Size; 244 248 break; 249 + 245 250 case GridSizeMode.AutoSize: 246 251 float size = 0; 247 252
+6
osu.Framework/Graphics/Containers/LifetimeManagementContainer.cs
··· 69 69 } 70 70 71 71 bool aliveChildrenChanged = false; 72 + 72 73 if (newState == LifetimeState.Current) 73 74 { 74 75 MakeChildAlive(child); ··· 92 93 private void enqueueEvents(Drawable child, LifetimeState oldState, LifetimeState newState) 93 94 { 94 95 Debug.Assert(oldState != newState); 96 + 95 97 switch (oldState) 96 98 { 97 99 case LifetimeState.Future: ··· 99 101 if (newState == LifetimeState.Past) 100 102 eventQueue.Enqueue(new LifetimeBoundaryCrossedEvent(child, LifetimeBoundaryKind.End, LifetimeBoundaryCrossingDirection.Forward)); 101 103 break; 104 + 102 105 case LifetimeState.Current: 103 106 eventQueue.Enqueue(newState == LifetimeState.Past 104 107 ? new LifetimeBoundaryCrossedEvent(child, LifetimeBoundaryKind.End, LifetimeBoundaryCrossingDirection.Forward) 105 108 : new LifetimeBoundaryCrossedEvent(child, LifetimeBoundaryKind.Start, LifetimeBoundaryCrossingDirection.Backward)); 106 109 break; 110 + 107 111 case LifetimeState.Past: 108 112 eventQueue.Enqueue(new LifetimeBoundaryCrossedEvent(child, LifetimeBoundaryKind.End, LifetimeBoundaryCrossingDirection.Backward)); 109 113 if (newState == LifetimeState.Future) ··· 198 202 { 199 203 case LifetimeState.Future: 200 204 return futureChildren; 205 + 201 206 case LifetimeState.Past: 202 207 return pastChildren; 208 + 203 209 default: 204 210 return null; 205 211 }
+10
osu.Framework/Graphics/Containers/Markdown/MarkdownContainer.cs
··· 153 153 case ThematicBreakBlock thematicBlock: 154 154 container.Add(CreateSeparator(thematicBlock)); 155 155 break; 156 + 156 157 case HeadingBlock headingBlock: 157 158 container.Add(CreateHeading(headingBlock)); 158 159 break; 160 + 159 161 case ParagraphBlock paragraphBlock: 160 162 container.Add(CreateParagraph(paragraphBlock, level)); 161 163 break; 164 + 162 165 case QuoteBlock quoteBlock: 163 166 container.Add(CreateQuoteBlock(quoteBlock)); 164 167 break; 168 + 165 169 case FencedCodeBlock fencedCodeBlock: 166 170 container.Add(CreateFencedCodeBlock(fencedCodeBlock)); 167 171 break; 172 + 168 173 case Table table: 169 174 container.Add(CreateTable(table)); 170 175 break; 176 + 171 177 case ListBlock listBlock: 172 178 var childContainer = CreateList(listBlock); 173 179 container.Add(childContainer); 174 180 foreach (var single in listBlock) 175 181 AddMarkdownComponent(single, childContainer, level + 1); 176 182 break; 183 + 177 184 case ListItemBlock listItemBlock: 178 185 foreach (var single in listItemBlock) 179 186 AddMarkdownComponent(single, container, level); 180 187 break; 188 + 181 189 case HtmlBlock _: 182 190 // HTML is not supported 183 191 break; 192 + 184 193 case LinkReferenceDefinitionGroup _: 185 194 // Link reference doesn't need to be displayed. 186 195 break; 196 + 187 197 default: 188 198 container.Add(CreateNotImplemented(markdownObject)); 189 199 break;
+4
osu.Framework/Graphics/Containers/Markdown/MarkdownHeading.cs
··· 48 48 { 49 49 case 1: 50 50 return 2.7f; 51 + 51 52 case 2: 52 53 return 2; 54 + 53 55 case 3: 54 56 return 1.5f; 57 + 55 58 case 4: 56 59 return 1.3f; 60 + 57 61 default: 58 62 return 1; 59 63 }
+2
osu.Framework/Graphics/Containers/Markdown/MarkdownTableCell.cs
··· 65 65 case TableColumnAlign.Center: 66 66 textFlow.TextAnchor = Anchor.Centre; 67 67 break; 68 + 68 69 case TableColumnAlign.Right: 69 70 textFlow.TextAnchor = Anchor.CentreRight; 70 71 break; 72 + 71 73 default: 72 74 textFlow.TextAnchor = Anchor.CentreLeft; 73 75 break;
+8
osu.Framework/Graphics/Containers/Markdown/MarkdownTextFlowContainer.cs
··· 68 68 addEmphasis(text, emphases); 69 69 70 70 break; 71 + 71 72 case LinkInline linkInline: 72 73 { 73 74 if (!linkInline.IsImage) ··· 82 83 } 83 84 84 85 break; 86 + 85 87 case CodeInline codeInline: 86 88 AddCodeInLine(codeInline); 87 89 break; 90 + 88 91 case LinkInline linkInline when linkInline.IsImage: 89 92 AddImage(linkInline); 90 93 break; 94 + 91 95 case HtmlInline _: 92 96 case HtmlEntityInline _: 93 97 // Handled by the next literal 94 98 break; 99 + 95 100 case LineBreakInline lineBreak: 96 101 if (lineBreak.IsHard) 97 102 NewParagraph(); 98 103 else 99 104 NewLine(); 100 105 break; 106 + 101 107 case ContainerInline innerContainer: 102 108 AddInlineText(innerContainer); 103 109 break; 110 + 104 111 default: 105 112 AddNotImplementedInlineText(single); 106 113 break; ··· 139 146 case "_": 140 147 hasItalic = true; 141 148 break; 149 + 142 150 case "**": 143 151 case "__": 144 152 hasBold = true;
+126 -63
osu.Framework/Graphics/Containers/ModelBackedDrawable.cs
··· 3 3 4 4 using System; 5 5 using System.Collections.Generic; 6 + using JetBrains.Annotations; 6 7 using osu.Framework.Graphics.Transforms; 7 8 using osu.Framework.Lists; 8 9 ··· 17 18 /// <summary> 18 19 /// The currently displayed <see cref="Drawable"/>. Null if no drawable is displayed. 19 20 /// </summary> 20 - protected Drawable DisplayedDrawable { get; private set; } 21 + protected Drawable DisplayedDrawable => displayedWrapper?.Content; 21 22 22 23 /// <summary> 23 24 /// The <see cref="IEqualityComparer{T}"/> used to compare models to ensure that <see cref="Drawable"/>s are not updated unnecessarily. ··· 49 50 } 50 51 51 52 /// <summary> 53 + /// The wrapper which has the current displayed content. 54 + /// </summary> 55 + private DelayedLoadWrapper displayedWrapper; 56 + 57 + /// <summary> 58 + /// The wrapper which is currently loading, or has finished loading (i.e <see cref="displayedWrapper"/>). 59 + /// </summary> 60 + private DelayedLoadWrapper currentWrapper; 61 + 62 + /// <summary> 52 63 /// Constructs a new <see cref="ModelBackedDrawable{T}"/> with the default <typeparamref name="T"/> equality comparer. 53 64 /// </summary> 54 65 protected ModelBackedDrawable() ··· 80 91 updateDrawable(); 81 92 } 82 93 83 - private DelayedLoadWrapper currentWrapper; 84 - private bool placeholderDisplayed; 85 - 86 94 private void updateDrawable() 87 95 { 88 - if (model == null) 89 - loadPlaceholder(); 96 + if (TransformImmediately) 97 + { 98 + // If loading to a new model and we've requested to transform immediately, load a null model to allow such transforms to occur 99 + loadDrawable(null); 100 + } 101 + 102 + loadDrawable(() => CreateDrawable(model)); 103 + } 104 + 105 + private void loadDrawable(Func<Drawable> createDrawableFunc) 106 + { 107 + // Remove the previous wrapper if the inner drawable hasn't finished loading. 108 + if (currentWrapper?.DelayedLoadCompleted == false) 109 + { 110 + RemoveInternal(currentWrapper); 111 + DisposeChildAsync(currentWrapper); 112 + } 113 + 114 + currentWrapper = createWrapper(createDrawableFunc, LoadDelay); 115 + 116 + if (currentWrapper == null) 117 + { 118 + OnLoadStarted(); 119 + finishLoad(currentWrapper); 120 + OnLoadFinished(); 121 + } 90 122 else 91 123 { 92 - if (FadeOutImmediately) loadPlaceholder(); 93 - loadDrawable(CreateDrawable(model), false); 124 + AddInternal(currentWrapper); 125 + currentWrapper.DelayedLoadStarted += _ => OnLoadStarted(); 126 + currentWrapper.DelayedLoadComplete += _ => 127 + { 128 + finishLoad(currentWrapper); 129 + OnLoadFinished(); 130 + }; 94 131 } 95 132 } 96 133 97 - private void loadPlaceholder() 134 + /// <summary> 135 + /// Invoked when a <see cref="DelayedLoadWrapper"/> has finished loading its contents. 136 + /// May be invoked multiple times for each <see cref="DelayedLoadWrapper"/>. 137 + /// </summary> 138 + /// <param name="wrapper">The <see cref="DelayedLoadWrapper"/>.</param> 139 + private void finishLoad(DelayedLoadWrapper wrapper) 98 140 { 99 - if (placeholderDisplayed) 141 + var lastWrapper = displayedWrapper; 142 + 143 + // If the wrapper hasn't changed then this invocation must be a result of a reload (e.g. DelayedLoadUnloadWrapper) 144 + // In this case, we do not want to transform/expire the wrapper 145 + if (lastWrapper == wrapper) 100 146 return; 101 147 102 - var placeholder = CreateDrawable(null); 148 + // Make the new wrapper initially hidden 149 + ApplyHideTransforms(wrapper); 150 + wrapper?.FinishTransforms(); 151 + 152 + var showTransforms = ApplyShowTransforms(wrapper); 153 + 154 + // If we have a non-null new wrapper, we need to wait for the show transformation to complete before hiding the old wrapper, 155 + // otherwise, we can hide the old wrapper instantaneously and leave a blank display 156 + var hideTransforms = wrapper == null 157 + ? ApplyHideTransforms(lastWrapper) 158 + : ((Drawable)lastWrapper)?.Delay(TransformDuration)?.Append(ApplyHideTransforms); 103 159 104 - loadDrawable(placeholder, true); 160 + // Expire the last wrapper after the front-most transform has completed (the last wrapper is assumed to be invisible by that point) 161 + (showTransforms ?? hideTransforms)?.OnComplete(_ => lastWrapper?.Expire()); 105 162 106 - // in the case a placeholder has not been specified, this should not be set as to allow for a potential runtime change 107 - // of placeholder logic on a future load operation. 108 - placeholderDisplayed = placeholder != null; 163 + displayedWrapper = wrapper; 109 164 } 110 165 111 - private void loadDrawable(Drawable newDrawable, bool isPlaceholder) 166 + /// <summary> 167 + /// Creates a <see cref="DelayedLoadWrapper"/> which supports reloading. 168 + /// </summary> 169 + /// <param name="createContentFunc">A function that creates the wrapped <see cref="Drawable"/>.</param> 170 + /// <param name="timeBeforeLoad">The time before loading should begin.</param> 171 + /// <returns>A <see cref="DelayedLoadWrapper"/> or null if <see cref="createContentFunc"/> returns null.</returns> 172 + private DelayedLoadWrapper createWrapper(Func<Drawable> createContentFunc, double timeBeforeLoad) 112 173 { 113 - // Remove the previous wrapper if the inner drawable hasn't finished loading. 114 - // We check IsLoaded on the content rather than DelayedLoadCompleted so that we can ensure that finishLoad() has not been called and DisplayedDrawable hasn't been updated 115 - if (currentWrapper?.Content.IsLoaded == false) 116 - { 117 - // Using .Expire() will be one frame too late, since children's lifetime has already been updated this frame 118 - RemoveInternal(currentWrapper); 119 - DisposeChildAsync(currentWrapper); 120 - } 174 + var content = createContentFunc?.Invoke(); 121 175 122 - currentWrapper = null; 176 + if (content == null) 177 + return null; 123 178 124 - if (newDrawable != null) 179 + return CreateDelayedLoadWrapper(() => 125 180 { 126 - AddInternal(isPlaceholder ? newDrawable : currentWrapper = CreateDelayedLoadWrapper(newDrawable, LoadDelay)); 127 - 128 - if (isPlaceholder) 181 + try 129 182 { 130 - // Although the drawable is technically not loaded, it does have a clock and we need DisplayedDrawable to be updated instantly 131 - finishLoad(); 183 + // optimisation to use already constructed object (used above for null check). 184 + return content ?? createContentFunc(); 185 + } 186 + finally 187 + { 188 + // consume initial object if not already. 189 + content = null; 132 190 } 133 - else 134 - newDrawable.OnLoadComplete += _ => finishLoad(); 135 - } 136 - else 137 - finishLoad(); 191 + }, timeBeforeLoad); 192 + } 138 193 139 - void finishLoad() 140 - { 141 - var currentDrawable = DisplayedDrawable; 194 + /// <summary> 195 + /// Invoked when the <see cref="Drawable"/> representation of a model begins loading. 196 + /// </summary> 197 + protected virtual void OnLoadStarted() 198 + { 199 + } 142 200 143 - var transform = ReplaceDrawable(currentDrawable, newDrawable) ?? (currentDrawable ?? newDrawable)?.DelayUntilTransformsFinished(); 144 - transform?.OnComplete(_ => currentDrawable?.Expire()); 145 - 146 - DisplayedDrawable = newDrawable; 147 - placeholderDisplayed = isPlaceholder; 148 - } 201 + /// <summary> 202 + /// Invoked when the <see cref="Drawable"/> representation of a model has finished loading. 203 + /// </summary> 204 + protected virtual void OnLoadFinished() 205 + { 149 206 } 150 207 151 208 /// <summary> 152 - /// Determines whether the current <see cref="Drawable"/> should fade out straight away when switching to a new model, 153 - /// or whether it should wait until the new <see cref="Drawable"/> has finished loading. 209 + /// Determines whether <see cref="ApplyHideTransforms"/> should be invoked immediately on the currently-displayed drawable when switching to a new model. 154 210 /// </summary> 155 - protected virtual bool FadeOutImmediately => false; 211 + protected virtual bool TransformImmediately => false; 156 212 157 213 /// <summary> 158 - /// The time in milliseconds that <see cref="Drawable"/>s will fade in and out. 214 + /// The default time in milliseconds for transforms applied through <see cref="ApplyHideTransforms"/> and <see cref="ApplyShowTransforms"/>. 159 215 /// </summary> 160 - protected virtual double FadeDuration => 1000; 216 + protected virtual double TransformDuration => 1000; 161 217 162 218 /// <summary> 163 219 /// The delay in milliseconds before <see cref="Drawable"/>s will begin loading. ··· 167 223 /// <summary> 168 224 /// Allows subclasses to customise the <see cref="DelayedLoadWrapper"/>. 169 225 /// </summary> 170 - protected virtual DelayedLoadWrapper CreateDelayedLoadWrapper(Drawable content, double timeBeforeLoad) => 171 - new DelayedLoadWrapper(content, timeBeforeLoad); 226 + [NotNull] 227 + protected virtual DelayedLoadWrapper CreateDelayedLoadWrapper([NotNull] Func<Drawable> createContentFunc, double timeBeforeLoad) => 228 + new DelayedLoadWrapper(createContentFunc(), timeBeforeLoad); 172 229 173 230 /// <summary> 174 - /// Override to instantiate a custom <see cref="Drawable"/> based on the passed model. 175 - /// May be null to indicate that the model has no visual representation, 176 - /// in which case the placeholder will be used if it exists. 231 + /// Creates a custom <see cref="Drawable"/> to display a model. 177 232 /// </summary> 178 233 /// <param name="model">The model that the <see cref="Drawable"/> should represent.</param> 179 - protected abstract Drawable CreateDrawable(T model); 234 + /// <returns>A <see cref="Drawable"/> that represents <paramref name="model"/>, or null if no <see cref="Drawable"/> should be displayed.</returns> 235 + [CanBeNull] 236 + protected abstract Drawable CreateDrawable([CanBeNull] T model); 180 237 181 238 /// <summary> 182 - /// Returns a <see cref="TransformSequence{Drawable}"/> that replaces the given <see cref="Drawable"/>s. 183 - /// Default functionality is to fade in the target from zero, or if it is null, to fade out the source. 239 + /// Hides a drawable. 240 + /// </summary> 241 + /// <param name="drawable">The drawable that is to be hidden.</param> 242 + /// <returns>The transform sequence.</returns> 243 + protected virtual TransformSequence<Drawable> ApplyHideTransforms([CanBeNull] Drawable drawable) 244 + => drawable?.FadeOut(TransformDuration, Easing.OutQuint); 245 + 246 + /// <summary> 247 + /// Shows a drawable. 184 248 /// </summary> 185 - /// <returns>The drawable.</returns> 186 - /// <param name="source">The <see cref="Drawable"/> to be replaced.</param> 187 - /// <param name="target">The <see cref="Drawable"/> we are replacing with.</param> 188 - protected virtual TransformSequence<Drawable> ReplaceDrawable(Drawable source, Drawable target) => 189 - target?.FadeInFromZero(FadeDuration, Easing.OutQuint) ?? source?.FadeOut(FadeDuration, Easing.OutQuint); 249 + /// <param name="drawable">The drawable that is to be shown.</param> 250 + /// <returns>The transform sequence.</returns> 251 + protected virtual TransformSequence<Drawable> ApplyShowTransforms([CanBeNull] Drawable drawable) 252 + => drawable?.FadeIn(TransformDuration, Easing.OutQuint); 190 253 } 191 254 }
+5 -2
osu.Framework/Graphics/Containers/ScrollContainer.cs
··· 263 263 case Key.PageUp: 264 264 ScrollTo(target - displayableContent); 265 265 return true; 266 + 266 267 case Key.PageDown: 267 268 ScrollTo(target + displayableContent); 268 269 return true; ··· 508 509 if (ScrollDirection == Direction.Horizontal) 509 510 { 510 511 Scrollbar.X = Current * Scrollbar.Size.X; 511 - content.X = -Current; 512 + content.X = -Current + scrollableExtent * content.RelativeAnchorPosition.X; 512 513 } 513 514 else 514 515 { 515 516 Scrollbar.Y = Current * Scrollbar.Size.Y; 516 - content.Y = -Current; 517 + content.Y = -Current + scrollableExtent * content.RelativeAnchorPosition.Y; 517 518 } 518 519 } 519 520 ··· 634 635 case PlatformActionType.LineStart: 635 636 ScrollToStart(); 636 637 return true; 638 + 637 639 case PlatformActionType.LineEnd: 638 640 ScrollToEnd(); 639 641 return true; 642 + 640 643 default: 641 644 return false; 642 645 }
+2
osu.Framework/Graphics/Containers/TabbableContainer.cs
··· 53 53 stack.Push(target); 54 54 55 55 bool started = false; 56 + 56 57 while (stack.Count > 0) 57 58 { 58 59 var drawable = stack.Pop(); ··· 66 67 { 67 68 var newChildren = composite.InternalChildren.ToList(); 68 69 int bound = reverse ? newChildren.Count : 0; 70 + 69 71 if (!started) 70 72 { 71 73 // Find self, to know starting point
+4
osu.Framework/Graphics/Containers/TextFlowContainer.cs
··· 275 275 { 276 276 bool first = true; 277 277 var sprites = new List<Drawable>(); 278 + 278 279 foreach (string l in line.Text.Split('\n')) 279 280 { 280 281 if (!first) 281 282 { 282 283 Drawable lastChild = Children.LastOrDefault(); 284 + 283 285 if (lastChild != null) 284 286 { 285 287 var newLine = new NewLineContainer(newLineIsParagraph); ··· 332 334 { 333 335 var childrenByLine = new List<List<Drawable>>(); 334 336 var curLine = new List<Drawable>(); 337 + 335 338 foreach (var c in Children) 336 339 { 337 340 c.Anchor = TextAnchor; ··· 361 364 362 365 bool isFirstLine = true; 363 366 float lastLineHeight = 0f; 367 + 364 368 foreach (var line in childrenByLine) 365 369 { 366 370 bool isFirstChild = true;
+36 -36
osu.Framework/Graphics/Containers/VisibilityContainer.cs
··· 1 1 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 - using System; 4 + using osu.Framework.Bindables; 5 5 6 6 namespace osu.Framework.Graphics.Containers 7 7 { 8 8 /// <summary> 9 9 /// A container which adds a basic visibility state. 10 10 /// </summary> 11 - public abstract class VisibilityContainer : Container, IStateful<Visibility> 11 + public abstract class VisibilityContainer : Container 12 12 { 13 + /// <summary> 14 + /// The current visibility state. 15 + /// </summary> 16 + public readonly Bindable<Visibility> State = new Bindable<Visibility>(); 17 + 13 18 /// <summary> 14 19 /// Whether we should be in a hidden state when first displayed. 15 20 /// Override this and set to true to *always* perform a <see cref="PopIn"/> animation even when the state is non-hidden at 16 21 /// first display. 17 22 /// </summary> 18 - protected virtual bool StartHidden => state == Visibility.Hidden; 23 + protected virtual bool StartHidden => State.Value == Visibility.Hidden; 19 24 20 25 protected override void LoadComplete() 21 26 { ··· 26 31 FinishTransforms(true); 27 32 } 28 33 29 - if (state != Visibility.Hidden) 30 - updateState(); 34 + State.BindValueChanged(updateState, State.Value != Visibility.Hidden); 31 35 32 36 base.LoadComplete(); 33 37 } 34 38 35 - private Visibility state; 39 + /// <summary> 40 + /// Hide this container by setting its visibility to <see cref="Visibility.Visible"/>. 41 + /// </summary> 42 + public override void Show() => State.Value = Visibility.Visible; 36 43 37 - public Visibility State 38 - { 39 - get => state; 40 - set 41 - { 42 - if (value == state) return; 44 + /// <summary> 45 + /// Hide this container by setting its visibility to <see cref="Visibility.Hidden"/>. 46 + /// </summary> 47 + public override void Hide() => State.Value = Visibility.Hidden; 43 48 44 - state = value; 49 + /// <summary> 50 + /// Toggle this container's visibility. 51 + /// </summary> 52 + public void ToggleVisibility() => State.Value = State.Value == Visibility.Visible ? Visibility.Hidden : Visibility.Visible; 45 53 46 - if (!IsLoaded) return; 54 + public override bool PropagateNonPositionalInputSubTree => base.PropagateNonPositionalInputSubTree && State.Value == Visibility.Visible; 55 + public override bool PropagatePositionalInputSubTree => base.PropagatePositionalInputSubTree && State.Value == Visibility.Visible; 47 56 48 - updateState(); 49 - } 50 - } 57 + /// <summary> 58 + /// Implement any transition to be played when <see cref="State"/> becomes <see cref="Visibility.Visible"/>. 59 + /// </summary> 60 + protected abstract void PopIn(); 51 61 52 - private void updateState() 62 + /// <summary> 63 + /// Implement any transition to be played when <see cref="State"/> becomes <see cref="Visibility.Hidden"/>. 64 + /// Will be invoked once on <see cref="LoadComplete"/> if <see cref="StartHidden"/> is set. 65 + /// </summary> 66 + protected abstract void PopOut(); 67 + 68 + private void updateState(ValueChangedEvent<Visibility> e) 53 69 { 54 - switch (state) 70 + switch (e.NewValue) 55 71 { 56 72 case Visibility.Hidden: 57 73 PopOut(); 58 74 break; 75 + 59 76 case Visibility.Visible: 60 77 PopIn(); 61 78 break; 62 79 } 63 - 64 - StateChanged?.Invoke(state); 65 80 } 66 - 67 - public override void Hide() => State = Visibility.Hidden; 68 - 69 - public override void Show() => State = Visibility.Visible; 70 - 71 - public override bool PropagateNonPositionalInputSubTree => base.PropagateNonPositionalInputSubTree && State == Visibility.Visible; 72 - public override bool PropagatePositionalInputSubTree => base.PropagatePositionalInputSubTree && State == Visibility.Visible; 73 - 74 - public event Action<Visibility> StateChanged; 75 - 76 - protected abstract void PopIn(); 77 - 78 - protected abstract void PopOut(); 79 - 80 - public void ToggleVisibility() => State = State == Visibility.Visible ? Visibility.Hidden : Visibility.Visible; 81 81 } 82 82 83 83 public enum Visibility
+1
osu.Framework/Graphics/Cursor/ContextMenuContainer.cs
··· 80 80 relativeCursorPosition = ToSpaceOfOtherDrawable(menu.Position, menuTarget); 81 81 menu.Open(); 82 82 return true; 83 + 83 84 default: 84 85 menu.Close(); 85 86 return false;
+1 -1
osu.Framework/Graphics/Cursor/CursorContainer.cs
··· 22 22 Depth = float.MinValue; 23 23 RelativeSizeAxes = Axes.Both; 24 24 25 - State = Visibility.Visible; 25 + State.Value = Visibility.Visible; 26 26 } 27 27 28 28 [BackgroundDependencyLoader]
+1
osu.Framework/Graphics/Cursor/CursorEffectContainer.cs
··· 53 53 // We keep track of all drawables we found while traversing the parent chain upwards. 54 54 newChildDrawables.Clear(); 55 55 newChildDrawables.Add(candidate); 56 + 56 57 // When we encounter a drawable we already encountered before, then there is no need 57 58 // to keep going upward, since we already recorded it previously. At that point we know 58 59 // the drawables we found are in fact children of ours.
+3
osu.Framework/Graphics/Cursor/TooltipContainer.cs
··· 140 140 base.Update(); 141 141 142 142 IHasTooltip target = findTooltipTarget(); 143 + 143 144 if (target != null && target != currentlyDisplayed) 144 145 { 145 146 currentlyDisplayed = target; ··· 178 179 return hasValidTooltip(draggedTarget) ? draggedTarget : null; 179 180 180 181 IHasTooltip targetCandidate = FindTargets().Find(t => t.TooltipText != null); 182 + 181 183 // check this first - if we find no target candidate we still want to clear the recorded positions and update the lastCandidate. 182 184 if (targetCandidate != lastCandidate) 183 185 { ··· 191 193 double appearDelay = (targetCandidate as IHasAppearDelay)?.AppearDelay ?? AppearDelay; 192 194 // Always keep 10 positions at equally-sized time intervals that add up to AppearDelay. 193 195 double positionRecordInterval = appearDelay / 10; 196 + 194 197 if (Time.Current - lastRecordedPositionTime >= positionRecordInterval) 195 198 { 196 199 lastRecordedPositionTime = Time.Current;
+196 -5
osu.Framework/Graphics/DrawNode.cs
··· 3 3 4 4 using osu.Framework.Graphics.OpenGL; 5 5 using System; 6 + using System.Runtime.CompilerServices; 7 + using osu.Framework.Graphics.Batches; 8 + using osu.Framework.Graphics.Colour; 9 + using osu.Framework.Graphics.OpenGL.Buffers; 10 + using osu.Framework.Graphics.OpenGL.Textures; 6 11 using osu.Framework.Graphics.OpenGL.Vertices; 12 + using osu.Framework.Graphics.Primitives; 13 + using osu.Framework.Graphics.Textures; 14 + using osu.Framework.MathUtils.Clipping; 7 15 using osu.Framework.Threading; 16 + using osuTK; 8 17 9 18 namespace osu.Framework.Graphics 10 19 { ··· 22 31 /// <summary> 23 32 /// Contains the colour and blending information of this <see cref="DrawNode"/>. 24 33 /// </summary> 25 - protected internal DrawColourInfo DrawColourInfo { get; internal set; } 34 + protected DrawColourInfo DrawColourInfo { get; private set; } 26 35 27 36 /// <summary> 28 37 /// Identifies the state of this draw node with an invalidation state of its corresponding ··· 39 48 private readonly AtomicCounter referenceCount = new AtomicCounter(); 40 49 41 50 /// <summary> 51 + /// The depth at which drawing should take place. 52 + /// This is written to from the front-to-back pass and used in both passes. 53 + /// </summary> 54 + private float drawDepth; 55 + 56 + /// <summary> 42 57 /// Creates a new <see cref="DrawNode"/>. 43 58 /// </summary> 44 59 /// <param name="source">The <see cref="Drawable"/> to draw with this <see cref="DrawNode"/>.</param> ··· 61 76 } 62 77 63 78 /// <summary> 64 - /// Draws this draw node to the screen. 79 + /// Draws this <see cref="DrawNode"/> to the screen. 65 80 /// </summary> 66 - /// <param name="vertexAction">The action to be performed on each vertex of 67 - /// the draw node in order to draw it if required. This is primarily used by 68 - /// textured sprites.</param> 81 + /// <remarks> 82 + /// This is the back-to-front (BTF) pass. The back-buffer depth test function used is GL_LESS.<br /> 83 + /// The depth test will fail for samples that overlap the opaque interior of this <see cref="DrawNode"/> and any <see cref="DrawNode"/>s above this one.<br /> 84 + /// </remarks> 85 + /// <param name="vertexAction">The action to be performed on each vertex of the draw node in order to draw it if required. This is primarily used by textured sprites.</param> 69 86 public virtual void Draw(Action<TexturedVertex2D> vertexAction) 70 87 { 71 88 GLWrapper.SetBlend(DrawColourInfo.Blending); 89 + GLWrapper.SetDrawDepth(drawDepth); 90 + } 91 + 92 + /// <summary> 93 + /// Draws the opaque interior of this <see cref="DrawNode"/> and all <see cref="DrawNode"/>s further down the scene graph, invoking <see cref="DrawOpaqueInterior"/> if <see cref="CanDrawOpaqueInterior"/> 94 + /// indicates that an opaque interior can be drawn for each relevant <see cref="DrawNode"/>. 95 + /// </summary> 96 + /// <remarks> 97 + /// This is the front-to-back pass. The back-buffer depth test function used is GL_LESS.<br /> 98 + /// If an opaque interior is not drawn: the current value of <paramref name="depthValue"/> is stored.<br /> 99 + /// If an opaque interior is drawn: <paramref name="depthValue"/> is incremented, stored, and the opaque interior vertices are drawn at the post-incremented depth value. 100 + /// Incrementing <paramref name="depthValue"/> at this point allows for early-z testing to also occur within the front-to-back pass.<br /> 101 + /// </remarks> 102 + /// <param name="depthValue">The previous depth value.</param> 103 + /// <param name="vertexAction">The action to be performed on each vertex of the draw node in order to draw it if required. This is primarily used by textured sprites.</param> 104 + internal virtual void DrawOpaqueInteriorSubTree(DepthValue depthValue, Action<TexturedVertex2D> vertexAction) 105 + { 106 + if (!depthValue.CanIncrement || !CanDrawOpaqueInterior) 107 + { 108 + // The back-to-front pass requires the depth value 109 + drawDepth = depthValue; 110 + return; 111 + } 112 + 113 + // It is crucial to draw with an incremented depth value, consider the case of a box: 114 + // In the front-to-back pass, the inner conservative area is drawn at depth X 115 + // In the back-to-front pass, the full area is drawn at depth X, and the depth test function is set to GL_LESS, so the inner conservative area is not redrawn 116 + // Furthermore, a back-to-front-drawn object above the box will be visible since it will be drawn with a depth of (X - increment), satisfying the depth test 117 + drawDepth = depthValue.Increment(); 118 + 119 + DrawOpaqueInterior(vertexAction); 120 + } 121 + 122 + /// <summary> 123 + /// Draws the opaque interior of this <see cref="DrawNode"/> to the screen. 124 + /// The opaque interior must be a fully-opaque, non-blended area of this <see cref="DrawNode"/>, clipped to the current masking area via <code>DrawClipped()</code>. 125 + /// See <see cref="Shapes.Box.BoxDrawNode"/> for an example implementation. 126 + /// </summary> 127 + /// <param name="vertexAction">The action to be performed on each vertex of the draw node in order to draw it if required. This is primarily used by textured sprites.</param> 128 + protected virtual void DrawOpaqueInterior(Action<TexturedVertex2D> vertexAction) 129 + { 130 + GLWrapper.SetBlend(DrawColourInfo.Blending); 131 + GLWrapper.SetDrawDepth(drawDepth); 132 + } 133 + 134 + /// <summary> 135 + /// Whether this <see cref="DrawNode"/> can draw a opaque interior. <see cref="DrawOpaqueInterior"/> will only be invoked if this value is <code>true</code>. 136 + /// Should not return <code>true</code> if <see cref="DrawOpaqueInterior"/> will result in a no-op. 137 + /// </summary> 138 + protected internal virtual bool CanDrawOpaqueInterior => false; 139 + 140 + /// <summary> 141 + /// Draws a triangle to the screen. 142 + /// </summary> 143 + /// <param name="texture">The texture to fill the triangle with.</param> 144 + /// <param name="vertexTriangle">The triangle to draw.</param> 145 + /// <param name="textureRect">The texture rectangle.</param> 146 + /// <param name="drawColour">The vertex colour.</param> 147 + /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 148 + /// <param name="inflationPercentage">The percentage amount that <see cref="textureRect"/> should be inflated.</param> 149 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 150 + protected void DrawTriangle(Texture texture, Triangle vertexTriangle, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, 151 + Vector2? inflationPercentage = null) 152 + => texture.DrawTriangle(vertexTriangle, drawColour, textureRect, vertexAction, inflationPercentage); 153 + 154 + /// <summary> 155 + /// Draws a triangle to the screen. 156 + /// </summary> 157 + /// <param name="texture">The texture to fill the triangle with.</param> 158 + /// <param name="vertexTriangle">The triangle to draw.</param> 159 + /// <param name="drawColour">The vertex colour.</param> 160 + /// <param name="textureRect">The texture rectangle.</param> 161 + /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 162 + /// <param name="inflationPercentage">The percentage amount that <see cref="textureRect"/> should be inflated.</param> 163 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 164 + protected void DrawTriangle(TextureGL texture, Triangle vertexTriangle, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, 165 + Vector2? inflationPercentage = null) 166 + => texture.DrawTriangle(vertexTriangle, drawColour, textureRect, vertexAction, inflationPercentage); 167 + 168 + /// <summary> 169 + /// Draws a quad to the screen. 170 + /// </summary> 171 + /// <param name="texture">The texture to fill the triangle with.</param> 172 + /// <param name="vertexQuad">The quad to draw.</param> 173 + /// <param name="textureRect">The texture rectangle.</param> 174 + /// <param name="drawColour">The vertex colour.</param> 175 + /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 176 + /// <param name="inflationPercentage">The percentage amount that <see cref="textureRect"/> should be inflated.</param> 177 + /// <param name="blendRangeOverride">The range over which the edges of the <see cref="textureRect"/> should be blended.</param> 178 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 179 + protected void DrawQuad(Texture texture, Quad vertexQuad, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, 180 + Vector2? inflationPercentage = null, Vector2? blendRangeOverride = null) 181 + => texture.DrawQuad(vertexQuad, drawColour, textureRect, vertexAction, inflationPercentage: inflationPercentage, blendRangeOverride: blendRangeOverride); 182 + 183 + /// <summary> 184 + /// Draws a quad to the screen. 185 + /// </summary> 186 + /// <param name="texture">The texture to fill the triangle with.</param> 187 + /// <param name="vertexQuad">The quad to draw.</param> 188 + /// <param name="drawColour">The vertex colour.</param> 189 + /// <param name="textureRect">The texture rectangle.</param> 190 + /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 191 + /// <param name="inflationPercentage">The percentage amount that <see cref="textureRect"/> should be inflated.</param> 192 + /// <param name="blendRangeOverride">The range over which the edges of the <see cref="textureRect"/> should be blended.</param> 193 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 194 + protected void DrawQuad(TextureGL texture, Quad vertexQuad, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, 195 + Vector2? inflationPercentage = null, Vector2? blendRangeOverride = null) 196 + => texture.DrawQuad(vertexQuad, drawColour, textureRect, vertexAction, inflationPercentage: inflationPercentage, blendRangeOverride: blendRangeOverride); 197 + 198 + /// <summary> 199 + /// Clips a <see cref="IConvexPolygon"/> to the current masking area and draws the resulting triangles to the screen using the specified texture. 200 + /// </summary> 201 + /// <param name="polygon">The polygon to draw.</param> 202 + /// <param name="texture">The texture to fill the triangle with.</param> 203 + /// <param name="textureRect">The texture rectangle.</param> 204 + /// <param name="drawColour">The vertex colour.</param> 205 + /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 206 + /// <param name="inflationPercentage">The percentage amount that <see cref="textureRect"/> should be inflated.</param> 207 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 208 + protected void DrawClipped<T>(ref T polygon, Texture texture, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, 209 + Vector2? inflationPercentage = null) 210 + where T : IConvexPolygon 211 + { 212 + var maskingQuad = GLWrapper.CurrentMaskingInfo.ConservativeScreenSpaceQuad; 213 + 214 + var clipper = new ConvexPolygonClipper<Quad, T>(ref maskingQuad, ref polygon); 215 + Span<Vector2> buffer = stackalloc Vector2[clipper.GetClipBufferSize()]; 216 + Span<Vector2> clippedRegion = clipper.Clip(buffer); 217 + 218 + for (int i = 2; i < clippedRegion.Length; i++) 219 + DrawTriangle(texture, new Triangle(clippedRegion[0], clippedRegion[i - 1], clippedRegion[i]), drawColour, textureRect, vertexAction, inflationPercentage); 220 + } 221 + 222 + /// <summary> 223 + /// Clips a <see cref="IConvexPolygon"/> to the current masking area and draws the resulting triangles to the screen using the specified texture. 224 + /// </summary> 225 + /// <param name="polygon">The polygon to draw.</param> 226 + /// <param name="texture">The texture to fill the triangle with.</param> 227 + /// <param name="textureRect">The texture rectangle.</param> 228 + /// <param name="drawColour">The vertex colour.</param> 229 + /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 230 + /// <param name="inflationPercentage">The percentage amount that <see cref="textureRect"/> should be inflated.</param> 231 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 232 + protected void DrawClipped<T>(ref T polygon, TextureGL texture, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, 233 + Vector2? inflationPercentage = null) 234 + where T : IConvexPolygon 235 + { 236 + var maskingQuad = GLWrapper.CurrentMaskingInfo.ConservativeScreenSpaceQuad; 237 + 238 + var clipper = new ConvexPolygonClipper<Quad, T>(ref maskingQuad, ref polygon); 239 + Span<Vector2> buffer = stackalloc Vector2[clipper.GetClipBufferSize()]; 240 + Span<Vector2> clippedRegion = clipper.Clip(buffer); 241 + 242 + for (int i = 2; i < clippedRegion.Length; i++) 243 + DrawTriangle(texture, new Triangle(clippedRegion[0], clippedRegion[i - 1], clippedRegion[i]), drawColour, textureRect, vertexAction, inflationPercentage); 244 + } 245 + 246 + /// <summary> 247 + /// Draws a <see cref="FrameBuffer"/> to the screen. 248 + /// </summary> 249 + /// <param name="frameBuffer">The <see cref="FrameBuffer"/> to draw.</param> 250 + /// <param name="vertexQuad">The destination vertices.</param> 251 + /// <param name="drawColour">The colour to draw the <paramref name="frameBuffer"/> with.</param> 252 + /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 253 + /// <param name="inflationPercentage">The percentage amount that the frame buffer area should be inflated.</param> 254 + /// <param name="blendRangeOverride">The range over which the edges of the frame buffer should be blended.</param> 255 + protected void DrawFrameBuffer(FrameBuffer frameBuffer, Quad vertexQuad, ColourInfo drawColour, Action<TexturedVertex2D> vertexAction = null, 256 + Vector2? inflationPercentage = null, Vector2? blendRangeOverride = null) 257 + { 258 + // The strange Y coordinate and Height are a result of OpenGL coordinate systems having Y grow upwards and not downwards. 259 + RectangleF textureRect = new RectangleF(0, frameBuffer.Texture.Height, frameBuffer.Texture.Width, -frameBuffer.Texture.Height); 260 + 261 + if (frameBuffer.Texture.Bind()) 262 + DrawQuad(frameBuffer.Texture, vertexQuad, drawColour, textureRect, vertexAction, inflationPercentage, blendRangeOverride); 72 263 } 73 264 74 265 /// <summary>
+24
osu.Framework/Graphics/Drawable.cs
··· 592 592 get 593 593 { 594 594 Vector2 offset = Vector2.Zero; 595 + 595 596 if (Parent != null && RelativePositionAxes != Axes.None) 596 597 { 597 598 offset = Parent.RelativeChildOffset; ··· 844 845 private void updateBypassAutoSizeAxes() 845 846 { 846 847 var value = RelativePositionAxes | RelativeSizeAxes | bypassAutoSizeAdditionalAxes; 848 + 847 849 if (bypassAutoSizeAxes != value) 848 850 { 849 851 var changedAxes = bypassAutoSizeAxes ^ value; ··· 1406 1408 protected InputManager GetContainingInputManager() 1407 1409 { 1408 1410 Drawable search = Parent; 1411 + 1409 1412 while (search != null) 1410 1413 { 1411 1414 if (search is InputManager test) return test; ··· 1717 1720 internal virtual DrawNode GenerateDrawNodeSubtree(ulong frame, int treeIndex, bool forceNewDrawNode) 1718 1721 { 1719 1722 DrawNode node = drawNodes[treeIndex]; 1723 + 1720 1724 if (node == null || forceNewDrawNode) 1721 1725 { 1722 1726 drawNodes[treeIndex] = node = CreateDrawNode(); ··· 1836 1840 { 1837 1841 case MouseMoveEvent mouseMove: 1838 1842 return OnMouseMove(mouseMove); 1843 + 1839 1844 case HoverEvent hover: 1840 1845 return OnHover(hover); 1846 + 1841 1847 case HoverLostEvent hoverLost: 1842 1848 OnHoverLost(hoverLost); 1843 1849 return false; 1850 + 1844 1851 case MouseDownEvent mouseDown: 1845 1852 return OnMouseDown(mouseDown); 1853 + 1846 1854 case MouseUpEvent mouseUp: 1847 1855 return OnMouseUp(mouseUp); 1856 + 1848 1857 case ClickEvent click: 1849 1858 return OnClick(click); 1859 + 1850 1860 case DoubleClickEvent doubleClick: 1851 1861 return OnDoubleClick(doubleClick); 1862 + 1852 1863 case DragStartEvent dragStart: 1853 1864 return OnDragStart(dragStart); 1865 + 1854 1866 case DragEvent drag: 1855 1867 return OnDrag(drag); 1868 + 1856 1869 case DragEndEvent dragEnd: 1857 1870 return OnDragEnd(dragEnd); 1871 + 1858 1872 case ScrollEvent scroll: 1859 1873 return OnScroll(scroll); 1874 + 1860 1875 case FocusEvent focus: 1861 1876 OnFocus(focus); 1862 1877 return false; 1878 + 1863 1879 case FocusLostEvent focusLost: 1864 1880 OnFocusLost(focusLost); 1865 1881 return false; 1882 + 1866 1883 case KeyDownEvent keyDown: 1867 1884 return OnKeyDown(keyDown); 1885 + 1868 1886 case KeyUpEvent keyUp: 1869 1887 return OnKeyUp(keyUp); 1888 + 1870 1889 case JoystickPressEvent joystickPress: 1871 1890 return OnJoystickPress(joystickPress); 1891 + 1872 1892 case JoystickReleaseEvent joystickRelease: 1873 1893 return OnJoystickRelease(joystickRelease); 1894 + 1874 1895 default: 1875 1896 return false; 1876 1897 } ··· 2007 2028 private static bool get(Drawable drawable, ConcurrentDictionary<Type, bool> cache, bool positional) 2008 2029 { 2009 2030 var type = drawable.GetType(); 2031 + 2010 2032 if (!cache.TryGetValue(type, out var value)) 2011 2033 { 2012 2034 value = compute(type, positional); ··· 2019 2041 private static bool compute(Type type, bool positional) 2020 2042 { 2021 2043 var inputMethods = positional ? positional_input_methods : non_positional_input_methods; 2044 + 2022 2045 foreach (var inputMethod in inputMethods) 2023 2046 { 2024 2047 // check for any input method overrides which are at a higher level than drawable. ··· 2031 2054 } 2032 2055 2033 2056 var inputInterfaces = positional ? positional_input_interfaces : non_positional_input_interfaces; 2057 + 2034 2058 foreach (var inputInterface in inputInterfaces) 2035 2059 { 2036 2060 // check if this type implements any interface which requires a drawable to handle input.
+12 -3
osu.Framework/Graphics/Drawable_ProxyDrawable.cs
··· 76 76 { 77 77 } 78 78 79 + internal override void DrawOpaqueInteriorSubTree(DepthValue depthValue, Action<TexturedVertex2D> vertexAction) 80 + => getCurrentFrameSource()?.DrawOpaqueInteriorSubTree(depthValue, vertexAction); 81 + 79 82 public override void Draw(Action<TexturedVertex2D> vertexAction) 83 + => getCurrentFrameSource()?.Draw(vertexAction); 84 + 85 + protected internal override bool CanDrawOpaqueInterior => getCurrentFrameSource()?.CanDrawOpaqueInterior ?? false; 86 + 87 + private DrawNode getCurrentFrameSource() 80 88 { 81 89 var target = Source.originalDrawNodes[DrawNodeIndex]; 90 + 82 91 if (target == null) 83 - return; 92 + return null; 84 93 85 94 if (Source.drawNodeValidationIds[DrawNodeIndex] != FrameCount) 86 - return; 95 + return null; 87 96 88 - target.Draw(vertexAction); 97 + return target; 89 98 } 90 99 } 91 100 }
+30
osu.Framework/Graphics/IBufferedDrawable.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using osu.Framework.Graphics.OpenGL.Buffers; 5 + using osuTK.Graphics; 6 + 7 + namespace osu.Framework.Graphics 8 + { 9 + /// <summary> 10 + /// Interface for <see cref="Drawable"/>s which can be drawn by a <see cref="BufferedDrawNode"/>. 11 + /// </summary> 12 + public interface IBufferedDrawable : ITexturedShaderDrawable 13 + { 14 + /// <summary> 15 + /// The background colour of the <see cref="FrameBuffer"/>s. 16 + /// Visually changes the colour which rendered alpha is blended against. 17 + /// </summary> 18 + /// <remarks> 19 + /// This should generally be transparent-black or transparent-white, but can also be used to 20 + /// colourise the background colour of the <see cref="FrameBuffer"/> with non-transparent colours. 21 + /// </remarks> 22 + Color4 BackgroundColour { get; } 23 + 24 + /// <summary> 25 + /// The colour with which the <see cref="FrameBuffer"/>s are rendered to the screen. 26 + /// A null value implies the <see cref="FrameBuffer"/>s should be drawn as they are. 27 + /// </summary> 28 + DrawColourInfo? FrameBufferDrawColour { get; } 29 + } 30 + }
+18 -4
osu.Framework/Graphics/Lines/Path.cs
··· 8 8 using osu.Framework.Allocation; 9 9 using System.Collections.Generic; 10 10 using osu.Framework.Caching; 11 + using osuTK.Graphics; 12 + using osuTK.Graphics.ES30; 11 13 12 14 namespace osu.Framework.Graphics.Lines 13 15 { 14 - public partial class Path : Drawable, ITexturedShaderDrawable 16 + public partial class Path : Drawable, IBufferedDrawable 15 17 { 16 18 public IShader RoundedTextureShader { get; private set; } 17 19 public IShader TextureShader { get; private set; } 20 + private IShader pathShader; 18 21 19 22 [BackgroundDependencyLoader] 20 23 private void load(ShaderManager shaders) 21 24 { 22 - RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE_ROUNDED); 23 - TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE); 25 + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); 26 + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); 27 + pathShader = shaders.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE); 24 28 } 25 29 26 30 private readonly List<Vector2> vertices = new List<Vector2>(); ··· 174 178 } 175 179 } 176 180 177 - protected override DrawNode CreateDrawNode() => new PathDrawNode(this); 181 + public DrawColourInfo? FrameBufferDrawColour => base.DrawColourInfo; 182 + 183 + // The path should not receive the true colour to avoid colour doubling when the frame-buffer is rendered to the back-buffer. 184 + // Removal of blending allows for correct blending between the wedges of the path. 185 + public override DrawColourInfo DrawColourInfo => new DrawColourInfo(Color4.White, new BlendingInfo(BlendingMode.None)); 186 + 187 + public Color4 BackgroundColour => new Color4(0, 0, 0, 0); 188 + 189 + private readonly BufferedDrawNodeSharedData sharedData = new BufferedDrawNodeSharedData(); 190 + 191 + protected override DrawNode CreateDrawNode() => new BufferedDrawNode(this, new PathDrawNode(this), sharedData, new[] { RenderbufferInternalFormat.DepthComponent16 }); 178 192 } 179 193 }
+6 -3
osu.Framework/Graphics/Lines/Path_DrawNode.cs
··· 12 12 using osu.Framework.Graphics.OpenGL.Vertices; 13 13 using osuTK.Graphics; 14 14 using osu.Framework.Graphics.Colour; 15 + using osu.Framework.Graphics.Shaders; 15 16 16 17 namespace osu.Framework.Graphics.Lines 17 18 { 18 19 public partial class Path 19 20 { 20 - private class PathDrawNode : TexturedShaderDrawNode 21 + private class PathDrawNode : DrawNode 21 22 { 22 23 public const int MAX_RES = 24; 23 24 ··· 28 29 private Texture texture; 29 30 private Vector2 drawSize; 30 31 private float radius; 32 + private IShader pathShader; 31 33 32 34 // We multiply the size param by 3 such that the amount of vertices is a multiple of the amount of vertices 33 35 // per primitive (triangles in this case). Otherwise overflowing the batch will result in wrong ··· 50 52 texture = Source.Texture; 51 53 drawSize = Source.DrawSize; 52 54 radius = Source.PathRadius; 55 + pathShader = Source.pathShader; 53 56 } 54 57 55 58 private Vector2 pointOnCircle(float angle) => new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)); ··· 207 210 208 211 GLWrapper.PushDepthInfo(DepthInfo.Default); 209 212 210 - Shader.Bind(); 213 + pathShader.Bind(); 211 214 212 215 texture.TextureGL.WrapMode = TextureWrapMode.ClampToEdge; 213 216 texture.TextureGL.Bind(); 214 217 215 218 updateVertexBuffer(); 216 219 217 - Shader.Unbind(); 220 + pathShader.Unbind(); 218 221 219 222 GLWrapper.PopDepthInfo(); 220 223 }
+1
osu.Framework/Graphics/OpenGL/Buffers/FrameBuffer.cs
··· 110 110 public void Bind() 111 111 { 112 112 GLWrapper.BindFrameBuffer(frameBuffer); 113 + 113 114 foreach (var r in attachedRenderBuffers) 114 115 { 115 116 r.Size = Size;
+1 -1
osu.Framework/Graphics/OpenGL/Buffers/LinearVertexBuffer.cs
··· 26 26 { 27 27 private readonly int amountVertices; 28 28 29 - public LinearVertexBuffer(int amountVertices, PrimitiveType type, BufferUsageHint usage) 29 + internal LinearVertexBuffer(int amountVertices, PrimitiveType type, BufferUsageHint usage) 30 30 : base(amountVertices, usage) 31 31 { 32 32 this.amountVertices = amountVertices;
+2 -1
osu.Framework/Graphics/OpenGL/Buffers/QuadVertexBuffer.cs
··· 23 23 { 24 24 private readonly int amountQuads; 25 25 26 - public QuadVertexBuffer(int amountQuads, BufferUsageHint usage) 26 + internal QuadVertexBuffer(int amountQuads, BufferUsageHint usage) 27 27 : base(amountQuads * 4, usage) 28 28 { 29 29 this.amountQuads = amountQuads; ··· 34 34 base.Initialise(); 35 35 36 36 int amountIndices = amountQuads * 6; 37 + 37 38 if (amountIndices > QuadIndexData.MaxAmountIndices) 38 39 { 39 40 ushort[] indices = new ushort[amountIndices];
+2
osu.Framework/Graphics/OpenGL/Buffers/RenderBuffer.cs
··· 93 93 case RenderbufferInternalFormat.DepthComponent16: 94 94 GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, RenderbufferTarget.Renderbuffer, info.RenderBufferID); 95 95 break; 96 + 96 97 case RenderbufferInternalFormat.Rgb565: 97 98 case RenderbufferInternalFormat.Rgb5A1: 98 99 case RenderbufferInternalFormat.Rgba4: 99 100 GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, RenderbufferTarget.Renderbuffer, info.RenderBufferID); 100 101 break; 102 + 101 103 case RenderbufferInternalFormat.StencilIndex8: 102 104 GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, RenderbufferTarget.Renderbuffer, info.RenderBufferID); 103 105 break;
+30 -9
osu.Framework/Graphics/OpenGL/Buffers/VertexBuffer.cs
··· 12 12 public abstract class VertexBuffer<T> : IDisposable 13 13 where T : struct, IEquatable<T>, IVertex 14 14 { 15 - protected static readonly int STRIDE = VertexUtils<T>.STRIDE; 15 + protected static readonly int STRIDE = VertexUtils<DepthWrappingVertex<T>>.STRIDE; 16 16 17 - public readonly T[] Vertices; 17 + private readonly DepthWrappingVertex<T>[] vertices; 18 18 19 19 private readonly BufferUsageHint usage; 20 20 ··· 25 25 { 26 26 this.usage = usage; 27 27 28 - Vertices = new T[amountVertices]; 28 + vertices = new DepthWrappingVertex<T>[amountVertices]; 29 + } 30 + 31 + /// <summary> 32 + /// Sets the vertex at a specific index of this <see cref="VertexBuffer{T}"/>. 33 + /// </summary> 34 + /// <param name="vertexIndex">The index of the vertex.</param> 35 + /// <param name="vertex">The vertex.</param> 36 + /// <returns>Whether the vertex changed.</returns> 37 + public bool SetVertex(int vertexIndex, T vertex) 38 + { 39 + bool isNewVertex = !vertices[vertexIndex].Equals(vertex) || vertices[vertexIndex].BackbufferDrawDepth != GLWrapper.BackbufferDrawDepth; 40 + 41 + vertices[vertexIndex].Vertex = vertex; 42 + vertices[vertexIndex].BackbufferDrawDepth = GLWrapper.BackbufferDrawDepth; 43 + 44 + return isNewVertex; 29 45 } 46 + 47 + /// <summary> 48 + /// Gets the number of vertices in this <see cref="VertexBuffer{T}"/>. 49 + /// </summary> 50 + public int Size => vertices.Length; 30 51 31 52 /// <summary> 32 53 /// Initialises this <see cref="VertexBuffer{T}"/>. Guaranteed to be run on the draw thread. ··· 38 59 GL.GenBuffers(1, out vboId); 39 60 40 61 if (GLWrapper.BindBuffer(BufferTarget.ArrayBuffer, vboId)) 41 - VertexUtils<T>.Bind(); 62 + VertexUtils<DepthWrappingVertex<T>>.Bind(); 42 63 43 - GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(Vertices.Length * STRIDE), IntPtr.Zero, usage); 64 + GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(vertices.Length * STRIDE), IntPtr.Zero, usage); 44 65 } 45 66 46 67 ~VertexBuffer() ··· 82 103 } 83 104 84 105 if (GLWrapper.BindBuffer(BufferTarget.ArrayBuffer, vboId)) 85 - VertexUtils<T>.Bind(); 106 + VertexUtils<DepthWrappingVertex<T>>.Bind(); 86 107 } 87 108 88 109 public virtual void Unbind() ··· 97 118 98 119 public void Draw() 99 120 { 100 - DrawRange(0, Vertices.Length); 121 + DrawRange(0, vertices.Length); 101 122 } 102 123 103 124 public void DrawRange(int startIndex, int endIndex) ··· 112 133 113 134 public void Update() 114 135 { 115 - UpdateRange(0, Vertices.Length); 136 + UpdateRange(0, vertices.Length); 116 137 } 117 138 118 139 public void UpdateRange(int startIndex, int endIndex) ··· 120 141 Bind(false); 121 142 122 143 int amountVertices = endIndex - startIndex; 123 - GL.BufferSubData(BufferTarget.ArrayBuffer, (IntPtr)(startIndex * STRIDE), (IntPtr)(amountVertices * STRIDE), ref Vertices[startIndex]); 144 + GL.BufferSubData(BufferTarget.ArrayBuffer, (IntPtr)(startIndex * STRIDE), (IntPtr)(amountVertices * STRIDE), ref vertices[startIndex]); 124 145 125 146 Unbind(); 126 147
+56
osu.Framework/Graphics/OpenGL/DepthValue.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System.Runtime.CompilerServices; 5 + 6 + namespace osu.Framework.Graphics.OpenGL 7 + { 8 + /// <summary> 9 + /// The depth value used to draw 2D objects to the screen. 10 + /// Starts at -1f and increments to 1f for each <see cref="Drawable"/> which draws a opaque interior through <see cref="DrawNode.DrawOpaqueInterior"/>. 11 + /// </summary> 12 + public class DepthValue 13 + { 14 + /// <summary> 15 + /// A safe value, such that rounding issues don't occur within 16-bit float precision. 16 + /// </summary> 17 + private const float increment = 0.001f; 18 + 19 + /// <summary> 20 + /// Calculated as (1 - (-1)) / increment - 1. 21 + /// -1 is subtracted since a depth of 1.0f conflicts with the default backbuffer clear value. 22 + /// </summary> 23 + private const int max_count = 1999; 24 + 25 + private float depth = -1; 26 + private int count; 27 + 28 + /// <summary> 29 + /// Increments the depth value. 30 + /// </summary> 31 + /// <returns>The post-incremented depth value.</returns> 32 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 33 + internal float Increment() 34 + { 35 + if (count == max_count) 36 + return depth; 37 + 38 + depth += increment; 39 + count++; 40 + 41 + return depth; 42 + } 43 + 44 + /// <summary> 45 + /// Whether the depth value can be incremented. 46 + /// </summary> 47 + internal bool CanIncrement 48 + { 49 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 50 + get => count < max_count; 51 + } 52 + 53 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 54 + public static implicit operator float(DepthValue d) => d.depth; 55 + } 56 + }
+31 -2
osu.Framework/Graphics/OpenGL/GLWrapper.cs
··· 17 17 using osu.Framework.MathUtils; 18 18 using osu.Framework.Graphics.Primitives; 19 19 using osu.Framework.Graphics.Colour; 20 + using osu.Framework.Graphics.OpenGL.Buffers; 20 21 using osu.Framework.Platform; 22 + using GameWindow = osu.Framework.Platform.GameWindow; 21 23 22 24 namespace osu.Framework.Graphics.OpenGL 23 25 { ··· 34 36 public static RectangleF Ortho { get; private set; } 35 37 public static Matrix4 ProjectionMatrix { get; private set; } 36 38 public static DepthInfo CurrentDepthInfo { get; private set; } 39 + 40 + public static float BackbufferDrawDepth { get; private set; } 37 41 38 42 public static bool UsingBackbuffer => frame_buffer_stack.Peek() == DefaultFrameBuffer; 39 43 ··· 65 69 { 66 70 if (IsInitialized) return; 67 71 68 - isEmbedded = host.Window.IsEmbedded; 72 + if (host.Window is GameWindow win) 73 + isEmbedded = win.IsEmbedded; 69 74 70 75 GLWrapper.host = new WeakReference<GameHost>(host); 71 76 reset_scheduler.SetCurrentThread(); ··· 141 146 AlphaExponent = 1, 142 147 }, true); 143 148 144 - PushDepthInfo(new DepthInfo(false)); 149 + PushDepthInfo(DepthInfo.Default); 145 150 Clear(ClearInfo.Default); 146 151 } 147 152 ··· 149 154 150 155 public static void Clear(ClearInfo clearInfo) 151 156 { 157 + PushDepthInfo(new DepthInfo(writeDepth: true)); 158 + 152 159 if (clearInfo.Colour != currentClearInfo.Colour) 153 160 GL.ClearColor(clearInfo.Colour); 154 161 ··· 174 181 GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit); 175 182 176 183 currentClearInfo = clearInfo; 184 + 185 + PopDepthInfo(); 177 186 } 178 187 179 188 /// <summary> ··· 440 449 GlobalPropertyManager.Set(GlobalProperty.CornerRadius, maskingInfo.CornerRadius); 441 450 442 451 GlobalPropertyManager.Set(GlobalProperty.BorderThickness, maskingInfo.BorderThickness / maskingInfo.BlendRange); 452 + 443 453 if (maskingInfo.BorderThickness > 0) 444 454 { 445 455 GlobalPropertyManager.Set(GlobalProperty.BorderColour, new Vector4( ··· 575 585 } 576 586 577 587 /// <summary> 588 + /// Sets the current draw depth. 589 + /// The draw depth is written to every vertex added to <see cref="VertexBuffer{T}"/>s. 590 + /// </summary> 591 + /// <param name="drawDepth">The draw depth.</param> 592 + internal static void SetDrawDepth(float drawDepth) => BackbufferDrawDepth = drawDepth; 593 + 594 + /// <summary> 578 595 /// Binds a framebuffer. 579 596 /// </summary> 580 597 /// <param name="frameBuffer">The framebuffer to bind.</param> ··· 590 607 { 591 608 FlushCurrentBatch(); 592 609 GL.BindFramebuffer(FramebufferTarget.Framebuffer, frameBuffer); 610 + GlobalPropertyManager.Set(GlobalProperty.BackbufferDraw, UsingBackbuffer); 593 611 } 594 612 595 613 GlobalPropertyManager.Set(GlobalProperty.GammaCorrection, UsingBackbuffer); ··· 611 629 FlushCurrentBatch(); 612 630 GL.BindFramebuffer(FramebufferTarget.Framebuffer, frame_buffer_stack.Peek()); 613 631 632 + GlobalPropertyManager.Set(GlobalProperty.BackbufferDraw, UsingBackbuffer); 614 633 GlobalPropertyManager.Set(GlobalProperty.GammaCorrection, UsingBackbuffer); 615 634 } 616 635 ··· 672 691 case IUniformWithValue<bool> b: 673 692 GL.Uniform1(uniform.Location, b.GetValue() ? 1 : 0); 674 693 break; 694 + 675 695 case IUniformWithValue<int> i: 676 696 GL.Uniform1(uniform.Location, i.GetValue()); 677 697 break; 698 + 678 699 case IUniformWithValue<float> f: 679 700 GL.Uniform1(uniform.Location, f.GetValue()); 680 701 break; 702 + 681 703 case IUniformWithValue<Vector2> v2: 682 704 GL.Uniform2(uniform.Location, ref v2.GetValueByRef()); 683 705 break; 706 + 684 707 case IUniformWithValue<Vector3> v3: 685 708 GL.Uniform3(uniform.Location, ref v3.GetValueByRef()); 686 709 break; 710 + 687 711 case IUniformWithValue<Vector4> v4: 688 712 GL.Uniform4(uniform.Location, ref v4.GetValueByRef()); 689 713 break; 714 + 690 715 case IUniformWithValue<Matrix2> m2: 691 716 GL.UniformMatrix2(uniform.Location, false, ref m2.GetValueByRef()); 692 717 break; 718 + 693 719 case IUniformWithValue<Matrix3> m3: 694 720 GL.UniformMatrix3(uniform.Location, false, ref m3.GetValueByRef()); 695 721 break; 722 + 696 723 case IUniformWithValue<Matrix4> m4: 697 724 GL.UniformMatrix4(uniform.Location, false, ref m4.GetValueByRef()); 698 725 break; ··· 704 731 { 705 732 public RectangleI ScreenSpaceAABB; 706 733 public RectangleF MaskingRect; 734 + 735 + public Quad ConservativeScreenSpaceQuad; 707 736 708 737 /// <summary> 709 738 /// This matrix transforms screen space coordinates to masking space (likely the parent
+18 -4
osu.Framework/Graphics/OpenGL/Textures/TextureGL.cs
··· 3 3 4 4 using System; 5 5 using System.Threading; 6 + using osu.Framework.Graphics.Batches; 6 7 using osu.Framework.Graphics.Primitives; 7 8 using osuTK.Graphics.ES30; 8 9 using osuTK; ··· 71 72 public abstract RectangleF GetTextureRect(RectangleF? textureRect); 72 73 73 74 /// <summary> 74 - /// Blit a triangle to OpenGL display with specified parameters. 75 + /// Draws a triangle to the screen. 75 76 /// </summary> 76 - public abstract void DrawTriangle(Triangle vertexTriangle, RectangleF? textureRect, ColourInfo drawColour, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null); 77 + /// <param name="vertexTriangle">The triangle to draw.</param> 78 + /// <param name="drawColour">The vertex colour.</param> 79 + /// <param name="textureRect">The texture rectangle.</param> 80 + /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 81 + /// <param name="inflationPercentage">The percentage amount that <see cref="textureRect"/> should be inflated.</param> 82 + internal abstract void DrawTriangle(Triangle vertexTriangle, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, 83 + Vector2? inflationPercentage = null); 77 84 78 85 /// <summary> 79 - /// Blit a quad to OpenGL display with specified parameters. 86 + /// Draws a quad to the screen. 80 87 /// </summary> 81 - public abstract void DrawQuad(Quad vertexQuad, RectangleF? textureRect, ColourInfo drawColour, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null, Vector2? blendRangeOverride = null); 88 + /// <param name="vertexQuad">The quad to draw.</param> 89 + /// <param name="drawColour">The vertex colour.</param> 90 + /// <param name="textureRect">The texture rectangle.</param> 91 + /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 92 + /// <param name="inflationPercentage">The percentage amount that <see cref="textureRect"/> should be inflated.</param> 93 + /// <param name="blendRangeOverride">The range over which the edges of the <see cref="textureRect"/> should be blended.</param> 94 + internal abstract void DrawQuad(Quad vertexQuad, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null, 95 + Vector2? blendRangeOverride = null); 82 96 83 97 /// <summary> 84 98 /// Bind as active texture.
+4 -3
osu.Framework/Graphics/OpenGL/Textures/TextureGLSingle.cs
··· 141 141 return texRect; 142 142 } 143 143 144 - public override void DrawTriangle(Triangle vertexTriangle, RectangleF? textureRect, ColourInfo drawColour, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null) 144 + internal override void DrawTriangle(Triangle vertexTriangle, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, 145 + Vector2? inflationPercentage = null) 145 146 { 146 147 if (!Available) 147 148 throw new ObjectDisposedException(ToString(), "Can not draw a triangle with a disposed texture."); ··· 215 216 FrameStatistics.Add(StatisticsCounterType.Pixels, (long)vertexTriangle.ConservativeArea); 216 217 } 217 218 218 - public override void DrawQuad(Quad vertexQuad, RectangleF? textureRect, ColourInfo drawColour, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null, 219 - Vector2? blendRangeOverride = null) 219 + internal override void DrawQuad(Quad vertexQuad, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null, 220 + Vector2? blendRangeOverride = null) 220 221 { 221 222 if (!Available) 222 223 throw new ObjectDisposedException(ToString(), "Can not draw a quad with a disposed texture.");
+6 -4
osu.Framework/Graphics/OpenGL/Textures/TextureGLSub.cs
··· 61 61 62 62 public override RectangleF GetTextureRect(RectangleF? textureRect) => parent.GetTextureRect(boundsInParent(textureRect)); 63 63 64 - public override void DrawTriangle(Triangle vertexTriangle, RectangleF? textureRect, ColourInfo drawColour, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null) 64 + internal override void DrawTriangle(Triangle vertexTriangle, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, 65 + Vector2? inflationPercentage = null) 65 66 { 66 - parent.DrawTriangle(vertexTriangle, boundsInParent(textureRect), drawColour, vertexAction, inflationPercentage); 67 + parent.DrawTriangle(vertexTriangle, drawColour, boundsInParent(textureRect), vertexAction, inflationPercentage); 67 68 } 68 69 69 - public override void DrawQuad(Quad vertexQuad, RectangleF? textureRect, ColourInfo drawColour, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null, Vector2? blendRangeOverride = null) 70 + internal override void DrawQuad(Quad vertexQuad, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null, 71 + Vector2? blendRangeOverride = null) 70 72 { 71 - parent.DrawQuad(vertexQuad, boundsInParent(textureRect), drawColour, vertexAction, inflationPercentage, blendRangeOverride); 73 + parent.DrawQuad(vertexQuad, drawColour, boundsInParent(textureRect), vertexAction, inflationPercentage: inflationPercentage, blendRangeOverride: blendRangeOverride); 72 74 } 73 75 74 76 internal override bool Upload() => false;
+26
osu.Framework/Graphics/OpenGL/Vertices/DepthWrappingVertex.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using System.Runtime.InteropServices; 6 + using osuTK.Graphics.ES30; 7 + 8 + namespace osu.Framework.Graphics.OpenGL.Vertices 9 + { 10 + [StructLayout(LayoutKind.Sequential)] 11 + internal struct DepthWrappingVertex<TVertex> : IVertex, IEquatable<DepthWrappingVertex<TVertex>>, IEquatable<TVertex> 12 + where TVertex : IVertex, IEquatable<TVertex> 13 + { 14 + public TVertex Vertex; 15 + 16 + [VertexMember(1, VertexAttribPointerType.Float)] 17 + public float BackbufferDrawDepth; 18 + 19 + public bool Equals(DepthWrappingVertex<TVertex> other) 20 + => Vertex.Equals(other.Vertex) 21 + && BackbufferDrawDepth.Equals(other.BackbufferDrawDepth); 22 + 23 + public bool Equals(TVertex other) 24 + => Vertex.Equals(other); 25 + } 26 + }
+23 -9
osu.Framework/Graphics/OpenGL/Vertices/VertexUtils.cs
··· 3 3 4 4 // ReSharper disable StaticMemberInGenericType 5 5 6 + using System; 6 7 using System.Collections.Generic; 7 - using System.Linq; 8 8 using System.Reflection; 9 9 using System.Runtime.InteropServices; 10 - using osuTK; 11 10 using osuTK.Graphics.ES30; 12 11 13 12 namespace osu.Framework.Graphics.OpenGL.Vertices ··· 21 20 /// <summary> 22 21 /// The stride of the vertex of type <see cref="T"/>. 23 22 /// </summary> 24 - public static readonly int STRIDE = BlittableValueType.StrideOf(default(T)); 23 + public static readonly int STRIDE = Marshal.SizeOf(default(T)); 25 24 26 25 private static readonly List<VertexMemberAttribute> attributes = new List<VertexMemberAttribute>(); 27 26 private static int amountEnabledAttributes; 28 27 29 28 static VertexUtils() 30 29 { 31 - // Use reflection to retrieve the members attached with a VertexMemberAttribute 32 - foreach (FieldInfo field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(t => t.IsDefined(typeof(VertexMemberAttribute), true))) 30 + addAttributesRecursive(typeof(T), 0); 31 + } 32 + 33 + private static void addAttributesRecursive(Type type, int currentOffset) 34 + { 35 + foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) 33 36 { 34 - var attrib = (VertexMemberAttribute)field.GetCustomAttribute(typeof(VertexMemberAttribute)); 37 + int fieldOffset = currentOffset + Marshal.OffsetOf(type, field.Name).ToInt32(); 35 38 36 - // Because this is an un-seen vertex, the attribute locations are unknown, but they're needed for marshalling 37 - attrib.Offset = Marshal.OffsetOf(typeof(T), field.Name); 39 + if (typeof(IVertex).IsAssignableFrom(field.FieldType)) 40 + { 41 + // Vertices may contain others, but the attributes of contained vertices belong to the parent when marshalled, so they are recursively added for their parent 42 + // Their field offsets must be adjusted to reflect the position of the child attribute in the parent vertex 43 + addAttributesRecursive(field.FieldType, fieldOffset); 44 + } 45 + else if (field.IsDefined(typeof(VertexMemberAttribute), true)) 46 + { 47 + var attrib = (VertexMemberAttribute)field.GetCustomAttribute(typeof(VertexMemberAttribute)); 38 48 39 - attributes.Add(attrib); 49 + // Because this is an un-seen vertex, the attribute locations are unknown, but they're needed for marshalling 50 + attrib.Offset = new IntPtr(fieldOffset); 51 + 52 + attributes.Add(attrib); 53 + } 40 54 } 41 55 } 42 56
+20 -6
osu.Framework/Graphics/Performance/FrameStatisticsDisplay.cs
··· 81 81 labelText.Origin = Anchor.CentreRight; 82 82 labelText.Rotation = 0; 83 83 break; 84 + 84 85 case FrameStatisticsMode.Full: 85 86 mainContainer.AutoSizeAxes = Axes.None; 86 87 mainContainer.Size = new Vector2(WIDTH, HEIGHT); ··· 99 100 } 100 101 } 101 102 102 - public FrameStatisticsDisplay(GameThread thread, TextureAtlas atlas) 103 + public FrameStatisticsDisplay(GameThread thread) 103 104 { 104 105 Name = thread.Name; 105 106 monitor = thread.Monitor; ··· 143 144 { 144 145 counterBarBackground = new Sprite 145 146 { 146 - Texture = new Texture(atlas.Add(1, HEIGHT)), 147 + Texture = new Texture(1, HEIGHT), 147 148 RelativeSizeAxes = Axes.Both, 148 149 Size = new Vector2(1, 1), 149 150 }, ··· 177 178 RelativeSizeAxes = Axes.Both, 178 179 Children = timeBars = new[] 179 180 { 180 - new TimeBar(atlas), 181 - new TimeBar(atlas), 181 + new TimeBar(), 182 + new TimeBar(), 182 183 }, 183 184 }, 184 185 frameTimeDisplay = new FrameTimeDisplay(monitor.Clock) ··· 312 313 case Key.ControlLeft: 313 314 Expanded = true; 314 315 break; 316 + 315 317 case Key.ShiftLeft: 316 318 Running = false; 317 319 break; ··· 327 329 case Key.ControlLeft: 328 330 Expanded = false; 329 331 break; 332 + 330 333 case Key.ShiftLeft: 331 334 Running = true; 332 335 break; ··· 403 406 { 404 407 default: 405 408 return Color4.YellowGreen; 409 + 406 410 case PerformanceCollectionType.SwapBuffer: 407 411 return Color4.Red; 408 412 #if DEBUG ··· 411 415 #endif 412 416 case PerformanceCollectionType.Sleep: 413 417 return Color4.DarkBlue; 418 + 414 419 case PerformanceCollectionType.Scheduler: 415 420 return Color4.HotPink; 421 + 416 422 case PerformanceCollectionType.WndProc: 417 423 return Color4.GhostWhite; 424 + 418 425 case PerformanceCollectionType.GLReset: 419 426 return Color4.Cyan; 420 427 } ··· 428 435 { 429 436 default: 430 437 return Color4.BlueViolet; 438 + 431 439 case 1: 432 440 return Color4.YellowGreen; 441 + 433 442 case 2: 434 443 return Color4.HotPink; 444 + 435 445 case 3: 436 446 return Color4.Red; 447 + 437 448 case 4: 438 449 return Color4.Cyan; 450 + 439 451 case 5: 440 452 return Color4.Yellow; 453 + 441 454 case 6: 442 455 return Color4.SkyBlue; 443 456 } ··· 466 479 bool acceptableRange = (float)currentHeight / HEIGHT > 1 - monitor.FrameAimTime / visible_ms_range; 467 480 468 481 float brightnessAdjust = 1; 482 + 469 483 if (!frameTimeType.HasValue) 470 484 { 471 485 int step = amountSteps / HEIGHT; ··· 486 500 { 487 501 public readonly Sprite Sprite; 488 502 489 - public TimeBar(TextureAtlas atlas) 503 + public TimeBar() 490 504 { 491 505 Size = new Vector2(WIDTH, HEIGHT); 492 506 Child = Sprite = new Sprite(); 493 507 494 - Sprite.Texture = new Texture(atlas.Add(WIDTH, HEIGHT)); 508 + Sprite.Texture = new Texture(WIDTH, HEIGHT); 495 509 } 496 510 } 497 511
+12 -7
osu.Framework/Graphics/Performance/PerformanceOverlay.cs
··· 3 3 4 4 using System; 5 5 using osu.Framework.Graphics.Containers; 6 - using osu.Framework.Graphics.OpenGL; 7 - using osu.Framework.Graphics.Textures; 8 - using osuTK.Graphics.ES30; 9 6 using osu.Framework.Threading; 10 7 using System.Collections.Generic; 11 8 ··· 13 10 { 14 11 internal class PerformanceOverlay : FillFlowContainer<FrameStatisticsDisplay>, IStateful<FrameStatisticsMode> 15 12 { 13 + private readonly IEnumerable<GameThread> threads; 16 14 private FrameStatisticsMode state; 17 15 18 16 public event Action<FrameStatisticsMode> StateChanged; 19 17 18 + private bool initialised; 19 + 20 20 public FrameStatisticsMode State 21 21 { 22 22 get => state; ··· 31 31 case FrameStatisticsMode.None: 32 32 this.FadeOut(100); 33 33 break; 34 + 34 35 case FrameStatisticsMode.Minimal: 35 36 case FrameStatisticsMode.Full: 37 + if (!initialised) 38 + { 39 + initialised = true; 40 + foreach (GameThread t in threads) 41 + Add(new FrameStatisticsDisplay(t) { State = state }); 42 + } 43 + 36 44 this.FadeIn(100); 37 45 break; 38 46 } ··· 46 54 47 55 public PerformanceOverlay(IEnumerable<GameThread> threads) 48 56 { 57 + this.threads = threads; 49 58 Direction = FillDirection.Vertical; 50 - TextureAtlas atlas = new TextureAtlas(GLWrapper.MaxTextureSize, GLWrapper.MaxTextureSize, true, All.Nearest); 51 - 52 - foreach (GameThread t in threads) 53 - Add(new FrameStatisticsDisplay(t, atlas) { State = state }); 54 59 } 55 60 } 56 61
+11 -7
osu.Framework/Graphics/Primitives/IPolygon.cs
··· 9 9 public interface IPolygon 10 10 { 11 11 /// <summary> 12 - /// The vertices that define the axes spanned by this polygon. 12 + /// The vertices that define the axes spanned by this polygon in screen-space counter-clockwise orientation. 13 13 /// </summary> 14 14 /// <remarks> 15 - /// Must be returned in a clockwise orientation. For best performance, vertices that form colinear edges should not be included. 15 + /// Counter-clockwise orientation in screen-space coordinates is equivalent to a clockwise orientation in standard coordinates. 16 + /// <para> 17 + /// E.g. For the set of vertices { (0, 0), (1, 0), (0, 1), (1, 1) }, a counter-clockwise orientation is { (0, 0), (0, 1), (1, 1), (1, 0) }. 18 + /// </para> 16 19 /// </remarks> 17 - /// <returns> 18 - /// The vertices that define the axes spanned by this polygon. 19 - /// </returns> 20 + /// <returns>The vertices that define the axes spanned by this polygon.</returns> 20 21 ReadOnlySpan<Vector2> GetAxisVertices(); 21 22 22 23 /// <summary> 23 - /// Retrieves the vertices of this polygon. 24 + /// Retrieves the vertices of this polygon in screen-space counter-clockwise orientation. 24 25 /// </summary> 25 26 /// <remarks> 26 - /// Must be returned in a clockwise orientation. 27 + /// Counter-clockwise orientation in screen-space coordinates is equivalent to a clockwise orientation in standard coordinates. 28 + /// <para> 29 + /// E.g. For the set of vertices { (0, 0), (1, 0), (0, 1), (1, 1) }, a counter-clockwise orientation is { (0, 0), (0, 1), (1, 1), (1, 0) }. 30 + /// </para> 27 31 /// </remarks> 28 32 /// <returns>The vertices of this polygon.</returns> 29 33 ReadOnlySpan<Vector2> GetVertices();
+46 -13
osu.Framework/Graphics/Primitives/Line.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; 5 + using System.Runtime.CompilerServices; 5 6 using osu.Framework.MathUtils; 6 7 using osuTK; 7 8 ··· 10 11 /// <summary> 11 12 /// Represents a single line segment. 12 13 /// </summary> 13 - public struct Line 14 + public readonly struct Line 14 15 { 15 16 /// <summary> 16 17 /// Begin point of the line. 17 18 /// </summary> 18 - public Vector2 StartPoint; 19 + public readonly Vector2 StartPoint; 19 20 20 21 /// <summary> 21 22 /// End point of the line. 22 23 /// </summary> 23 - public Vector2 EndPoint; 24 + public readonly Vector2 EndPoint; 24 25 25 26 /// <summary> 26 27 /// The length of the line. ··· 62 63 /// </summary> 63 64 /// <param name="t">A parameter representing the position along the line to compute. 0 yields the start point and 1 yields the end point.</param> 64 65 /// <returns>The position along the line.</returns> 65 - public Vector2 At(float t) => StartPoint + Direction * t; 66 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 67 + public Vector2 At(float t) => new Vector2(StartPoint.X + (EndPoint.X - StartPoint.X) * t, StartPoint.Y + (EndPoint.Y - StartPoint.Y) * t); 66 68 67 69 /// <summary> 68 70 /// Intersects this line with another. ··· 71 73 /// <returns>Whether the two lines intersect and, if so, the distance along this line at which the intersection occurs. 72 74 /// An intersection may occur even if the two lines don't touch, at which point the parameter will be outside the [0, 1] range. 73 75 /// To compute the point of intersection, <see cref="At"/>.</returns> 74 - public (bool success, float distance) IntersectWith(Line other) 76 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 77 + public (bool success, float distance) IntersectWith(in Line other) 75 78 { 76 - Vector2 diff1 = Direction; 77 - Vector2 diff2 = other.Direction; 79 + bool success = TryIntersectWith(other, out var distance); 80 + return (success, distance); 81 + } 82 + 83 + /// <summary> 84 + /// Intersects this line with another. 85 + /// </summary> 86 + /// <param name="other">The line to intersect with.</param> 87 + /// <param name="distance">The distance along this line at which the intersection occurs. To compute the point of intersection, <see cref="At"/>.</param> 88 + /// <returns>Whether the two lines intersect. An intersection may occur even if the two lines don't touch, at which point the parameter will be outside the [0, 1] range.</returns> 89 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 90 + public bool TryIntersectWith(in Line other, out float distance) 91 + { 92 + var startPoint = other.StartPoint; 93 + var endPoint = other.EndPoint; 94 + 95 + return TryIntersectWith(ref startPoint, ref endPoint, out distance); 96 + } 78 97 79 - float denom = diff1.X * diff2.Y - diff1.Y * diff2.X; 98 + /// <summary> 99 + /// Intersects this line with another. 100 + /// </summary> 101 + /// <param name="otherStart">The start point of the other line to intsersect with.</param> 102 + /// <param name="otherEnd">The end point of the other line to intersect with.</param> 103 + /// <param name="distance">The distance along this line at which the intersection occurs. To compute the point of intersection, <see cref="At"/>.</param> 104 + /// <returns>Whether the two lines intersect. An intersection may occur even if the two lines don't touch, at which point the parameter will be outside the [0, 1] range.</returns> 105 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 106 + public bool TryIntersectWith(ref Vector2 otherStart, ref Vector2 otherEnd, out float distance) 107 + { 108 + float otherYDist = otherEnd.Y - otherStart.Y; 109 + float otherXDist = otherEnd.X - otherStart.X; 80 110 81 - if (Precision.AlmostEquals(0, denom)) 82 - return (false, 0); // Co-linear 111 + float denom = (EndPoint.X - StartPoint.X) * otherYDist - (EndPoint.Y - StartPoint.Y) * otherXDist; 83 112 84 - Vector2 d = other.StartPoint - StartPoint; 85 - float t = (d.X * diff2.Y - d.Y * diff2.X) / denom; 113 + if (Precision.AlmostEquals(denom, 0)) 114 + { 115 + distance = 0; 116 + return false; 117 + } 86 118 87 - return (true, t); 119 + distance = ((otherStart.X - StartPoint.X) * otherYDist - (otherStart.Y - StartPoint.Y) * otherXDist) / denom; 120 + return true; 88 121 } 89 122 90 123 /// <summary>
+4 -3
osu.Framework/Graphics/Primitives/Quad.cs
··· 12 12 [StructLayout(LayoutKind.Sequential)] 13 13 public struct Quad : IConvexPolygon, IEquatable<Quad> 14 14 { 15 - // Note: Do not change the order of vertices. 15 + // Note: Do not change the order of vertices. They are ordered in screen-space counter-clockwise fashion. 16 + // See: IPolygon.GetVertices() 16 17 public Vector2 TopLeft; 17 - public Vector2 TopRight; 18 + public Vector2 BottomLeft; 18 19 public Vector2 BottomRight; 19 - public Vector2 BottomLeft; 20 + public Vector2 TopRight; 20 21 21 22 public Quad(Vector2 topLeft, Vector2 topRight, Vector2 bottomLeft, Vector2 bottomRight) 22 23 {
+24
osu.Framework/Graphics/Primitives/SimpleConvexPolygon.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using osuTK; 6 + 7 + namespace osu.Framework.Graphics.Primitives 8 + { 9 + public class SimpleConvexPolygon : IConvexPolygon 10 + { 11 + private readonly Vector2[] vertices; 12 + 13 + public SimpleConvexPolygon(Vector2[] vertices) 14 + { 15 + this.vertices = vertices; 16 + } 17 + 18 + public ReadOnlySpan<Vector2> GetAxisVertices() => vertices; 19 + 20 + public ReadOnlySpan<Vector2> GetVertices() => vertices; 21 + 22 + public int MaxClipVertices => vertices.Length * 2; 23 + } 24 + }
+1
osu.Framework/Graphics/Shaders/GlobalProperty.cs
··· 20 20 DiscardInner, 21 21 InnerCornerRadius, 22 22 GammaCorrection, 23 + BackbufferDraw, 23 24 } 24 25 }
+18 -28
osu.Framework/Graphics/Shaders/GlobalPropertyManager.cs
··· 31 31 global_properties[(int)GlobalProperty.DiscardInner] = new UniformMapping<bool>("g_DiscardInner"); 32 32 global_properties[(int)GlobalProperty.InnerCornerRadius] = new UniformMapping<float>("g_InnerCornerRadius"); 33 33 global_properties[(int)GlobalProperty.GammaCorrection] = new UniformMapping<bool>("g_GammaCorrection"); 34 + 35 + // Backbuffer internals 36 + global_properties[(int)GlobalProperty.BackbufferDraw] = new UniformMapping<bool>("g_BackbufferDraw"); 34 37 } 35 38 36 39 /// <summary> ··· 42 45 public static void Set<T>(GlobalProperty property, T value) 43 46 where T : struct 44 47 { 45 - lock (global_properties) 46 - ((UniformMapping<T>)global_properties[(int)property]).UpdateValue(ref value); 48 + ((UniformMapping<T>)global_properties[(int)property]).UpdateValue(ref value); 47 49 } 48 50 49 51 public static void Register(Shader shader) 50 52 { 51 - lock (global_properties) 53 + // transfer all existing global properties across. 54 + foreach (var global in global_properties) 52 55 { 53 - // transfer all existing global properties across. 54 - foreach (var global in global_properties) 55 - { 56 - if (!shader.Uniforms.TryGetValue(global.Name, out IUniform uniform)) 57 - continue; 56 + if (!shader.Uniforms.TryGetValue(global.Name, out IUniform uniform)) 57 + continue; 58 58 59 - global.LinkShaderUniform(uniform); 60 - } 59 + global.LinkShaderUniform(uniform); 60 + } 61 61 62 - all_shaders.Add(shader); 63 - } 62 + all_shaders.Add(shader); 64 63 } 65 64 66 65 public static void Unregister(Shader shader) 67 66 { 68 - lock (global_properties) 67 + if (!all_shaders.Remove(shader)) return; 68 + 69 + foreach (var global in global_properties) 69 70 { 70 - if (!all_shaders.Remove(shader)) return; 71 + if (!shader.Uniforms.TryGetValue(global.Name, out IUniform uniform)) 72 + continue; 71 73 72 - foreach (var global in global_properties) 73 - { 74 - if (!shader.Uniforms.TryGetValue(global.Name, out IUniform uniform)) 75 - continue; 76 - 77 - global.UnlinkShaderUniform(uniform); 78 - } 74 + global.UnlinkShaderUniform(uniform); 79 75 } 80 76 } 81 77 ··· 84 80 /// </summary> 85 81 /// <param name="name">The name to check</param> 86 82 /// <returns>Whether a global exists.</returns> 87 - public static bool CheckGlobalExists(string name) 88 - { 89 - lock (global_properties) 90 - { 91 - return global_properties.Any(m => m.Name == name); 92 - } 93 - } 83 + public static bool CheckGlobalExists(string name) => global_properties.Any(m => m.Name == name); 94 84 } 95 85 }
+11
osu.Framework/Graphics/Shaders/Shader.cs
··· 47 47 return; 48 48 49 49 programID = GL.CreateProgram(); 50 + 50 51 foreach (ShaderPart p in parts) 51 52 { 52 53 if (!p.Compiled) p.Compile(); ··· 63 64 64 65 Log.AppendLine(string.Format(ShaderPart.BOUNDARY, name)); 65 66 Log.AppendLine($"Linked: {linkResult == 1}"); 67 + 66 68 if (linkResult == 0) 67 69 { 68 70 Log.AppendLine("Log:"); ··· 95 97 } 96 98 97 99 IUniform uniform; 100 + 98 101 switch (type) 99 102 { 100 103 case ActiveUniformType.Bool: 101 104 uniform = createUniform<bool>(uniformName); 102 105 break; 106 + 103 107 case ActiveUniformType.Float: 104 108 uniform = createUniform<float>(uniformName); 105 109 break; 110 + 106 111 case ActiveUniformType.Int: 107 112 uniform = createUniform<int>(uniformName); 108 113 break; 114 + 109 115 case ActiveUniformType.FloatMat3: 110 116 uniform = createUniform<Matrix3>(uniformName); 111 117 break; 118 + 112 119 case ActiveUniformType.FloatMat4: 113 120 uniform = createUniform<Matrix4>(uniformName); 114 121 break; 122 + 115 123 case ActiveUniformType.FloatVec2: 116 124 uniform = createUniform<Vector2>(uniformName); 117 125 break; 126 + 118 127 case ActiveUniformType.FloatVec3: 119 128 uniform = createUniform<Vector3>(uniformName); 120 129 break; 130 + 121 131 case ActiveUniformType.FloatVec4: 122 132 uniform = createUniform<Vector4>(uniformName); 123 133 break; 134 + 124 135 default: 125 136 continue; 126 137 }
+1
osu.Framework/Graphics/Shaders/ShaderManager.cs
··· 31 31 { 32 32 case ShaderType.FragmentShader: 33 33 return @".fs"; 34 + 34 35 case ShaderType.VertexShader: 35 36 return @".vs"; 36 37 }
+18 -3
osu.Framework/Graphics/Shaders/ShaderPart.cs
··· 25 25 26 26 internal ShaderType Type; 27 27 28 + private bool isVertexShader => Type == ShaderType.VertexShader || Type == ShaderType.VertexShaderArb; 29 + 28 30 private int partID = -1; 29 31 30 32 private int lastShaderInputIndex; ··· 43 45 44 46 this.manager = manager; 45 47 46 - shaderCodes.Add(loadFile(data)); 48 + shaderCodes.Add(loadFile(data, true)); 47 49 shaderCodes.RemoveAll(string.IsNullOrEmpty); 48 50 49 51 if (shaderCodes.Count == 0) ··· 52 54 HasCode = true; 53 55 } 54 56 55 - private string loadFile(byte[] bytes) 57 + private string loadFile(byte[] bytes, bool mainFile) 56 58 { 57 59 if (bytes == null) 58 60 return null; ··· 76 78 } 77 79 78 80 Match includeMatch = includeRegex.Match(line); 81 + 79 82 if (includeMatch.Success) 80 83 { 81 84 string includeName = includeMatch.Groups[1].Value.Trim(); ··· 85 88 // if (File.Exists(includeName)) 86 89 // rawData = File.ReadAllBytes(includeName); 87 90 //#endif 88 - code += loadFile(manager.LoadRaw(includeName)) + '\n'; 91 + code += loadFile(manager.LoadRaw(includeName), false) + '\n'; 89 92 } 90 93 else 91 94 code += line + '\n'; ··· 93 96 if (Type == ShaderType.VertexShader || Type == ShaderType.VertexShaderArb) 94 97 { 95 98 Match inputMatch = shaderInputRegex.Match(line); 99 + 96 100 if (inputMatch.Success) 97 101 { 98 102 ShaderInputs.Add(new ShaderInputInfo ··· 104 108 } 105 109 } 106 110 111 + if (mainFile && isVertexShader) 112 + { 113 + string realMainName = "real_main_" + Guid.NewGuid().ToString("N"); 114 + 115 + string backbufferCode = loadFile(manager.LoadRaw("sh_Backbuffer_Internal.h"), false); 116 + 117 + backbufferCode = backbufferCode.Replace("{{ real_main }}", realMainName); 118 + code = Regex.Replace(code, @"void main\((.*)\)", $"void {realMainName}()") + backbufferCode + '\n'; 119 + } 120 + 107 121 return code; 108 122 } 109 123 } ··· 130 144 string compileLog = GL.GetShaderInfoLog(this); 131 145 Log.AppendLine(string.Format('\t' + BOUNDARY, Name)); 132 146 Log.AppendLine($"\tCompiled: {Compiled}"); 147 + 133 148 if (!Compiled) 134 149 { 135 150 Log.AppendLine("\tLog:");
+72 -1
osu.Framework/Graphics/Shapes/Box.cs
··· 1 1 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 + using System; 5 + using osu.Framework.Graphics.OpenGL; 6 + using osu.Framework.Graphics.OpenGL.Vertices; 7 + using osu.Framework.Graphics.Primitives; 4 8 using osu.Framework.Graphics.Sprites; 5 9 using osu.Framework.Graphics.Textures; 10 + using osuTK; 11 + using osuTK.Graphics.ES30; 6 12 7 13 namespace osu.Framework.Graphics.Shapes 8 14 { ··· 11 17 /// </summary> 12 18 public class Box : Sprite 13 19 { 20 + /// <summary> 21 + /// The screen space draw quad of this <see cref="Box"/> free from inflation due to edge smoothing. 22 + /// </summary> 23 + private Quad conservativeScreenSpaceDrawQuad; 24 + 14 25 public Box() 15 26 { 16 - Texture = Texture.WhitePixel; 27 + base.Texture = Texture.WhitePixel; 28 + } 29 + 30 + public override Texture Texture 31 + { 32 + get => base.Texture; 33 + set => throw new InvalidOperationException($"The texture of a {nameof(Box)} cannot be set."); 34 + } 35 + 36 + protected override DrawNode CreateDrawNode() => new BoxDrawNode(this); 37 + 38 + protected override Quad ComputeScreenSpaceDrawQuad() 39 + { 40 + conservativeScreenSpaceDrawQuad = ToScreenSpace(DrawRectangle); 41 + return base.ComputeScreenSpaceDrawQuad(); 42 + } 43 + 44 + protected class BoxDrawNode : SpriteDrawNode 45 + { 46 + protected new Box Source => (Box)base.Source; 47 + 48 + private Quad conservativeScreenSpaceDrawQuad; 49 + private bool hasOpaqueInterior; 50 + 51 + public BoxDrawNode(Box source) 52 + : base(source) 53 + { 54 + } 55 + 56 + public override void ApplyState() 57 + { 58 + base.ApplyState(); 59 + 60 + conservativeScreenSpaceDrawQuad = Source.conservativeScreenSpaceDrawQuad; 61 + 62 + hasOpaqueInterior = DrawColourInfo.Colour.MinAlpha == 1 63 + && DrawColourInfo.Blending.Equals(new BlendingInfo(BlendingMode.Mixture)) 64 + && DrawColourInfo.Colour.HasSingleColour; 65 + } 66 + 67 + protected override void DrawOpaqueInterior(Action<TexturedVertex2D> vertexAction) 68 + { 69 + base.DrawOpaqueInterior(vertexAction); 70 + 71 + TextureShader.Bind(); 72 + Texture.TextureGL.WrapMode = WrapTexture ? TextureWrapMode.Repeat : TextureWrapMode.ClampToEdge; 73 + 74 + if (GLWrapper.IsMaskingActive) 75 + DrawClipped(ref conservativeScreenSpaceDrawQuad, Texture, DrawColourInfo.Colour, vertexAction: vertexAction); 76 + else 77 + { 78 + ReadOnlySpan<Vector2> vertices = conservativeScreenSpaceDrawQuad.GetVertices(); 79 + 80 + for (int i = 2; i < vertices.Length; i++) 81 + DrawTriangle(Texture, new Primitives.Triangle(vertices[0], vertices[i - 1], vertices[i]), DrawColourInfo.Colour, vertexAction: vertexAction); 82 + } 83 + 84 + TextureShader.Unbind(); 85 + } 86 + 87 + protected internal override bool CanDrawOpaqueInterior => Texture?.Available == true && hasOpaqueInterior; 17 88 } 18 89 } 19 90 }
+1 -3
osu.Framework/Graphics/Shapes/Triangle.cs
··· 36 36 37 37 private class TriangleDrawNode : SpriteDrawNode 38 38 { 39 - protected new Triangle Source => (Triangle)base.Source; 40 - 41 39 public TriangleDrawNode(Triangle source) 42 40 : base(source) 43 41 { ··· 45 43 46 44 protected override void Blit(Action<TexturedVertex2D> vertexAction) 47 45 { 48 - Texture.DrawTriangle(toTriangle(ScreenSpaceDrawQuad), DrawColourInfo.Colour, null, null, 46 + DrawTriangle(Texture, toTriangle(ScreenSpaceDrawQuad), DrawColourInfo.Colour, null, null, 49 47 new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height)); 50 48 } 51 49 }
+189
osu.Framework/Graphics/Sprites/BufferedContainerView.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using osu.Framework.Allocation; 6 + using osu.Framework.Graphics.Colour; 7 + using osu.Framework.Graphics.Containers; 8 + using osu.Framework.Graphics.OpenGL; 9 + using osu.Framework.Graphics.OpenGL.Vertices; 10 + using osu.Framework.Graphics.Primitives; 11 + using osu.Framework.Graphics.Shaders; 12 + 13 + namespace osu.Framework.Graphics.Sprites 14 + { 15 + /// <summary> 16 + /// A view that displays the contents of a <see cref="BufferedContainer{T}"/>. 17 + /// </summary> 18 + public class BufferedContainerView<T> : Drawable, ITexturedShaderDrawable 19 + where T : Drawable 20 + { 21 + public IShader TextureShader { get; private set; } 22 + public IShader RoundedTextureShader { get; private set; } 23 + 24 + private BufferedContainer<T> container; 25 + private BufferedDrawNodeSharedData sharedData; 26 + 27 + internal BufferedContainerView(BufferedContainer<T> container, BufferedDrawNodeSharedData sharedData) 28 + { 29 + this.container = container; 30 + this.sharedData = sharedData; 31 + 32 + container.OnDispose += removeContainer; 33 + } 34 + 35 + [BackgroundDependencyLoader] 36 + private void load(ShaderManager shaders) 37 + { 38 + TextureShader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); 39 + RoundedTextureShader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); 40 + } 41 + 42 + protected override DrawNode CreateDrawNode() => new BufferSpriteDrawNode(this); 43 + 44 + private bool synchronisedDrawQuad; 45 + 46 + /// <summary> 47 + /// Whether this <see cref="BufferedContainerView{T}"/> should be drawn using the original <see cref="BufferedContainer{T}"/>'s draw quad. 48 + /// </summary> 49 + /// <remarks> 50 + /// This can be useful to display the <see cref="BufferedContainer{T}"/> as an overlay on top of itself. 51 + /// </remarks> 52 + public bool SynchronisedDrawQuad 53 + { 54 + get => synchronisedDrawQuad; 55 + set 56 + { 57 + if (value == synchronisedDrawQuad) 58 + return; 59 + 60 + synchronisedDrawQuad = value; 61 + 62 + Invalidate(Invalidation.DrawNode); 63 + } 64 + } 65 + 66 + private bool displayOriginalEffects; 67 + 68 + /// <summary> 69 + /// Whether the effects drawn by the <see cref="BufferedContainer{T}"/> should also be drawn for this view. 70 + /// </summary> 71 + public bool DisplayOriginalEffects 72 + { 73 + get => displayOriginalEffects; 74 + set 75 + { 76 + if (displayOriginalEffects == value) 77 + return; 78 + 79 + displayOriginalEffects = value; 80 + 81 + Invalidate(Invalidation.DrawNode); 82 + } 83 + } 84 + 85 + private void removeContainer() 86 + { 87 + if (container == null) 88 + return; 89 + 90 + container.OnDispose -= removeContainer; 91 + 92 + container = null; 93 + sharedData = null; 94 + 95 + Invalidate(Invalidation.DrawNode); 96 + } 97 + 98 + protected override void Dispose(bool isDisposing) 99 + { 100 + base.Dispose(isDisposing); 101 + 102 + removeContainer(); 103 + } 104 + 105 + private class BufferSpriteDrawNode : TexturedShaderDrawNode 106 + { 107 + protected new BufferedContainerView<T> Source => (BufferedContainerView<T>)base.Source; 108 + 109 + private Quad screenSpaceDrawQuad; 110 + private BufferedDrawNodeSharedData shared; 111 + private bool displayOriginalEffects; 112 + 113 + private bool sourceDrawsOriginal; 114 + private ColourInfo sourceEffectColour; 115 + private BlendingParameters sourceEffectBlending; 116 + private EffectPlacement sourceEffectPlacement; 117 + 118 + public BufferSpriteDrawNode(BufferedContainerView<T> source) 119 + : base(source) 120 + { 121 + } 122 + 123 + public override void ApplyState() 124 + { 125 + base.ApplyState(); 126 + 127 + screenSpaceDrawQuad = Source.synchronisedDrawQuad ? Source.container.ScreenSpaceDrawQuad : Source.ScreenSpaceDrawQuad; 128 + shared = Source.sharedData; 129 + 130 + displayOriginalEffects = Source.displayOriginalEffects; 131 + sourceDrawsOriginal = Source.container.DrawOriginal; 132 + sourceEffectColour = Source.container.EffectColour; 133 + sourceEffectBlending = Source.container.DrawEffectBlending; 134 + sourceEffectPlacement = Source.container.EffectPlacement; 135 + } 136 + 137 + public override void Draw(Action<TexturedVertex2D> vertexAction) 138 + { 139 + base.Draw(vertexAction); 140 + 141 + if (shared?.MainBuffer?.Texture?.Available != true || shared.DrawVersion == -1) 142 + return; 143 + 144 + Shader.Bind(); 145 + 146 + if (sourceEffectPlacement == EffectPlacement.InFront) 147 + drawMainBuffer(vertexAction); 148 + 149 + drawEffectBuffer(vertexAction); 150 + 151 + if (sourceEffectPlacement == EffectPlacement.Behind) 152 + drawMainBuffer(vertexAction); 153 + 154 + Shader.Unbind(); 155 + } 156 + 157 + private void drawMainBuffer(Action<TexturedVertex2D> vertexAction) 158 + { 159 + // If the original was drawn, draw it. 160 + // Otherwise, if an effect will also not be drawn then we still need to display something - the original. 161 + // Keep in mind that the effect MAY be the original itself, but is drawn through drawEffectBuffer(). 162 + if (!sourceDrawsOriginal && shouldDrawEffectBuffer) 163 + return; 164 + 165 + GLWrapper.SetBlend(DrawColourInfo.Blending); 166 + DrawFrameBuffer(shared.MainBuffer, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction); 167 + } 168 + 169 + private void drawEffectBuffer(Action<TexturedVertex2D> vertexAction) 170 + { 171 + if (!shouldDrawEffectBuffer) 172 + return; 173 + 174 + GLWrapper.SetBlend(new BlendingInfo(sourceEffectBlending)); 175 + ColourInfo finalEffectColour = DrawColourInfo.Colour; 176 + finalEffectColour.ApplyChild(sourceEffectColour); 177 + 178 + DrawFrameBuffer(shared.CurrentEffectBuffer, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction); 179 + } 180 + 181 + /// <summary> 182 + /// Whether the source's current effect buffer should be drawn. 183 + /// This is true if we explicitly want to draw it or if no effects were drawn by the source. In the case that no effects were drawn by the source, 184 + /// the current effect buffer will be the main buffer, and what will be drawn is the main buffer with the effect blending applied. 185 + /// </summary> 186 + private bool shouldDrawEffectBuffer => displayOriginalEffects || shared.CurrentEffectBuffer == shared.MainBuffer; 187 + } 188 + } 189 + }
+1 -1
osu.Framework/Graphics/Sprites/Sprite.cs
··· 67 67 /// of this sprite will be set to the size of the texture. 68 68 /// <see cref="Drawable.FillAspectRatio"/> is automatically set to the aspect ratio of the given texture or 1 if the texture is null. 69 69 /// </summary> 70 - public Texture Texture 70 + public virtual Texture Texture 71 71 { 72 72 get => texture; 73 73 set
+1 -1
osu.Framework/Graphics/Sprites/SpriteDrawNode.cs
··· 43 43 44 44 protected virtual void Blit(Action<TexturedVertex2D> vertexAction) 45 45 { 46 - Texture.DrawQuad(ScreenSpaceDrawQuad, DrawColourInfo.Colour, null, vertexAction, 46 + DrawQuad(Texture, ScreenSpaceDrawQuad, DrawColourInfo.Colour, null, vertexAction, 47 47 new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height)); 48 48 } 49 49
+4
osu.Framework/Graphics/Sprites/SpriteText.cs
··· 7 7 using osu.Framework.Allocation; 8 8 using osu.Framework.Bindables; 9 9 using osu.Framework.Caching; 10 + using osu.Framework.Development; 10 11 using osu.Framework.Graphics.Containers; 11 12 using osu.Framework.Graphics.Primitives; 12 13 using osu.Framework.Graphics.Shaders; ··· 425 426 /// </summary> 426 427 private void computeCharacters() 427 428 { 429 + if (LoadState >= LoadState.Loaded) 430 + ThreadSafety.EnsureUpdateThread(); 431 + 428 432 if (store == null) 429 433 return; 430 434
+2 -2
osu.Framework/Graphics/Sprites/SpriteText_DrawNode.cs
··· 68 68 shadowQuad.BottomLeft += shadowOffset; 69 69 shadowQuad.BottomRight += shadowOffset; 70 70 71 - parts[i].Texture.DrawQuad(shadowQuad, finalShadowColour, vertexAction: vertexAction); 71 + DrawQuad(parts[i].Texture, shadowQuad, finalShadowColour, vertexAction: vertexAction); 72 72 } 73 73 74 - parts[i].Texture.DrawQuad(parts[i].DrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); 74 + DrawQuad(parts[i].Texture, parts[i].DrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); 75 75 } 76 76 77 77 Shader.Unbind();
+5 -5
osu.Framework/Graphics/TexturedShaderDrawNode.cs
··· 8 8 { 9 9 public abstract class TexturedShaderDrawNode : DrawNode 10 10 { 11 - protected IShader Shader => RequiresRoundedShader ? roundedTextureShader : textureShader; 11 + protected IShader Shader => RequiresRoundedShader ? RoundedTextureShader : TextureShader; 12 12 13 - private IShader textureShader; 14 - private IShader roundedTextureShader; 13 + protected IShader TextureShader { get; private set; } 14 + protected IShader RoundedTextureShader { get; private set; } 15 15 16 16 protected new ITexturedShaderDrawable Source => (ITexturedShaderDrawable)base.Source; 17 17 ··· 24 24 { 25 25 base.ApplyState(); 26 26 27 - textureShader = Source.TextureShader; 28 - roundedTextureShader = Source.RoundedTextureShader; 27 + TextureShader = Source.TextureShader; 28 + RoundedTextureShader = Source.RoundedTextureShader; 29 29 } 30 30 31 31 protected virtual bool RequiresRoundedShader => GLWrapper.IsMaskingActive;
-20
osu.Framework/Graphics/Textures/PrefixTextureStore.cs
··· 1 - // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 - // See the LICENCE file in the repository root for full licence text. 3 - 4 - using osu.Framework.IO.Stores; 5 - 6 - namespace osu.Framework.Graphics.Textures 7 - { 8 - public class PrefixTextureStore : TextureStore 9 - { 10 - private readonly string prefix; 11 - 12 - public PrefixTextureStore(string prefix, IResourceStore<TextureUpload> stores) 13 - : base(stores) 14 - { 15 - this.prefix = prefix; 16 - } 17 - 18 - public override Texture Get(string name) => base.Get($@"{prefix}-{name}"); 19 - } 20 - }
+24 -4
osu.Framework/Graphics/Textures/Texture.cs
··· 3 3 4 4 using System; 5 5 using System.IO; 6 + using osu.Framework.Graphics.Batches; 6 7 using osu.Framework.Graphics.OpenGL.Textures; 7 8 using osu.Framework.Graphics.Primitives; 8 9 using osuTK; ··· 112 113 113 114 public RectangleF GetTextureRect(RectangleF? textureRect = null) => TextureGL.GetTextureRect(TextureBounds(textureRect)); 114 115 115 - public void DrawTriangle(Triangle vertexTriangle, ColourInfo colour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null) 116 + /// <summary> 117 + /// Draws a triangle to the screen. 118 + /// </summary> 119 + /// <param name="vertexTriangle">The triangle to draw.</param> 120 + /// <param name="drawColour">The vertex colour.</param> 121 + /// <param name="textureRect">The texture rectangle.</param> 122 + /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 123 + /// <param name="inflationPercentage">The percentage amount that <see cref="textureRect"/> should be inflated.</param> 124 + internal void DrawTriangle(Triangle vertexTriangle, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, 125 + Vector2? inflationPercentage = null) 116 126 { 117 127 if (TextureGL == null || !TextureGL.Bind()) return; 118 128 119 - TextureGL.DrawTriangle(vertexTriangle, TextureBounds(textureRect), colour, vertexAction, inflationPercentage); 129 + TextureGL.DrawTriangle(vertexTriangle, drawColour, TextureBounds(textureRect), vertexAction, inflationPercentage); 120 130 } 121 131 122 - public void DrawQuad(Quad vertexQuad, ColourInfo colour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null, Vector2? blendRangeOverride = null) 132 + /// <summary> 133 + /// Draws a quad to the screen. 134 + /// </summary> 135 + /// <param name="vertexQuad">The quad to draw.</param> 136 + /// <param name="drawColour">The vertex colour.</param> 137 + /// <param name="textureRect">The texture rectangle.</param> 138 + /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 139 + /// <param name="inflationPercentage">The percentage amount that <see cref="textureRect"/> should be inflated.</param> 140 + /// <param name="blendRangeOverride">The range over which the edges of the <see cref="textureRect"/> should be blended.</param> 141 + internal void DrawQuad(Quad vertexQuad, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null, 142 + Vector2? blendRangeOverride = null) 123 143 { 124 144 if (TextureGL == null || !TextureGL.Bind()) return; 125 145 126 - TextureGL.DrawQuad(vertexQuad, TextureBounds(textureRect), colour, vertexAction, inflationPercentage, blendRangeOverride); 146 + TextureGL.DrawQuad(vertexQuad, drawColour, TextureBounds(textureRect), vertexAction, inflationPercentage, blendRangeOverride); 127 147 } 128 148 129 149 public override string ToString() => $@"{AssetName} ({Width}, {Height})";
+1
osu.Framework/Graphics/Textures/TextureAtlas.cs
··· 85 85 Vector2I res = new Vector2I(0, currentY); 86 86 87 87 int maxY = currentY; 88 + 88 89 foreach (RectangleI bounds in subTextureBounds) 89 90 { 90 91 // +1 is required to prevent aliasing issues with sub-pixel positions while drawing. Bordering edged of other textures can show without it.
+35 -3
osu.Framework/Graphics/Textures/TextureLoaderStore.cs
··· 1 1 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 + using System; 5 + using System.Collections.Generic; 4 6 using System.IO; 5 7 using System.Threading.Tasks; 6 8 using osu.Framework.IO.Stores; ··· 9 11 10 12 namespace osu.Framework.Graphics.Textures 11 13 { 12 - public class TextureLoaderStore : ResourceStore<TextureUpload> 14 + public class TextureLoaderStore : IResourceStore<TextureUpload> 13 15 { 14 16 private IResourceStore<byte[]> store { get; } 15 17 ··· 20 22 (store as ResourceStore<byte[]>)?.AddExtension(@"jpg"); 21 23 } 22 24 23 - public override Task<TextureUpload> GetAsync(string name) => Task.Run(() => Get(name)); 25 + public Task<TextureUpload> GetAsync(string name) => Task.Run(() => Get(name)); 24 26 25 - public override TextureUpload Get(string name) 27 + public TextureUpload Get(string name) 26 28 { 27 29 try 28 30 { ··· 38 40 39 41 return null; 40 42 } 43 + 44 + public Stream GetStream(string name) => store.GetStream(name); 41 45 42 46 protected virtual Image<TPixel> ImageFromStream<TPixel>(Stream stream) where TPixel : struct, IPixel<TPixel> 43 47 => Image.Load<TPixel>(stream); 48 + 49 + public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources(); 50 + 51 + #region IDisposable Support 52 + 53 + private bool isDisposed; 54 + 55 + protected virtual void Dispose(bool disposing) 56 + { 57 + if (!isDisposed) 58 + { 59 + isDisposed = true; 60 + store.Dispose(); 61 + } 62 + } 63 + 64 + ~TextureLoaderStore() 65 + { 66 + Dispose(false); 67 + } 68 + 69 + public void Dispose() 70 + { 71 + Dispose(true); 72 + GC.SuppressFinalize(this); 73 + } 74 + 75 + #endregion 44 76 } 45 77 }
+1 -1
osu.Framework/Graphics/Textures/TextureStore.cs
··· 86 86 } 87 87 } 88 88 } 89 - } 89 + }
+1
osu.Framework/Graphics/TransformableExtensions.cs
··· 318 318 { 319 319 case Direction.Horizontal: 320 320 return drawable.MoveToX(destination, duration, easing); 321 + 321 322 case Direction.Vertical: 322 323 return drawable.MoveToY(destination, duration, easing); 323 324 }
+2
osu.Framework/Graphics/Transforms/TransformCustom.cs
··· 74 74 private static Accessor findAccessor(Type type, string propertyOrFieldName) 75 75 { 76 76 PropertyInfo property = type.GetProperty(propertyOrFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 77 + 77 78 if (property != null) 78 79 { 79 80 if (property.PropertyType != typeof(TValue)) ··· 101 102 } 102 103 103 104 FieldInfo field = type.GetField(propertyOrFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); 105 + 104 106 if (field != null) 105 107 { 106 108 if (field.FieldType != typeof(TValue))
+2
osu.Framework/Graphics/Transforms/TransformSequence.cs
··· 335 335 throw new InvalidOperationException($"Can not perform {nameof(Loop)} on an endless {nameof(TransformSequence<T>)}."); 336 336 337 337 var iterDuration = endTime - startTime + pause; 338 + 338 339 foreach (var t in transforms) 339 340 { 340 341 Action tmpOnAbort = t.OnAbort; ··· 347 348 // inserted in the correct order such that none of them trigger abortions on 348 349 // each other due to instant re-sorting upon adding. 349 350 double currentTransformTime = t.TargetTransformable.Time.Current; 351 + 350 352 while (t.EndTime <= currentTransformTime) 351 353 { 352 354 t.StartTime += iterDuration;
+2
osu.Framework/Graphics/Transforms/Transformable.cs
··· 274 274 return; 275 275 276 276 Transform[] toAbort; 277 + 277 278 if (targetMember == null) 278 279 { 279 280 toAbort = transformsLazy.Value.Where(t => t.StartTime >= time).ToArray(); ··· 438 439 for (int i = insertionIndex + 1; i < transforms.Count; ++i) 439 440 { 440 441 var t = transforms[i]; 442 + 441 443 if (t.TargetMember == transform.TargetMember) 442 444 { 443 445 transforms.RemoveAt(i--);
+1
osu.Framework/Graphics/UserInterface/Button.cs
··· 46 46 Background = new Box 47 47 { 48 48 RelativeSizeAxes = Axes.Both, 49 + Colour = Color4.DarkSlateGray, 49 50 Anchor = Anchor.Centre, 50 51 Origin = Anchor.Centre, 51 52 },
+1
osu.Framework/Graphics/UserInterface/CircularProgressDrawNode.cs
··· 101 101 // dir is used so negative angles result in negative angularOffset. 102 102 float angularOffset = dir * Math.Min(i * step, dir * angle); 103 103 float normalisedOffset = angularOffset / MathHelper.TwoPi; 104 + 104 105 if (dir < 0) 105 106 { 106 107 normalisedOffset += 1.0f;
+3
osu.Framework/Graphics/UserInterface/Dropdown.cs
··· 159 159 { 160 160 case MenuItem i: 161 161 return i.Text.Value; 162 + 162 163 case IHasText t: 163 164 return t.Text; 165 + 164 166 case Enum e: 165 167 return e.GetDescription(); 168 + 166 169 default: 167 170 return item?.ToString() ?? "null"; 168 171 }
+3
osu.Framework/Graphics/UserInterface/Menu.cs
··· 114 114 case Direction.Horizontal: 115 115 ItemsContainer.AutoSizeAxes = Axes.X; 116 116 break; 117 + 117 118 case Direction.Vertical: 118 119 ItemsContainer.AutoSizeAxes = Axes.Y; 119 120 break; ··· 236 237 case MenuState.Closed: 237 238 AnimateClose(); 238 239 break; 240 + 239 241 case MenuState.Open: 240 242 AnimateOpen(); 241 243 if (!TopLevelMenu) ··· 453 455 case MenuState.Closed: 454 456 selectedItem.State = MenuItemState.NotSelected; 455 457 break; 458 + 456 459 case MenuState.Open: 457 460 selectedItem.State = MenuItemState.Selected; 458 461 break;
+2
osu.Framework/Graphics/UserInterface/SliderBar.cs
··· 159 159 currentNumberInstantaneous.Add(step); 160 160 onUserChange(currentNumberInstantaneous.Value); 161 161 return true; 162 + 162 163 case Key.Left: 163 164 currentNumberInstantaneous.Add(-step); 164 165 onUserChange(currentNumberInstantaneous.Value); 165 166 return true; 167 + 166 168 default: 167 169 return false; 168 170 }
+11 -7
osu.Framework/Graphics/UserInterface/TextBox.cs
··· 293 293 pos = Parent.ToSpaceOfOtherDrawable(pos, TextFlow); 294 294 295 295 int i = 0; 296 + 296 297 foreach (Drawable d in TextFlow.Children) 297 298 { 298 299 if (d.DrawPosition.X + d.DrawSize.X / 2 > pos.X) ··· 449 450 450 451 if (oldStart != selectionStart || oldEnd != selectionEnd) 451 452 { 452 - audio.Sample.Get(@"Keyboard/key-movement")?.Play(); 453 + audio.Samples.Get(@"Keyboard/key-movement")?.Play(); 453 454 cursorAndLayout.Invalidate(); 454 455 } 455 456 } ··· 468 469 if (count == 0) return false; 469 470 470 471 if (sound) 471 - audio.Sample.Get(@"Keyboard/key-delete")?.Play(); 472 + audio.Samples.Get(@"Keyboard/key-delete")?.Play(); 472 473 473 474 foreach (var d in TextFlow.Children.Skip(start).Take(count).ToArray()) //ToArray since we are removing items from the children in this block. 474 475 { ··· 536 537 foreach (char c in addText) 537 538 { 538 539 var ch = addCharacter(c); 540 + 539 541 if (ch == null) 540 542 { 541 543 notifyInputError(); ··· 677 679 if (!string.IsNullOrEmpty(pendingText) && !ReadOnly) 678 680 { 679 681 if (pendingText.Any(char.IsUpper)) 680 - audio.Sample.Get(@"Keyboard/key-caps")?.Play(); 682 + audio.Samples.Get(@"Keyboard/key-caps")?.Play(); 681 683 else 682 - audio.Sample.Get($@"Keyboard/key-press-{RNG.Next(1, 5)}")?.Play(); 684 + audio.Samples.Get($@"Keyboard/key-press-{RNG.Next(1, 5)}")?.Play(); 683 685 684 686 insertString(pendingText); 685 687 } ··· 704 706 case Key.Escape: 705 707 KillFocus(); 706 708 return true; 709 + 707 710 case Key.KeypadEnter: 708 711 case Key.Enter: 709 712 Commit(); ··· 736 739 Background.ClearTransforms(); 737 740 Background.FlashColour(BackgroundCommit, 400); 738 741 739 - audio.Sample.Get(@"Keyboard/key-confirm")?.Play(); 742 + audio.Samples.Get(@"Keyboard/key-confirm")?.Play(); 740 743 OnCommit?.Invoke(this, true); 741 744 } 742 745 ··· 949 952 { 950 953 //in the case of backspacing (or a NOP), we can exit early here. 951 954 if (didDelete) 952 - audio.Sample.Get(@"Keyboard/key-delete")?.Play(); 955 + audio.Samples.Get(@"Keyboard/key-delete")?.Play(); 953 956 return; 954 957 } 955 958 ··· 957 960 for (int i = matchCount; i < s.Length; i++) 958 961 { 959 962 Drawable dr = addCharacter(s[i]); 963 + 960 964 if (dr != null) 961 965 { 962 966 dr.Colour = Color4.Aqua; ··· 965 969 } 966 970 } 967 971 968 - audio.Sample.Get($@"Keyboard/key-press-{RNG.Next(1, 5)}")?.Play(); 972 + audio.Samples.Get($@"Keyboard/key-press-{RNG.Next(1, 5)}")?.Play(); 969 973 } 970 974 971 975 #endregion
+37
osu.Framework/Graphics/Vector2Extensions.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; 5 + using System.Runtime.CompilerServices; 6 + using osu.Framework.Graphics.Primitives; 5 7 using osuTK; 6 8 7 9 namespace osu.Framework.Graphics ··· 73 75 { 74 76 result = (vec2.X - vec1.X) * (vec2.X - vec1.X) + (vec2.Y - vec1.Y) * (vec2.Y - vec1.Y); 75 77 } 78 + 79 + /// <summary> 80 + /// Retrieves the orientation of a set of vertices. 81 + /// </summary> 82 + /// <param name="vertices">The vertices.</param> 83 + /// <returns>Twice the area enclosed by the vertices. 84 + /// The vertices are clockwise-oriented if the value is positive. 85 + /// The vertices are counter-clockwise-oriented if the value is negative.</returns> 86 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 87 + public static float GetOrientation(in ReadOnlySpan<Vector2> vertices) 88 + { 89 + if (vertices.Length == 0) 90 + return 0; 91 + 92 + float rotation = 0; 93 + for (int i = 0; i < vertices.Length - 1; ++i) 94 + rotation += (vertices[i + 1].X - vertices[i].X) * (vertices[i + 1].Y + vertices[i].Y); 95 + 96 + rotation += (vertices[0].X - vertices[vertices.Length - 1].X) * (vertices[0].Y + vertices[vertices.Length - 1].Y); 97 + 98 + return rotation; 99 + } 100 + 101 + /// <summary> 102 + /// Determines whether a point is within the right half-plane of a line. 103 + /// </summary> 104 + /// <param name="line">The line.</param> 105 + /// <param name="point">The point.</param> 106 + /// <returns>Whether <paramref name="point"/> is in the right half-plane of <paramref name="line"/>. 107 + /// If the point is colinear to the line, it is said to be in the right half-plane of the line. 108 + /// </returns> 109 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 110 + public static bool InRightHalfPlaneOf(this Vector2 point, in Line line) 111 + => (line.EndPoint.X - line.StartPoint.X) * (point.Y - line.StartPoint.Y) 112 + - (line.EndPoint.Y - line.StartPoint.Y) * (point.X - line.StartPoint.X) <= 0; 76 113 } 77 114 }
+10
osu.Framework/Graphics/Video/VideoDecoder.cs
··· 214 214 case StdIo.SEEK_CUR: 215 215 videoStream.Seek(offset, SeekOrigin.Current); 216 216 break; 217 + 217 218 case StdIo.SEEK_END: 218 219 videoStream.Seek(offset, SeekOrigin.End); 219 220 break; 221 + 220 222 case StdIo.SEEK_SET: 221 223 videoStream.Seek(offset, SeekOrigin.Begin); 222 224 break; 225 + 223 226 case ffmpeg.AVSEEK_SIZE: 224 227 return videoStream.Length; 228 + 225 229 default: 226 230 return -1; 227 231 } ··· 248 252 throw new Exception("Could not find stream info."); 249 253 250 254 var nStreams = formatContext->nb_streams; 255 + 251 256 for (var i = 0; i < nStreams; ++i) 252 257 { 253 258 stream = formatContext->streams[i]; 254 259 255 260 codecParams = *stream->codecpar; 261 + 256 262 if (codecParams.codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO) 257 263 { 258 264 timeBaseInSeconds = stream->time_base.GetValue(); ··· 306 312 if (readFrameResult >= 0) 307 313 { 308 314 state = DecoderState.Running; 315 + 309 316 if (packet->stream_index == stream->index) 310 317 { 311 318 if (ffmpeg.avcodec_send_packet(stream->codec, packet) < 0) 312 319 throw new Exception("Error sending packet."); 313 320 314 321 var result = ffmpeg.avcodec_receive_frame(stream->codec, frame); 322 + 315 323 if (result == 0) 316 324 { 317 325 var frameTime = (frame->best_effort_timestamp - stream->start_time) * timeBaseInSeconds * 1000; 326 + 318 327 if (!skipOutputUntilTime.HasValue || skipOutputUntilTime.Value < frameTime) 319 328 { 320 329 skipOutputUntilTime = null; 321 330 322 331 SwsContext* swsCtx = null; 332 + 323 333 try 324 334 { 325 335 swsCtx = ffmpeg.sws_getContext(codecParams.width, codecParams.height, (AVPixelFormat)frame->format, codecParams.width, codecParams.height, AVPixelFormat.AV_PIX_FMT_RGBA, 0, null, null, null);
+1
osu.Framework/Graphics/Video/VideoSprite.cs
··· 112 112 startTime = Clock.CurrentTime; 113 113 114 114 var nextFrame = availableFrames.Count > 0 ? availableFrames.Peek() : null; 115 + 115 116 if (nextFrame != null) 116 117 { 117 118 bool tooFarBehind = Math.Abs(PlaybackPosition - nextFrame.Time) > lenience_before_seek &&
+9 -7
osu.Framework/Graphics/Visualisation/DrawVisualiser.cs
··· 44 44 Drawable lastHighlight = highlightedTarget?.Target; 45 45 46 46 var parent = Target?.Parent; 47 + 47 48 if (parent != null) 48 49 { 49 50 var lastVisualiser = targetVisualiser; ··· 58 59 if (lastHighlight != null) 59 60 { 60 61 VisualisedDrawable visualised = targetVisualiser.FindVisualisedDrawable(lastHighlight); 62 + 61 63 if (visualised != null) 62 64 { 63 - propertyDisplay.State = Visibility.Visible; 65 + propertyDisplay.Show(); 64 66 setHighlight(visualised); 65 67 } 66 68 } ··· 72 74 73 75 propertyDisplay.ToggleVisibility(); 74 76 75 - if (propertyDisplay.State == Visibility.Visible) 77 + if (propertyDisplay.State.Value == Visibility.Visible) 76 78 setHighlight(targetVisualiser); 77 79 }, 78 80 }, ··· 81 83 82 84 propertyDisplay = treeContainer.PropertyDisplay; 83 85 84 - propertyDisplay.StateChanged += visibility => 86 + propertyDisplay.State.ValueChanged += v => 85 87 { 86 - switch (visibility) 88 + switch (v.NewValue) 87 89 { 88 90 case Visibility.Hidden: 89 91 // Dehighlight everything automatically if property display is closed ··· 127 129 128 130 visualiser.HighlightTarget = d => 129 131 { 130 - propertyDisplay.State = Visibility.Visible; 132 + propertyDisplay.Show(); 131 133 132 134 // Either highlight or dehighlight the target, depending on whether 133 135 // it is currently highlighted ··· 146 148 treeContainer.Remove(visualiser); 147 149 148 150 if (Target == null) 149 - propertyDisplay.State = Visibility.Hidden; 151 + propertyDisplay.Hide(); 150 152 } 151 153 152 154 private VisualisedDrawable targetVisualiser; ··· 254 256 } 255 257 256 258 // Only update when property display is visible 257 - if (propertyDisplay.State == Visibility.Visible) 259 + if (propertyDisplay.State.Value == Visibility.Visible) 258 260 { 259 261 highlightedTarget = newHighlight; 260 262 newHighlight.IsHighlighted = true;
+5 -1
osu.Framework/Graphics/Visualisation/LogOverlay.cs
··· 120 120 private void load(FrameworkConfigManager config) 121 121 { 122 122 enabled = config.GetBindable<bool>(FrameworkSetting.ShowLogOverlay); 123 - enabled.ValueChanged += e => State = e.NewValue ? Visibility.Visible : Visibility.Hidden; 123 + enabled.ValueChanged += e => State.Value = e.NewValue ? Visibility.Visible : Visibility.Hidden; 124 124 enabled.TriggerChange(); 125 125 } 126 126 ··· 212 212 { 213 213 case LoggingTarget.Runtime: 214 214 return Color4.YellowGreen; 215 + 215 216 case LoggingTarget.Network: 216 217 return Color4.BlueViolet; 218 + 217 219 case LoggingTarget.Performance: 218 220 return Color4.HotPink; 221 + 219 222 case LoggingTarget.Information: 220 223 return Color4.CadetBlue; 224 + 221 225 default: 222 226 return Color4.Cyan; 223 227 }
+2
osu.Framework/Graphics/Visualisation/PropertyDisplay.cs
··· 84 84 public PropertyItem(MemberInfo info, IDrawable d) 85 85 { 86 86 Type type; 87 + 87 88 switch (info.MemberType) 88 89 { 89 90 case MemberTypes.Property: ··· 164 165 private void updateValue() 165 166 { 166 167 object value; 168 + 167 169 try 168 170 { 169 171 value = getValue() ?? "<null>";
+2 -1
osu.Framework/Graphics/Visualisation/TreeContainer.cs
··· 52 52 case TreeContainerStatus.Offscreen: 53 53 this.Delay(500).FadeTo(0.7f, 300); 54 54 break; 55 + 55 56 case TreeContainerStatus.Onscreen: 56 57 this.FadeIn(300, Easing.OutQuint); 57 58 break; ··· 164 165 } 165 166 }); 166 167 167 - PropertyDisplay.StateChanged += v => propertyButton.BackgroundColour = v == Visibility.Visible ? buttonBackgroundHighlighted : buttonBackground; 168 + PropertyDisplay.State.ValueChanged += v => propertyButton.BackgroundColour = v.NewValue == Visibility.Visible ? buttonBackgroundHighlighted : buttonBackground; 168 169 } 169 170 170 171 protected override void Update()
+1
osu.Framework/Graphics/Visualisation/VisualisedDrawable.cs
··· 335 335 private void updateSpecifics() 336 336 { 337 337 Vector2 posInTree = ToSpaceOfOtherDrawable(Vector2.Zero, tree); 338 + 338 339 if (posInTree.Y < -previewBox.DrawHeight || posInTree.Y > tree.Height) 339 340 { 340 341 text.Text = string.Empty;
+3
osu.Framework/Host.cs
··· 24 24 { 25 25 case RuntimeInfo.Platform.MacOsx: 26 26 return new MacOSGameHost(gameName, bindIPC, toolkitOptions, portableInstallation); 27 + 27 28 case RuntimeInfo.Platform.Linux: 28 29 return new LinuxGameHost(gameName, bindIPC, toolkitOptions, portableInstallation); 30 + 29 31 case RuntimeInfo.Platform.Windows: 30 32 return new WindowsGameHost(gameName, bindIPC, toolkitOptions, portableInstallation); 33 + 31 34 default: 32 35 throw new InvalidOperationException($"Could not find a suitable host for the selected operating system ({Enum.GetName(typeof(RuntimeInfo.Platform), RuntimeInfo.OS)})."); 33 36 }
+4
osu.Framework/IO/AsyncBufferStream.cs
··· 76 76 return; 77 77 78 78 int last = -1; 79 + 79 80 while (!isLoaded && !isClosed) 80 81 { 81 82 cancellationToken.Token.ThrowIfCancellationRequested(); 82 83 83 84 int curr = nextBlockToLoad; 85 + 84 86 if (curr < 0) 85 87 { 86 88 Thread.Sleep(1); ··· 211 213 case SeekOrigin.Begin: 212 214 Position = offset; 213 215 break; 216 + 214 217 case SeekOrigin.Current: 215 218 Position += offset; 216 219 break; 220 + 217 221 case SeekOrigin.End: 218 222 Position = data.Length + offset; 219 223 break;
+7
osu.Framework/IO/File/FileSafety.cs
··· 131 131 } 132 132 133 133 string deathLocation = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); 134 + 134 135 try 135 136 { 136 137 System.IO.File.Move(filename, deathLocation); ··· 231 232 } 232 233 233 234 bool didExist = Directory.Exists(newDirectory); 235 + 234 236 if (!didExist) 235 237 { 236 238 DirectoryInfo newDirectoryInfo = Directory.CreateDirectory(newDirectory); 239 + 237 240 try 238 241 { 239 242 if (new DirectoryInfo(oldDirectory).Attributes.HasFlag(FileAttributes.Hidden)) ··· 249 252 string newFile = Path.Combine(newDirectory, Path.GetFileName(file)); 250 253 251 254 bool didMove = FileMove(file, newFile, didExist); 255 + 252 256 if (!didMove) 253 257 { 254 258 try ··· 297 301 return string.Empty; 298 302 299 303 char[] converted = new char[(encoded.Length + 1) / 2]; 304 + 300 305 fixed (byte* bytePtr = encoded) 301 306 fixed (char* stringPtr = converted) 302 307 { 303 308 byte* stringBytes = (byte*)stringPtr; 304 309 byte* stringEnd = (byte*)stringPtr + converted.Length * 2; 305 310 byte* bytePtr2 = bytePtr; 311 + 306 312 do 307 313 { 308 314 *stringBytes = *bytePtr2++; ··· 344 350 public static void CreateBackup(string filename) 345 351 { 346 352 string backupFilename = filename + @"." + DateTime.Now.Ticks + @".bak"; 353 + 347 354 if (System.IO.File.Exists(filename) && !System.IO.File.Exists(backupFilename)) 348 355 { 349 356 Debug.Print(@"Backup created: " + backupFilename);
+4
osu.Framework/IO/Network/UrlEncoding.cs
··· 52 52 { 53 53 int num = 0; 54 54 int num2 = 0; 55 + 55 56 for (int i = 0; i < count; i++) 56 57 { 57 58 char ch = (char)bytes[offset + i]; 59 + 58 60 if (paramEncode && ch == ' ') 59 61 { 60 62 num++; ··· 72 74 73 75 byte[] buffer = new byte[count + num2 * 2]; 74 76 int num4 = 0; 77 + 75 78 for (int j = 0; j < count; j++) 76 79 { 77 80 byte num6 = bytes[offset + j]; 78 81 char ch2 = (char)num6; 82 + 79 83 if (IsSafe(ch2)) 80 84 { 81 85 buffer[num4++] = num6;
+2
osu.Framework/IO/Network/WebRequest.cs
··· 230 230 private async Task internalPerform() 231 231 { 232 232 var url = Url; 233 + 233 234 if (!AllowInsecureRequests && !url.StartsWith(@"https://")) 234 235 { 235 236 logger.Add($"Insecure request was automatically converted to https ({Url})"); ··· 459 460 { 460 461 // in the case we fail a request, spitting out the response in the log is quite helpful. 461 462 ResponseStream.Seek(0, SeekOrigin.Begin); 463 + 462 464 using (StreamReader r = new StreamReader(ResponseStream, new UTF8Encoding(false, true), true, 1024, true)) 463 465 { 464 466 try
-113
osu.Framework/IO/Stores/CachedResourceStore.cs
··· 1 - // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 - // See the LICENCE file in the repository root for full licence text. 3 - 4 - using System.Collections.Generic; 5 - using System.Threading.Tasks; 6 - 7 - namespace osu.Framework.IO.Stores 8 - { 9 - public class CachedResourceStore<T> : ResourceStore<T> 10 - { 11 - private readonly Dictionary<string, T> cache = new Dictionary<string, T>(); 12 - 13 - /// <summary> 14 - /// Initializes a resource store with no stores. 15 - /// </summary> 16 - public CachedResourceStore() 17 - { 18 - } 19 - 20 - /// <summary> 21 - /// Initializes a resource store with a single store. 22 - /// </summary> 23 - /// <param name="stores">A collection of stores to add.</param> 24 - public CachedResourceStore(IResourceStore<T>[] stores) 25 - : base(stores) 26 - { 27 - } 28 - 29 - /// <summary> 30 - /// Initializes a resource store with a collection of stores. 31 - /// </summary> 32 - /// <param name="store">The store.</param> 33 - public CachedResourceStore(IResourceStore<T> store) 34 - : base(store) 35 - { 36 - } 37 - 38 - /// <summary> 39 - /// Notifies a bound delegate that the resource has changed. 40 - /// </summary> 41 - /// <param name="name">The resource that has changed.</param> 42 - protected override void NotifyChanged(string name) 43 - { 44 - cache.Remove(name); 45 - base.NotifyChanged(name); 46 - } 47 - 48 - /// <summary> 49 - /// Adds a resource store to this store. 50 - /// </summary> 51 - /// <param name="store">The store to add.</param> 52 - public override void AddStore(IResourceStore<T> store) 53 - { 54 - base.AddStore(store); 55 - 56 - if (store is ChangeableResourceStore<T> crm) 57 - crm.OnChanged += NotifyChanged; 58 - } 59 - 60 - /// <summary> 61 - /// Removes a store from this store. 62 - /// </summary> 63 - /// <param name="store">The store to remove.</param> 64 - public override void RemoveStore(IResourceStore<T> store) 65 - { 66 - base.RemoveStore(store); 67 - 68 - if (store is ChangeableResourceStore<T> crm) 69 - crm.OnChanged -= NotifyChanged; 70 - } 71 - 72 - /// <summary> 73 - /// Retrieves an object from the store. 74 - /// </summary> 75 - /// <param name="name">The name of the object.</param> 76 - /// <returns>The object.</returns> 77 - public override async Task<T> GetAsync(string name) 78 - { 79 - if (cache.TryGetValue(name, out T result)) 80 - return result; 81 - 82 - result = await base.GetAsync(name); 83 - 84 - if (result != null) 85 - cache[name] = result; 86 - 87 - return result; 88 - } 89 - 90 - /// <summary> 91 - /// Releases a resource from the cache. 92 - /// </summary> 93 - /// <param name="name">The resource to release.</param> 94 - public void Release(string name) 95 - { 96 - cache.Remove(name); 97 - } 98 - 99 - /// <summary> 100 - /// Releases all resources stored in the cache. 101 - /// </summary> 102 - public void ResetCache() 103 - { 104 - cache.Clear(); 105 - } 106 - 107 - protected override void Dispose(bool disposing) 108 - { 109 - base.Dispose(disposing); 110 - ResetCache(); 111 - } 112 - } 113 - }
-17
osu.Framework/IO/Stores/ChangeableResourceStore.cs
··· 1 - // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 - // See the LICENCE file in the repository root for full licence text. 3 - 4 - using System; 5 - 6 - namespace osu.Framework.IO.Stores 7 - { 8 - public class ChangeableResourceStore<T> : ResourceStore<T> 9 - { 10 - public event Action<string> OnChanged; 11 - 12 - protected void TriggerOnChanged(string name) 13 - { 14 - OnChanged?.Invoke(name); 15 - } 16 - } 17 - }
+5 -4
osu.Framework/IO/Stores/DllResourceStore.cs
··· 54 54 /// <summary> 55 55 /// Retrieve a list of available resources provided by this store. 56 56 /// </summary> 57 - public IEnumerable<string> AvailableResources => 57 + public IEnumerable<string> GetAvailableResources() => 58 58 assembly.GetManifestResourceNames().Select(n => 59 59 { 60 - var chars = n.ToCharArray(); 60 + n = n.Substring(n.StartsWith(prefix) ? prefix.Length + 1 : 0); 61 61 62 - int startIndex = n.StartsWith(prefix) ? prefix.Length + 1 : 0; 63 62 int lastDot = n.LastIndexOf('.'); 64 63 65 - for (int i = startIndex; i < lastDot; i++) 64 + var chars = n.ToCharArray(); 65 + 66 + for (int i = 0; i < lastDot; i++) 66 67 if (chars[i] == '.') 67 68 chars[i] = '/'; 68 69
-66
osu.Framework/IO/Stores/FileSystemResourceStore.cs
··· 1 - // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 - // See the LICENCE file in the repository root for full licence text. 3 - 4 - using System.IO; 5 - using System.Threading.Tasks; 6 - 7 - namespace osu.Framework.IO.Stores 8 - { 9 - public class FileSystemResourceStore : ChangeableResourceStore<byte[]> 10 - { 11 - private readonly FileSystemWatcher watcher; 12 - private readonly string directory; 13 - 14 - private bool isDisposed; 15 - 16 - public FileSystemResourceStore(string directory) 17 - { 18 - this.directory = directory; 19 - 20 - watcher = new FileSystemWatcher(directory) 21 - { 22 - EnableRaisingEvents = true 23 - }; 24 - watcher.Renamed += watcherChanged; 25 - watcher.Changed += watcherChanged; 26 - watcher.Created += watcherChanged; 27 - } 28 - 29 - #region Disposal 30 - 31 - ~FileSystemResourceStore() 32 - { 33 - Dispose(false); 34 - } 35 - 36 - protected override void Dispose(bool disposing) 37 - { 38 - if (isDisposed) 39 - return; 40 - 41 - isDisposed = true; 42 - 43 - watcher.Dispose(); 44 - } 45 - 46 - #endregion 47 - 48 - private void watcherChanged(object sender, FileSystemEventArgs e) 49 - { 50 - TriggerOnChanged(e.FullPath.Replace(directory, string.Empty)); 51 - } 52 - 53 - public override async Task<byte[]> GetAsync(string name) 54 - { 55 - byte[] result; 56 - 57 - using (FileStream stream = System.IO.File.OpenRead(Path.Combine(directory, name))) 58 - { 59 - result = new byte[stream.Length]; 60 - await stream.ReadAsync(result, 0, (int)stream.Length); 61 - } 62 - 63 - return result; 64 - } 65 - } 66 - }
+2
osu.Framework/IO/Stores/FontStore.cs
··· 43 43 case FontStore fs: 44 44 nestedFontStores.Add(fs); 45 45 return; 46 + 46 47 case GlyphStore gs: 47 48 glyphStores.Add(gs); 48 49 queueLoad(gs); ··· 85 86 case FontStore fs: 86 87 nestedFontStores.Remove(fs); 87 88 return; 89 + 88 90 case GlyphStore gs: 89 91 glyphStores.Remove(gs); 90 92 break;
+3
osu.Framework/IO/Stores/GlyphStore.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; 5 + using System.Collections.Generic; 5 6 using System.IO; 6 7 using System.Linq; 7 8 using System.Threading.Tasks; ··· 139 140 } 140 141 141 142 public Stream GetStream(string name) => throw new NotSupportedException(); 143 + 144 + public IEnumerable<string> GetAvailableResources() => Font.Characters.Keys.Select(k => $"{FontName}/{(char)k}"); 142 145 143 146 private int loadedPageCount; 144 147 private int loadedGlyphCount;
+7
osu.Framework/IO/Stores/IResourceStore.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; 5 + using System.Collections.Generic; 5 6 using System.IO; 6 7 using System.Threading.Tasks; 7 8 ··· 24 25 Task<T> GetAsync(string name); 25 26 26 27 Stream GetStream(string name); 28 + 29 + /// <summary> 30 + /// Gets a collection of string representations of the resources available in this store. 31 + /// </summary> 32 + /// <returns>String representations of the resources available.</returns> 33 + IEnumerable<string> GetAvailableResources(); 27 34 } 28 35 }
+5
osu.Framework/IO/Stores/NamespacedResourceStore.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System.Collections.Generic; 5 + using System.Linq; 5 6 6 7 namespace osu.Framework.IO.Stores 7 8 { ··· 21 22 } 22 23 23 24 protected override IEnumerable<string> GetFilenames(string name) => base.GetFilenames($@"{Namespace}/{name}"); 25 + 26 + public override IEnumerable<string> GetAvailableResources() => base.GetAvailableResources() 27 + .Where(x => x.StartsWith($"{Namespace}/")) 28 + .Select(x => x.Remove(0, $"{Namespace}/".Length)); 24 29 } 25 30 }
+5 -1
osu.Framework/IO/Stores/OnlineStore.cs
··· 1 - // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; 5 + using System.Collections.Generic; 5 6 using System.IO; 7 + using System.Linq; 6 8 using System.Threading.Tasks; 7 9 using WebRequest = osu.Framework.IO.Network.WebRequest; 8 10 ··· 49 51 50 52 return new MemoryStream(ret); 51 53 } 54 + 55 + public IEnumerable<string> GetAvailableResources() => Enumerable.Empty<string>(); 52 56 53 57 #region IDisposable Support 54 58
+5
osu.Framework/IO/Stores/ResourceStore.cs
··· 185 185 searchExtensions.Add(extension); 186 186 } 187 187 188 + public virtual IEnumerable<string> GetAvailableResources() 189 + { 190 + lock (stores) return stores.SelectMany(s => s.GetAvailableResources()); 191 + } 192 + 188 193 #region IDisposable Support 189 194 190 195 private bool isDisposed;
+5
osu.Framework/IO/Stores/StorageBackedResourceStore.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; 5 + using System.Collections.Generic; 5 6 using System.IO; 7 + using System.Linq; 6 8 using System.Threading.Tasks; 7 9 using osu.Framework.Platform; 8 10 ··· 45 47 } 46 48 47 49 public Stream GetStream(string name) => storage.GetStream(name); 50 + 51 + public IEnumerable<string> GetAvailableResources() => 52 + storage.GetDirectories(string.Empty).SelectMany(storage.GetFiles); 48 53 49 54 #region IDisposable Support 50 55
+1
osu.Framework/Input/Bindings/KeyBindingContainer.cs
··· 102 102 protected override bool Handle(UIEvent e) 103 103 { 104 104 var state = e.CurrentState; 105 + 105 106 switch (e) 106 107 { 107 108 case MouseDownEvent mouseDown:
+51
osu.Framework/Input/Bindings/KeyCombination.cs
··· 119 119 { 120 120 case InputKey.None: 121 121 return string.Empty; 122 + 122 123 case InputKey.Shift: 123 124 return "Shift"; 125 + 124 126 case InputKey.Control: 125 127 return "Ctrl"; 128 + 126 129 case InputKey.Alt: 127 130 return "Alt"; 131 + 128 132 case InputKey.Super: 129 133 return "Win"; 134 + 130 135 case InputKey.Escape: 131 136 return "Esc"; 137 + 132 138 case InputKey.BackSpace: 133 139 return "Backsp"; 140 + 134 141 case InputKey.Insert: 135 142 return "Ins"; 143 + 136 144 case InputKey.Delete: 137 145 return "Del"; 146 + 138 147 case InputKey.PageUp: 139 148 return "Pgup"; 149 + 140 150 case InputKey.PageDown: 141 151 return "Pgdn"; 152 + 142 153 case InputKey.CapsLock: 143 154 return "Caps"; 155 + 144 156 case InputKey.Number0: 145 157 case InputKey.Keypad0: 146 158 return "0"; 159 + 147 160 case InputKey.Number1: 148 161 case InputKey.Keypad1: 149 162 return "1"; 163 + 150 164 case InputKey.Number2: 151 165 case InputKey.Keypad2: 152 166 return "2"; 167 + 153 168 case InputKey.Number3: 154 169 case InputKey.Keypad3: 155 170 return "3"; 171 + 156 172 case InputKey.Number4: 157 173 case InputKey.Keypad4: 158 174 return "4"; 175 + 159 176 case InputKey.Number5: 160 177 case InputKey.Keypad5: 161 178 return "5"; 179 + 162 180 case InputKey.Number6: 163 181 case InputKey.Keypad6: 164 182 return "6"; 183 + 165 184 case InputKey.Number7: 166 185 case InputKey.Keypad7: 167 186 return "7"; 187 + 168 188 case InputKey.Number8: 169 189 case InputKey.Keypad8: 170 190 return "8"; 191 + 171 192 case InputKey.Number9: 172 193 case InputKey.Keypad9: 173 194 return "9"; 195 + 174 196 case InputKey.Tilde: 175 197 return "~"; 198 + 176 199 case InputKey.Minus: 177 200 return "-"; 201 + 178 202 case InputKey.Plus: 179 203 return "+"; 204 + 180 205 case InputKey.BracketLeft: 181 206 return "("; 207 + 182 208 case InputKey.BracketRight: 183 209 return ")"; 210 + 184 211 case InputKey.Semicolon: 185 212 return ";"; 213 + 186 214 case InputKey.Quote: 187 215 return "\""; 216 + 188 217 case InputKey.Comma: 189 218 return ","; 219 + 190 220 case InputKey.Period: 191 221 return "."; 222 + 192 223 case InputKey.Slash: 193 224 return "/"; 225 + 194 226 case InputKey.BackSlash: 195 227 case InputKey.NonUSBackSlash: 196 228 return "\\"; 229 + 197 230 case InputKey.MouseLeft: 198 231 return "M1"; 232 + 199 233 case InputKey.MouseMiddle: 200 234 return "M3"; 235 + 201 236 case InputKey.MouseRight: 202 237 return "M2"; 238 + 203 239 case InputKey.MouseButton1: 204 240 return "M4"; 241 + 205 242 case InputKey.MouseButton2: 206 243 return "M5"; 244 + 207 245 case InputKey.MouseButton3: 208 246 return "M6"; 247 + 209 248 case InputKey.MouseButton4: 210 249 return "M7"; 250 + 211 251 case InputKey.MouseButton5: 212 252 return "M8"; 253 + 213 254 case InputKey.MouseButton6: 214 255 return "M9"; 256 + 215 257 case InputKey.MouseButton7: 216 258 return "M10"; 259 + 217 260 case InputKey.MouseButton8: 218 261 return "M11"; 262 + 219 263 case InputKey.MouseButton9: 220 264 return "M12"; 265 + 221 266 case InputKey.MouseWheelDown: 222 267 return "Wheel Down"; 268 + 223 269 case InputKey.MouseWheelUp: 224 270 return "Wheel Up"; 271 + 225 272 default: 226 273 return key.ToString(); 227 274 } ··· 233 280 { 234 281 case Key.RShift: 235 282 return InputKey.Shift; 283 + 236 284 case Key.RAlt: 237 285 return InputKey.Alt; 286 + 238 287 case Key.RControl: 239 288 return InputKey.Control; 289 + 240 290 case Key.RWin: 241 291 return InputKey.Super; 242 292 } ··· 304 354 if (!keys.Contains(iKey)) 305 355 keys.Add(iKey); 306 356 break; 357 + 307 358 default: 308 359 keys.Add(iKey); 309 360 break;
+1
osu.Framework/Input/CustomInputManager.cs
··· 20 20 if (!handler.Initialize(Host)) return; 21 21 22 22 int index = inputHandlers.BinarySearch(handler, new InputHandlerComparer()); 23 + 23 24 if (index < 0) 24 25 { 25 26 index = ~index;
+2 -2
osu.Framework/Input/GameWindowTextInput.cs
··· 8 8 { 9 9 public class GameWindowTextInput : ITextInputSource 10 10 { 11 - private readonly GameWindow window; 11 + private readonly IWindow window; 12 12 13 13 private string pending = string.Empty; 14 14 15 - public GameWindowTextInput(GameWindow window) 15 + public GameWindowTextInput(IWindow window) 16 16 { 17 17 this.window = window; 18 18 }
+1
osu.Framework/Input/Handlers/Joystick/OsuTKJoystickHandler.cs
··· 80 80 for (int index = 0; index < rawStates.Count; index++) 81 81 { 82 82 var rawState = rawStates[index]; 83 + 83 84 if (index < devices.Count) 84 85 { 85 86 devices[index].UpdateRawState(rawState);
+2
osu.Framework/Input/Handlers/Mouse/OsuTKMouseHandlerBase.cs
··· 36 36 else 37 37 { 38 38 var delta = state.Position - lastState.Position; 39 + 39 40 if (delta != Vector2.Zero) 40 41 { 41 42 PendingInputs.Enqueue(new MousePositionRelativeInput { Delta = delta }); ··· 46 47 if (lastState != null && state.WasActive) 47 48 { 48 49 var scrollDelta = state.Scroll - lastState.Scroll; 50 + 49 51 if (scrollDelta != Vector2.Zero) 50 52 { 51 53 PendingInputs.Enqueue(new MouseScrollRelativeInput { Delta = scrollDelta, IsPrecise = state.HasPreciseScroll });
+8
osu.Framework/Input/InputManager.cs
··· 141 141 { 142 142 case MouseButton.Left: 143 143 return new MouseLeftButtonEventManager(button); 144 + 144 145 default: 145 146 return new MouseMinorButtonEventManager(button); 146 147 } ··· 260 261 if (!(keyboardRepeatKey is Key key)) return; 261 262 262 263 keyboardRepeatTime -= Time.Elapsed; 264 + 263 265 while (keyboardRepeatTime < 0) 264 266 { 265 267 handleKeyDown(state, key, true); ··· 358 360 } 359 361 360 362 d.IsHovered = true; 363 + 361 364 if (d.TriggerEvent(new HoverEvent(state))) 362 365 { 363 366 hoverHandledDrawable = d; ··· 430 433 case MousePositionChangeEvent mousePositionChange: 431 434 HandleMousePositionChange(mousePositionChange); 432 435 return; 436 + 433 437 case MouseScrollChangeEvent mouseScrollChange: 434 438 HandleMouseScrollChange(mouseScrollChange); 435 439 return; 440 + 436 441 case ButtonStateChangeEvent<MouseButton> mouseButtonStateChange: 437 442 HandleMouseButtonStateChange(mouseButtonStateChange); 438 443 return; 444 + 439 445 case ButtonStateChangeEvent<Key> keyboardKeyStateChange: 440 446 HandleKeyboardKeyStateChange(keyboardKeyStateChange); 441 447 return; 448 + 442 449 case ButtonStateChangeEvent<JoystickButton> joystickButtonStateChange: 443 450 HandleJoystickButtonStateChange(joystickButtonStateChange); 444 451 return; ··· 518 525 { 519 526 //ensure we are visible 520 527 CompositeDrawable d = FocusedDrawable.Parent; 528 + 521 529 while (d != null) 522 530 { 523 531 if (!d.IsPresent || !d.IsAlive)
+1
osu.Framework/Input/MouseButtonEventManager.cs
··· 171 171 172 172 // only drawables up to the one that handled mouse down should handle mouse up 173 173 MouseDownInputQueue = positionalInputQueue; 174 + 174 175 if (handledBy != null) 175 176 { 176 177 var count = MouseDownInputQueue.IndexOf(handledBy) + 1;
+1
osu.Framework/Input/StateChanges/ButtonInput.cs
··· 65 65 public void Apply(InputState state, IInputStateChangeHandler handler) 66 66 { 67 67 var buttonStates = GetButtonStates(state); 68 + 68 69 foreach (var entry in Entries) 69 70 { 70 71 if (buttonStates.SetPressed(entry.Button, entry.IsPressed))
+1
osu.Framework/Input/StateChanges/MousePositionAbsoluteInput.cs
··· 24 24 public void Apply(InputState state, IInputStateChangeHandler handler) 25 25 { 26 26 var mouse = state.Mouse; 27 + 27 28 if (!mouse.IsPositionValid || mouse.Position != Position) 28 29 { 29 30 var lastPosition = mouse.IsPositionValid ? mouse.Position : Position;
+1
osu.Framework/Input/StateChanges/MousePositionRelativeInput.cs
··· 22 22 public void Apply(InputState state, IInputStateChangeHandler handler) 23 23 { 24 24 var mouse = state.Mouse; 25 + 25 26 if (mouse.IsPositionValid && Delta != Vector2.Zero) 26 27 { 27 28 var lastPosition = mouse.Position;
+1
osu.Framework/Input/StateChanges/MouseScrollRelativeInput.cs
··· 26 26 public void Apply(InputState state, IInputStateChangeHandler handler) 27 27 { 28 28 var mouse = state.Mouse; 29 + 29 30 if (Delta != Vector2.Zero) 30 31 { 31 32 var lastScroll = mouse.Scroll;
+1
osu.Framework/Logging/Logger.cs
··· 204 204 lock (static_sync_lock) 205 205 { 206 206 var nameLower = name.ToLower(); 207 + 207 208 if (!static_loggers.TryGetValue(nameLower, out Logger l)) 208 209 { 209 210 static_loggers[nameLower] = l = Enum.TryParse(name, true, out LoggingTarget target) ? new Logger(target) : new Logger(name);
+146
osu.Framework/MathUtils/Clipping/ConvexPolygonClipper.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using System.Runtime.CompilerServices; 6 + using osu.Framework.Graphics; 7 + using osu.Framework.Graphics.Primitives; 8 + using osuTK; 9 + 10 + namespace osu.Framework.MathUtils.Clipping 11 + { 12 + public readonly ref struct ConvexPolygonClipper<TClip, TSubject> 13 + where TClip : IConvexPolygon 14 + where TSubject : IConvexPolygon 15 + { 16 + private readonly TClip clipPolygon; 17 + private readonly TSubject subjectPolygon; 18 + 19 + public ConvexPolygonClipper(ref TClip clipPolygon, ref TSubject subjectPolygon) 20 + { 21 + this.clipPolygon = clipPolygon; 22 + this.subjectPolygon = subjectPolygon; 23 + } 24 + 25 + /// <summary> 26 + /// Determines the minimum buffer size required to clip the two polygons. 27 + /// </summary> 28 + /// <returns>The minimum buffer size required for <see cref="clipPolygon"/> to clip <see cref="subjectPolygon"/>.</returns> 29 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 30 + public int GetClipBufferSize() 31 + { 32 + // There can only be at most two intersections for each of the subject's vertices 33 + return subjectPolygon.GetVertices().Length * 2; 34 + } 35 + 36 + /// <summary> 37 + /// Clips <see cref="subjectPolygon"/> by <see cref="clipPolygon"/>. 38 + /// </summary> 39 + /// <returns>A clockwise-ordered set of vertices representing the result of clipping <see cref="subjectPolygon"/> by <see cref="clipPolygon"/>.</returns> 40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 41 + public Span<Vector2> Clip() => Clip(new Vector2[GetClipBufferSize()]); 42 + 43 + /// <summary> 44 + /// Clips <see cref="subjectPolygon"/> by <see cref="clipPolygon"/> using an intermediate buffer. 45 + /// </summary> 46 + /// <param name="buffer">The buffer to contain the clipped vertices. Must have a length of <see cref="GetClipBufferSize"/>.</param> 47 + /// <returns>A clockwise-ordered set of vertices representing the result of clipping <see cref="subjectPolygon"/> by <see cref="clipPolygon"/>.</returns> 48 + public Span<Vector2> Clip(in Span<Vector2> buffer) 49 + { 50 + if (buffer.Length < GetClipBufferSize()) 51 + { 52 + throw new ArgumentException($"Clip buffer must have a length of {GetClipBufferSize()}, but was {buffer.Length}." 53 + + "Use GetClipBufferSize() to calculate the size of the buffer.", nameof(buffer)); 54 + } 55 + 56 + ReadOnlySpan<Vector2> origSubjectVertices = subjectPolygon.GetVertices(); 57 + if (origSubjectVertices.Length == 0) 58 + return Span<Vector2>.Empty; 59 + 60 + ReadOnlySpan<Vector2> origClipVertices = clipPolygon.GetVertices(); 61 + if (origClipVertices.Length == 0) 62 + return Span<Vector2>.Empty; 63 + 64 + // Add the subject vertices to the buffer and immediately normalise them 65 + Span<Vector2> subjectVertices = getNormalised(origSubjectVertices, buffer.Slice(0, origSubjectVertices.Length), true); 66 + 67 + // Since the clip vertices aren't modified, we can use them as they are if they are normalised 68 + // However if they are not normalised, then we must add them to the buffer and normalise them there 69 + bool clipNormalised = Vector2Extensions.GetOrientation(origClipVertices) >= 0; 70 + Span<Vector2> clipBuffer = clipNormalised ? null : stackalloc Vector2[origClipVertices.Length]; 71 + ReadOnlySpan<Vector2> clipVertices = clipNormalised 72 + ? origClipVertices 73 + : getNormalised(origClipVertices, clipBuffer, false); 74 + 75 + // Number of vertices in the buffer that need to be tested against 76 + // This becomes the number of vertices in the resulting polygon after each clipping iteration 77 + int inputCount = subjectVertices.Length; 78 + 79 + // Process the clip edge connecting the last vertex to the first vertex 80 + inputCount = processClipEdge(new Line(clipVertices[clipVertices.Length - 1], clipVertices[0]), buffer, inputCount); 81 + 82 + // Process all other edges 83 + for (int c = 1; c < clipVertices.Length; c++) 84 + { 85 + if (inputCount == 0) 86 + break; 87 + 88 + inputCount = processClipEdge(new Line(clipVertices[c - 1], clipVertices[c]), buffer, inputCount); 89 + } 90 + 91 + return buffer.Slice(0, inputCount); 92 + } 93 + 94 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 95 + private int processClipEdge(in Line clipEdge, in Span<Vector2> buffer, in int inputCount) 96 + { 97 + // Temporary storage for the vertices from the buffer as the buffer gets altered 98 + Span<Vector2> inputVertices = stackalloc Vector2[buffer.Length]; 99 + 100 + // Store the original vertices (buffer will get altered) 101 + buffer.CopyTo(inputVertices); 102 + 103 + int outputCount = 0; 104 + 105 + // Process the edge connecting the last vertex with the first vertex 106 + outputVertices(ref inputVertices[inputCount - 1], ref inputVertices[0], clipEdge, buffer, ref outputCount); 107 + 108 + // Process all other vertices 109 + for (int i = 1; i < inputCount; i++) 110 + outputVertices(ref inputVertices[i - 1], ref inputVertices[i], clipEdge, buffer, ref outputCount); 111 + 112 + return outputCount; 113 + } 114 + 115 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 116 + private void outputVertices(ref Vector2 startVertex, ref Vector2 endVertex, in Line clipEdge, in Span<Vector2> buffer, ref int bufferIndex) 117 + { 118 + if (endVertex.InRightHalfPlaneOf(clipEdge)) 119 + { 120 + if (!startVertex.InRightHalfPlaneOf(clipEdge)) 121 + { 122 + clipEdge.TryIntersectWith(ref startVertex, ref endVertex, out var t); 123 + buffer[bufferIndex++] = clipEdge.At(t); 124 + } 125 + 126 + buffer[bufferIndex++] = endVertex; 127 + } 128 + else if (startVertex.InRightHalfPlaneOf(clipEdge)) 129 + { 130 + clipEdge.TryIntersectWith(ref startVertex, ref endVertex, out var t); 131 + buffer[bufferIndex++] = clipEdge.At(t); 132 + } 133 + } 134 + 135 + [MethodImpl(MethodImplOptions.AggressiveInlining)] 136 + private Span<Vector2> getNormalised(in ReadOnlySpan<Vector2> original, in Span<Vector2> bufferSlice, bool verify) 137 + { 138 + original.CopyTo(bufferSlice); 139 + 140 + if (!verify || Vector2Extensions.GetOrientation(original) < 0) 141 + bufferSlice.Reverse(); 142 + 143 + return bufferSlice; 144 + } 145 + } 146 + }
+23
osu.Framework/MathUtils/Interpolation.cs
··· 73 73 { 74 74 int n = points.Length; 75 75 double[] w = new double[n]; 76 + 76 77 for (int i = 0; i < n; i++) 77 78 { 78 79 w[i] = 1; ··· 257 258 case Easing.In: 258 259 case Easing.InQuad: 259 260 return time * time; 261 + 260 262 case Easing.Out: 261 263 case Easing.OutQuad: 262 264 return time * (2 - time); 265 + 263 266 case Easing.InOutQuad: 264 267 if (time < .5) return time * time * 2; 265 268 ··· 267 270 268 271 case Easing.InCubic: 269 272 return time * time * time; 273 + 270 274 case Easing.OutCubic: 271 275 return --time * time * time + 1; 276 + 272 277 case Easing.InOutCubic: 273 278 if (time < .5) return time * time * time * 4; 274 279 ··· 276 281 277 282 case Easing.InQuart: 278 283 return time * time * time * time; 284 + 279 285 case Easing.OutQuart: 280 286 return 1 - --time * time * time * time; 287 + 281 288 case Easing.InOutQuart: 282 289 if (time < .5) return time * time * time * time * 8; 283 290 ··· 285 292 286 293 case Easing.InQuint: 287 294 return time * time * time * time * time; 295 + 288 296 case Easing.OutQuint: 289 297 return --time * time * time * time * time + 1; 298 + 290 299 case Easing.InOutQuint: 291 300 if (time < .5) return time * time * time * time * time * 16; 292 301 ··· 294 303 295 304 case Easing.InSine: 296 305 return 1 - Math.Cos(time * Math.PI * .5); 306 + 297 307 case Easing.OutSine: 298 308 return Math.Sin(time * Math.PI * .5); 309 + 299 310 case Easing.InOutSine: 300 311 return .5 - .5 * Math.Cos(Math.PI * time); 301 312 302 313 case Easing.InExpo: 303 314 return Math.Pow(2, 10 * (time - 1)); 315 + 304 316 case Easing.OutExpo: 305 317 return -Math.Pow(2, -10 * time) + 1; 318 + 306 319 case Easing.InOutExpo: 307 320 if (time < .5) return .5 * Math.Pow(2, 20 * time - 10); 308 321 ··· 310 323 311 324 case Easing.InCirc: 312 325 return 1 - Math.Sqrt(1 - time * time); 326 + 313 327 case Easing.OutCirc: 314 328 return Math.Sqrt(1 - --time * time); 329 + 315 330 case Easing.InOutCirc: 316 331 if ((time *= 2) < 1) return .5 - .5 * Math.Sqrt(1 - time * time); 317 332 ··· 319 334 320 335 case Easing.InElastic: 321 336 return -Math.Pow(2, -10 + 10 * time) * Math.Sin((1 - elastic_const2 - time) * elastic_const); 337 + 322 338 case Easing.OutElastic: 323 339 return Math.Pow(2, -10 * time) * Math.Sin((time - elastic_const2) * elastic_const) + 1; 340 + 324 341 case Easing.OutElasticHalf: 325 342 return Math.Pow(2, -10 * time) * Math.Sin((.5 * time - elastic_const2) * elastic_const) + 1; 343 + 326 344 case Easing.OutElasticQuarter: 327 345 return Math.Pow(2, -10 * time) * Math.Sin((.25 * time - elastic_const2) * elastic_const) + 1; 346 + 328 347 case Easing.InOutElastic: 329 348 if ((time *= 2) < 1) 330 349 return -.5 * Math.Pow(2, -10 + 10 * time) * Math.Sin((1 - elastic_const2 * 1.5 - time) * elastic_const / 1.5); ··· 333 352 334 353 case Easing.InBack: 335 354 return time * time * ((back_const + 1) * time - back_const); 355 + 336 356 case Easing.OutBack: 337 357 return --time * time * ((back_const + 1) * time + back_const) + 1; 358 + 338 359 case Easing.InOutBack: 339 360 if ((time *= 2) < 1) return .5 * time * time * ((back_const2 + 1) * time - back_const2); 340 361 ··· 350 371 return 1 - (7.5625 * (time -= 2.25 * bounce_const) * time + .9375); 351 372 352 373 return 1 - (7.5625 * (time -= 2.625 * bounce_const) * time + .984375); 374 + 353 375 case Easing.OutBounce: 354 376 if (time < bounce_const) 355 377 return 7.5625 * time * time; ··· 359 381 return 7.5625 * (time -= 2.25 * bounce_const) * time + .9375; 360 382 361 383 return 7.5625 * (time -= 2.625 * bounce_const) * time + .984375; 384 + 362 385 case Easing.InOutBounce: 363 386 if (time < .5) return .5 - .5 * ApplyEasing(Easing.OutBounce, 1 - time * 2); 364 387
+4
osu.Framework/MathUtils/PathApproximator.cs
··· 52 52 while (toFlatten.Count > 0) 53 53 { 54 54 Vector2[] parent = toFlatten.Pop(); 55 + 55 56 if (bezierIsFlatEnough(parent)) 56 57 { 57 58 // If the control points we currently operate on are sufficiently "flat", we use ··· 155 156 // AC B lies. 156 157 Vector2 orthoAtoC = c - a; 157 158 orthoAtoC = new Vector2(orthoAtoC.Y, -orthoAtoC.X); 159 + 158 160 if (Vector2.Dot(orthoAtoC, b - a) < 0) 159 161 { 160 162 dir = -dir; ··· 211 213 212 214 float minX = controlPoints[0].X; 213 215 float maxX = controlPoints[0].X; 216 + 214 217 for (int i = 1; i < controlPoints.Length; i++) 215 218 { 216 219 minX = Math.Min(minX, controlPoints[i].X); ··· 293 296 l[count + i] = r[i + 1]; 294 297 295 298 output.Add(controlPoints[0]); 299 + 296 300 for (int i = 1; i < count - 1; ++i) 297 301 { 298 302 int index = 2 * i;
+3
osu.Framework/Physics/RigidBodyContainer.cs
··· 127 127 float usableLength = Math.Max(length - 2 * cornerRadius, 0); 128 128 129 129 Vector2 normal = (b - a).PerpendicularRight.Normalized(); 130 + 130 131 for (int j = 0; j < amount_side_steps; ++j) 131 132 { 132 133 Vertices.Add(a + dir * (cornerRadius + j * usableLength / (amount_side_steps - 1))); ··· 135 136 } 136 137 137 138 const int amount_corner_steps = 10; 139 + 138 140 if (cornerRadius > 0) 139 141 { 140 142 // Rounded corners ··· 207 209 return false; 208 210 209 211 bool didCollide = false; 212 + 210 213 for (int i = 0; i < Vertices.Count; ++i) 211 214 { 212 215 if (other.BodyContains(Vector2Extensions.Transform(Vertices[i], SimulationToScreenSpace)))
+4
osu.Framework/Platform/DesktopGameWindow.cs
··· 176 176 case Input.ConfineMouseMode.Fullscreen: 177 177 confine = WindowMode.Value != Configuration.WindowMode.Windowed; 178 178 break; 179 + 179 180 case Input.ConfineMouseMode.Always: 180 181 confine = true; 181 182 break; ··· 202 203 try 203 204 { 204 205 inWindowModeTransition = true; 206 + 205 207 switch (newMode) 206 208 { 207 209 case Configuration.WindowMode.Fullscreen: ··· 210 212 211 213 WindowState = WindowState.Fullscreen; 212 214 break; 215 + 213 216 case Configuration.WindowMode.Borderless: 214 217 if (lastFullscreenDisplay != null) 215 218 RestoreResolution(lastFullscreenDisplay); ··· 222 225 ClientSize = new Size(currentDisplay.Bounds.Width + 1, currentDisplay.Bounds.Height + 1); 223 226 Location = currentDisplay.Bounds.Location; 224 227 break; 228 + 225 229 case Configuration.WindowMode.Windowed: 226 230 if (lastFullscreenDisplay != null) 227 231 RestoreResolution(lastFullscreenDisplay);
+51 -18
osu.Framework/Platform/GameHost.cs
··· 42 42 { 43 43 public abstract class GameHost : IIpcHost, IDisposable 44 44 { 45 - public GameWindow Window { get; protected set; } 45 + public IWindow Window { get; protected set; } 46 46 47 - private FrameworkDebugConfigManager debugConfig; 47 + protected FrameworkDebugConfigManager DebugConfig { get; private set; } 48 48 49 - private FrameworkConfigManager config; 49 + protected FrameworkConfigManager Config { get; private set; } 50 50 51 51 /// <summary> 52 - /// Whether the <see cref="GameWindow"/> is active (in the foreground). 52 + /// Whether the <see cref="IWindow"/> is active (in the foreground). 53 53 /// </summary> 54 54 public readonly IBindable<bool> IsActive = new Bindable<bool>(true); 55 55 ··· 271 271 272 272 if (Window == null) 273 273 { 274 - var windowedSize = config.Get<Size>(FrameworkSetting.WindowedSize); 274 + var windowedSize = Config.Get<Size>(FrameworkSetting.WindowedSize); 275 275 Root.Size = new Vector2(windowedSize.Width, windowedSize.Height); 276 276 } 277 277 else if (Window.WindowState != WindowState.Minimized) ··· 326 326 using (drawMonitor.BeginCollecting(PerformanceCollectionType.GLReset)) 327 327 GLWrapper.Reset(new Vector2(Window.ClientSize.Width, Window.ClientSize.Height)); 328 328 329 + if (!bypassFrontToBackPass.Value) 330 + { 331 + var depthValue = new DepthValue(); 332 + 333 + GLWrapper.PushDepthInfo(DepthInfo.Default); 334 + 335 + // Front pass 336 + buffer.Object.DrawOpaqueInteriorSubTree(depthValue, null); 337 + 338 + GLWrapper.PopDepthInfo(); 339 + 340 + // The back pass doesn't write depth, but needs to depth test properly 341 + GLWrapper.PushDepthInfo(new DepthInfo(true, false)); 342 + } 343 + else 344 + { 345 + // Disable depth testing 346 + GLWrapper.PushDepthInfo(new DepthInfo()); 347 + } 348 + 349 + // Back pass 329 350 buffer.Object.Draw(null); 351 + 352 + GLWrapper.PopDepthInfo(); 353 + 330 354 lastDrawFrameId = buffer.FrameId; 331 355 break; 332 356 } ··· 462 486 463 487 ExecutionState = ExecutionState.Running; 464 488 465 - setupConfig(game.GetFrameworkConfigDefaults()); 489 + SetupConfig(game.GetFrameworkConfigDefaults()); 466 490 467 491 if (Window != null) 468 492 { 469 - Window.SetupWindow(config); 493 + Window.SetupWindow(Config); 470 494 Window.Title = $@"osu!framework (running ""{Name}"")"; 471 495 472 496 IsActive.BindTo(Window.IsActive); ··· 552 576 h.Dispose(); 553 577 554 578 AvailableInputHandlers = CreateAvailableInputHandlers(); 579 + 555 580 foreach (var handler in AvailableInputHandlers) 556 581 { 557 582 if (!handler.Initialize(this)) ··· 632 657 633 658 private InvokeOnDisposal inputPerformanceCollectionPeriod; 634 659 660 + private Bindable<bool> bypassFrontToBackPass; 661 + 635 662 private Bindable<GCLatencyMode> activeGCMode; 636 663 637 664 private Bindable<FrameSync> frameSyncMode; ··· 643 670 644 671 private Bindable<WindowMode> windowMode; 645 672 646 - private void setupConfig(IDictionary<FrameworkSetting, object> gameDefaults) 673 + protected virtual void SetupConfig(IDictionary<FrameworkSetting, object> gameDefaults) 647 674 { 648 675 var hostDefaults = new Dictionary<FrameworkSetting, object> 649 676 { ··· 657 684 hostDefaults[d.Key] = d.Value; 658 685 } 659 686 660 - Dependencies.Cache(debugConfig = new FrameworkDebugConfigManager()); 661 - Dependencies.Cache(config = new FrameworkConfigManager(Storage, hostDefaults)); 687 + Dependencies.Cache(DebugConfig = new FrameworkDebugConfigManager()); 688 + Dependencies.Cache(Config = new FrameworkConfigManager(Storage, hostDefaults)); 662 689 663 - windowMode = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode); 690 + windowMode = Config.GetBindable<WindowMode>(FrameworkSetting.WindowMode); 664 691 665 692 windowMode.BindValueChanged(mode => 666 693 { ··· 671 698 windowMode.Value = Window.DefaultWindowMode; 672 699 }, true); 673 700 674 - activeGCMode = debugConfig.GetBindable<GCLatencyMode>(DebugSetting.ActiveGCMode); 701 + activeGCMode = DebugConfig.GetBindable<GCLatencyMode>(DebugSetting.ActiveGCMode); 675 702 activeGCMode.ValueChanged += e => { GCSettings.LatencyMode = IsActive.Value ? e.NewValue : GCLatencyMode.Interactive; }; 676 703 677 - frameSyncMode = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync); 704 + frameSyncMode = Config.GetBindable<FrameSync>(FrameworkSetting.FrameSync); 678 705 frameSyncMode.ValueChanged += e => 679 706 { 680 707 float refreshRate = DisplayDevice.Default?.RefreshRate ?? 0; ··· 693 720 drawLimiter = int.MaxValue; 694 721 updateLimiter *= 2; 695 722 break; 723 + 696 724 case FrameSync.Limit2x: 697 725 drawLimiter *= 2; 698 726 updateLimiter *= 2; 699 727 break; 728 + 700 729 case FrameSync.Limit4x: 701 730 drawLimiter *= 4; 702 731 updateLimiter *= 4; 703 732 break; 733 + 704 734 case FrameSync.Limit8x: 705 735 drawLimiter *= 8; 706 736 updateLimiter *= 8; 707 737 break; 738 + 708 739 case FrameSync.Unlimited: 709 740 drawLimiter = updateLimiter = int.MaxValue; 710 741 break; ··· 714 745 if (UpdateThread != null) UpdateThread.ActiveHz = updateLimiter; 715 746 }; 716 747 717 - ignoredInputHandlers = config.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers); 748 + ignoredInputHandlers = Config.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers); 718 749 ignoredInputHandlers.ValueChanged += e => 719 750 { 720 751 var configIgnores = e.NewValue.Split(' ').Where(s => !string.IsNullOrWhiteSpace(s)); ··· 738 769 } 739 770 }; 740 771 741 - cursorSensitivity = config.GetBindable<double>(FrameworkSetting.CursorSensitivity); 772 + cursorSensitivity = Config.GetBindable<double>(FrameworkSetting.CursorSensitivity); 742 773 743 - config.BindWith(FrameworkSetting.PerformanceLogging, performanceLogging); 774 + Config.BindWith(FrameworkSetting.PerformanceLogging, performanceLogging); 744 775 performanceLogging.BindValueChanged(logging => threads.ForEach(t => t.Monitor.EnablePerformanceProfiling = logging.NewValue), true); 776 + 777 + bypassFrontToBackPass = DebugConfig.GetBindable<bool>(DebugSetting.BypassFrontToBackPass); 745 778 } 746 779 747 780 private void setVSyncMode() ··· 781 814 Root?.Dispose(); 782 815 Root = null; 783 816 784 - config?.Dispose(); 785 - debugConfig?.Dispose(); 817 + Config?.Dispose(); 818 + DebugConfig?.Dispose(); 786 819 787 820 Window?.Dispose(); 788 821
+9 -4
osu.Framework/Platform/GameWindow.cs
··· 15 15 using System.Drawing; 16 16 using JetBrains.Annotations; 17 17 using osu.Framework.Bindables; 18 + using osu.Framework.Graphics; 18 19 using Icon = osuTK.Icon; 19 20 20 21 namespace osu.Framework.Platform 21 22 { 22 - public abstract class GameWindow : IGameWindow 23 + public abstract class GameWindow : IWindow 23 24 { 24 25 /// <summary> 25 26 /// The <see cref="IGraphicsContext"/> associated with this <see cref="GameWindow"/>. ··· 86 87 87 88 FocusedChanged += (o, e) => isActive.Value = Focused; 88 89 89 - SupportedWindowModes.AddRange(DefaultSupportedWindowModes); 90 + supportedWindowModes.AddRange(DefaultSupportedWindowModes); 90 91 91 92 bool firstUpdate = true; 92 93 UpdateFrame += (o, e) => ··· 228 229 /// devices. This usually corresponds to areas of the screen hidden under notches and rounded corners. 229 230 /// The safe area insets are provided by the operating system and dynamically change as the user rotates the device. 230 231 /// </summary> 231 - public readonly BindableMarginPadding SafeAreaPadding = new BindableMarginPadding(); 232 + public virtual IBindable<MarginPadding> SafeAreaPadding { get; } = new BindableMarginPadding(); 232 233 233 - public readonly BindableList<WindowMode> SupportedWindowModes = new BindableList<WindowMode>(); 234 + private readonly BindableList<WindowMode> supportedWindowModes = new BindableList<WindowMode>(); 235 + 236 + public IBindableList<WindowMode> SupportedWindowModes => supportedWindowModes; 234 237 235 238 public virtual WindowMode DefaultWindowMode => SupportedWindowModes.First(); 236 239 ··· 249 252 case Configuration.WindowMode.Windowed: 250 253 currentValue = Configuration.WindowMode.Borderless; 251 254 break; 255 + 252 256 case Configuration.WindowMode.Borderless: 253 257 currentValue = Configuration.WindowMode.Fullscreen; 254 258 break; 259 + 255 260 case Configuration.WindowMode.Fullscreen: 256 261 currentValue = Configuration.WindowMode.Windowed; 257 262 break;
+91
osu.Framework/Platform/IWindow.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using System.Collections.Generic; 6 + using JetBrains.Annotations; 7 + using osu.Framework.Bindables; 8 + using osu.Framework.Configuration; 9 + using osu.Framework.Graphics; 10 + using osuTK; 11 + using osuTK.Platform; 12 + 13 + namespace osu.Framework.Platform 14 + { 15 + /// <summary> 16 + /// Interface representation of the game window, intended to hide any implementation-specific code. 17 + /// Currently inherits from osuTK; this will be removed as part of the <see cref="GameWindow"/> refactor. 18 + /// </summary> 19 + public interface IWindow : IGameWindow 20 + { 21 + /// <summary> 22 + /// Cycles through the available <see cref="WindowMode"/>s as determined by <see cref="SupportedWindowModes"/>. 23 + /// </summary> 24 + void CycleMode(); 25 + 26 + /// <summary> 27 + /// Configures the <see cref="IWindow"/> based on the provided <see cref="FrameworkConfigManager"/>. 28 + /// </summary> 29 + /// <param name="config">The configuration manager to use.</param> 30 + void SetupWindow(FrameworkConfigManager config); 31 + 32 + /// <summary> 33 + /// Return value decides whether we should intercept and cancel this exit (if possible). 34 + /// </summary> 35 + [CanBeNull] 36 + event Func<bool> ExitRequested; 37 + 38 + /// <summary> 39 + /// Invoked when the <see cref="IWindow"/> has closed. 40 + /// </summary> 41 + [CanBeNull] 42 + event Action Exited; 43 + 44 + /// <summary> 45 + /// Whether the OS cursor is currently contained within the game window. 46 + /// </summary> 47 + bool CursorInWindow { get; } 48 + 49 + /// <summary> 50 + /// Controls the state of the OS cursor. 51 + /// </summary> 52 + CursorState CursorState { get; set; } 53 + 54 + /// <summary> 55 + /// Controls the vertical sync mode of the screen. 56 + /// </summary> 57 + VSyncMode VSync { get; set; } 58 + 59 + /// <summary> 60 + /// Returns the default <see cref="WindowMode"/> for the implementation. 61 + /// </summary> 62 + WindowMode DefaultWindowMode { get; } 63 + 64 + /// <summary> 65 + /// Gets the <see cref="DisplayDevice"/> that this window is currently on. 66 + /// </summary> 67 + DisplayDevice CurrentDisplay { get; } 68 + 69 + /// <summary> 70 + /// Whether this <see cref="IWindow"/> is active (in the foreground). 71 + /// </summary> 72 + IBindable<bool> IsActive { get; } 73 + 74 + /// <summary> 75 + /// Provides a <see cref="IBindable{MarginPadding}"/> that can be used to keep track of the "safe area" insets on mobile 76 + /// devices. This usually corresponds to areas of the screen hidden under notches and rounded corners. 77 + /// The safe area insets are provided by the operating system and dynamically change as the user rotates the device. 78 + /// </summary> 79 + IBindable<MarginPadding> SafeAreaPadding { get; } 80 + 81 + /// <summary> 82 + /// The <see cref="WindowMode"/>s supported by this <see cref="IWindow"/> implementation. 83 + /// </summary> 84 + IBindableList<WindowMode> SupportedWindowModes { get; } 85 + 86 + /// <summary> 87 + /// Available resolutions for full-screen display. 88 + /// </summary> 89 + IEnumerable<DisplayResolution> AvailableResolutions { get; } 90 + } 91 + }
+1
osu.Framework/Platform/Linux/Native/Library.cs
··· 22 22 public static void Load(string library, LoadFlags flags) 23 23 { 24 24 var paths = (string)AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES"); 25 + 25 26 foreach (var path in paths.Split(':')) 26 27 { 27 28 if (dlopen(Path.Combine(path, library), flags) != IntPtr.Zero)
+1
osu.Framework/Platform/MacOS/MacOSGameWindow.cs
··· 130 130 { 131 131 // update the window mode if we have an update queued 132 132 WindowMode? mode = pendingWindowMode; 133 + 133 134 if (mode.HasValue) 134 135 { 135 136 pendingWindowMode = null;
+1 -1
osu.Framework/Platform/MacOS/MacOSTextInput.cs
··· 16 16 17 17 private static bool isCapsLockOn => (Cocoa.CGEventSourceFlagsState(event_source_state_hid_system_state) & event_flag_mask_alpha_shift) != 0; 18 18 19 - public MacOSTextInput(GameWindow window) 19 + public MacOSTextInput(IWindow window) 20 20 : base(window) 21 21 { 22 22 }
+1
osu.Framework/Platform/NativeStorage.cs
··· 79 79 if (!File.Exists(path)) return null; 80 80 81 81 return File.Open(path, FileMode.Open, access, FileShare.Read); 82 + 82 83 default: 83 84 return File.Open(path, mode, access); 84 85 }
+3
osu.Framework/Platform/TcpIpcProvider.cs
··· 25 25 public bool Bind() 26 26 { 27 27 listener = new TcpListener(IPAddress.Loopback, ipc_port); 28 + 28 29 try 29 30 { 30 31 listener.Start(); ··· 45 46 public async Task StartAsync() 46 47 { 47 48 var token = cancelListener.Token; 49 + 48 50 try 49 51 { 50 52 while (!token.IsCancellationRequested) ··· 101 103 using (var client = new TcpClient()) 102 104 { 103 105 await client.ConnectAsync(IPAddress.Loopback, ipc_port); 106 + 104 107 using (var stream = client.GetStream()) 105 108 { 106 109 var str = JsonConvert.SerializeObject(message, Formatting.None);
+1
osu.Framework/Platform/Windows/Native/IconGroup.cs
··· 83 83 { 84 84 int requested = Math.Min(width, height); 85 85 int closest = -1; 86 + 86 87 for (int i = 0; i < iconDir.Count; i++) 87 88 { 88 89 var entry = iconDir.Entries[i];
+14
osu.Framework/Resources/Shaders/sh_Backbuffer_Internal.h
··· 1 + // Automatically included for every vertex shader. 2 + 3 + attribute float m_BackbufferDrawDepth; 4 + 5 + // Whether the backbuffer is currently being drawn to 6 + uniform bool g_BackbufferDraw; 7 + 8 + void main() 9 + { 10 + {{ real_main }}(); // Invoke real main func 11 + 12 + if (g_BackbufferDraw) 13 + gl_Position.z = m_BackbufferDrawDepth; 14 + }
+29 -28
osu.Framework/Resources/Shaders/sh_Texture2D.vs
··· 1 - #include "sh_Utils.h" 2 - 3 - attribute vec2 m_Position; 4 - attribute vec4 m_Colour; 5 - attribute vec2 m_TexCoord; 6 - attribute vec4 m_TexRect; 7 - attribute vec2 m_BlendRange; 8 - 9 - varying vec2 v_MaskingPosition; 10 - varying vec4 v_Colour; 11 - varying vec2 v_TexCoord; 12 - varying vec4 v_TexRect; 13 - varying vec2 v_BlendRange; 14 - 15 - uniform mat4 g_ProjMatrix; 16 - uniform mat3 g_ToMaskingSpace; 17 - 18 - void main(void) 19 - { 20 - // Transform to position to masking space. 21 - vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0); 22 - v_MaskingPosition = maskingPos.xy / maskingPos.z; 23 - 24 - v_Colour = m_Colour; 25 - v_TexCoord = m_TexCoord; 26 - v_TexRect = m_TexRect; 27 - v_BlendRange = m_BlendRange; 28 - gl_Position = g_ProjMatrix * vec4(m_Position, 1.0, 1.0); 1 + #include "sh_Utils.h" 2 + 3 + attribute vec2 m_Position; 4 + attribute vec4 m_Colour; 5 + attribute vec2 m_TexCoord; 6 + attribute vec4 m_TexRect; 7 + attribute vec2 m_BlendRange; 8 + 9 + varying vec2 v_MaskingPosition; 10 + varying vec4 v_Colour; 11 + varying vec2 v_TexCoord; 12 + varying vec4 v_TexRect; 13 + varying vec2 v_BlendRange; 14 + 15 + uniform mat4 g_ProjMatrix; 16 + uniform mat3 g_ToMaskingSpace; 17 + 18 + void main(void) 19 + { 20 + // Transform from screen space to masking space. 21 + vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0); 22 + v_MaskingPosition = maskingPos.xy / maskingPos.z; 23 + 24 + v_Colour = m_Colour; 25 + v_TexCoord = m_TexCoord; 26 + v_TexRect = m_TexRect; 27 + v_BlendRange = m_BlendRange; 28 + 29 + gl_Position = g_ProjMatrix * vec4(m_Position, 1.0, 1.0); 29 30 }
+1
osu.Framework/RuntimeInfo.cs
··· 51 51 if (OS == Platform.Windows) 52 52 { 53 53 IntPtr hModule = GetModuleHandle(@"ntdll.dll"); 54 + 54 55 if (hModule == IntPtr.Zero) 55 56 IsWine = false; 56 57 else
+4
osu.Framework/Screens/IScreen.cs
··· 94 94 case null: 95 95 onFail?.Invoke(); 96 96 return; 97 + 97 98 case ScreenStack stack: 98 99 onRoot(stack); 99 100 break; 101 + 100 102 default: 101 103 runOnRoot(current.Parent, onRoot, onFail); 102 104 break; ··· 112 114 return onFail.Invoke(); 113 115 114 116 return default; 117 + 115 118 case ScreenStack stack: 116 119 return onRoot(stack); 120 + 117 121 default: 118 122 return runOnRoot(current.Parent, onRoot, onFail); 119 123 }
+1
osu.Framework/Statistics/PerformanceMonitor.cs
··· 120 120 for (int i = 0; i < lastAmountGarbageCollects.Length; ++i) 121 121 { 122 122 int amountCollections = GC.CollectionCount(i); 123 + 123 124 if (lastAmountGarbageCollects[i] != amountCollections) 124 125 { 125 126 lastAmountGarbageCollects[i] = amountCollections;
+2
osu.Framework/Testing/DrawFrameRecordingContainer.cs
··· 41 41 currentFrame.Value = currentFrame.MaxValue = 0; 42 42 43 43 return base.GenerateDrawNodeSubtree(frame, treeIndex, forceNewDrawNode); 44 + 44 45 case RecordState.Recording: 45 46 var node = base.GenerateDrawNodeSubtree(frame, treeIndex, true); 46 47 ··· 50 51 currentFrame.Value = currentFrame.MaxValue = recordedFrames.Count - 1; 51 52 52 53 return node; 54 + 53 55 case RecordState.Stopped: 54 56 return recordedFrames[currentFrame.Value]; 55 57 }
+2
osu.Framework/Testing/Drawables/Sections/ToolbarRecordSection.cs
··· 109 109 recordButton.Text = "record"; 110 110 playbackControls.Hide(); 111 111 break; 112 + 112 113 case RecordState.Recording: 113 114 recordButton.Text = "stop"; 114 115 playbackControls.Hide(); 115 116 break; 117 + 116 118 case RecordState.Stopped: 117 119 recordButton.Text = "reset"; 118 120 playbackControls.Show();
+1
osu.Framework/Testing/Drawables/TestSceneButton.cs
··· 83 83 text.AddText(TestScene.RemovePrefix(test.Name)); 84 84 85 85 var description = test.GetCustomAttribute<DescriptionAttribute>()?.Description; 86 + 86 87 if (description != null) 87 88 { 88 89 text.NewLine();
+2
osu.Framework/Testing/Input/ManualInputManager.cs
··· 177 177 case MouseButton.Left: 178 178 left.FadeIn(); 179 179 break; 180 + 180 181 case MouseButton.Right: 181 182 right.FadeIn(); 182 183 break; ··· 193 194 case MouseButton.Left: 194 195 left.FadeOut(500); 195 196 break; 197 + 196 198 case MouseButton.Right: 197 199 right.FadeOut(500); 198 200 break;
+91 -8
osu.Framework/Testing/ManualInputManagerTestScene.cs
··· 5 5 using osu.Framework.Extensions.IEnumerableExtensions; 6 6 using osu.Framework.Graphics; 7 7 using osu.Framework.Graphics.Containers; 8 + using osu.Framework.Graphics.Shapes; 9 + using osu.Framework.Graphics.Sprites; 10 + using osu.Framework.Graphics.UserInterface; 8 11 using osu.Framework.Testing.Input; 9 12 using osuTK; 13 + using osuTK.Graphics; 10 14 11 15 namespace osu.Framework.Testing 12 16 { ··· 28 32 /// </summary> 29 33 protected ManualInputManager InputManager { get; } 30 34 35 + private readonly Button buttonTest; 36 + private readonly Button buttonLocal; 37 + 38 + [SetUp] 39 + public virtual void SetUp() => ResetInput(); 40 + 31 41 protected ManualInputManagerTestScene() 32 42 { 33 - base.Content.Add(InputManager = new ManualInputManager()); 34 - AddStep("return user input", () => InputManager.UseParentInput = true); 43 + base.Content.AddRange(new Drawable[] 44 + { 45 + InputManager = new ManualInputManager 46 + { 47 + UseParentInput = true, 48 + }, 49 + new Container 50 + { 51 + Depth = float.MinValue, 52 + AutoSizeAxes = Axes.Both, 53 + Anchor = Anchor.TopRight, 54 + Origin = Anchor.TopRight, 55 + Margin = new MarginPadding(5), 56 + CornerRadius = 5, 57 + Masking = true, 58 + Children = new Drawable[] 59 + { 60 + new Box 61 + { 62 + Colour = Color4.Black, 63 + RelativeSizeAxes = Axes.Both, 64 + Alpha = 0.5f, 65 + }, 66 + new FillFlowContainer 67 + { 68 + AutoSizeAxes = Axes.Both, 69 + Direction = FillDirection.Vertical, 70 + Margin = new MarginPadding(5), 71 + Spacing = new Vector2(5), 72 + Children = new Drawable[] 73 + { 74 + new SpriteText 75 + { 76 + Anchor = Anchor.TopCentre, 77 + Origin = Anchor.TopCentre, 78 + Text = "Input Priority" 79 + }, 80 + new FillFlowContainer 81 + { 82 + AutoSizeAxes = Axes.Both, 83 + Anchor = Anchor.TopCentre, 84 + Origin = Anchor.TopCentre, 85 + Margin = new MarginPadding(5), 86 + Spacing = new Vector2(5), 87 + Direction = FillDirection.Horizontal, 88 + 89 + Children = new Drawable[] 90 + { 91 + buttonLocal = new Button 92 + { 93 + Text = "local", 94 + Size = new Vector2(50, 30), 95 + Action = returnUserInput 96 + }, 97 + buttonTest = new Button 98 + { 99 + Text = "test", 100 + Size = new Vector2(50, 30), 101 + Action = returnTestInput 102 + }, 103 + } 104 + }, 105 + } 106 + }, 107 + } 108 + }, 109 + }); 110 + } 111 + 112 + protected override void Update() 113 + { 114 + base.Update(); 115 + 116 + buttonTest.Enabled.Value = InputManager.UseParentInput; 117 + buttonLocal.Enabled.Value = !InputManager.UseParentInput; 35 118 } 36 119 37 120 /// <summary> ··· 39 122 /// </summary> 40 123 protected void ResetInput() 41 124 { 42 - InputManager.UseParentInput = false; 125 + InputManager.UseParentInput = true; 43 126 var currentState = InputManager.CurrentState; 44 127 45 128 var mouse = currentState.Mouse; ··· 54 137 joystick.Buttons.ForEach(InputManager.ReleaseJoystickButton); 55 138 } 56 139 57 - [SetUp] 58 - public virtual void SetUp() 59 - { 60 - ResetInput(); 61 - } 140 + private void returnUserInput() => 141 + InputManager.UseParentInput = true; 142 + 143 + private void returnTestInput() => 144 + InputManager.UseParentInput = false; 62 145 } 63 146 }
+3
osu.Framework/Testing/TestBrowser.cs
··· 236 236 backgroundCompiler.CompilationStarted += compileStarted; 237 237 backgroundCompiler.CompilationFinished += compileFinished; 238 238 backgroundCompiler.CompilationFailed += compileFailed; 239 + 239 240 try 240 241 { 241 242 backgroundCompiler.Start(); ··· 353 354 if (leftContainer.Width == 0) toggleTestList(); 354 355 GetContainingInputManager().ChangeFocus(searchTextBox); 355 356 return true; 357 + 356 358 case TestBrowserAction.Reload: 357 359 LoadTest(CurrentTest.GetType()); 358 360 return true; 361 + 359 362 case TestBrowserAction.ToggleTestList: 360 363 toggleTestList(); 361 364 return true;
+1
osu.Framework/Testing/TestScene.cs
··· 46 46 throw new InvalidCastException($"The test runner must be a {nameof(Game)}."); 47 47 48 48 runTask = Task.Factory.StartNew(() => host.Run(game), TaskCreationOptions.LongRunning); 49 + 49 50 while (!game.IsLoaded) 50 51 { 51 52 checkForErrors();
+3 -2
osu.Framework/Threading/ThreadedTaskScheduler.cs
··· 24 24 /// Initializes a new instance of the StaTaskScheduler class with the specified concurrency level. 25 25 /// </summary> 26 26 /// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param> 27 - public ThreadedTaskScheduler(int numberOfThreads) 27 + /// <param name="name">The thread name to give threads in this pool.</param> 28 + public ThreadedTaskScheduler(int numberOfThreads, string name) 28 29 { 29 30 if (numberOfThreads < 1) 30 31 throw new ArgumentOutOfRangeException(nameof(numberOfThreads)); ··· 35 36 { 36 37 var thread = new Thread(processTasks) 37 38 { 38 - Name = "LoadComponentThreadPool", 39 + Name = $"ThreadedTaskScheduler ({name})", 39 40 IsBackground = true 40 41 }; 41 42
+1 -1
osu.Framework/osu.Framework.csproj
··· 43 43 <PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0006" /> 44 44 <PackageReference Include="System.Drawing.Common" Version="4.5.1" /> 45 45 <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.0.0" /> 46 - <PackageReference Include="ppy.osuTK.NS20" Version="1.0.66" /> 47 46 <PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" /> 48 47 <PackageReference Include="ppy.Microsoft.Diagnostics.Runtime" Version="0.9.180305.1" /> 49 48 <PackageReference Include="NUnit" Version="3.12.0" /> ··· 54 53 <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" /> 55 54 <PackageReference Include="System.Reflection.Emit.ILGeneration" Version="4.3.0" /> 56 55 <PackageReference Include="JetBrains.Annotations" Version="2019.1.1" /> 56 + <PackageReference Include="ppy.osuTK.NS20" Version="1.0.101" /> 57 57 </ItemGroup> 58 58 <ItemGroup> 59 59 <Folder Include="Resources\Fonts\FontAwesome" />