diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/SampleGame.Android/AndroidManifest.xml b/SampleGame.Android/AndroidManifest.xml
index e69de29bb..b99f1e558 100644
--- a/SampleGame.Android/AndroidManifest.xml
+++ b/SampleGame.Android/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/SampleGame.Android/SampleGame.Android.csproj b/SampleGame.Android/SampleGame.Android.csproj
index e69de29bb..ab0f71c35 100644
--- a/SampleGame.Android/SampleGame.Android.csproj
+++ b/SampleGame.Android/SampleGame.Android.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0-android
+ Exe
+ SampleGame.Android
+ SampleGame.Android
+
+
+
+
+
+
diff --git a/SampleGame.Android/SampleGameActivity.cs b/SampleGame.Android/SampleGameActivity.cs
index e69de29bb..bdb800d5d 100644
--- a/SampleGame.Android/SampleGameActivity.cs
+++ b/SampleGame.Android/SampleGameActivity.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Android.App;
+using osu.Framework;
+using osu.Framework.Android;
+
+namespace SampleGame.Android
+{
+ [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
+ public class SampleGameActivity : AndroidGameActivity
+ {
+ protected override Game CreateGame() => new SampleGameGame();
+ }
+}
diff --git a/SampleGame.Desktop/Program.cs b/SampleGame.Desktop/Program.cs
new file mode 100644
index 000000000..4756edbaa
--- /dev/null
+++ b/SampleGame.Desktop/Program.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework;
+using osu.Framework.Platform;
+
+namespace SampleGame.Desktop
+{
+ public static class Program
+ {
+ [STAThread]
+ public static void Main(string[] args)
+ {
+ using (GameHost host = Host.GetSuitableDesktopHost(@"sample-game"))
+ using (Game game = new SampleGameGame())
+ host.Run(game);
+ }
+ }
+}
diff --git a/SampleGame.Desktop/SampleGame.Desktop.csproj b/SampleGame.Desktop/SampleGame.Desktop.csproj
new file mode 100644
index 000000000..7ddb6db1a
--- /dev/null
+++ b/SampleGame.Desktop/SampleGame.Desktop.csproj
@@ -0,0 +1,10 @@
+
+
+ net8.0
+ WinExe
+
+
+
+
+
+
diff --git a/SampleGame.iOS/AppDelegate.cs b/SampleGame.iOS/AppDelegate.cs
index e69de29bb..9712408a0 100644
--- a/SampleGame.iOS/AppDelegate.cs
+++ b/SampleGame.iOS/AppDelegate.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Foundation;
+using osu.Framework;
+using osu.Framework.iOS;
+
+namespace SampleGame.iOS
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : GameApplicationDelegate
+ {
+ protected override Game CreateGame() => new SampleGameGame();
+ }
+}
diff --git a/SampleGame.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/SampleGame.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
index e69de29bb..c7dd1941e 100644
--- a/SampleGame.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/SampleGame.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,231 @@
+{
+ "images": [
+ {
+ "idiom": "iphone",
+ "scale": "2x",
+ "size": "20x20"
+ },
+ {
+ "idiom": "iphone",
+ "scale": "3x",
+ "size": "20x20"
+ },
+ {
+ "idiom": "iphone",
+ "scale": "2x",
+ "size": "29x29"
+ },
+ {
+ "idiom": "iphone",
+ "scale": "3x",
+ "size": "29x29"
+ },
+ {
+ "idiom": "iphone",
+ "scale": "2x",
+ "size": "40x40"
+ },
+ {
+ "idiom": "iphone",
+ "scale": "3x",
+ "size": "40x40"
+ },
+ {
+ "idiom": "iphone",
+ "scale": "2x",
+ "size": "60x60"
+ },
+ {
+ "idiom": "iphone",
+ "scale": "3x",
+ "size": "60x60"
+ },
+ {
+ "idiom": "ipad",
+ "scale": "1x",
+ "size": "20x20"
+ },
+ {
+ "idiom": "ipad",
+ "scale": "2x",
+ "size": "20x20"
+ },
+ {
+ "idiom": "ipad",
+ "scale": "1x",
+ "size": "29x29"
+ },
+ {
+ "idiom": "ipad",
+ "scale": "2x",
+ "size": "29x29"
+ },
+ {
+ "idiom": "ipad",
+ "scale": "1x",
+ "size": "40x40"
+ },
+ {
+ "idiom": "ipad",
+ "scale": "2x",
+ "size": "40x40"
+ },
+ {
+ "idiom": "ipad",
+ "scale": "1x",
+ "size": "76x76"
+ },
+ {
+ "idiom": "ipad",
+ "scale": "2x",
+ "size": "76x76"
+ },
+ {
+ "idiom": "ipad",
+ "scale": "2x",
+ "size": "83.5x83.5"
+ },
+ {
+ "idiom": "ios-marketing",
+ "scale": "1x",
+ "size": "1024x1024"
+ },
+ {
+ "idiom": "car",
+ "scale": "2x",
+ "size": "60x60"
+ },
+ {
+ "idiom": "car",
+ "scale": "3x",
+ "size": "60x60"
+ },
+ {
+ "idiom": "watch",
+ "role": "notificationCenter",
+ "scale": "2x",
+ "size": "24x24",
+ "subtype": "38mm"
+ },
+ {
+ "idiom": "watch",
+ "role": "notificationCenter",
+ "scale": "2x",
+ "size": "27.5x27.5",
+ "subtype": "42mm"
+ },
+ {
+ "idiom": "watch",
+ "role": "companionSettings",
+ "scale": "2x",
+ "size": "29x29"
+ },
+ {
+ "idiom": "watch",
+ "role": "companionSettings",
+ "scale": "3x",
+ "size": "29x29"
+ },
+ {
+ "idiom": "watch",
+ "role": "appLauncher",
+ "scale": "2x",
+ "size": "40x40",
+ "subtype": "38mm"
+ },
+ {
+ "idiom": "watch",
+ "role": "appLauncher",
+ "scale": "2x",
+ "size": "44x44",
+ "subtype": "40mm"
+ },
+ {
+ "idiom": "watch",
+ "role": "appLauncher",
+ "scale": "2x",
+ "size": "50x50",
+ "subtype": "44mm"
+ },
+ {
+ "idiom": "watch",
+ "role": "quickLook",
+ "scale": "2x",
+ "size": "86x86",
+ "subtype": "38mm"
+ },
+ {
+ "idiom": "watch",
+ "role": "quickLook",
+ "scale": "2x",
+ "size": "98x98",
+ "subtype": "42mm"
+ },
+ {
+ "idiom": "watch",
+ "role": "quickLook",
+ "scale": "2x",
+ "size": "108x108",
+ "subtype": "44mm"
+ },
+ {
+ "idiom": "watch-marketing",
+ "scale": "1x",
+ "size": "1024x1024"
+ },
+ {
+ "idiom": "mac",
+ "scale": "1x",
+ "size": "16x16"
+ },
+ {
+ "idiom": "mac",
+ "scale": "2x",
+ "size": "16x16"
+ },
+ {
+ "idiom": "mac",
+ "scale": "1x",
+ "size": "32x32"
+ },
+ {
+ "idiom": "mac",
+ "scale": "2x",
+ "size": "32x32"
+ },
+ {
+ "idiom": "mac",
+ "scale": "1x",
+ "size": "128x128"
+ },
+ {
+ "idiom": "mac",
+ "scale": "2x",
+ "size": "128x128"
+ },
+ {
+ "idiom": "mac",
+ "scale": "1x",
+ "size": "256x256"
+ },
+ {
+ "idiom": "mac",
+ "scale": "2x",
+ "size": "256x256"
+ },
+ {
+ "idiom": "mac",
+ "scale": "1x",
+ "size": "512x512"
+ },
+ {
+ "idiom": "mac",
+ "scale": "2x",
+ "size": "512x512"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ }
+}
\ No newline at end of file
diff --git a/SampleGame.iOS/Assets.xcassets/Contents.json b/SampleGame.iOS/Assets.xcassets/Contents.json
index e69de29bb..4caf392f9 100644
--- a/SampleGame.iOS/Assets.xcassets/Contents.json
+++ b/SampleGame.iOS/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/SampleGame.iOS/Entitlements.plist b/SampleGame.iOS/Entitlements.plist
index e69de29bb..9ae599370 100644
--- a/SampleGame.iOS/Entitlements.plist
+++ b/SampleGame.iOS/Entitlements.plist
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/SampleGame.iOS/Info.plist b/SampleGame.iOS/Info.plist
index e69de29bb..417bfcd8e 100644
--- a/SampleGame.iOS/Info.plist
+++ b/SampleGame.iOS/Info.plist
@@ -0,0 +1,46 @@
+
+
+
+
+ CFBundleName
+ SampleGame.iOS
+ CFBundleIdentifier
+ sh.ppy.sample-game
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1.0
+ LSRequiresIPhoneOS
+
+ MinimumOSVersion
+ 13.4
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/AppIcon.appiconset
+ UIStatusBarHidden
+
+ UIRequiresFullScreen
+
+ UIApplicationSupportsIndirectInputEvents
+
+ CADisableMinimumFrameDurationOnPhone
+
+
+
diff --git a/SampleGame.iOS/LaunchScreen.storyboard b/SampleGame.iOS/LaunchScreen.storyboard
index e69de29bb..5d2e905aa 100644
--- a/SampleGame.iOS/LaunchScreen.storyboard
+++ b/SampleGame.iOS/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SampleGame.iOS/Program.cs b/SampleGame.iOS/Program.cs
index e69de29bb..b5dbf419e 100644
--- a/SampleGame.iOS/Program.cs
+++ b/SampleGame.iOS/Program.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using UIKit;
+
+namespace SampleGame.iOS
+{
+ public static class Program
+ {
+ public static void Main(string[] args)
+ {
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+ }
+}
diff --git a/SampleGame.iOS/SampleGame.iOS.csproj b/SampleGame.iOS/SampleGame.iOS.csproj
index e69de29bb..8048e4f2b 100644
--- a/SampleGame.iOS/SampleGame.iOS.csproj
+++ b/SampleGame.iOS/SampleGame.iOS.csproj
@@ -0,0 +1,13 @@
+
+
+ Exe
+ net8.0-ios
+ 13.4
+
+
+
+
+
+
+
+
diff --git a/SampleGame/SampleGame.csproj b/SampleGame/SampleGame.csproj
new file mode 100644
index 000000000..d3d384f9c
--- /dev/null
+++ b/SampleGame/SampleGame.csproj
@@ -0,0 +1,8 @@
+
+
+ net8.0
+
+
+
+
+
diff --git a/SampleGame/SampleGameGame.cs b/SampleGame/SampleGameGame.cs
new file mode 100644
index 000000000..ae72ffa24
--- /dev/null
+++ b/SampleGame/SampleGameGame.cs
@@ -0,0 +1,35 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework;
+using osu.Framework.Graphics;
+using osuTK;
+using osuTK.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Allocation;
+
+namespace SampleGame
+{
+ public partial class SampleGameGame : Game
+ {
+ private Box box = null!;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Add(box = new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(150, 150),
+ Colour = Color4.Tomato
+ });
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+ box.Rotation += (float)Time.Elapsed / 10;
+ }
+ }
+}
diff --git a/osu.Framework.SourceGeneration.Tests/Dependencies/DependencyInjectionSourceGeneratorTests.cs b/osu.Framework.SourceGeneration.Tests/Dependencies/DependencyInjectionSourceGeneratorTests.cs
index 770f39fa2..b65d8a1e9 100644
--- a/osu.Framework.SourceGeneration.Tests/Dependencies/DependencyInjectionSourceGeneratorTests.cs
+++ b/osu.Framework.SourceGeneration.Tests/Dependencies/DependencyInjectionSourceGeneratorTests.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
using Xunit;
using VerifyCS = osu.Framework.SourceGeneration.Tests.Verifiers.CSharpSourceGeneratorVerifier;
@@ -47,5 +48,19 @@ namespace osu.Framework.SourceGeneration.Tests.Dependencies
return VerifyCS.VerifyAsync(commonSourceFiles, sourceFiles, commonGeneratedFiles, generatedFiles);
}
+
+ [Theory]
+ [InlineData("EmptyFile")]
+ public Task CheckDebugNoOutput(string name)
+ {
+ GetTestSources(name,
+ out (string filename, string content)[] commonSourceFiles,
+ out (string filename, string content)[] sourceFiles,
+ out _,
+ out _
+ );
+
+ return VerifyCS.VerifyAsync(commonSourceFiles, sourceFiles, [], [], OptimizationLevel.Debug);
+ }
}
}
diff --git a/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier.cs b/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier.cs
index b92dfc04f..c50fa830f 100644
--- a/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier.cs
+++ b/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier.cs
@@ -4,6 +4,7 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using osu.Framework.SourceGeneration.Generators;
@@ -16,9 +17,10 @@ namespace osu.Framework.SourceGeneration.Tests.Verifiers
(string filename, string content)[] commonSources,
(string filename, string content)[] sources,
(string filename, string content)[] commonGenerated,
- (string filename, string content)[] generated)
+ (string filename, string content)[] generated,
+ OptimizationLevel optimizationLevel = OptimizationLevel.Release)
{
- var test = new Test();
+ var test = new Test(optimizationLevel);
foreach (var s in commonSources)
test.TestState.Sources.Add((s.filename, SourceText.From(s.content, Encoding.UTF8)));
diff --git a/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier_Test.cs b/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier_Test.cs
index a18203f56..6860c5e8f 100644
--- a/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier_Test.cs
+++ b/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier_Test.cs
@@ -16,13 +16,20 @@ namespace osu.Framework.SourceGeneration.Tests.Verifiers
{
public class Test : CSharpSourceGeneratorTest
{
+ private readonly OptimizationLevel optimizationLevel;
+
+ public Test(OptimizationLevel optimizationLevel = OptimizationLevel.Release)
+ {
+ this.optimizationLevel = optimizationLevel;
+ }
+
public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.Default;
protected override IEnumerable GetSourceGenerators() => [typeof(TSourceGenerator)];
protected override CompilationOptions CreateCompilationOptions()
{
- return base.CreateCompilationOptions().WithOptimizationLevel(OptimizationLevel.Release);
+ return base.CreateCompilationOptions().WithOptimizationLevel(optimizationLevel);
}
protected override ParseOptions CreateParseOptions()
diff --git a/osu.Framework.SourceGeneration/Generators/AbstractIncrementalGenerator.cs b/osu.Framework.SourceGeneration/Generators/AbstractIncrementalGenerator.cs
index 60bd8c9bd..e6c79c404 100644
--- a/osu.Framework.SourceGeneration/Generators/AbstractIncrementalGenerator.cs
+++ b/osu.Framework.SourceGeneration/Generators/AbstractIncrementalGenerator.cs
@@ -20,11 +20,15 @@ namespace osu.Framework.SourceGeneration.Generators
IncrementalValuesProvider syntaxTargets =
context.SyntaxProvider.CreateSyntaxProvider(
(n, _) => isSyntaxTarget(n),
- (ctx, _) => returnWithEvent(new IncrementalSyntaxTarget((ClassDeclarationSyntax)ctx.Node, ctx.SemanticModel), EventDriver.OnSyntaxTargetCreated))
- .Select((t, _) => t.WithName())
- .Combine(context.CompilationProvider)
- .Where(c => c.Right.Options.OptimizationLevel == OptimizationLevel.Release)
- .Select((t, _) => t.Item1)
+ (ctx, _) =>
+ {
+ if (ctx.SemanticModel.Compilation.Options.OptimizationLevel == OptimizationLevel.Debug)
+ return null;
+
+ return returnWithEvent(new IncrementalSyntaxTarget((ClassDeclarationSyntax)ctx.Node, ctx.SemanticModel), EventDriver.OnSyntaxTargetCreated);
+ })
+ .Where(t => t != null)
+ .Select((t, _) => t!.WithName())
.Select((t, _) => returnWithEvent(t.WithSemanticTarget(CreateSemanticTarget), EventDriver.OnSemanticTargetCreated));
// Stage 2: Separate out the old and new syntax targets for the same class object.
diff --git a/osu.Framework.Tests/Visual/Containers/TestSceneAcrylicContainer.cs b/osu.Framework.Tests/Visual/Containers/TestSceneAcrylicContainer.cs
deleted file mode 100644
index 19ce63607..000000000
--- a/osu.Framework.Tests/Visual/Containers/TestSceneAcrylicContainer.cs
+++ /dev/null
@@ -1,191 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
-using osu.Framework.Testing;
-using osuTK;
-
-namespace osu.Framework.Tests.Visual.Containers
-{
- public partial class TestSceneAcrylicContainer : TestScene
- {
- private bool isWhiteTint = true;
-
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- Children = new Drawable[]
- {
- // Background texture (full screen)
- new Sprite
- {
- RelativeSizeAxes = Axes.Both,
- Texture = textures.Get("sample-texture")
- },
-
- // Left side: Semi-transparent overlay to show original texture
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Width = 0.5f,
- Child = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Colour4.White.Opacity(0.1f) // Very subtle overlay
- }
- },
-
- // Right side: AcrylicContainer with blur effect (buffered container)
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- Width = 0.5f,
- Child = new AcrylicContainer()
- },
-
- // Labels
- new SpriteText
- {
- Text = "Original Texture (No Blur)",
- Font = FontUsage.Default.With(size: 24),
- Colour = Colour4.White,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Position = new Vector2(-200, 20),
- },
- new SpriteText
- {
- Text = "AcrylicContainer (Buffered Blur)",
- Font = FontUsage.Default.With(size: 24),
- Colour = Colour4.White,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Position = new Vector2(200, 20),
- }
- };
-
- // Blur strength control
- AddSliderStep("blur strength", 0f, 20f, 10f, strength =>
- {
- if (Children[2] is AcrylicContainer acrylic)
- acrylic.BlurStrength = strength;
- });
-
- // Tint colour controls
- AddSliderStep("tint alpha", 0f, 1f, 0.8f, alpha =>
- {
- if (Children[2] is AcrylicContainer acrylic)
- acrylic.TintColour = Colour4.White.Opacity(alpha);
- });
-
- AddSliderStep("tint red", 0f, 1f, 1f, red =>
- {
- if (Children[2] is AcrylicContainer acrylic)
- {
- var currentColour = acrylic.TintColour;
- acrylic.TintColour = new Colour4(red, currentColour.G, currentColour.B, currentColour.A);
- }
- });
-
- AddSliderStep("tint green", 0f, 1f, 1f, green =>
- {
- if (Children[2] is AcrylicContainer acrylic)
- {
- var currentColour = acrylic.TintColour;
- acrylic.TintColour = new Colour4(currentColour.R, green, currentColour.B, currentColour.A);
- }
- });
-
- AddSliderStep("tint blue", 0f, 1f, 1f, blue =>
- {
- if (Children[2] is AcrylicContainer acrylic)
- {
- var currentColour = acrylic.TintColour;
- acrylic.TintColour = new Colour4(currentColour.R, currentColour.G, blue, currentColour.A);
- }
- });
-
- AddStep("toggle tint colour", () =>
- {
- if (Children[2] is AcrylicContainer acrylic)
- {
- isWhiteTint = !isWhiteTint;
- acrylic.TintColour = isWhiteTint
- ? Colour4.White.Opacity(0.8f)
- : Colour4.Blue.Opacity(0.8f);
- }
- });
-
- // Test different blur scenarios
- AddStep("no blur", () =>
- {
- if (Children[2] is AcrylicContainer acrylic) acrylic.BlurStrength = 0;
- });
- AddStep("light blur", () =>
- {
- if (Children[2] is AcrylicContainer acrylic) acrylic.BlurStrength = 5;
- });
- AddStep("medium blur", () =>
- {
- if (Children[2] is AcrylicContainer acrylic) acrylic.BlurStrength = 10;
- });
- AddStep("heavy blur", () =>
- {
- if (Children[2] is AcrylicContainer acrylic) acrylic.BlurStrength = 20;
- });
-
- // Test tint scenarios
- AddStep("no tint", () =>
- {
- if (Children[2] is AcrylicContainer acrylic) acrylic.TintColour = Colour4.White.Opacity(0);
- });
- AddStep("subtle tint", () =>
- {
- if (Children[2] is AcrylicContainer acrylic) acrylic.TintColour = Colour4.White.Opacity(0.3f);
- });
- AddStep("medium tint", () =>
- {
- if (Children[2] is AcrylicContainer acrylic) acrylic.TintColour = Colour4.White.Opacity(0.6f);
- });
- AddStep("strong tint", () =>
- {
- if (Children[2] is AcrylicContainer acrylic) acrylic.TintColour = Colour4.White.Opacity(0.9f);
- });
-
- // Debug presets
- AddStep("debug: high contrast", () =>
- {
- if (Children[2] is AcrylicContainer acrylic)
- {
- acrylic.BlurStrength = 15f;
- acrylic.TintColour = Colour4.Red.Opacity(0.7f);
- }
- });
-
- AddStep("debug: subtle effect", () =>
- {
- if (Children[2] is AcrylicContainer acrylic)
- {
- acrylic.BlurStrength = 3f;
- acrylic.TintColour = Colour4.Black.Opacity(0.2f);
- }
- });
-
- AddStep("debug: reset to default", () =>
- {
- if (Children[2] is AcrylicContainer acrylic)
- {
- acrylic.BlurStrength = 10f;
- acrylic.TintColour = Colour4.White.Opacity(0.8f);
- }
- });
- }
- }
-}
diff --git a/osu.Framework.Tests/Visual/Containers/TestSceneAcrylicContainerNew.cs b/osu.Framework.Tests/Visual/Containers/TestSceneAcrylicContainerNew.cs
deleted file mode 100644
index edc30e8cb..000000000
--- a/osu.Framework.Tests/Visual/Containers/TestSceneAcrylicContainerNew.cs
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Colour;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Testing;
-using osu.Framework.Utils;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Framework.Tests.Visual.Containers
-{
- ///
- /// 测试真正的毛玻璃效果 - 使用新重构的AcrylicContainer
- /// 这个容器可以对下层的任何内容进行实时模糊,包括视频、动画等
- ///
- public partial class TestSceneAcrylicContainerNew : TestScene
- {
- private AcrylicContainer? acrylicEffect;
- private Box? animatedBox;
- private readonly List movingBoxes = new List();
-
- [BackgroundDependencyLoader]
- private void load()
- {
- // 创建一个彩色渐变背景层
- Add(new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = ColourInfo.GradientVertical(Color4.Blue, Color4.Purple)
- });
-
- // 添加一些移动的彩色方块作为背景内容
- for (int i = 0; i < 5; i++)
- {
- var box = new Box
- {
- Size = new Vector2(100),
- Colour = new Color4(
- (float)RNG.NextDouble(),
- (float)RNG.NextDouble(),
- (float)RNG.NextDouble(),
- 1f
- ),
- Position = new Vector2(
- (float)(RNG.NextDouble() * 800),
- (float)(RNG.NextDouble() * 600)
- )
- };
-
- Add(box);
- movingBoxes.Add(box);
- }
-
- // 添加一个持续旋转的大方块
- animatedBox = new Box
- {
- Size = new Vector2(200),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Colour = Color4.Yellow
- };
-
- Add(animatedBox);
-
- // 在上面添加毛玻璃效果容器
- Add(new Container
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding(100),
- Child = acrylicEffect = new AcrylicContainer
- {
- RelativeSizeAxes = Axes.Both,
- BlurStrength = 50f,
- TintColour = new Color4(1, 1, 1, 0.5f),
- Children = new Drawable[]
- {
- // 在毛玻璃效果上面显示一些文本
- new SpriteText
- {
- Text = "毛玻璃效果 (Acrylic Effect)\n\n此容器实时模糊背后的所有内容,\n包括背景、动画和兄弟元素。",
- Font = FontUsage.Default.With(size: 30),
- Colour = Color4.White,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Shadow = true
- }
- }
- }
- });
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- // 在LoadComplete后启动动画
- // 让方块循环移动
- foreach (var box in movingBoxes)
- {
- box.MoveTo(new Vector2(
- (float)(RNG.NextDouble() * 800),
- (float)(RNG.NextDouble() * 600)
- ), 3000).Then().MoveTo(new Vector2(
- (float)(RNG.NextDouble() * 800),
- (float)(RNG.NextDouble() * 600)
- ), 3000).Loop();
- }
-
- // 让黄色方块持续旋转
- animatedBox?.RotateTo(360, 4000).Then().RotateTo(0, 0).Loop();
-
- // 添加控制UI
- AddLabel("模糊强度 (Blur Strength)");
- AddSliderStep("blur", 0f, 50f, 15f, value =>
- {
- if (acrylicEffect != null)
- acrylicEffect.BlurStrength = value;
- });
-
- AddLabel("着色透明度 (Tint Alpha)");
- AddSliderStep("alpha", 0f, 1f, 0.3f, value =>
- {
- if (acrylicEffect != null)
- {
- var current = acrylicEffect.TintColour;
- acrylicEffect.TintColour = new Color4(current.R, current.G, current.B, value);
- }
- });
-
- AddLabel("着色颜色 (Tint Color)");
- AddStep("黑色 (Black)", () =>
- {
- if (acrylicEffect != null)
- {
- var alpha = acrylicEffect.TintColour.A;
- acrylicEffect.TintColour = new Color4(0, 0, 0, alpha);
- }
- });
-
- AddStep("白色 (White)", () =>
- {
- if (acrylicEffect != null)
- {
- var alpha = acrylicEffect.TintColour.A;
- acrylicEffect.TintColour = new Color4(1, 1, 1, alpha);
- }
- });
-
- AddStep("红色 (Red)", () =>
- {
- if (acrylicEffect != null)
- {
- var alpha = acrylicEffect.TintColour.A;
- acrylicEffect.TintColour = new Color4(1, 0, 0, alpha);
- }
- });
-
- AddStep("蓝色 (Blue)", () =>
- {
- if (acrylicEffect != null)
- {
- var alpha = acrylicEffect.TintColour.A;
- acrylicEffect.TintColour = new Color4(0, 0, 1, alpha);
- }
- });
-
- AddLabel("效果演示 (Demos)");
- AddStep("显示/隐藏毛玻璃", () =>
- {
- if (acrylicEffect != null)
- acrylicEffect.Alpha = acrylicEffect.Alpha > 0 ? 0 : 1;
- });
-
- AddStep("脉冲动画", () =>
- {
- acrylicEffect?.ScaleTo(1.1f, 500, Easing.OutQuint)
- .Then()
- .ScaleTo(1f, 500, Easing.InQuint);
- });
- }
- }
-}
diff --git a/osu.Framework.Tests/Visual/Drawables/TestSceneCubicBezierEasing.cs b/osu.Framework.Tests/Visual/Drawables/TestSceneCubicBezierEasing.cs
new file mode 100644
index 000000000..3f3bae265
--- /dev/null
+++ b/osu.Framework.Tests/Visual/Drawables/TestSceneCubicBezierEasing.cs
@@ -0,0 +1,329 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Lines;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Transforms;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Events;
+using osu.Framework.Platform;
+using osu.Framework.Testing;
+using osuTK;
+
+namespace osu.Framework.Tests.Visual.Drawables
+{
+ public partial class TestSceneCubicBezierEasing : TestScene
+ {
+ private SpriteText easingText = null!;
+ private EasingEditor easingEditor = null!;
+
+ [Resolved]
+ private Clipboard clipboard { get; set; } = null!;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Padding = new MarginPadding { Horizontal = 100 },
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(20),
+ Children = new Drawable[]
+ {
+ easingEditor = new EasingEditor
+ {
+ Size = new Vector2(200)
+ },
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(30),
+ Children = new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(4),
+ Children = new Drawable[]
+ {
+ easingText = new SpriteText
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Width = 420,
+ },
+ new BasicButton
+ {
+ AutoSizeAxes = Axes.Y,
+ Width = 50,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Text = "Copy",
+ FlashColour = FrameworkColour.BlueGreen.Lighten(0.5f),
+ FlashDuration = 1000,
+ Action = () => clipboard.SetText(easingText.Text.ToString()),
+ }
+ }
+ },
+ new EasingPreview(easingEditor.EasingFunction.GetBoundCopy())
+ }
+ }
+ }
+ };
+
+ easingEditor.EasingFunction.BindValueChanged(e =>
+ {
+ var easing = e.NewValue;
+
+ easingText.Text = FormattableString.Invariant($"new {nameof(CubicBezierEasingFunction)}({easing.X1:0.##}, {easing.Y1:0.##}, {easing.X2:0.##}, {easing.Y2:0.##})");
+ });
+ }
+
+ private partial class EasingEditor : CompositeDrawable
+ {
+ private readonly Bindable p1 = new Bindable(new Vector2(0.5f, 0f));
+ private readonly Bindable p2 = new Bindable(new Vector2(0.5f, 1f));
+ private readonly SmoothPath path;
+ private readonly Box line1, line2;
+
+ public readonly Bindable EasingFunction = new Bindable();
+
+ public EasingEditor()
+ {
+ InternalChild = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.1f,
+ },
+ path = new SmoothPath
+ {
+ Position = new Vector2(1, -1),
+ PathRadius = 1f,
+ Anchor = Anchor.BottomLeft,
+ },
+ line1 = new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 1,
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.CentreLeft,
+ EdgeSmoothness = new Vector2(1),
+ Alpha = 0.1f,
+ },
+ line2 = new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 1,
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.CentreRight,
+ EdgeSmoothness = new Vector2(1),
+ Alpha = 0.1f,
+ },
+ new ControlPointHandle
+ {
+ ControlPoint = { BindTarget = p1 },
+ },
+ new ControlPointHandle
+ {
+ ControlPoint = { BindTarget = p2 },
+ },
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ p1.BindValueChanged(_ => Scheduler.AddOnce(easingChanged));
+ p2.BindValueChanged(_ => Scheduler.AddOnce(easingChanged));
+ easingChanged();
+ }
+
+ private void easingChanged()
+ {
+ path.ClearVertices();
+
+ var easing = EasingFunction.Value = new CubicBezierEasingFunction(p1.Value.X, p1.Value.Y, p2.Value.X, p2.Value.Y);
+
+ for (double d = 0; d < 1; d += 0.01)
+ {
+ double value = easing.ApplyEasing(d);
+
+ path.AddVertex(new Vector2((float)d * DrawWidth, 1 - (float)value * DrawHeight));
+ }
+
+ path.AddVertex(new Vector2(DrawWidth, 1 - (float)easing.ApplyEasing(1) * DrawHeight));
+
+ path.OriginPosition = path.PositionInBoundingBox(new Vector2());
+
+ line1.Width = p1.Value.Length;
+ line1.Rotation = -MathHelper.RadiansToDegrees(MathF.Atan2(p1.Value.Y, p1.Value.X));
+
+ line2.Width = Vector2.Distance(p2.Value, Vector2.One);
+ line2.Rotation = -MathHelper.RadiansToDegrees(MathF.Atan2(1 - p2.Value.Y, 1 - p2.Value.X));
+ }
+ }
+
+ private partial class ControlPointHandle : CompositeDrawable
+ {
+ public readonly Bindable ControlPoint = new Bindable();
+
+ public ControlPointHandle()
+ {
+ RelativePositionAxes = Axes.Both;
+ Size = new Vector2(20);
+ Origin = Anchor.Centre;
+
+ InternalChild = new Circle
+ {
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.5f),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ ControlPoint.BindValueChanged(p => Position = new Vector2(p.NewValue.X, 1 - p.NewValue.Y), true);
+ }
+
+ protected override bool OnDragStart(DragStartEvent e) => true;
+
+ protected override void OnDrag(DragEvent e)
+ {
+ base.OnDrag(e);
+
+ var position = Vector2.Divide(Parent!.ToLocalSpace(e.ScreenSpaceMousePosition), Parent.ChildSize);
+
+ ControlPoint.Value = new Vector2(
+ float.Round(float.Clamp(position.X, 0, 1), 2),
+ float.Round(1f - position.Y, 2)
+ );
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ this.ScaleTo(1.35f, 50);
+ return true;
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ this.ScaleTo(1f, 50);
+ }
+ }
+
+ private partial class EasingPreview : CompositeDrawable
+ {
+ private readonly Box box;
+ private readonly SpriteText durationText;
+
+ private bool flipped;
+ private readonly IBindable easingFunction;
+
+ private readonly BindableDouble duration = new BindableDouble
+ {
+ Value = 1000,
+ MinValue = 100,
+ MaxValue = 5000,
+ };
+
+ public EasingPreview(IBindable easingFunction)
+ {
+ this.easingFunction = easingFunction;
+
+ Width = 400;
+ AutoSizeAxes = Axes.Y;
+
+ InternalChild = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(10),
+ Children = new Drawable[]
+ {
+ new GridContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 25,
+ RowDimensions = [new Dimension(GridSizeMode.AutoSize)],
+ ColumnDimensions = [new Dimension(GridSizeMode.Absolute, 150), new Dimension(), new Dimension(GridSizeMode.Absolute, 20), new Dimension(GridSizeMode.AutoSize)],
+
+ Content = new Drawable?[][]
+ {
+ [
+ durationText = new SpriteText
+ {
+ Text = "Duration: 1000"
+ },
+ new BasicSliderBar
+ {
+ Current = duration,
+ RelativeSizeAxes = Axes.X,
+ Height = 20,
+ },
+ null,
+ new BasicButton
+ {
+ RelativeSizeAxes = Axes.Y,
+ Width = 50,
+ Text = "Play",
+ FlashColour = FrameworkColour.BlueGreen.Lighten(0.5f),
+ Action = play,
+ }
+ ]
+ }
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding { Right = 50 },
+ Child = box = new Box
+ {
+ RelativePositionAxes = Axes.X,
+ Size = new Vector2(50),
+ },
+ }
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ duration.BindValueChanged(e => durationText.Text = FormattableString.Invariant($"Duration: {e.NewValue:N0}"), true);
+ }
+
+ private void play()
+ {
+ box.MoveToX(flipped ? 0 : 1, duration.Value, easingFunction.Value);
+
+ flipped = !flipped;
+ }
+ }
+ }
+}
diff --git a/osu.Framework.Tests/Visual/Graphics/TestSceneFrostedGlass.cs b/osu.Framework.Tests/Visual/Graphics/TestSceneFrostedGlass.cs
new file mode 100644
index 000000000..ecf803ded
--- /dev/null
+++ b/osu.Framework.Tests/Visual/Graphics/TestSceneFrostedGlass.cs
@@ -0,0 +1,102 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Graphics.UserInterface;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Framework.Tests.Visual.Graphics
+{
+ public partial class TestSceneFrostedGlass : FrameworkTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ Container backgroundCircles;
+
+ Children = new Drawable[]
+ {
+ // Background with animated circles
+ backgroundCircles = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ // Frosted glass container overlaying part of the screen
+ // new FrostedGlassContainer
+ // {
+ // RelativeSizeAxes = Axes.Both,
+ // Width = 0.5f,
+ // BlurSigma = new Vector2(10),
+ // Children = new Drawable[]
+ // {
+ // new Box
+ // {
+ // RelativeSizeAxes = Axes.Both,
+ // Colour = new Color4(1, 1, 1, 0.5f),
+ // },
+ // new SpriteText
+ // {
+ // Text = "Frosted Glass Effect",
+ // Anchor = Anchor.Centre,
+ // Origin = Anchor.Centre,
+ // Colour = Color4.Black,
+ // }
+ // }
+ // },
+ new Label("Background"),
+ new Label("FrostedGlassContainer")
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight
+ }
+ };
+
+ const float circle_radius = 0.05f;
+ const float spacing = 0.01f;
+
+ for (float xPos = 0; xPos < 1; xPos += circle_radius + spacing)
+ {
+ for (float yPos = 0; yPos < 1; yPos += circle_radius + spacing)
+ {
+ backgroundCircles.Add(new CircularProgress
+ {
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(circle_radius),
+ RelativePositionAxes = Axes.Both,
+ Position = new Vector2(xPos, yPos),
+ Progress = 1,
+ Colour = Color4.HotPink,
+ });
+ }
+ }
+ }
+
+ private partial class Label : Container
+ {
+ public Label(string text)
+ {
+ AutoSizeAxes = Axes.Both;
+ Margin = new MarginPadding(10);
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black
+ },
+ new SpriteText
+ {
+ Text = text,
+ Margin = new MarginPadding(10)
+ }
+ };
+ }
+ }
+ }
+}
diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneDisplayBoundsWindowBorder.cs b/osu.Framework.Tests/Visual/Platform/TestSceneDisplayBoundsWindowBorder.cs
new file mode 100644
index 000000000..d96b71c27
--- /dev/null
+++ b/osu.Framework.Tests/Visual/Platform/TestSceneDisplayBoundsWindowBorder.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Graphics;
+
+namespace osu.Framework.Tests.Visual.Platform
+{
+ public partial class TestSceneDisplayBoundsWindowBorder : FrameworkTestScene
+ {
+ [Test]
+ public void TestDisplayUsableBounds()
+ {
+ AddStep("Set up scene", () => Child = new WindowDisplaysPreview(true)
+ {
+ RelativeSizeAxes = Axes.Both,
+ });
+ }
+
+ [Test]
+ public void TestWindowBorder()
+ {
+ AddStep("Set up scene", () => Child = new WindowDisplaysPreview(false, true)
+ {
+ RelativeSizeAxes = Axes.Both,
+ });
+ }
+
+ [Test]
+ public void TestBoth()
+ {
+ AddStep("Set up scene", () => Child = new WindowDisplaysPreview(true, true)
+ {
+ RelativeSizeAxes = Axes.Both,
+ });
+ }
+ }
+}
diff --git a/osu.Framework.Tests/Visual/Platform/WindowDisplaysPreview.cs b/osu.Framework.Tests/Visual/Platform/WindowDisplaysPreview.cs
index 78fec929c..bba4f05f3 100644
--- a/osu.Framework.Tests/Visual/Platform/WindowDisplaysPreview.cs
+++ b/osu.Framework.Tests/Visual/Platform/WindowDisplaysPreview.cs
@@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -27,6 +28,7 @@ namespace osu.Framework.Tests.Visual.Platform
private readonly Container paddedContainer;
private readonly Container screenContainer;
private readonly Container windowContainer;
+ private readonly Container? borderContainer;
private Vector2 screenContainerOffset;
private static readonly Color4 active_fill = new Color4(255, 138, 104, 255);
@@ -35,13 +37,18 @@ namespace osu.Framework.Tests.Visual.Platform
private static readonly Color4 screen_stroke = new Color4(244, 137, 25, 255);
private static readonly Color4 window_fill = new Color4(95, 113, 197, 255);
private static readonly Color4 window_stroke = new Color4(36, 59, 166, 255);
+ private static readonly Color4 window_border_fill = new Color4(85, 207, 89, 200);
+ private static readonly Color4 window_border_stroke = new Color4(50, 122, 53, 255);
private IWindow? window;
private readonly Bindable windowMode = new Bindable();
private readonly Bindable currentDisplay = new Bindable();
- public WindowDisplaysPreview()
+ private readonly bool showDisplayUsableBounds;
+
+ public WindowDisplaysPreview(bool showDisplayUsableBounds = false, bool showWindowBorder = false)
{
+ this.showDisplayUsableBounds = showDisplayUsableBounds;
Child = new Container
{
RelativeSizeAxes = Axes.Both,
@@ -85,6 +92,22 @@ namespace osu.Framework.Tests.Visual.Platform
}
}
};
+
+ if (showWindowBorder)
+ {
+ screenContainer.Add(borderContainer = new Container
+ {
+ BorderColour = window_border_stroke,
+ BorderThickness = 20,
+ Masking = true,
+ Depth = -5,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = window_border_fill
+ }
+ });
+ }
}
[BackgroundDependencyLoader]
@@ -110,13 +133,13 @@ namespace osu.Framework.Tests.Visual.Platform
private void refreshScreens(IEnumerable displays)
{
- screenContainer.RemoveAll(d => d != windowContainer, false);
+ screenContainer.RemoveAll(d => d != windowContainer && d != borderContainer, false);
var bounds = new RectangleI();
foreach (var display in displays)
{
- screenContainer.Add(createScreen(display, window.AsNonNull().CurrentDisplayBindable.Value.Index));
+ screenContainer.AddRange(createScreen(display, window.AsNonNull().CurrentDisplayBindable.Value.Index));
bounds = RectangleI.Union(bounds, new RectangleI(display.Bounds.X, display.Bounds.Y, display.Bounds.Width, display.Bounds.Height));
}
@@ -130,11 +153,11 @@ namespace osu.Framework.Tests.Visual.Platform
screenContainer.Size = bounds.Size;
}
- private Container createScreen(Display display, int activeDisplayIndex)
+ private IEnumerable createScreen(Display display, int activeDisplayIndex)
{
bool isActive = display.Index == activeDisplayIndex;
- return new Container
+ yield return new Container
{
X = display.Bounds.X,
Y = display.Bounds.Y,
@@ -166,6 +189,30 @@ namespace osu.Framework.Tests.Visual.Platform
}
}
};
+
+ if (showDisplayUsableBounds)
+ {
+ yield return new Container
+ {
+ X = display.UsableBounds.X,
+ Y = display.UsableBounds.Y,
+ Width = display.UsableBounds.Width,
+ Height = display.UsableBounds.Height,
+
+ BorderColour = isActive ? Color4.MediumPurple : Color4.Green,
+ BorderThickness = 20,
+ Masking = true,
+
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = (isActive ? Color4.MediumPurple : Color4.Green).Opacity(0.2f)
+ },
+ }
+ };
+ }
}
private string modeName(DisplayMode mode) => $"{mode.Size.Width}x{mode.Size.Height}@{mode.RefreshRate}";
@@ -183,6 +230,17 @@ namespace osu.Framework.Tests.Visual.Platform
windowContainer.Height = fullscreen ? currentBounds.Height : window.Size.Height;
windowContainer.Position -= screenContainerOffset;
windowCaption.Text = $"{windowMode}\nSize: {window.Size.Width}x{window.Size.Height}\nClient: {window.ClientSize.Width}x{window.ClientSize.Height}";
+
+ if (borderContainer != null)
+ {
+ var borderSize = window.BorderSize.Value;
+
+ borderContainer.X = window.Position.X - borderSize.Left;
+ borderContainer.Y = window.Position.Y - borderSize.Top;
+ borderContainer.Width = windowContainer.Width + borderSize.TotalHorizontal;
+ borderContainer.Height = windowContainer.Height + borderSize.TotalVertical;
+ borderContainer.Position -= screenContainerOffset;
+ }
}
protected override void Update()
diff --git a/osu.Framework/Extensions/InputKeyExtensions.cs b/osu.Framework/Extensions/InputKeyExtensions.cs
index 50b83acd0..0fc997247 100644
--- a/osu.Framework/Extensions/InputKeyExtensions.cs
+++ b/osu.Framework/Extensions/InputKeyExtensions.cs
@@ -38,5 +38,43 @@ namespace osu.Framework.Extensions
return false;
}
}
+
+ ///
+ /// If is a physical key which is covered by another virtual key, returns that virtual key.
+ /// Otherwise, returns .
+ ///
+ ///
+ ///
+ /// > InputKey.LShift.GetVirtualKey()
+ /// Shift
+ /// > InputKey.RSuper.GetVirtualKey()
+ /// Super
+ /// > InputKey.A.GetVirtualKey()
+ /// null
+ ///
+ ///
+ public static InputKey? GetVirtualKey(this InputKey key)
+ {
+ switch (key)
+ {
+ case InputKey.LShift:
+ case InputKey.RShift:
+ return InputKey.Shift;
+
+ case InputKey.LControl:
+ case InputKey.RControl:
+ return InputKey.Control;
+
+ case InputKey.LAlt:
+ case InputKey.RAlt:
+ return InputKey.Alt;
+
+ case InputKey.LSuper:
+ case InputKey.RSuper:
+ return InputKey.Super;
+ }
+
+ return null;
+ }
}
}
diff --git a/osu.Framework/Graphics/Containers/AcrylicBlurLayer.cs b/osu.Framework/Graphics/Containers/AcrylicBlurLayer.cs
deleted file mode 100644
index aef25fdad..000000000
--- a/osu.Framework/Graphics/Containers/AcrylicBlurLayer.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using osuTK.Graphics;
-using osu.Framework.Graphics.Rendering;
-using osu.Framework.Graphics.Shaders;
-using osu.Framework.Allocation;
-
-namespace osu.Framework.Graphics.Containers
-{
- ///
- /// A specialized layer for acrylic blur effects that fills the entire area and applies blur to background content.
- /// This layer handles the actual blurring logic using a custom shader and manages background capture automatically.
- ///
- internal partial class AcrylicBlurLayer : Drawable
- {
- ///
- /// The strength of the blur effect.
- ///
- public float BlurStrength { get; set; } = 10f;
-
- ///
- /// The tint colour applied over the blurred background.
- ///
- public Color4 TintColour { get; set; } = Color4.White;
-
- ///
- /// The darkening factor for depth effect.
- ///
- public float DarkenFactor { get; set; } = 0.1f;
-
- [Resolved]
- private IRenderer? renderer { get; set; }
-
- [Resolved]
- private ShaderManager shaderManager { get; set; } = null!;
-
- public AcrylicBlurLayer()
- {
- RelativeSizeAxes = Axes.Both;
- }
-
- protected override DrawNode CreateDrawNode() => new AcrylicBlurLayerDrawNode(this);
- }
-}
diff --git a/osu.Framework/Graphics/Containers/AcrylicBlurLayerDrawNode.cs b/osu.Framework/Graphics/Containers/AcrylicBlurLayerDrawNode.cs
deleted file mode 100644
index c6e5deab3..000000000
--- a/osu.Framework/Graphics/Containers/AcrylicBlurLayerDrawNode.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable disable
-
-using System;
-using osu.Framework.Graphics.Colour;
-using osu.Framework.Graphics.Primitives;
-using osu.Framework.Graphics.Rendering;
-using osu.Framework.Graphics.Shaders;
-using osu.Framework.Graphics.Shaders.Types;
-using osuTK;
-using osuTK.Graphics;
-using System.Runtime.InteropServices;
-using osu.Framework.Graphics.Textures;
-
-namespace osu.Framework.Graphics.Containers
-{
- internal partial class AcrylicBlurLayer
- {
- private class AcrylicBlurLayerDrawNode : DrawNode
- {
- protected new AcrylicBlurLayer Source => (AcrylicBlurLayer)base.Source;
-
- private RectangleF drawRectangle;
- private IShader acrylicShader;
- private IFrameBuffer backgroundBuffer;
- private IUniformBuffer acrylicParametersBuffer;
-
- public AcrylicBlurLayerDrawNode(AcrylicBlurLayer source)
- : base(source)
- {
- }
-
- public override void ApplyState()
- {
- base.ApplyState();
- drawRectangle = Source.ScreenSpaceDrawQuad.AABBFloat;
- }
-
- protected override void Draw(IRenderer renderer)
- {
- // 检查依赖是否可用
- if (Source.renderer == null || Source.shaderManager == null)
- {
- // 如果依赖不可用,跳过绘制
- return;
- }
-
- // 获取或创建背景缓冲区(延迟到绘制线程)
- if (backgroundBuffer == null)
- {
- try
- {
- backgroundBuffer = renderer.CreateFrameBuffer(null, TextureFilteringMode.Linear);
- }
- catch
- {
- // 如果创建失败,跳过绘制
- return;
- }
- }
-
- // 捕获当前屏幕到缓冲区
- try
- {
- renderer.CaptureScreenToFrameBuffer(backgroundBuffer);
- }
- catch
- {
- // 如果捕获失败,使用固定的背景色
- renderer.Clear(new ClearInfo(Color4.Gray));
- return;
- }
-
- // 尝试加载毛玻璃着色器
- if (acrylicShader == null)
- {
- try
- {
- acrylicShader = Source.shaderManager.Load("AcrylicBlur", "Texture");
- acrylicParametersBuffer = renderer.CreateUniformBuffer();
- }
- catch (Exception ex)
- {
- // 如果加载失败,使用备用方案:直接绘制背景
- Console.WriteLine($"Failed to load acrylic shader: {ex.Message}");
- renderer.DrawFrameBuffer(backgroundBuffer, drawRectangle, ColourInfo.SingleColour(Source.TintColour));
- return;
- }
- }
-
- // 使用着色器绘制
- if (acrylicShader != null && acrylicParametersBuffer != null)
- {
- acrylicParametersBuffer.Data = acrylicParametersBuffer.Data with
- {
- TexSize = backgroundBuffer.Size,
- Radius = (int)Source.BlurStrength,
- Sigma = Source.BlurStrength / 2f,
- BlurDirection = Vector2.One,
- TintColour = new Vector4(Source.TintColour.R, Source.TintColour.G, Source.TintColour.B, Source.TintColour.A),
- DarkenFactor = Source.DarkenFactor
- };
-
- acrylicShader.BindUniformBlock("m_AcrylicParameters", acrylicParametersBuffer);
- acrylicShader.Bind();
-
- // 绘制背景到当前区域
- renderer.DrawFrameBuffer(backgroundBuffer, drawRectangle, ColourInfo.SingleColour(new Color4(1, 1, 1, 1)));
-
- acrylicShader.Unbind();
- }
- else
- {
- // 备用方案:直接绘制背景
- renderer.DrawFrameBuffer(backgroundBuffer, drawRectangle, ColourInfo.SingleColour(Source.TintColour));
- }
- }
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
- acrylicParametersBuffer?.Dispose();
- backgroundBuffer?.Dispose();
- }
-
- [StructLayout(LayoutKind.Sequential, Pack = 1)]
- private record struct AcrylicParameters
- {
- public UniformVector2 TexSize;
- public UniformInt Radius;
- public UniformFloat Sigma;
- public UniformVector2 BlurDirection;
- public UniformVector4 TintColour;
- public UniformFloat DarkenFactor;
- private readonly UniformPadding12 pad1;
- }
- }
- }
-}
diff --git a/osu.Framework/Graphics/Containers/AcrylicContainer.cs b/osu.Framework/Graphics/Containers/AcrylicContainer.cs
deleted file mode 100644
index 1ae23d4be..000000000
--- a/osu.Framework/Graphics/Containers/AcrylicContainer.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using osuTK.Graphics;
-using osu.Framework.Graphics.Rendering;
-using osu.Framework.Graphics.Shaders;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics.Shapes;
-
-namespace osu.Framework.Graphics.Containers
-{
- ///
- /// A container that applies a true acrylic/mica effect by blurring the content behind it.
- /// This implementation uses a layered approach with a blur background layer and a darkening overlay.
- /// The effect is applied regardless of drawing order and adapts to background changes in real-time.
- ///
- public partial class AcrylicContainer : Container
- {
- ///
- /// The strength of the blur effect applied to the background content.
- ///
- public float BlurStrength { get; set; } = 10f;
-
- ///
- /// The tint colour applied over the blurred background.
- ///
- public Color4 TintColour { get; set; } = Color4.White;
-
- ///
- /// The darkening factor applied to create depth.
- ///
- public float DarkenFactor { get; set; } = 0.1f;
-
- private AcrylicBlurLayer blurLayer = null!;
- private Box darkenLayer = null!;
-
- [Resolved]
- private IRenderer? renderer { get; set; }
-
- [Resolved]
- private ShaderManager shaderManager { get; set; } = null!;
-
- ///
- /// Constructs a new acrylic container.
- ///
- public AcrylicContainer()
- {
- // 默认不设置RelativeSizeAxes,让用户决定
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- // 添加虚化背景层
- Add(blurLayer = new AcrylicBlurLayer
- {
- RelativeSizeAxes = Axes.Both,
- BlurStrength = BlurStrength,
- TintColour = TintColour,
- DarkenFactor = DarkenFactor
- });
-
- // 添加暗化层
- Add(darkenLayer = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = new Color4(0, 0, 0, DarkenFactor),
- Depth = -1 // 确保在虚化层之上
- });
- }
-
- protected override void Update()
- {
- base.Update();
-
- // 同步属性到层
- blurLayer.BlurStrength = BlurStrength;
- blurLayer.TintColour = TintColour;
- blurLayer.DarkenFactor = DarkenFactor;
-
- darkenLayer.Colour = new Color4(0, 0, 0, DarkenFactor);
- }
- }
-}
diff --git a/osu.Framework/Graphics/Containers/AutoBackgroundCapture.cs b/osu.Framework/Graphics/Containers/AutoBackgroundCapture.cs
deleted file mode 100644
index e804e9686..000000000
--- a/osu.Framework/Graphics/Containers/AutoBackgroundCapture.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-// 自动背景捕获组件 - 在游戏中自动管理背景缓冲区更新
-
-using osu.Framework.Graphics.Rendering;
-using osu.Framework.Allocation;
-
-namespace osu.Framework.Graphics.Containers
-{
- ///
- /// A component that automatically captures the screen background for acrylic effects.
- /// Add this to your game once to enable automatic background capture for all AcrylicContainer instances.
- ///
- public partial class AutoBackgroundCapture : Drawable
- {
- [Resolved]
- private IRenderer? renderer { get; set; }
-
- private bool initialized;
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- // 初始化延迟到Update方法中进行
- }
-
- protected override void Update()
- {
- base.Update();
-
- // 延迟初始化,确保在正确的线程中
- if (!initialized && renderer != null)
- {
- BackgroundBufferManager.Instance.Initialize(renderer);
- initialized = true;
- }
-
- // 自动更新背景缓冲区
- BackgroundBufferManager.Instance.UpdateBackgroundBuffer();
- }
- }
-}
diff --git a/osu.Framework/Graphics/Containers/BackgroundBufferManager.cs b/osu.Framework/Graphics/Containers/BackgroundBufferManager.cs
deleted file mode 100644
index 87a19cdc9..000000000
--- a/osu.Framework/Graphics/Containers/BackgroundBufferManager.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-// 全局背景缓冲区管理器 - 自动管理背景捕获
-
-using osu.Framework.Graphics.Rendering;
-using osu.Framework.Graphics.Textures;
-
-namespace osu.Framework.Graphics.Containers
-{
- public class BackgroundBufferManager
- {
- private static BackgroundBufferManager? instance;
- private static readonly object lockObject = new object();
-
- public static BackgroundBufferManager Instance
- {
- get
- {
- if (instance == null)
- {
- lock (lockObject)
- {
- instance ??= new BackgroundBufferManager();
- }
- }
-
- return instance;
- }
- }
-
- private IFrameBuffer? backgroundBuffer;
- private IRenderer? renderer;
- private bool initialized;
-
- // 初始化管理器(在游戏启动时调用一次)
- public void Initialize(IRenderer renderer)
- {
- this.renderer = renderer;
- initialized = false;
- }
-
- // 获取背景缓冲区 - 不再自动创建,只返回已存在的
- public IFrameBuffer? GetBackgroundBuffer()
- {
- return backgroundBuffer;
- }
-
- // 获取或创建背景缓冲区(在绘制线程调用 - 延迟创建模式)
- public IFrameBuffer GetOrCreateBackgroundBuffer(IRenderer renderer)
- {
- if (backgroundBuffer == null)
- {
- this.renderer = renderer;
- backgroundBuffer = renderer.CreateFrameBuffer(null, TextureFilteringMode.Linear);
- initialized = true;
- }
-
- return backgroundBuffer;
- }
-
- // 确保背景缓冲区已创建(在绘制线程安全的地方调用)
- public void EnsureBackgroundBuffer()
- {
- if (!initialized && renderer != null)
- {
- backgroundBuffer ??= renderer.CreateFrameBuffer(null, TextureFilteringMode.Linear);
- initialized = true;
- }
- }
-
- // 更新背景缓冲区(在UpdateAfterChildren中调用)
- public void UpdateBackgroundBuffer()
- {
- // 暂时禁用屏幕捕获,直到实现正确的API
- // if (renderer is Renderer concreteRenderer && backgroundBuffer != null)
- // {
- // concreteRenderer.CaptureScreenToFrameBuffer(backgroundBuffer);
- // }
- }
-
- // 清理资源
- public void Dispose()
- {
- backgroundBuffer?.Dispose();
- backgroundBuffer = null;
- renderer = null;
- }
- }
-}
diff --git a/osu.Framework/Graphics/Containers/FrostedGlassContainer.cs b/osu.Framework/Graphics/Containers/FrostedGlassContainer.cs
deleted file mode 100644
index 004ef875f..000000000
--- a/osu.Framework/Graphics/Containers/FrostedGlassContainer.cs
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable disable
-
-using osuTK;
-using osuTK.Graphics;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics.Colour;
-using osu.Framework.Graphics.Primitives;
-using osu.Framework.Graphics.Rendering;
-using osu.Framework.Graphics.Shaders;
-using osu.Framework.Utils;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Layout;
-
-namespace osu.Framework.Graphics.Containers
-{
- ///
- /// A container that renders the background (from the screen) to an internal framebuffer, applies blur, and then
- /// blits the framebuffer to the screen, allowing for frosted glass effects on the background.
- /// If all children are of a specific non- type, use the
- /// generic version .
- ///
- public partial class FrostedGlassContainer : FrostedGlassContainer
- {
- ///
- public FrostedGlassContainer(RenderBufferFormat[] formats = null, bool pixelSnapping = false)
- : base(formats, pixelSnapping)
- {
- }
- }
-
- ///
- /// A container that renders the background (from the screen) to an internal framebuffer, applies blur, and then
- /// blits the framebuffer to the screen, allowing for frosted glass effects on the background.
- ///
- public partial class FrostedGlassContainer : Container, IBufferedContainer, IBufferedDrawable
- where T : Drawable
- {
- private Vector2 blurSigma = Vector2.Zero;
-
- ///
- /// Controls the amount of blurring in two orthogonal directions (X and Y if
- /// is zero).
- /// Blur is parametrized by a gaussian image filter. This property controls
- /// the standard deviation (sigma) of the gaussian kernel.
- ///
- public Vector2 BlurSigma
- {
- get => blurSigma;
- set
- {
- if (blurSigma == value)
- return;
-
- blurSigma = value;
- ForceRedraw();
- }
- }
-
- private float blurRotation;
-
- ///
- /// Rotates the blur kernel clockwise. In degrees. Has no effect if
- /// has the same magnitude in both directions.
- ///
- public float BlurRotation
- {
- get => blurRotation;
- set
- {
- if (blurRotation == value)
- return;
-
- blurRotation = value;
- ForceRedraw();
- }
- }
-
- private ColourInfo effectColour = Color4.White;
-
- ///
- /// The multiplicative colour of drawn buffered object after applying all effects (e.g. blur). Default is .
- ///
- public ColourInfo EffectColour
- {
- get => effectColour;
- set
- {
- if (effectColour.Equals(value))
- return;
-
- effectColour = value;
- Invalidate(Invalidation.DrawNode);
- }
- }
-
- private BlendingParameters effectBlending = BlendingParameters.Inherit;
-
- ///
- /// The to use after applying all effects. Default is .
- ///
- public BlendingParameters EffectBlending
- {
- get => effectBlending;
- set
- {
- if (effectBlending == value)
- return;
-
- effectBlending = value;
- Invalidate(Invalidation.DrawNode);
- }
- }
-
- private Color4 backgroundColour = new Color4(0, 0, 0, 0);
-
- ///
- /// The background colour of the framebuffer. Transparent black by default.
- ///
- public Color4 BackgroundColour
- {
- get => backgroundColour;
- set
- {
- if (backgroundColour == value)
- return;
-
- backgroundColour = value;
- ForceRedraw();
- }
- }
-
- private Vector2 frameBufferScale = Vector2.One;
-
- public Vector2 FrameBufferScale
- {
- get => frameBufferScale;
- set
- {
- if (frameBufferScale == value)
- return;
-
- frameBufferScale = value;
- ForceRedraw();
- }
- }
-
- private float grayscaleStrength;
-
- public float GrayscaleStrength
- {
- get => grayscaleStrength;
- set
- {
- if (grayscaleStrength == value)
- return;
-
- grayscaleStrength = value;
- ForceRedraw();
- }
- }
-
- ///
- /// Forces a redraw of the framebuffer before it is blitted the next time.
- ///
- public void ForceRedraw() => Invalidate(Invalidation.DrawNode);
-
- ///
- /// In order to signal the draw thread to re-draw the frosted glass container we version it.
- /// Our own version (update) keeps track of which version we are on, whereas the
- /// drawVersion keeps track of the version the draw thread is on.
- /// When forcing a redraw we increment updateVersion, pass it into each new drawnode
- /// and the draw thread will realize its drawVersion is lagging behind, thus redrawing.
- ///
- private long updateVersion;
-
- private readonly BufferedContainerDrawNodeSharedData sharedData;
-
- public IShader TextureShader { get; private set; }
-
- private IShader blurShader;
- private IShader grayscaleShader;
-
- ///
- /// Constructs an empty frosted glass container.
- ///
- /// The render buffer formats attached to the frame buffer of this .
- ///
- /// Whether the frame buffer position should be snapped to the nearest pixel when blitting.
- /// This amounts to setting the texture filtering mode to "nearest".
- ///
- public FrostedGlassContainer(RenderBufferFormat[] formats = null, bool pixelSnapping = false)
- {
- sharedData = new BufferedContainerDrawNodeSharedData(formats, pixelSnapping, false);
- }
-
- [BackgroundDependencyLoader]
- private void load(ShaderManager shaders)
- {
- TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
- blurShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR);
- grayscaleShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.GRAYSCALE);
- }
-
- protected override DrawNode CreateDrawNode() => new FrostedGlassDrawNode(this, sharedData);
-
- ///
- /// The blending which uses for the effect.
- ///
- public BlendingParameters DrawEffectBlending
- {
- get
- {
- BlendingParameters blending = EffectBlending;
-
- blending.CopyFromParent(Blending);
- blending.ApplyDefaultToInherited();
-
- return blending;
- }
- }
-
- protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
- {
- bool result = base.OnInvalidate(invalidation, source);
-
- if ((invalidation & Invalidation.DrawNode) > 0)
- {
- ++updateVersion;
- result = true;
- }
-
- return result;
- }
-
- public DrawColourInfo? FrameBufferDrawColour => base.DrawColourInfo;
-
- // Children should not receive the true colour to avoid colour doubling when the frame-buffers are rendered to the back-buffer.
- public override DrawColourInfo DrawColourInfo
- {
- get
- {
- var blending = Blending;
- blending.ApplyDefaultToInherited();
-
- return new DrawColourInfo(Color4.White, blending);
- }
- }
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- sharedData.Dispose();
- }
- }
-}
diff --git a/osu.Framework/Graphics/Containers/FrostedGlassContainer_DrawNode.cs b/osu.Framework/Graphics/Containers/FrostedGlassContainer_DrawNode.cs
deleted file mode 100644
index 8c1b86f6a..000000000
--- a/osu.Framework/Graphics/Containers/FrostedGlassContainer_DrawNode.cs
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable disable
-
-using System.Collections.Generic;
-using osuTK;
-using osuTK.Graphics;
-using osu.Framework.Graphics.Primitives;
-using osu.Framework.Graphics.Shaders;
-using System;
-using System.Runtime.InteropServices;
-using osu.Framework.Graphics.Colour;
-using osu.Framework.Graphics.Rendering;
-using osu.Framework.Graphics.Shaders.Types;
-using osu.Framework.Utils;
-using osu.Framework.Statistics;
-using osu.Framework.Allocation;
-
-namespace osu.Framework.Graphics.Containers
-{
- public partial class FrostedGlassContainer
- {
- private class FrostedGlassDrawNode : BufferedDrawNode, ICompositeDrawNode
- {
- protected new FrostedGlassContainer Source => (FrostedGlassContainer)base.Source;
-
- protected new CompositeDrawableDrawNode Child => (CompositeDrawableDrawNode)base.Child;
-
- private ColourInfo effectColour;
- private BlendingParameters effectBlending;
-
- private Vector2 blurSigma;
- private Vector2I blurRadius;
- private float blurRotation;
- private float grayscaleStrength;
-
- private long updateVersion;
- private IShader blurShader;
- private IShader grayscaleShader;
-
- public FrostedGlassDrawNode(FrostedGlassContainer source, BufferedContainerDrawNodeSharedData sharedData)
- : base(source, new CompositeDrawableDrawNode(source), sharedData)
- {
- }
-
- public override void ApplyState()
- {
- base.ApplyState();
-
- updateVersion = Source.updateVersion;
-
- effectColour = Source.EffectColour;
- effectBlending = Source.DrawEffectBlending;
-
- blurSigma = Source.BlurSigma;
- blurRadius = new Vector2I(Blur.KernelSize(blurSigma.X), Blur.KernelSize(blurSigma.Y));
- blurRotation = Source.BlurRotation;
- grayscaleStrength = Source.GrayscaleStrength;
-
- blurShader = Source.blurShader;
- grayscaleShader = Source.grayscaleShader;
- }
-
- protected override long GetDrawVersion() => updateVersion;
-
- protected override void PopulateContents(IRenderer renderer)
- {
- // Capture the screen to the main buffer
- renderer.CaptureScreenToFrameBuffer(SharedData.MainBuffer);
-
- // Then apply effects
- base.PopulateContents(renderer);
- }
-
- protected override void DrawContents(IRenderer renderer)
- {
- renderer.SetBlend(effectBlending);
-
- ColourInfo finalEffectColour = DrawColourInfo.Colour;
- finalEffectColour.ApplyChild(effectColour);
-
- renderer.DrawFrameBuffer(SharedData.CurrentEffectBuffer, DrawRectangle, finalEffectColour);
-
- // Draw children on top
- DrawOther(Child, renderer);
- }
-
- private IUniformBuffer blurParametersBuffer;
-
- private void drawBlurredFrameBuffer(IRenderer renderer, int kernelRadius, float sigma, float blurRotation)
- {
- blurParametersBuffer ??= renderer.CreateUniformBuffer();
-
- IFrameBuffer current = SharedData.CurrentEffectBuffer;
- IFrameBuffer target = SharedData.GetNextEffectBuffer();
-
- renderer.SetBlend(BlendingParameters.None);
-
- using (BindFrameBuffer(target))
- {
- float radians = float.DegreesToRadians(blurRotation);
-
- blurParametersBuffer.Data = blurParametersBuffer.Data with
- {
- Radius = kernelRadius,
- Sigma = sigma,
- TexSize = current.Size,
- Direction = new Vector2(MathF.Cos(radians), MathF.Sin(radians))
- };
-
- blurShader.BindUniformBlock("m_BlurParameters", blurParametersBuffer);
- blurShader.Bind();
- renderer.DrawFrameBuffer(current, new RectangleF(0, 0, current.Texture.Width, current.Texture.Height), ColourInfo.SingleColour(Color4.White));
- blurShader.Unbind();
- }
- }
-
- public List Children
- {
- get => Child.Children;
- set => Child.Children = value;
- }
-
- public bool AddChildDrawNodes => RequiresRedraw;
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
- blurParametersBuffer?.Dispose();
- }
-
- [StructLayout(LayoutKind.Sequential, Pack = 1)]
- private record struct BlurParameters
- {
- public UniformVector2 TexSize;
- public UniformInt Radius;
- public UniformFloat Sigma;
- public UniformVector2 Direction;
- private readonly UniformPadding8 pad1;
- }
- }
-
- private class BufferedContainerDrawNodeSharedData : BufferedDrawNodeSharedData
- {
- public BufferedContainerDrawNodeSharedData(RenderBufferFormat[] mainBufferFormats, bool pixelSnapping, bool clipToRootNode)
- : base(2, mainBufferFormats, pixelSnapping, clipToRootNode)
- {
- }
- }
- }
-}
diff --git a/osu.Framework/Graphics/Containers/TextFlowContainer.cs b/osu.Framework/Graphics/Containers/TextFlowContainer.cs
index 8fc1dfeed..cecbb1c84 100644
--- a/osu.Framework/Graphics/Containers/TextFlowContainer.cs
+++ b/osu.Framework/Graphics/Containers/TextFlowContainer.cs
@@ -535,9 +535,9 @@ namespace osu.Framework.Graphics.Containers
// Things will be accounted for accurately later.
// All calls to `spacingFactor()` in the original code thus reduce to returning (0,0).
if (c.RelativeAnchorPosition != Vector2.Zero)
- throw new InvalidOperationException($"All drawables in a {nameof(TextFlowContainer)} must not specify custom {RelativeAnchorPosition}s. Only (0,0) is supported.");
+ throw new InvalidOperationException($"All drawables in a {nameof(TextFlowContainer)} must not specify custom {nameof(RelativeAnchorPosition)}s. Only (0,0) is supported.");
if (c.RelativeOriginPosition != Vector2.Zero)
- throw new InvalidOperationException($"All drawables in a {nameof(TextFlowContainer)} must not specify custom {RelativeOriginPosition}s. Only (0,0) is supported.");
+ throw new InvalidOperationException($"All drawables in a {nameof(TextFlowContainer)} must not specify custom {nameof(RelativeOriginPosition)}s. Only (0,0) is supported.");
// Populate running variables with sane initial values.
if (i == 0)
diff --git a/osu.Framework/Graphics/Drawables/ScreenBlurDrawable.cs b/osu.Framework/Graphics/Drawables/ScreenBlurDrawable.cs
deleted file mode 100644
index e69de29bb..000000000
diff --git a/osu.Framework/Graphics/Lines/Path.cs b/osu.Framework/Graphics/Lines/Path.cs
index 85c69475d..73a02e943 100644
--- a/osu.Framework/Graphics/Lines/Path.cs
+++ b/osu.Framework/Graphics/Lines/Path.cs
@@ -10,9 +10,11 @@ using osuTK;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Allocation;
using System.Collections.Generic;
+using System.Runtime.InteropServices;
using osu.Framework.Caching;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics.Rendering;
+using osu.Framework.Graphics.Shaders.Types;
using osu.Framework.Layout;
using osuTK.Graphics;
@@ -34,8 +36,8 @@ namespace osu.Framework.Graphics.Lines
[BackgroundDependencyLoader]
private void load(ShaderManager shaders)
{
- TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
- pathShader = shaders.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE);
+ TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "Path");
+ pathShader = shaders.Load("PathPrepass", "PathPrepass");
}
private readonly List vertices = new List();
@@ -291,20 +293,12 @@ namespace osu.Framework.Graphics.Lines
// The path should not receive the true colour to avoid colour doubling when the frame-buffer is rendered to the back-buffer.
public override DrawColourInfo DrawColourInfo => new DrawColourInfo(Color4.White, base.DrawColourInfo.Blending);
- private Color4 backgroundColour = new Color4(0, 0, 0, 0);
+ private static readonly Color4 background_colour = new Color4(0, 0, 0, 0);
///
/// The background colour to be used for the frame buffer this path is rendered to.
///
- public virtual Color4 BackgroundColour
- {
- get => backgroundColour;
- set
- {
- backgroundColour = value;
- Invalidate(Invalidation.DrawNode);
- }
- }
+ public Color4 BackgroundColour => background_colour;
public long PathInvalidationID { get; private set; }
@@ -321,7 +315,7 @@ namespace osu.Framework.Graphics.Lines
return result;
}
- private readonly BufferedDrawNodeSharedData sharedData = new BufferedDrawNodeSharedData(new[] { RenderBufferFormat.D16 }, clipToRootNode: true);
+ private readonly BufferedDrawNodeSharedData sharedData = new BufferedDrawNodeSharedData(clipToRootNode: true);
protected override DrawNode CreateDrawNode() => new PathBufferedDrawNode(this, new PathDrawNode(this), sharedData);
@@ -335,11 +329,38 @@ namespace osu.Framework.Graphics.Lines
}
private long pathInvalidationID = -1;
+ private Texture texture;
+ private Vector4 textureRect;
+ private IUniformBuffer parametersBuffer;
public override void ApplyState()
{
base.ApplyState();
pathInvalidationID = Source.PathInvalidationID;
+ texture = Source.Texture;
+
+ var rect = texture.GetTextureRect();
+ textureRect = new Vector4(rect.Left, rect.Top, rect.Width, rect.Height);
+ }
+
+ protected override void BindUniformResources(IShader shader, IRenderer renderer)
+ {
+ base.BindUniformResources(shader, renderer);
+
+ parametersBuffer ??= renderer.CreateUniformBuffer();
+ parametersBuffer.Data = new PathTextureParameters
+ {
+ TexRect1 = textureRect,
+ };
+ shader.BindUniformBlock("m_PathTextureParameters", parametersBuffer);
+
+ texture?.Bind(1);
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ private record struct PathTextureParameters
+ {
+ public UniformVector4 TexRect1;
}
protected override long GetDrawVersion() => pathInvalidationID;
diff --git a/osu.Framework/Graphics/Lines/Path_DrawNode.cs b/osu.Framework/Graphics/Lines/Path_DrawNode.cs
index aae041d7e..428714213 100644
--- a/osu.Framework/Graphics/Lines/Path_DrawNode.cs
+++ b/osu.Framework/Graphics/Lines/Path_DrawNode.cs
@@ -2,17 +2,16 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Primitives;
-using osu.Framework.Graphics.Textures;
using osuTK;
using System;
using System.Collections.Generic;
-using osuTK.Graphics;
-using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Graphics.Shaders;
using System.Diagnostics;
+using System.Runtime.InteropServices;
using osu.Framework.Utils;
+using osuTK.Graphics.ES30;
namespace osu.Framework.Graphics.Lines
{
@@ -26,12 +25,10 @@ namespace osu.Framework.Graphics.Lines
private readonly List segments = new List();
- private Texture? texture;
- private Vector2 drawSize;
private float radius;
private IShader? pathShader;
- private IVertexBatch? triangleBatch;
+ private IVertexBatch? triangleBatch;
public PathDrawNode(Path source)
: base(source)
@@ -45,8 +42,6 @@ namespace osu.Framework.Graphics.Lines
segments.Clear();
segments.AddRange(Source.segments);
- texture = Source.Texture;
- drawSize = Source.DrawSize;
radius = Source.PathRadius;
pathShader = Source.pathShader;
}
@@ -55,136 +50,65 @@ namespace osu.Framework.Graphics.Lines
{
base.Draw(renderer);
- if (texture?.Available != true || segments.Count == 0 || pathShader == null)
+ if (segments.Count == 0 || pathShader == null || radius == 0f)
return;
// We multiply the size args by 3 such that the amount of vertices is a multiple of the amount of vertices
// per primitive (triangles in this case). Otherwise overflowing the batch will result in wrong
// grouping of vertices into primitives.
- triangleBatch ??= renderer.CreateLinearBatch(max_res * 200 * 3, 10, PrimitiveTopology.Triangles);
+ triangleBatch ??= renderer.CreateLinearBatch(max_res * 200 * 3, 10, PrimitiveTopology.Triangles);
renderer.PushLocalMatrix(DrawInfo.Matrix);
- renderer.PushDepthInfo(DepthInfo.Default);
- // Blending is removed to allow for correct blending between the wedges of the path.
- renderer.SetBlend(BlendingParameters.None);
+ renderer.SetBlend(new BlendingParameters
+ {
+ Source = BlendingType.One,
+ Destination = BlendingType.One,
+ SourceAlpha = BlendingType.One,
+ DestinationAlpha = BlendingType.One,
+ RGBEquation = BlendingEquation.Max,
+ AlphaEquation = BlendingEquation.Max,
+ });
pathShader.Bind();
- texture.Bind();
-
updateVertexBuffer();
pathShader.Unbind();
- renderer.PopDepthInfo();
renderer.PopLocalMatrix();
}
- private Vector2 pointOnCircle(float angle) => new Vector2(MathF.Cos(angle), MathF.Sin(angle));
-
- private Vector2 relativePosition(Vector2 localPos) => Vector2.Divide(localPos, drawSize);
-
- private Color4 colourAt(Vector2 localPos) => DrawColourInfo.Colour.TryExtractSingleColour(out SRGBColour colour)
- ? colour.SRGB
- : DrawColourInfo.Colour.Interpolate(relativePosition(localPos)).SRGB;
-
- private void addSegmentQuads(SegmentWithThickness segment, RectangleF texRect)
+ private void addCap(Line cap)
{
- Debug.Assert(triangleBatch != null);
+ // The provided line is perpendicular to the end/start of a segment.
+ // To get the remaining quad positions we are expanding said segment by the path radius.
+ Vector2 ortho = cap.OrthogonalDirection;
+ if (float.IsNaN(ortho.X) || float.IsNaN(ortho.Y))
+ ortho = Vector2.UnitY;
- // Each segment of the path is actually rendered as 2 quads, being split in half along the approximating line.
- // On this line the depth is 1 instead of 0, which is done in order to properly handle self-overlap using the depth buffer.
- Vector3 firstMiddlePoint = new Vector3(segment.Guide.StartPoint.X, segment.Guide.StartPoint.Y, 1);
- Vector3 secondMiddlePoint = new Vector3(segment.Guide.EndPoint.X, segment.Guide.EndPoint.Y, 1);
- Color4 firstMiddleColour = colourAt(segment.Guide.StartPoint);
- Color4 secondMiddleColour = colourAt(segment.Guide.EndPoint);
+ Vector2 v2 = cap.StartPoint + ortho * radius;
+ Vector2 v3 = cap.EndPoint + ortho * radius;
- // Each of the quads (mentioned above) is rendered as 2 triangles:
- // Outer quad, triangle 1
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = new Vector3(segment.EdgeRight.EndPoint.X, segment.EdgeRight.EndPoint.Y, 0),
- TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y),
- Colour = colourAt(segment.EdgeRight.EndPoint)
- });
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = new Vector3(segment.EdgeRight.StartPoint.X, segment.EdgeRight.StartPoint.Y, 0),
- TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y),
- Colour = colourAt(segment.EdgeRight.StartPoint)
- });
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = firstMiddlePoint,
- TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y),
- Colour = firstMiddleColour
- });
-
- // Outer quad, triangle 2
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = firstMiddlePoint,
- TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y),
- Colour = firstMiddleColour
- });
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = secondMiddlePoint,
- TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y),
- Colour = secondMiddleColour
- });
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = new Vector3(segment.EdgeRight.EndPoint.X, segment.EdgeRight.EndPoint.Y, 0),
- TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y),
- Colour = colourAt(segment.EdgeRight.EndPoint)
- });
-
- // Inner quad, triangle 1
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = firstMiddlePoint,
- TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y),
- Colour = firstMiddleColour
- });
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = secondMiddlePoint,
- TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y),
- Colour = secondMiddleColour
- });
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = new Vector3(segment.EdgeLeft.EndPoint.X, segment.EdgeLeft.EndPoint.Y, 0),
- TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y),
- Colour = colourAt(segment.EdgeLeft.EndPoint)
- });
-
- // Inner quad, triangle 2
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = new Vector3(segment.EdgeLeft.EndPoint.X, segment.EdgeLeft.EndPoint.Y, 0),
- TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y),
- Colour = colourAt(segment.EdgeLeft.EndPoint)
- });
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = new Vector3(segment.EdgeLeft.StartPoint.X, segment.EdgeLeft.StartPoint.Y, 0),
- TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y),
- Colour = colourAt(segment.EdgeLeft.StartPoint)
- });
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = firstMiddlePoint,
- TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y),
- Colour = firstMiddleColour
- });
+ drawQuad
+ (
+ new Quad(cap.StartPoint, v2, cap.EndPoint, v3),
+ new Quad(new Vector2(0, -1), new Vector2(1, -1), new Vector2(0, 1), Vector2.One)
+ );
}
- private void addSegmentCaps(float thetaDiff, Line segmentLeft, Line segmentRight, Line prevSegmentLeft, Line prevSegmentRight, RectangleF texRect)
+ private void addSegmentQuad(DrawableSegment segment)
{
- Debug.Assert(triangleBatch != null);
+ drawQuad
+ (
+ segment.DrawQuad,
+ new Quad(new Vector2(0, -1), new Vector2(0, -1), new Vector2(0, 1), new Vector2(0, 1))
+ );
+ }
+
+ private void addConnectionBetween(DrawableSegment segment, DrawableSegment prevSegment)
+ {
+ float thetaDiff = segment.Guide.Theta - prevSegment.Guide.Theta;
if (Math.Abs(thetaDiff) > MathF.PI)
thetaDiff = -Math.Sign(thetaDiff) * 2 * MathF.PI + thetaDiff;
@@ -192,83 +116,129 @@ namespace osu.Framework.Graphics.Lines
if (thetaDiff == 0f)
return;
- Vector2 origin = (segmentLeft.StartPoint + segmentRight.StartPoint) / 2;
-
- // Use segment end points instead of calculating start/end via theta to guarantee
- // that the vertices have the exact same position as the quads, which prevents
- // possible pixel gaps during rasterization.
- Vector2 current = thetaDiff > 0f ? prevSegmentRight.EndPoint : prevSegmentLeft.EndPoint;
- Vector2 end = thetaDiff > 0f ? segmentRight.StartPoint : segmentLeft.StartPoint;
-
- Line start = thetaDiff > 0f ? new Line(prevSegmentLeft.EndPoint, prevSegmentRight.EndPoint) : new Line(prevSegmentRight.EndPoint, prevSegmentLeft.EndPoint);
- float theta0 = start.Theta;
- float thetaStep = Math.Sign(thetaDiff) * MathF.PI / max_res;
- int stepCount = (int)MathF.Ceiling(thetaDiff / thetaStep);
-
- Color4 originColour = colourAt(origin);
- Color4 currentColour = colourAt(current);
-
- for (int i = 1; i <= stepCount; i++)
+ // more than 90 degrees - add end cap to the previous segment
+ if (Math.Abs(thetaDiff) > Math.PI * 0.5)
{
- // Center point
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = new Vector3(origin.X, origin.Y, 1),
- TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y),
- Colour = originColour
- });
-
- // First outer point
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = new Vector3(current.X, current.Y, 0),
- TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y),
- Colour = currentColour
- });
-
- current = i < stepCount ? origin + pointOnCircle(theta0 + i * thetaStep) * radius : end;
- currentColour = colourAt(current);
-
- // Second outer point
- triangleBatch.Add(new TexturedVertex3D
- {
- Position = new Vector3(current.X, current.Y, 0),
- TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y),
- Colour = currentColour
- });
+ addEndCap(prevSegment);
+ return;
}
+
+ Vector2 origin = segment.Guide.StartPoint;
+ Line end = thetaDiff > 0f ? new Line(segment.BottomLeft, segment.TopLeft) : new Line(segment.TopLeft, segment.BottomLeft);
+ Line start = thetaDiff > 0f ? new Line(prevSegment.TopRight, prevSegment.BottomRight) : new Line(prevSegment.BottomRight, prevSegment.TopRight);
+
+ // position of a vertex which is located slightly below segments intersection
+ Vector2 innerVertex = Vector2.Lerp(start.StartPoint, end.EndPoint, 0.5f);
+
+ // at this small angle curvature of the connection isn't noticeable, we can get away with a single triangle
+ if (Math.Abs(thetaDiff) < Math.PI / max_res)
+ {
+ drawTriangle(new Triangle(start.EndPoint, innerVertex, end.StartPoint), origin);
+ return;
+ }
+
+ // 2 triangles for the remaining cases
+ Vector2 middle1 = Vector2.Lerp(start.EndPoint, end.StartPoint, 0.5f);
+ Vector2 outerVertex = Vector2.Lerp(origin, middle1, radius / (float)Math.Cos(Math.Abs(thetaDiff) * 0.5) / Vector2.Distance(origin, middle1));
+ drawQuad(new Quad(start.EndPoint, outerVertex, innerVertex, end.StartPoint), origin);
+ }
+
+ private void drawTriangle(Triangle triangle, Vector2 origin)
+ {
+ drawTriangle
+ (
+ triangle,
+ new Triangle
+ (
+ Vector2.Divide(triangle.P0 - origin, radius),
+ Vector2.Divide(triangle.P1 - origin, radius),
+ Vector2.Divide(triangle.P2 - origin, radius)
+ )
+ );
+ }
+
+ private void drawTriangle(Triangle triangle, Triangle relativePos)
+ {
+ Debug.Assert(triangleBatch != null);
+
+ triangleBatch.Add(new PathVertex
+ {
+ Position = triangle.P0,
+ RelativePos = relativePos.P0
+ });
+ triangleBatch.Add(new PathVertex
+ {
+ Position = triangle.P1,
+ RelativePos = relativePos.P1
+ });
+ triangleBatch.Add(new PathVertex
+ {
+ Position = triangle.P2,
+ RelativePos = relativePos.P2
+ });
+ }
+
+ private void drawQuad(Quad quad, Vector2 origin)
+ {
+ drawQuad
+ (
+ quad,
+ new Quad
+ (
+ Vector2.Divide(quad.TopLeft - origin, radius),
+ Vector2.Divide(quad.TopRight - origin, radius),
+ Vector2.Divide(quad.BottomLeft - origin, radius),
+ Vector2.Divide(quad.BottomRight - origin, radius)
+ )
+ );
+ }
+
+ private void drawQuad(Quad quad, Quad relativePos)
+ {
+ Debug.Assert(triangleBatch != null);
+
+ triangleBatch.Add(new PathVertex
+ {
+ Position = quad.TopLeft,
+ RelativePos = relativePos.TopLeft
+ });
+ triangleBatch.Add(new PathVertex
+ {
+ Position = quad.TopRight,
+ RelativePos = relativePos.TopRight
+ });
+ triangleBatch.Add(new PathVertex
+ {
+ Position = quad.BottomLeft,
+ RelativePos = relativePos.BottomLeft
+ });
+
+ triangleBatch.Add(new PathVertex
+ {
+ Position = quad.BottomLeft,
+ RelativePos = relativePos.BottomLeft
+ });
+ triangleBatch.Add(new PathVertex
+ {
+ Position = quad.TopRight,
+ RelativePos = relativePos.TopRight
+ });
+ triangleBatch.Add(new PathVertex
+ {
+ Position = quad.BottomRight,
+ RelativePos = relativePos.BottomRight
+ });
}
private void updateVertexBuffer()
{
- // Explanation of the terms "left" and "right":
- // "Left" and "right" are used here in terms of a typical (Cartesian) coordinate system.
- // So "left" corresponds to positive angles (anti-clockwise), and "right" corresponds
- // to negative angles (clockwise).
- //
- // Note that this is not the same as the actually used coordinate system, in which the
- // y-axis is flipped. In this system, "left" corresponds to negative angles (clockwise)
- // and "right" corresponds to positive angles (anti-clockwise).
- //
- // Using a Cartesian system makes the calculations more consistent with typical math,
- // such as in angle<->coordinate conversions and ortho vectors. For example, the x-unit
- // vector (1, 0) has the orthogonal y-unit vector (0, 1). This would be "left" in the
- // Cartesian system. But in the actual system, it's "right" and clockwise. Where
- // this becomes confusing is during debugging, because OpenGL uses a Cartesian system.
- // So to make debugging a bit easier (i.e. w/ RenderDoc or Nsight), this code uses terms
- // that make sense in the realm of OpenGL, rather than terms which are technically
- // accurate in the actually used "flipped" system.
-
- Debug.Assert(texture != null);
Debug.Assert(segments.Count > 0);
- RectangleF texRect = texture.GetTextureRect(new RectangleF(0.5f, 0.5f, texture.Width - 1, texture.Height - 1));
-
Line? segmentToDraw = null;
SegmentStartLocation location = SegmentStartLocation.Outside;
SegmentStartLocation modifiedLocation = SegmentStartLocation.Outside;
SegmentStartLocation nextLocation = SegmentStartLocation.End;
- SegmentWithThickness? lastDrawnSegment = null;
+ DrawableSegment? lastDrawnSegment = null;
for (int i = 0; i < segments.Count; i++)
{
@@ -309,9 +279,9 @@ namespace osu.Framework.Graphics.Lines
}
else // Otherwise draw the expanded segment
{
- SegmentWithThickness s = new SegmentWithThickness(segmentToDraw.Value, radius, location, modifiedLocation);
- addSegmentQuads(s, texRect);
- connect(s, lastDrawnSegment, texRect);
+ DrawableSegment s = new DrawableSegment(segmentToDraw.Value, radius, location, modifiedLocation);
+ addSegmentQuad(s);
+ connect(s, lastDrawnSegment);
lastDrawnSegment = s;
segmentToDraw = segments[i];
@@ -328,22 +298,22 @@ namespace osu.Framework.Graphics.Lines
// Finish drawing last segment (if exists)
if (segmentToDraw.HasValue)
{
- SegmentWithThickness s = new SegmentWithThickness(segmentToDraw.Value, radius, location, modifiedLocation);
- addSegmentQuads(s, texRect);
- connect(s, lastDrawnSegment, texRect);
- addEndCap(s, texRect);
+ DrawableSegment s = new DrawableSegment(segmentToDraw.Value, radius, location, modifiedLocation);
+ addSegmentQuad(s);
+ connect(s, lastDrawnSegment);
+ addEndCap(s);
}
}
///
/// Connects the start of the segment to the end of a previous one.
///
- private void connect(SegmentWithThickness segment, SegmentWithThickness? prevSegment, RectangleF texRect)
+ private void connect(DrawableSegment segment, DrawableSegment? prevSegment)
{
if (!prevSegment.HasValue)
{
// Nothing to connect to - add start cap
- addStartCap(segment, texRect);
+ addStartCap(segment);
return;
}
@@ -352,13 +322,13 @@ namespace osu.Framework.Graphics.Lines
default:
case SegmentStartLocation.End:
// Segment starts at the end of the previous one
- addConnectionBetween(segment, prevSegment.Value, texRect);
+ addConnectionBetween(segment, prevSegment.Value);
break;
case SegmentStartLocation.Start:
case SegmentStartLocation.Middle:
// Segment starts at the start or the middle of the previous one - add end cap to the previous segment
- addEndCap(prevSegment.Value, texRect);
+ addEndCap(prevSegment.Value);
break;
case SegmentStartLocation.Outside:
@@ -372,39 +342,19 @@ namespace osu.Framework.Graphics.Lines
// line since horizontal one will pass through it. However, that wouldn't be the case if horizontal line was located at
// the middle and so end cap would be required.
if (segment.StartLocation != SegmentStartLocation.End)
- addEndCap(prevSegment.Value, texRect);
+ addEndCap(prevSegment.Value);
// add start cap to the current one
- addStartCap(segment, texRect);
+ addStartCap(segment);
break;
}
}
- private void addConnectionBetween(SegmentWithThickness segment, SegmentWithThickness prevSegment, RectangleF texRect)
- {
- float thetaDiff = segment.Guide.Theta - prevSegment.Guide.Theta;
- addSegmentCaps(thetaDiff, segment.EdgeLeft, segment.EdgeRight, prevSegment.EdgeLeft, prevSegment.EdgeRight, texRect);
- }
+ private void addEndCap(DrawableSegment segment) =>
+ addCap(new Line(segment.TopRight, segment.BottomRight));
- private void addEndCap(SegmentWithThickness segment, RectangleF texRect)
- {
- // Explanation of semi-circle caps:
- // Semi-circles are essentially 180 degree caps. So to create these caps, we
- // can simply "fake" a segment that's 180 degrees flipped. This works because
- // we are taking advantage of the fact that a path which makes a 180 degree
- // bend would have a semi-circle cap.
-
- Line flippedLeft = new Line(segment.EdgeRight.EndPoint, segment.EdgeRight.StartPoint);
- Line flippedRight = new Line(segment.EdgeLeft.EndPoint, segment.EdgeLeft.StartPoint);
- addSegmentCaps(MathF.PI, flippedLeft, flippedRight, segment.EdgeLeft, segment.EdgeRight, texRect);
- }
-
- private void addStartCap(SegmentWithThickness segment, RectangleF texRect)
- {
- Line flippedLeft = new Line(segment.EdgeRight.EndPoint, segment.EdgeRight.StartPoint);
- Line flippedRight = new Line(segment.EdgeLeft.EndPoint, segment.EdgeLeft.StartPoint);
- addSegmentCaps(MathF.PI, segment.EdgeLeft, segment.EdgeRight, flippedLeft, flippedRight, texRect);
- }
+ private void addStartCap(DrawableSegment segment) =>
+ addCap(new Line(segment.BottomLeft, segment.TopLeft));
private static float progressFor(Line line, float length, Vector2 point)
{
@@ -427,38 +377,53 @@ namespace osu.Framework.Graphics.Lines
Outside
}
- private readonly struct SegmentWithThickness
+ private readonly struct DrawableSegment
{
///
- /// The line defining this .
+ /// The line defining this .
///
public Line Guide { get; }
///
- /// The line parallel to and located on the left side of it.
+ /// The draw quad of this .
///
- public Line EdgeLeft { get; }
+ public Quad DrawQuad { get; }
///
- /// The line parallel to and located on the right side of it.
+ /// The top-left position of the of this .
///
- public Line EdgeRight { get; }
+ public Vector2 TopLeft => DrawQuad.TopLeft;
///
- /// Position of this relative to the previous one.
+ /// The top-right position of the of this .
+ ///
+ public Vector2 TopRight => DrawQuad.TopRight;
+
+ ///
+ /// The bottom-left position of the of this .
+ ///
+ public Vector2 BottomLeft => DrawQuad.BottomLeft;
+
+ ///
+ /// The bottom-right position of the of this .
+ ///
+ public Vector2 BottomRight => DrawQuad.BottomRight;
+
+ ///
+ /// Position of this relative to the previous one.
///
public SegmentStartLocation StartLocation { get; }
///
- /// Position of this modified relative to the previous one.
+ /// Position of this modified relative to the previous one.
///
public SegmentStartLocation ModifiedStartLocation { get; }
- /// The line defining this .
- /// The distance at which and will be located from the .
- /// Position of this relative to the previous one.
- /// Position of this modified relative to the previous one.
- public SegmentWithThickness(Line guide, float distance, SegmentStartLocation startLocation, SegmentStartLocation modifiedStartLocation)
+ /// The line defining this .
+ /// The path radius.
+ /// Position of this relative to the previous one.
+ /// Position of this modified relative to the previous one.
+ public DrawableSegment(Line guide, float radius, SegmentStartLocation startLocation, SegmentStartLocation modifiedStartLocation)
{
Guide = guide;
StartLocation = startLocation;
@@ -468,10 +433,34 @@ namespace osu.Framework.Graphics.Lines
if (float.IsNaN(ortho.X) || float.IsNaN(ortho.Y))
ortho = Vector2.UnitY;
- EdgeLeft = new Line(Guide.StartPoint + ortho * distance, Guide.EndPoint + ortho * distance);
- EdgeRight = new Line(Guide.StartPoint - ortho * distance, Guide.EndPoint - ortho * distance);
+ DrawQuad = new Quad
+ (
+ Guide.StartPoint + ortho * radius,
+ Guide.EndPoint + ortho * radius,
+ Guide.StartPoint - ortho * radius,
+ Guide.EndPoint - ortho * radius
+ );
}
}
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct PathVertex : IEquatable, IVertex
+ {
+ [VertexMember(2, VertexAttribPointerType.Float)]
+ public Vector2 Position;
+
+ ///
+ /// A position of a vertex, where distance from that position to (0,0) defines it's colour.
+ /// Distance 0 means white and 1 means black.
+ /// This position is being interpolated between vertices and final colour is being applied in the fragment shader.
+ ///
+ [VertexMember(2, VertexAttribPointerType.Float)]
+ public Vector2 RelativePos;
+
+ public readonly bool Equals(PathVertex other) =>
+ Position.Equals(other.Position)
+ && RelativePos.Equals(other.RelativePos);
+ }
}
}
}
diff --git a/osu.Framework/Graphics/Lines/SmoothPath.cs b/osu.Framework/Graphics/Lines/SmoothPath.cs
index e01202032..6a683109f 100644
--- a/osu.Framework/Graphics/Lines/SmoothPath.cs
+++ b/osu.Framework/Graphics/Lines/SmoothPath.cs
@@ -4,7 +4,6 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Caching;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osuTK.Graphics;
@@ -38,18 +37,6 @@ namespace osu.Framework.Graphics.Lines
}
}
- private Color4? customBackgroundColour;
-
- ///
- /// The background colour to be used for the frame buffer this path is rendered to.
- /// For , this automatically defaults to the colour at 0 (the outermost colour of the path) to avoid aliasing issues.
- ///
- public override Color4 BackgroundColour
- {
- get => customBackgroundColour ?? base.BackgroundColour;
- set => customBackgroundColour = base.BackgroundColour = value;
- }
-
private readonly Cached textureCache = new Cached();
protected void InvalidateTexture()
@@ -63,7 +50,7 @@ namespace osu.Framework.Graphics.Lines
if (textureCache.IsValid)
return;
- int textureWidth = (int)PathRadius * 2;
+ int textureWidth = (int)Math.Max(PathRadius, 1) * 2;
//initialise background
var raw = new Image(textureWidth, 1);
@@ -89,9 +76,6 @@ namespace osu.Framework.Graphics.Lines
Texture = texture;
}
- if (customBackgroundColour == null)
- base.BackgroundColour = ColourAt(0).Opacity(0);
-
textureCache.Validate();
}
diff --git a/osu.Framework/Graphics/OpenGL/GLRenderer.cs b/osu.Framework/Graphics/OpenGL/GLRenderer.cs
index c522756af..a51d34870 100644
--- a/osu.Framework/Graphics/OpenGL/GLRenderer.cs
+++ b/osu.Framework/Graphics/OpenGL/GLRenderer.cs
@@ -375,13 +375,6 @@ namespace osu.Framework.Graphics.OpenGL
return image;
}
- public override void CaptureScreenToFrameBuffer(IFrameBuffer frameBuffer)
- {
- frameBuffer.Bind();
- GL.CopyTexSubImage2D(All.Texture2D, 0, 0, 0, 0, 0, frameBuffer.Texture.Width, frameBuffer.Texture.Height);
- frameBuffer.Unbind();
- }
-
protected internal override Image ExtractFrameBufferData(IFrameBuffer frameBuffer)
{
int width = frameBuffer.Texture.Width;
diff --git a/osu.Framework/Graphics/Rendering/Deferred/DeferredRenderer.cs b/osu.Framework/Graphics/Rendering/Deferred/DeferredRenderer.cs
index bc37250e5..61968a257 100644
--- a/osu.Framework/Graphics/Rendering/Deferred/DeferredRenderer.cs
+++ b/osu.Framework/Graphics/Rendering/Deferred/DeferredRenderer.cs
@@ -191,12 +191,6 @@ namespace osu.Framework.Graphics.Rendering.Deferred
protected internal override Image TakeScreenshot()
=> VeldridDevice.TakeScreenshot();
- public override void CaptureScreenToFrameBuffer(IFrameBuffer frameBuffer)
- {
- // TODO: Implement screen capture for DeferredRenderer
- // This is a placeholder implementation
- }
-
void IRenderer.EnterDrawNode(DrawNode node)
{
drawNodeStack.Push(node);
diff --git a/osu.Framework/Graphics/Rendering/Dummy/DummyRenderer.cs b/osu.Framework/Graphics/Rendering/Dummy/DummyRenderer.cs
index 1f40da1fb..0dff42beb 100644
--- a/osu.Framework/Graphics/Rendering/Dummy/DummyRenderer.cs
+++ b/osu.Framework/Graphics/Rendering/Dummy/DummyRenderer.cs
@@ -25,11 +25,6 @@ namespace osu.Framework.Graphics.Rendering.Dummy
protected internal override Image TakeScreenshot()
=> new Image(1, 1);
- public override void CaptureScreenToFrameBuffer(IFrameBuffer frameBuffer)
- {
- // Dummy implementation - do nothing
- }
-
protected override IShaderPart CreateShaderPart(IShaderStore store, string name, byte[]? rawData, ShaderPartType partType)
=> new DummyShaderPart();
diff --git a/osu.Framework/Graphics/Rendering/IRenderer.cs b/osu.Framework/Graphics/Rendering/IRenderer.cs
index c916bddf1..78ab6a979 100644
--- a/osu.Framework/Graphics/Rendering/IRenderer.cs
+++ b/osu.Framework/Graphics/Rendering/IRenderer.cs
@@ -350,12 +350,6 @@ namespace osu.Framework.Graphics.Rendering
///
protected internal Image TakeScreenshot();
- ///
- /// Captures the current screen content to a framebuffer for post-processing effects.
- ///
- /// The framebuffer to capture to.
- void CaptureScreenToFrameBuffer(IFrameBuffer frameBuffer);
-
///
/// Returns an image containing the content of a framebuffer.
///
@@ -398,13 +392,8 @@ namespace osu.Framework.Graphics.Rendering
/// The texture's horizontal wrap mode.
/// The texture's vertex wrap mode.
/// The .
- Texture CreateTexture(int width,
- int height,
- bool manualMipmaps = false,
- TextureFilteringMode filteringMode = TextureFilteringMode.Linear,
- WrapMode wrapModeS = WrapMode.None,
- WrapMode wrapModeT = WrapMode.None,
- Color4? initialisationColour = null);
+ Texture CreateTexture(int width, int height, bool manualMipmaps = false, TextureFilteringMode filteringMode = TextureFilteringMode.Linear, WrapMode wrapModeS = WrapMode.None,
+ WrapMode wrapModeT = WrapMode.None, Color4? initialisationColour = null);
///
/// Creates a new video texture.
@@ -471,7 +460,7 @@ namespace osu.Framework.Graphics.Rendering
{
}
-#region TextureVisualiser Support
+ #region TextureVisualiser Support
///
/// An event which is invoked every time a is created.
@@ -483,6 +472,6 @@ namespace osu.Framework.Graphics.Rendering
///
internal Texture[] GetAllTextures();
-#endregion
+ #endregion
}
}
diff --git a/osu.Framework/Graphics/Rendering/IScreenContentProvider.cs b/osu.Framework/Graphics/Rendering/IScreenContentProvider.cs
deleted file mode 100644
index e69de29bb..000000000
diff --git a/osu.Framework/Graphics/Rendering/Renderer.cs b/osu.Framework/Graphics/Rendering/Renderer.cs
index d8cd64499..b58ae4e00 100644
--- a/osu.Framework/Graphics/Rendering/Renderer.cs
+++ b/osu.Framework/Graphics/Rendering/Renderer.cs
@@ -328,10 +328,8 @@ namespace osu.Framework.Graphics.Rendering
protected internal abstract Image TakeScreenshot();
///
- /// Captures the current screen content to a framebuffer for post-processing effects.
+ /// Returns an image containing the content of a framebuffer.
///
- /// The framebuffer to capture to.
- public abstract void CaptureScreenToFrameBuffer(IFrameBuffer frameBuffer);
protected internal virtual Image? ExtractFrameBufferData(IFrameBuffer frameBuffer) => null;
///
diff --git a/osu.Framework/Graphics/Transforms/CubicBezierEasingFunction.cs b/osu.Framework/Graphics/Transforms/CubicBezierEasingFunction.cs
new file mode 100644
index 000000000..189da2ea8
--- /dev/null
+++ b/osu.Framework/Graphics/Transforms/CubicBezierEasingFunction.cs
@@ -0,0 +1,83 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Utils;
+using osuTK;
+
+namespace osu.Framework.Graphics.Transforms
+{
+ ///
+ /// An easing function that creates a smooth transition using a cubic bezier curve.
+ ///
+ public readonly struct CubicBezierEasingFunction : IEasingFunction
+ {
+ public readonly double X1;
+ public readonly double Y1;
+ public readonly double X2;
+ public readonly double Y2;
+
+ /// x position of the first control point. Must be in 0-1 range
+ /// y position of the first control point
+ /// x position of the second control point. Must be in 0-1 range
+ /// y position of the second control point
+ public CubicBezierEasingFunction(double x1, double y1, double x2, double y2)
+ {
+ if (Precision.DefinitelyBigger(0, x1) || Precision.DefinitelyBigger(x1, 1))
+ throw new ArgumentOutOfRangeException(nameof(x1), "Must be within [0, 1] range.");
+
+ if (Precision.DefinitelyBigger(0, x2) || Precision.DefinitelyBigger(x2, 1))
+ throw new ArgumentOutOfRangeException(nameof(x2), "Must be within [0, 1] range.");
+
+ X1 = x1;
+ Y1 = y1;
+ X2 = x2;
+ Y2 = y2;
+ }
+
+ /// position of the first control point.
+ /// position of the second control point.
+ ///
+ /// The x psoition of both control points must be in 0-1 range.
+ ///
+ public CubicBezierEasingFunction(Vector2 p1, Vector2 p2)
+ : this(p1.X, p1.Y, p2.X, p2.Y)
+ {
+ }
+
+ ///
+ /// Constructs an easing function with initializes to 0 and to 1, which will perfectly flatten out the curve on both ends.
+ ///
+ /// ease-in strength, in 0-1 range
+ /// ease-out strength, in 0-1 range
+ public CubicBezierEasingFunction(double easeIn, double easeOut)
+ : this(easeIn, 0, 1 - easeOut, 1)
+ {
+ }
+
+ private static double evaluateBezier(double t, double a1, double a2) => (((1 - 3 * a2 + 3 * a1) * t + (3 * a2 - 6 * a1)) * t + 3 * a1) * t;
+
+ private static double findTForX(double time, double x1, double x2)
+ {
+ double left = 0.0, right = 1.0, currentT = 0;
+
+ for (int i = 0; i < 100; i++)
+ {
+ currentT = left + (right - left) / 2;
+ double currentX = evaluateBezier(currentT, x1, x2) - time;
+
+ if (currentX > 0)
+ right = currentT;
+ else
+ left = currentT;
+
+ if (Math.Abs(currentX) <= 0.0000001)
+ break;
+ }
+
+ return currentT;
+ }
+
+ public double ApplyEasing(double time) => evaluateBezier(findTForX(time, X1, X2), Y1, Y2);
+ }
+}
diff --git a/osu.Framework/Graphics/UserInterface/TextBox.cs b/osu.Framework/Graphics/UserInterface/TextBox.cs
index 2701f6dc6..ceda46a2a 100644
--- a/osu.Framework/Graphics/UserInterface/TextBox.cs
+++ b/osu.Framework/Graphics/UserInterface/TextBox.cs
@@ -103,20 +103,22 @@ namespace osu.Framework.Graphics.UserInterface
// discard control/special characters.
return false;
- var currentNumberFormat = CultureInfo.CurrentCulture.NumberFormat;
-
- switch (InputProperties.Type)
+ if (InputProperties.Type.IsNumerical())
{
- case TextInputType.Decimal:
- return char.IsAsciiDigit(character) || currentNumberFormat.NumberDecimalSeparator.Contains(character);
+ bool validNumericalCharacter = false;
- case TextInputType.Number:
- case TextInputType.NumericalPassword:
- return char.IsAsciiDigit(character);
+ var currentNumberFormat = CultureInfo.CurrentCulture.NumberFormat;
- default:
- return true;
+ validNumericalCharacter |= char.IsAsciiDigit(character);
+ validNumericalCharacter |= selectionLeft == 0 && currentNumberFormat.NegativeSign.Contains(character);
+
+ if (InputProperties.Type == TextInputType.Decimal)
+ validNumericalCharacter |= currentNumberFormat.NumberDecimalSeparator.Contains(character);
+
+ return validNumericalCharacter;
}
+
+ return true;
}
private bool readOnly;
diff --git a/osu.Framework/Graphics/Veldrid/VeldridRenderer.ScreenContent.cs b/osu.Framework/Graphics/Veldrid/VeldridRenderer.ScreenContent.cs
deleted file mode 100644
index e69de29bb..000000000
diff --git a/osu.Framework/Graphics/Veldrid/VeldridRenderer.cs b/osu.Framework/Graphics/Veldrid/VeldridRenderer.cs
index fb2e05acc..403eeb10d 100644
--- a/osu.Framework/Graphics/Veldrid/VeldridRenderer.cs
+++ b/osu.Framework/Graphics/Veldrid/VeldridRenderer.cs
@@ -223,12 +223,6 @@ namespace osu.Framework.Graphics.Veldrid
protected internal override Image TakeScreenshot()
=> veldridDevice.TakeScreenshot();
- public override void CaptureScreenToFrameBuffer(IFrameBuffer frameBuffer)
- {
- // TODO: Implement screen capture for Veldrid
- // This is a placeholder implementation
- }
-
protected internal override Image? ExtractFrameBufferData(IFrameBuffer frameBuffer)
=> ExtractTexture((VeldridTexture)frameBuffer.Texture.NativeTexture);
diff --git a/osu.Framework/Input/Bindings/KeyBindingContainer.cs b/osu.Framework/Input/Bindings/KeyBindingContainer.cs
index 8ef1e9bb0..f9467ddf1 100644
--- a/osu.Framework/Input/Bindings/KeyBindingContainer.cs
+++ b/osu.Framework/Input/Bindings/KeyBindingContainer.cs
@@ -328,8 +328,8 @@ namespace osu.Framework.Input.Bindings
}
}
- if (handled != null)
- Logger.Log($"Pressed ({pressed}) handled by {handled}.", LoggingTarget.Runtime, LogLevel.Debug);
+ // if (handled != null)
+ // Logger.Log($"Pressed ({pressed}) handled by {handled}.", LoggingTarget.Runtime, LogLevel.Debug);
return handled;
}
diff --git a/osu.Framework/Input/Bindings/KeyCombination.cs b/osu.Framework/Input/Bindings/KeyCombination.cs
index 4f30fc504..adef2a8eb 100644
--- a/osu.Framework/Input/Bindings/KeyCombination.cs
+++ b/osu.Framework/Input/Bindings/KeyCombination.cs
@@ -108,30 +108,6 @@ namespace osu.Framework.Input.Bindings
return ContainsAll(Keys, pressedKeys.Keys, matchingMode);
}
- private static InputKey? getVirtualKey(InputKey key)
- {
- switch (key)
- {
- case InputKey.LShift:
- case InputKey.RShift:
- return InputKey.Shift;
-
- case InputKey.LControl:
- case InputKey.RControl:
- return InputKey.Control;
-
- case InputKey.LAlt:
- case InputKey.RAlt:
- return InputKey.Alt;
-
- case InputKey.LSuper:
- case InputKey.RSuper:
- return InputKey.Super;
- }
-
- return null;
- }
-
///
/// Check whether the provided set of pressed keys matches the candidate binding.
///
@@ -192,7 +168,7 @@ namespace osu.Framework.Input.Bindings
internal static bool KeyBindingContains(ImmutableArray candidateKeyBinding, InputKey physicalKey)
{
return candidateKeyBinding.Contains(physicalKey) ||
- (getVirtualKey(physicalKey) is InputKey vKey && candidateKeyBinding.Contains(vKey));
+ (physicalKey.GetVirtualKey() is InputKey vKey && candidateKeyBinding.Contains(vKey));
}
///
@@ -210,7 +186,7 @@ namespace osu.Framework.Input.Bindings
foreach (var pk in pressedPhysicalKeys)
{
- if (getVirtualKey(pk) == candidateKey)
+ if (pk.GetVirtualKey() == candidateKey)
return true;
}
diff --git a/osu.Framework/Input/Events/KeyDownEvent.cs b/osu.Framework/Input/Events/KeyDownEvent.cs
index 8071e75a8..a706b79c5 100644
--- a/osu.Framework/Input/Events/KeyDownEvent.cs
+++ b/osu.Framework/Input/Events/KeyDownEvent.cs
@@ -20,6 +20,6 @@ namespace osu.Framework.Input.Events
Repeat = repeat;
}
- public override string ToString() => $"{GetType().ReadableName()}({Key}, {Repeat})";
+ // public override string ToString() => $"{GetType().ReadableName()}({Key}, {Repeat})";
}
}
diff --git a/osu.Framework/Input/Handlers/Pen/PenHandler.cs b/osu.Framework/Input/Handlers/Pen/PenHandler.cs
index eeee2665b..04d0e6dfd 100644
--- a/osu.Framework/Input/Handlers/Pen/PenHandler.cs
+++ b/osu.Framework/Input/Handlers/Pen/PenHandler.cs
@@ -45,24 +45,20 @@ namespace osu.Framework.Input.Handlers.Pen
return true;
}
- // Pen input is not necessarily direct on mobile platforms (specifically Android, where external tablets are supported),
- // but until users experience issues with this, consider it "direct" for now.
- private static readonly TabletPenDeviceType device_type = RuntimeInfo.IsMobile ? TabletPenDeviceType.Direct : TabletPenDeviceType.Unknown;
-
- private void handlePenMove(Vector2 position, bool pressed)
+ private void handlePenMove(TabletPenDeviceType deviceType, Vector2 position, bool pressed)
{
- if (pressed && device_type == TabletPenDeviceType.Direct)
+ if (pressed && deviceType == TabletPenDeviceType.Direct)
enqueueInput(new TouchInput(new Input.Touch(TouchSource.PenTouch, position), true));
else
- enqueueInput(new MousePositionAbsoluteInputFromPen { DeviceType = device_type, Position = position });
+ enqueueInput(new MousePositionAbsoluteInputFromPen { DeviceType = deviceType, Position = position });
}
- private void handlePenTouch(bool pressed, Vector2 position)
+ private void handlePenTouch(TabletPenDeviceType deviceType, bool pressed, Vector2 position)
{
- if (device_type == TabletPenDeviceType.Direct)
+ if (deviceType == TabletPenDeviceType.Direct)
enqueueInput(new TouchInput(new Input.Touch(TouchSource.PenTouch, position), pressed));
else
- enqueueInput(new MouseButtonInputFromPen(pressed) { DeviceType = device_type });
+ enqueueInput(new MouseButtonInputFromPen(pressed) { DeviceType = deviceType });
}
private void handlePenButton(TabletPenButton button, bool pressed)
diff --git a/osu.Framework/Input/TextInputType.cs b/osu.Framework/Input/TextInputType.cs
index f4fde0371..76c4aba41 100644
--- a/osu.Framework/Input/TextInputType.cs
+++ b/osu.Framework/Input/TextInputType.cs
@@ -66,6 +66,20 @@ namespace osu.Framework.Input
}
}
+ public static bool IsNumerical(this TextInputType type)
+ {
+ switch (type)
+ {
+ case TextInputType.Number:
+ case TextInputType.NumericalPassword:
+ case TextInputType.Decimal:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
public static bool SupportsIme(this TextInputType type) => type == TextInputType.Name || type == TextInputType.Text;
}
}
diff --git a/osu.Framework/Platform/Display.cs b/osu.Framework/Platform/Display.cs
index 171a4af7d..da1b65385 100644
--- a/osu.Framework/Platform/Display.cs
+++ b/osu.Framework/Platform/Display.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
using System.Drawing;
using System.Linq;
@@ -24,6 +25,12 @@ namespace osu.Framework.Platform
///
public Rectangle Bounds { get; }
+ ///
+ /// The current usable bounds of the display in screen space.
+ /// This is smaller and contained within .
+ ///
+ public Rectangle UsableBounds { get; }
+
///
/// The available s on this display, or empty if the display mode cannot be configured (e.g. mobile displays).
///
@@ -34,11 +41,13 @@ namespace osu.Framework.Platform
///
public int Index { get; }
- public Display(int index, string? name, Rectangle bounds, DisplayMode[] displayModes)
+ public Display(int index, string? name, Rectangle bounds, Rectangle usableBounds, DisplayMode[] displayModes)
{
+ Debug.Assert(bounds.Contains(usableBounds));
Index = index;
Name = name;
Bounds = bounds;
+ UsableBounds = usableBounds;
DisplayModes = displayModes;
}
diff --git a/osu.Framework/Platform/IWindow.cs b/osu.Framework/Platform/IWindow.cs
index 1ac2fe764..78d800ba5 100644
--- a/osu.Framework/Platform/IWindow.cs
+++ b/osu.Framework/Platform/IWindow.cs
@@ -8,6 +8,7 @@ using System.Drawing;
using System.IO;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
+using osu.Framework.Graphics;
using RectangleF = osu.Framework.Graphics.Primitives.RectangleF;
namespace osu.Framework.Platform
@@ -146,6 +147,15 @@ namespace osu.Framework.Platform
///
BindableSafeArea SafeAreaPadding { get; }
+ ///
+ /// The size of the window decoration and border, relative to .
+ ///
+ ///
+ /// This may include the invisible resize border, even when maximised.
+ /// Usually 0 when in borderless or fullscreen.
+ ///
+ IBindable BorderSize { get; }
+
///
/// The s supported by this implementation.
///
diff --git a/osu.Framework/Platform/SDL2/SDL2Window.cs b/osu.Framework/Platform/SDL2/SDL2Window.cs
index 4621367f4..2324d0891 100644
--- a/osu.Framework/Platform/SDL2/SDL2Window.cs
+++ b/osu.Framework/Platform/SDL2/SDL2Window.cs
@@ -10,6 +10,7 @@ using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Extensions.ImageExtensions;
+using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Framework.Threading;
using SixLabors.ImageSharp;
@@ -38,6 +39,10 @@ namespace osu.Framework.Platform.SDL2
public BindableSafeArea SafeAreaPadding { get; } = new BindableSafeArea();
+ protected readonly Bindable BorderSize = new Bindable();
+
+ IBindable IWindow.BorderSize => BorderSize;
+
public virtual Point PointToClient(Point point) => point;
public virtual Point PointToScreen(Point point) => point;
diff --git a/osu.Framework/Platform/SDL2/SDL2Window_Windowing.cs b/osu.Framework/Platform/SDL2/SDL2Window_Windowing.cs
index 2584309b5..0522f8b33 100644
--- a/osu.Framework/Platform/SDL2/SDL2Window_Windowing.cs
+++ b/osu.Framework/Platform/SDL2/SDL2Window_Windowing.cs
@@ -10,6 +10,7 @@ using System.Drawing;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
+using osu.Framework.Graphics;
using osu.Framework.Logging;
using osuTK;
using static SDL2.SDL;
@@ -354,6 +355,12 @@ namespace osu.Framework.Platform.SDL2
return false;
}
+ if (SDL_GetDisplayUsableBounds(displayIndex, out var usableBounds) < 0)
+ {
+ Logger.Log($"Failed to get usable display bounds for display at index ({displayIndex}). Assuming whole display is usable. SDL Error: {SDL_GetError()}");
+ usableBounds = rect;
+ }
+
DisplayMode[] displayModes = Array.Empty();
if (RuntimeInfo.IsDesktop)
@@ -379,7 +386,11 @@ namespace osu.Framework.Platform.SDL2
.ToArray();
}
- display = new Display(displayIndex, SDL_GetDisplayName(displayIndex), new Rectangle(rect.x, rect.y, rect.w, rect.h), displayModes);
+ display = new Display(displayIndex,
+ SDL_GetDisplayName(displayIndex),
+ new Rectangle(rect.x, rect.y, rect.w, rect.h),
+ new Rectangle(usableBounds.x, usableBounds.y, usableBounds.w, usableBounds.h),
+ displayModes);
return true;
}
@@ -590,6 +601,27 @@ namespace osu.Framework.Platform.SDL2
currentDisplay = Displays.ElementAtOrDefault(displayIndex) ?? PrimaryDisplay;
CurrentDisplayBindable.Value = currentDisplay;
}
+
+ if (tryGetBorderSize(out var borderSize))
+ BorderSize.Value = borderSize;
+ }
+
+ private bool tryGetBorderSize(out MarginPadding borderSize)
+ {
+ if (SDL_GetWindowBordersSize(SDLWindowHandle, out int top, out int left, out int bottom, out int right) < 0)
+ {
+ borderSize = default;
+ return false;
+ }
+
+ borderSize = new MarginPadding
+ {
+ Top = top,
+ Left = left,
+ Bottom = bottom,
+ Right = right
+ };
+ return true;
}
///
diff --git a/osu.Framework/Platform/SDL3/SDL3Extensions.cs b/osu.Framework/Platform/SDL3/SDL3Extensions.cs
index 3467f9f21..158a71e82 100644
--- a/osu.Framework/Platform/SDL3/SDL3Extensions.cs
+++ b/osu.Framework/Platform/SDL3/SDL3Extensions.cs
@@ -6,6 +6,7 @@ using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.StateChanges;
using osuTK.Input;
using SDL;
using static SDL.SDL3;
@@ -1048,6 +1049,21 @@ namespace osu.Framework.Platform.SDL3
return new DisplayMode(SDL_GetPixelFormatName(mode.format), new Size(mode.w, mode.h), bpp, mode.refresh_rate, displayIndex);
}
+ public static TabletPenDeviceType ToTabletPenDeviceType(this SDL_PenDeviceType type)
+ {
+ switch (type)
+ {
+ case SDL_PenDeviceType.SDL_PEN_DEVICE_TYPE_DIRECT:
+ return TabletPenDeviceType.Direct;
+
+ case SDL_PenDeviceType.SDL_PEN_DEVICE_TYPE_INDIRECT:
+ return TabletPenDeviceType.Indirect;
+
+ default:
+ return TabletPenDeviceType.Unknown;
+ }
+ }
+
public static string ReadableName(this SDL_LogCategory category)
{
switch (category)
diff --git a/osu.Framework/Platform/SDL3/SDL3Window.cs b/osu.Framework/Platform/SDL3/SDL3Window.cs
index 19111e92d..79d627500 100644
--- a/osu.Framework/Platform/SDL3/SDL3Window.cs
+++ b/osu.Framework/Platform/SDL3/SDL3Window.cs
@@ -11,6 +11,7 @@ using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Extensions.ImageExtensions;
+using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Framework.Threading;
using SDL;
@@ -40,6 +41,10 @@ namespace osu.Framework.Platform.SDL3
public BindableSafeArea SafeAreaPadding { get; } = new BindableSafeArea();
+ protected readonly Bindable BorderSize = new Bindable();
+
+ IBindable IWindow.BorderSize => BorderSize;
+
public virtual Point PointToClient(Point point) => point;
public virtual Point PointToScreen(Point point) => point;
diff --git a/osu.Framework/Platform/SDL3/SDL3Window_Input.cs b/osu.Framework/Platform/SDL3/SDL3Window_Input.cs
index be0283f2f..ff947e408 100644
--- a/osu.Framework/Platform/SDL3/SDL3Window_Input.cs
+++ b/osu.Framework/Platform/SDL3/SDL3Window_Input.cs
@@ -11,6 +11,7 @@ using osu.Framework.Configuration;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Input;
+using osu.Framework.Input.StateChanges;
using osu.Framework.Input.States;
using osu.Framework.Logging;
using osuTK;
@@ -530,14 +531,16 @@ namespace osu.Framework.Platform.SDL3
private void handleKeymapChangedEvent() => KeymapChanged?.Invoke();
+ private static TabletPenDeviceType getPenType(SDL_PenID instanceID) => SDL_GetPenDeviceType(instanceID).ToTabletPenDeviceType();
+
private void handlePenMotionEvent(SDL_PenMotionEvent evtPenMotion)
{
- PenMove?.Invoke(new Vector2(evtPenMotion.x, evtPenMotion.y) * Scale, evtPenMotion.pen_state.HasFlagFast(SDL_PenInputFlags.SDL_PEN_INPUT_DOWN));
+ PenMove?.Invoke(getPenType(evtPenMotion.which), new Vector2(evtPenMotion.x, evtPenMotion.y) * Scale, evtPenMotion.pen_state.HasFlagFast(SDL_PenInputFlags.SDL_PEN_INPUT_DOWN));
}
private void handlePenTouchEvent(SDL_PenTouchEvent evtPenTouch)
{
- PenTouch?.Invoke(evtPenTouch.down, new Vector2(evtPenTouch.x, evtPenTouch.y) * Scale);
+ PenTouch?.Invoke(getPenType(evtPenTouch.which), evtPenTouch.down, new Vector2(evtPenTouch.x, evtPenTouch.y) * Scale);
}
///
@@ -745,13 +748,13 @@ namespace osu.Framework.Platform.SDL3
///
/// Invoked when a pen moves. Passes pen position and whether the pen is touching the tablet surface.
///
- public event Action? PenMove;
+ public event Action? PenMove;
///
/// Invoked when a pen touches (true) or lifts (false) from the tablet surface.
/// Also passes the current position of the pen.
///
- public event Action? PenTouch;
+ public event Action? PenTouch;
///
/// Invoked when a pen button is pressed (true) or released (false).
diff --git a/osu.Framework/Platform/SDL3/SDL3Window_Windowing.cs b/osu.Framework/Platform/SDL3/SDL3Window_Windowing.cs
index 42b79980d..d203a8719 100644
--- a/osu.Framework/Platform/SDL3/SDL3Window_Windowing.cs
+++ b/osu.Framework/Platform/SDL3/SDL3Window_Windowing.cs
@@ -10,6 +10,7 @@ using System.Drawing;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
+using osu.Framework.Graphics;
using osu.Framework.Logging;
using osuTK;
using SDL;
@@ -360,6 +361,14 @@ namespace osu.Framework.Platform.SDL3
return false;
}
+ SDL_Rect usableBounds;
+
+ if (!SDL_GetDisplayUsableBounds(displayID, &usableBounds))
+ {
+ Logger.Log($"Failed to get usable display bounds for display at index ({displayIndex}). Assuming whole display is usable. SDL Error: {SDL_GetError()}");
+ usableBounds = rect;
+ }
+
DisplayMode[] displayModes = Array.Empty();
if (RuntimeInfo.IsDesktop)
@@ -382,7 +391,11 @@ namespace osu.Framework.Platform.SDL3
displayModes[i] = modes[i].ToDisplayMode(displayIndex);
}
- display = new Display(displayIndex, SDL_GetDisplayName(displayID), new Rectangle(rect.x, rect.y, rect.w, rect.h), displayModes);
+ display = new Display(displayIndex,
+ SDL_GetDisplayName(displayID),
+ new Rectangle(rect.x, rect.y, rect.w, rect.h),
+ new Rectangle(usableBounds.x, usableBounds.y, usableBounds.w, usableBounds.h),
+ displayModes);
return true;
}
@@ -605,6 +618,29 @@ namespace osu.Framework.Platform.SDL3
CurrentDisplayBindable.Value = currentDisplay;
}
+
+ if (tryGetBorderSize(out var borderSize))
+ BorderSize.Value = borderSize;
+ }
+
+ private unsafe bool tryGetBorderSize(out MarginPadding borderSize)
+ {
+ int top, left, bottom, right;
+
+ if (!SDL_GetWindowBordersSize(SDLWindowHandle, &top, &left, &bottom, &right))
+ {
+ borderSize = default;
+ return false;
+ }
+
+ borderSize = new MarginPadding
+ {
+ Top = top,
+ Left = left,
+ Bottom = bottom,
+ Right = right
+ };
+ return true;
}
private static bool tryGetDisplayIndex(SDL_DisplayID id, out int index)
diff --git a/osu.Framework/Resources/Shaders/blur.frag b/osu.Framework/Resources/Shaders/blur.frag
deleted file mode 100644
index e69de29bb..000000000
diff --git a/osu.Framework/Resources/Shaders/sh_AcrylicBlur.fs b/osu.Framework/Resources/Shaders/sh_AcrylicBlur.fs
deleted file mode 100644
index e69de29bb..000000000
diff --git a/osu.Framework/Resources/Shaders/sh_AcrylicBlur.vs b/osu.Framework/Resources/Shaders/sh_AcrylicBlur.vs
deleted file mode 100644
index 54ba6f9a6..000000000
--- a/osu.Framework/Resources/Shaders/sh_AcrylicBlur.vs
+++ /dev/null
@@ -1,36 +0,0 @@
-#ifndef ACRYLIC_BLUR_VS
-#define ACRYLIC_BLUR_VS
-
-#include "sh_Utils.h"
-
-layout(location = 0) in highp vec2 m_Position;
-layout(location = 1) in lowp vec4 m_Colour;
-layout(location = 2) in highp vec2 m_TexCoord;
-layout(location = 3) in highp vec4 m_TexRect;
-layout(location = 4) in mediump vec2 m_BlendRange;
-layout(location = 5) in highp float m_BackbufferDrawDepth;
-
-layout(location = 0) out highp vec2 v_MaskingPosition;
-layout(location = 1) out lowp vec4 v_Colour;
-layout(location = 2) out highp vec2 v_TexCoord;
-layout(location = 3) out highp vec4 v_TexRect;
-layout(location = 4) out mediump vec2 v_BlendRange;
-
-void main(void)
-{
- // Transform from screen space to masking space.
- highp vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0);
- v_MaskingPosition = maskingPos.xy / maskingPos.z;
-
- v_Colour = m_Colour;
- v_TexCoord = m_TexCoord;
- v_TexRect = m_TexRect;
- v_BlendRange = m_BlendRange;
-
- gl_Position = g_ProjMatrix * vec4(m_Position, 1.0, 1.0);
-
- if (g_BackbufferDraw)
- gl_Position.z = m_BackbufferDrawDepth;
-}
-
-#endif
\ No newline at end of file
diff --git a/osu.Framework/Resources/Shaders/sh_Path.fs b/osu.Framework/Resources/Shaders/sh_Path.fs
new file mode 100644
index 000000000..92d5a9752
--- /dev/null
+++ b/osu.Framework/Resources/Shaders/sh_Path.fs
@@ -0,0 +1,37 @@
+#ifndef PATH_FS
+#define PATH_FS
+
+#include "sh_Utils.h"
+#include "sh_Masking.h"
+#include "sh_TextureWrapping.h"
+
+layout(location = 2) in mediump vec2 v_TexCoord;
+
+// FrameBuffer texture
+layout(set = 0, binding = 0) uniform lowp texture2D m_Texture;
+layout(set = 0, binding = 1) uniform lowp sampler m_Sampler;
+
+// Path texture
+layout(set = 1, binding = 0) uniform lowp texture2D m_Texture1;
+layout(set = 1, binding = 1) uniform lowp sampler m_Sampler1;
+
+layout(std140, set = 2, binding = 0) uniform m_PathTextureParameters
+{
+ highp vec4 TexRect1;
+};
+
+layout(location = 0) out vec4 o_Colour;
+
+void main(void)
+{
+ lowp vec4 frameBuffer = texture(sampler2D(m_Texture, m_Sampler), v_TexCoord, -0.9);
+ if (frameBuffer.r == 0.0)
+ {
+ o_Colour = vec4(0.0);
+ return;
+ }
+ lowp vec4 pathCol = texture(sampler2D(m_Texture1, m_Sampler1), TexRect1.xy + vec2(frameBuffer.r, 0.0) * TexRect1.zw, -0.9);
+ o_Colour = getRoundedColor(vec4(pathCol.rgb, pathCol.a * frameBuffer.a), v_TexCoord);
+}
+
+#endif
diff --git a/osu.Framework/Resources/Shaders/sh_PathPrepass.fs b/osu.Framework/Resources/Shaders/sh_PathPrepass.fs
new file mode 100644
index 000000000..5172d6844
--- /dev/null
+++ b/osu.Framework/Resources/Shaders/sh_PathPrepass.fs
@@ -0,0 +1,14 @@
+#ifndef PATH_PREPASS_FS
+#define PATH_PREPASS_FS
+
+layout(location = 0) in highp vec2 v_RelativePos;
+
+layout(location = 0) out vec4 o_Colour;
+
+void main(void)
+{
+ highp float dst = clamp(distance(v_RelativePos, vec2(0.0)), 0.0, 1.0);
+ o_Colour = vec4(vec3(1.0 - dst), float(dst < 1.0));
+}
+
+#endif
\ No newline at end of file
diff --git a/osu.Framework/Resources/Shaders/sh_PathPrepass.vs b/osu.Framework/Resources/Shaders/sh_PathPrepass.vs
new file mode 100644
index 000000000..68de4bd68
--- /dev/null
+++ b/osu.Framework/Resources/Shaders/sh_PathPrepass.vs
@@ -0,0 +1,17 @@
+#ifndef PATH_PREPASS_VS
+#define PATH_PREPASS_VS
+
+#include "sh_Utils.h"
+
+layout(location = 0) in highp vec2 m_Position;
+layout(location = 1) in highp vec2 m_RelativePos;
+
+layout(location = 0) out highp vec2 v_RelativePos;
+
+void main(void)
+{
+ v_RelativePos = m_RelativePos;
+ gl_Position = g_ProjMatrix * vec4(m_Position, 1.0, 1.0);
+}
+
+#endif
\ No newline at end of file
diff --git a/osu.Framework/Resources/Shaders/texture.vert b/osu.Framework/Resources/Shaders/texture.vert
deleted file mode 100644
index e69de29bb..000000000
diff --git a/osu.Framework/osu.Framework.csproj b/osu.Framework/osu.Framework.csproj
index e7e4803d2..bf3213f77 100644
--- a/osu.Framework/osu.Framework.csproj
+++ b/osu.Framework/osu.Framework.csproj
@@ -1,4 +1,4 @@
-
+
net8.0
Library
@@ -38,8 +38,8 @@
-
-
+
+