diff --git a/.github/workflows/build-ffmpeg.yml b/.github/workflows/build-ffmpeg.yml
index aebd3de365..c8812e0a5e 100644
--- a/.github/workflows/build-ffmpeg.yml
+++ b/.github/workflows/build-ffmpeg.yml
@@ -47,6 +47,8 @@ jobs:
- name: Combine
run: osu.Framework.NativeLibs/scripts/ffmpeg/combine_dylibs.sh
+ env:
+ platform: macOS
- name: Upload
uses: actions/upload-artifact@v4
@@ -54,6 +56,71 @@ jobs:
name: macOS-universal
path: macOS-universal
+ build-iOS:
+ name: Build iOS
+ runs-on: macos-12
+ strategy:
+ matrix:
+ arch: [arm64, simulator-arm64, simulator-x86_64]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - uses: ilammy/setup-nasm@v1
+ if: matrix.arch == 'simulator-x86_64'
+
+ - name: Setup gas-preprocessor
+ run: |
+ git clone --depth=1 https://github.com/FFmpeg/gas-preprocessor
+ echo "GAS_PREPROCESSOR=$PWD/gas-preprocessor/gas-preprocessor.pl" >> $GITHUB_ENV
+
+ - name: Build
+ run: osu.Framework.NativeLibs/scripts/ffmpeg/build-iOS.sh
+ env:
+ arch: ${{ matrix.arch }}
+
+ - name: Upload
+ uses: actions/upload-artifact@v4
+ with:
+ name: iOS-${{ matrix.arch }}
+ path: iOS-${{ matrix.arch }}
+
+ combine-iOS:
+ name: Combine iOS libs
+ runs-on: macos-12
+ needs: build-iOS
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - uses: actions/download-artifact@v4
+ with:
+ name: iOS-simulator-x86_64
+ path: iOS-simulator-x86_64
+ - uses: actions/download-artifact@v4
+ with:
+ name: iOS-simulator-arm64
+ path: iOS-simulator-arm64
+ - uses: actions/download-artifact@v4
+ with:
+ name: iOS-arm64
+ path: iOS-arm64
+
+ - name: Combine dylibs
+ run: osu.Framework.NativeLibs/scripts/ffmpeg/combine_dylibs.sh
+ env:
+ platform: iOS-simulator
+
+ - name: Create XCFrameworks
+ run: osu.Framework.NativeLibs/scripts/ffmpeg/create-xcframeworks.sh
+
+ - name: Upload
+ uses: actions/upload-artifact@v4
+ with:
+ name: iOS-xcframework
+ path: iOS-xcframework
+
build-win:
name: Build Windows
runs-on: ubuntu-22.04
@@ -112,7 +179,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update
- sudo apt-get install nasm libva-dev libvdpau-dev
+ sudo apt-get install nasm
- name: Checkout
uses: actions/checkout@v4
@@ -126,14 +193,42 @@ jobs:
name: linux-x64
path: linux-x64
+ build-android:
+ name: Build Android
+ runs-on: ubuntu-22.04
+ strategy:
+ matrix:
+ arch:
+ - armeabi-v7a
+ - arm64-v8a
+ - x86
+ - x86_64
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Build
+ run: osu.Framework.NativeLibs/scripts/ffmpeg/build-android.sh
+ env:
+ arch: ${{ matrix.arch }}
+
+ - name: Upload
+ uses: actions/upload-artifact@v4
+ with:
+ name: android-${{ matrix.arch }}
+ path: android-${{ matrix.arch }}
+
make-pr:
name: Create pull request
runs-on: ubuntu-22.04
needs:
- combine-macos
+ - combine-iOS
- build-win
- build-win-arm64
- build-linux
+ - build-android
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -142,6 +237,10 @@ jobs:
with:
name: macOS-universal
path: osu.Framework.NativeLibs/runtimes/osx/native
+ - uses: actions/download-artifact@v4
+ with:
+ name: iOS-xcframework
+ path: osu.Framework.iOS/runtimes/ios/native
- uses: actions/download-artifact@v4
with:
name: linux-x64
@@ -158,6 +257,22 @@ jobs:
with:
name: win-x86
path: osu.Framework.NativeLibs/runtimes/win-x86/native
+ - uses: actions/download-artifact@v4
+ with:
+ name: android-armeabi-v7a
+ path: osu.Framework.Android/armeabi-v7a
+ - uses: actions/download-artifact@v4
+ with:
+ name: android-arm64-v8a
+ path: osu.Framework.Android/arm64-v8a
+ - uses: actions/download-artifact@v4
+ with:
+ name: android-x86
+ path: osu.Framework.Android/x86
+ - uses: actions/download-artifact@v4
+ with:
+ name: android-x86_64
+ path: osu.Framework.Android/x86_64
- uses: peter-evans/create-pull-request@v6
with:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2a9cb7205a..5905850e62 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -76,6 +76,8 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
+ with:
+ go-version: 1.21.9
- name: Install httpbin
run: go install github.com/mccutchen/go-httpbin/v2/cmd/go-httpbin@latest
@@ -88,13 +90,13 @@ jobs:
run: dotnet build -c ${{matrix.os.configuration}} -warnaserror osu-framework.Desktop.slnf
- name: Test
- run: dotnet test $pwd/**/*.Tests/bin/${{matrix.os.configuration}}/*/*.Tests.dll --no-build --settings $pwd/build/vstestconfig.runsettings --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}-${{matrix.os.configuration}}.trx"
+ run: dotnet test $pwd/**/*.Tests/bin/${{matrix.os.configuration}}/*/*.Tests.dll --no-build --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}-${{matrix.os.configuration}}.trx" -- NUnit.ConsoleOut=0
shell: pwsh
# Attempt to upload results even if test fails.
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
- name: Upload Test Results
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
name: osu-framework-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}-${{matrix.os.configuration}}
@@ -139,9 +141,8 @@ jobs:
dotnet-version: "8.0.x"
- name: Restore .NET workloads
- # `dotnet workload restore` is bugged in .NET 7.0.101+ when restoring iOS projects,
- # see https://github.com/xamarin/xamarin-macios/issues/16400.
- run: dotnet workload install ios
+ run: dotnet workload install ios --from-rollback-file workloads.json
- name: Compile
run: dotnet build -c Debug osu-framework.iOS.slnf
+
diff --git a/.github/workflows/deploy-pack.yml b/.github/workflows/deploy-pack.yml
index 9a7921226a..88bc62e7ac 100644
--- a/.github/workflows/deploy-pack.yml
+++ b/.github/workflows/deploy-pack.yml
@@ -166,9 +166,7 @@ jobs:
dotnet-version: "8.0.x"
- name: Restore .NET Workloads
- # `dotnet workload restore` is bugged in .NET 7.0.101+ when restoring iOS projects,
- # see https://github.com/xamarin/xamarin-macios/issues/16400.
- run: dotnet workload install ios
+ run: dotnet workload install ios --from-rollback-file workloads.json
- name: Pack (iOS Framework)
run: dotnet pack -c Release osu.Framework.iOS /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml
index a26723d84f..476000a223 100644
--- a/.github/workflows/report-nunit.yml
+++ b/.github/workflows/report-nunit.yml
@@ -5,30 +5,40 @@
name: Annotate CI run with test results
on:
workflow_run:
- workflows: ["Continuous Integration"]
+ workflows: [ "Continuous Integration" ]
types:
- completed
+
+permissions:
+ contents: read
+ actions: read
+ checks: write
+
jobs:
annotate:
name: Annotate CI run with test results
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion != 'cancelled' }}
- strategy:
- fail-fast: false
- matrix:
- os:
- - { prettyname: Windows, configuration: Debug }
- - { prettyname: macOS, configuration: Debug }
- - { prettyname: Linux, configuration: Debug }
- - { prettyname: Linux, configuration: Release }
- threadingMode: ['SingleThread', 'MultiThreaded']
timeout-minutes: 5
steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ repository: ${{ github.event.workflow_run.repository.full_name }}
+ ref: ${{ github.event.workflow_run.head_sha }}
+
+ - name: Download results
+ uses: actions/download-artifact@v4
+ with:
+ pattern: osu-framework-test-results-*
+ merge-multiple: true
+ run-id: ${{ github.event.workflow_run.id }}
+ github-token: ${{ github.token }}
+
- name: Annotate CI run with test results
uses: dorny/test-reporter@v1.8.0
with:
- artifact: osu-framework-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}-${{matrix.os.configuration}}
- name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}}, ${{matrix.os.configuration}})
+ name: Results
path: "*.trx"
reporter: dotnet-trx
list-suites: 'failed'
diff --git a/.gitignore b/.gitignore
index d665f2912f..841e492d7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -308,12 +308,15 @@ fabric.properties
# Reference: https://github.com/JetBrains/resharper-rider-samples/blob/master/.gitignore
# User specific
-**/.idea/**/workspace.xml
-**/.idea/**/tasks.xml
-**/.idea/shelf/*
-**/.idea/dictionaries
**/.idea/httpRequests/
**/.idea/**/usage.statistics.xml
+**/.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+.idea/*/.idea/projectSettingsUpdater.xml
+.idea/*/.idea/encodings.xml
# Sensitive or high-churn files
**/.idea/**/dataSources/
@@ -339,3 +342,8 @@ inspectcode
.idea/.idea.osu-framework.Desktop/.idea/misc.xml
.idea/.idea.osu-framework.Android/.idea/deploymentTargetDropDown.xml
+
+# NativeLibs build folders and tarballs
+osu.Framework.NativeLibs/scripts/ffmpeg/*/
+osu.Framework.NativeLibs/scripts/ffmpeg/*.tar.gz
+
diff --git a/.idea/.idea.osu-framework.Android/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu-framework.Android/.idea/projectSettingsUpdater.xml
deleted file mode 100644
index 4bb9f4d2a0..0000000000
--- a/.idea/.idea.osu-framework.Android/.idea/projectSettingsUpdater.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.osu-framework.Desktop/.idea/encodings.xml b/.idea/.idea.osu-framework.Desktop/.idea/encodings.xml
deleted file mode 100644
index 15a15b218a..0000000000
--- a/.idea/.idea.osu-framework.Desktop/.idea/encodings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.osu-framework.Desktop/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu-framework.Desktop/.idea/projectSettingsUpdater.xml
deleted file mode 100644
index 4bb9f4d2a0..0000000000
--- a/.idea/.idea.osu-framework.Desktop/.idea/projectSettingsUpdater.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.osu-framework.iOS/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu-framework.iOS/.idea/projectSettingsUpdater.xml
deleted file mode 100644
index 4bb9f4d2a0..0000000000
--- a/.idea/.idea.osu-framework.iOS/.idea/projectSettingsUpdater.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.osu-framework/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu-framework/.idea/projectSettingsUpdater.xml
deleted file mode 100644
index 4bb9f4d2a0..0000000000
--- a/.idea/.idea.osu-framework/.idea/projectSettingsUpdater.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 1220a16e83..dc9e9161c4 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -58,7 +58,7 @@
"${workspaceRoot}/SampleGame.Desktop/bin/Debug/net8.0/SampleGame.Desktop.dll",
],
"cwd": "${workspaceRoot}",
- "preLaunchTask": "Build (Debug)",
+ "preLaunchTask": "Build SampleGame (Debug)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/SampleGame.Desktop/bin/Debug/net8.0:${env:LD_LIBRARY_PATH}"
@@ -75,7 +75,7 @@
"${workspaceRoot}/SampleGame.Desktop/bin/Release/net8.0/SampleGame.Desktop.dll",
],
"cwd": "${workspaceRoot}",
- "preLaunchTask": "Build (Release)",
+ "preLaunchTask": "Build SampleGame (Release)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/SampleGame.Desktop/bin/Release/net8.0:${env:LD_LIBRARY_PATH}"
diff --git a/README.md b/README.md
index f2d45c7a5d..b392ce6289 100644
--- a/README.md
+++ b/README.md
@@ -27,9 +27,9 @@ This framework is intended to take steps beyond what you would normally expect f
## Requirements
-- A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download).
+- A desktop platform with the [.NET 8.0 SDK](https://dotnet.microsoft.com/download).
- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
-- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net60&pivots=os-windows#dependencies)** may be required to correctly run .NET 6 applications if your operating system is not up-to-date with the latest service packs.
+- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net60&pivots=os-windows#dependencies)** may be required to correctly run .NET 8 applications if your operating system is not up-to-date with the latest service packs.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/), or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C#](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp) plugin installed.
### Building
diff --git a/build/vstestconfig.runsettings b/build/vstestconfig.runsettings
deleted file mode 100644
index f7ca0caa90..0000000000
--- a/build/vstestconfig.runsettings
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
- 300000
-
-
\ No newline at end of file
diff --git a/osu-framework.iOS.slnf b/osu-framework.iOS.slnf
index 30287a2443..4826b8adf2 100644
--- a/osu-framework.iOS.slnf
+++ b/osu-framework.iOS.slnf
@@ -16,4 +16,4 @@
"SampleGame\\SampleGame.csproj"
]
}
-}
+}
\ No newline at end of file
diff --git a/osu-framework.sln b/osu-framework.sln
index 3a3d02ae48..155884055c 100644
--- a/osu-framework.sln
+++ b/osu-framework.sln
@@ -80,297 +80,113 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
- Debug|iPhone = Debug|iPhone
- Debug|iPhoneSimulator = Debug|iPhoneSimulator
Release|Any CPU = Release|Any CPU
- Release|iPhone = Release|iPhone
- Release|iPhoneSimulator = Release|iPhoneSimulator
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|iPhone.Build.0 = Debug|Any CPU
- {C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|Any CPU.Build.0 = Release|Any CPU
- {C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|iPhone.ActiveCfg = Release|Any CPU
- {C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|iPhone.Build.0 = Release|Any CPU
- {C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.Build.0 = Debug|Any CPU
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.ActiveCfg = Release|Any CPU
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.Build.0 = Release|Any CPU
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{79803407-6F50-484F-93F5-641911EABD8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{79803407-6F50-484F-93F5-641911EABD8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {79803407-6F50-484F-93F5-641911EABD8A}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {79803407-6F50-484F-93F5-641911EABD8A}.Debug|iPhone.Build.0 = Debug|Any CPU
- {79803407-6F50-484F-93F5-641911EABD8A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {79803407-6F50-484F-93F5-641911EABD8A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{79803407-6F50-484F-93F5-641911EABD8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79803407-6F50-484F-93F5-641911EABD8A}.Release|Any CPU.Build.0 = Release|Any CPU
- {79803407-6F50-484F-93F5-641911EABD8A}.Release|iPhone.ActiveCfg = Release|Any CPU
- {79803407-6F50-484F-93F5-641911EABD8A}.Release|iPhone.Build.0 = Release|Any CPU
- {79803407-6F50-484F-93F5-641911EABD8A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {79803407-6F50-484F-93F5-641911EABD8A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Debug|iPhone.Build.0 = Debug|Any CPU
- {2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Release|Any CPU.Build.0 = Release|Any CPU
- {2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Release|iPhone.ActiveCfg = Release|Any CPU
- {2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Release|iPhone.Build.0 = Release|Any CPU
- {2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Debug|iPhone.Build.0 = Debug|Any CPU
- {F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Release|Any CPU.Build.0 = Release|Any CPU
- {F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Release|iPhone.ActiveCfg = Release|Any CPU
- {F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Release|iPhone.Build.0 = Release|Any CPU
- {F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{BBC0D18F-8595-43A6-AE61-5BF36A072CCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BBC0D18F-8595-43A6-AE61-5BF36A072CCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BBC0D18F-8595-43A6-AE61-5BF36A072CCE}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {BBC0D18F-8595-43A6-AE61-5BF36A072CCE}.Debug|iPhone.Build.0 = Debug|Any CPU
- {BBC0D18F-8595-43A6-AE61-5BF36A072CCE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {BBC0D18F-8595-43A6-AE61-5BF36A072CCE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{BBC0D18F-8595-43A6-AE61-5BF36A072CCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBC0D18F-8595-43A6-AE61-5BF36A072CCE}.Release|Any CPU.Build.0 = Release|Any CPU
- {BBC0D18F-8595-43A6-AE61-5BF36A072CCE}.Release|iPhone.ActiveCfg = Release|Any CPU
- {BBC0D18F-8595-43A6-AE61-5BF36A072CCE}.Release|iPhone.Build.0 = Release|Any CPU
- {BBC0D18F-8595-43A6-AE61-5BF36A072CCE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {BBC0D18F-8595-43A6-AE61-5BF36A072CCE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
- {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator
- {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Debug|iPhone.ActiveCfg = Debug|iPhone
- {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Debug|iPhone.Build.0 = Debug|iPhone
- {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
- {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
- {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator
- {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Release|iPhone.ActiveCfg = Release|iPhone
- {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Release|iPhone.Build.0 = Release|iPhone
- {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
- {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
- {D972753E-45FC-4B82-B017-34BDE485F1BB}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator
- {D972753E-45FC-4B82-B017-34BDE485F1BB}.Debug|iPhone.ActiveCfg = Debug|iPhone
- {D972753E-45FC-4B82-B017-34BDE485F1BB}.Debug|iPhone.Build.0 = Debug|iPhone
- {D972753E-45FC-4B82-B017-34BDE485F1BB}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
- {D972753E-45FC-4B82-B017-34BDE485F1BB}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
- {D972753E-45FC-4B82-B017-34BDE485F1BB}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator
- {D972753E-45FC-4B82-B017-34BDE485F1BB}.Release|iPhone.ActiveCfg = Release|iPhone
- {D972753E-45FC-4B82-B017-34BDE485F1BB}.Release|iPhone.Build.0 = Release|iPhone
- {D972753E-45FC-4B82-B017-34BDE485F1BB}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
- {D972753E-45FC-4B82-B017-34BDE485F1BB}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
+ {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {529D5E2E-774A-4831-9C9E-59E3E8DFF155}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D972753E-45FC-4B82-B017-34BDE485F1BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D972753E-45FC-4B82-B017-34BDE485F1BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D972753E-45FC-4B82-B017-34BDE485F1BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D972753E-45FC-4B82-B017-34BDE485F1BB}.Release|Any CPU.Build.0 = Release|Any CPU
{4D112E30-462B-4264-B44D-53B61ABB185E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D112E30-462B-4264-B44D-53B61ABB185E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4D112E30-462B-4264-B44D-53B61ABB185E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {4D112E30-462B-4264-B44D-53B61ABB185E}.Debug|iPhone.Build.0 = Debug|Any CPU
- {4D112E30-462B-4264-B44D-53B61ABB185E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {4D112E30-462B-4264-B44D-53B61ABB185E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{4D112E30-462B-4264-B44D-53B61ABB185E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D112E30-462B-4264-B44D-53B61ABB185E}.Release|Any CPU.Build.0 = Release|Any CPU
- {4D112E30-462B-4264-B44D-53B61ABB185E}.Release|iPhone.ActiveCfg = Release|Any CPU
- {4D112E30-462B-4264-B44D-53B61ABB185E}.Release|iPhone.Build.0 = Release|Any CPU
- {4D112E30-462B-4264-B44D-53B61ABB185E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {4D112E30-462B-4264-B44D-53B61ABB185E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{5A378BB7-11D6-4008-980E-A67507CD9969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A378BB7-11D6-4008-980E-A67507CD9969}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A378BB7-11D6-4008-980E-A67507CD9969}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
- {5A378BB7-11D6-4008-980E-A67507CD9969}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {5A378BB7-11D6-4008-980E-A67507CD9969}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{5A378BB7-11D6-4008-980E-A67507CD9969}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A378BB7-11D6-4008-980E-A67507CD9969}.Release|Any CPU.Build.0 = Release|Any CPU
{5A378BB7-11D6-4008-980E-A67507CD9969}.Release|Any CPU.Deploy.0 = Release|Any CPU
- {5A378BB7-11D6-4008-980E-A67507CD9969}.Release|iPhone.ActiveCfg = Release|Any CPU
- {5A378BB7-11D6-4008-980E-A67507CD9969}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{320089C6-A141-4D3E-BD5F-C4A6CE9E567B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{320089C6-A141-4D3E-BD5F-C4A6CE9E567B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{320089C6-A141-4D3E-BD5F-C4A6CE9E567B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
- {320089C6-A141-4D3E-BD5F-C4A6CE9E567B}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {320089C6-A141-4D3E-BD5F-C4A6CE9E567B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{320089C6-A141-4D3E-BD5F-C4A6CE9E567B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{320089C6-A141-4D3E-BD5F-C4A6CE9E567B}.Release|Any CPU.Build.0 = Release|Any CPU
{320089C6-A141-4D3E-BD5F-C4A6CE9E567B}.Release|Any CPU.Deploy.0 = Release|Any CPU
- {320089C6-A141-4D3E-BD5F-C4A6CE9E567B}.Release|iPhone.ActiveCfg = Release|Any CPU
- {320089C6-A141-4D3E-BD5F-C4A6CE9E567B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{F294C804-8AE2-4020-841A-AF0D97FBE80C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F294C804-8AE2-4020-841A-AF0D97FBE80C}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F294C804-8AE2-4020-841A-AF0D97FBE80C}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {F294C804-8AE2-4020-841A-AF0D97FBE80C}.Debug|iPhone.Build.0 = Debug|Any CPU
- {F294C804-8AE2-4020-841A-AF0D97FBE80C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {F294C804-8AE2-4020-841A-AF0D97FBE80C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{F294C804-8AE2-4020-841A-AF0D97FBE80C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F294C804-8AE2-4020-841A-AF0D97FBE80C}.Release|Any CPU.Build.0 = Release|Any CPU
- {F294C804-8AE2-4020-841A-AF0D97FBE80C}.Release|iPhone.ActiveCfg = Release|Any CPU
- {F294C804-8AE2-4020-841A-AF0D97FBE80C}.Release|iPhone.Build.0 = Release|Any CPU
- {F294C804-8AE2-4020-841A-AF0D97FBE80C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {F294C804-8AE2-4020-841A-AF0D97FBE80C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{A95175BB-95D0-44B4-8B82-EABD166943DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A95175BB-95D0-44B4-8B82-EABD166943DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A95175BB-95D0-44B4-8B82-EABD166943DA}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {A95175BB-95D0-44B4-8B82-EABD166943DA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{A95175BB-95D0-44B4-8B82-EABD166943DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A95175BB-95D0-44B4-8B82-EABD166943DA}.Release|Any CPU.Build.0 = Release|Any CPU
- {A95175BB-95D0-44B4-8B82-EABD166943DA}.Release|iPhone.ActiveCfg = Release|Any CPU
- {A95175BB-95D0-44B4-8B82-EABD166943DA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator
- {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Debug|iPhone.ActiveCfg = Debug|iPhone
- {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Debug|iPhone.Build.0 = Debug|iPhone
- {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
- {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
- {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator
- {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Release|iPhone.ActiveCfg = Release|iPhone
- {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Release|iPhone.Build.0 = Release|iPhone
- {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
- {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
- {48783186-230D-4048-A97A-E4F1DF43BF5C}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator
- {48783186-230D-4048-A97A-E4F1DF43BF5C}.Debug|iPhone.ActiveCfg = Debug|iPhone
- {48783186-230D-4048-A97A-E4F1DF43BF5C}.Debug|iPhone.Build.0 = Debug|iPhone
- {48783186-230D-4048-A97A-E4F1DF43BF5C}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
- {48783186-230D-4048-A97A-E4F1DF43BF5C}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
- {48783186-230D-4048-A97A-E4F1DF43BF5C}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator
- {48783186-230D-4048-A97A-E4F1DF43BF5C}.Release|iPhone.ActiveCfg = Release|iPhone
- {48783186-230D-4048-A97A-E4F1DF43BF5C}.Release|iPhone.Build.0 = Release|iPhone
- {48783186-230D-4048-A97A-E4F1DF43BF5C}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
- {48783186-230D-4048-A97A-E4F1DF43BF5C}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
+ {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7AA1DB5D-78DB-4693-AE50-D6078F5A0CAB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {48783186-230D-4048-A97A-E4F1DF43BF5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {48783186-230D-4048-A97A-E4F1DF43BF5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {48783186-230D-4048-A97A-E4F1DF43BF5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {48783186-230D-4048-A97A-E4F1DF43BF5C}.Release|Any CPU.Build.0 = Release|Any CPU
{6BEB95A6-0673-4AF5-892E-9146FF8B0948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BEB95A6-0673-4AF5-892E-9146FF8B0948}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6BEB95A6-0673-4AF5-892E-9146FF8B0948}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {6BEB95A6-0673-4AF5-892E-9146FF8B0948}.Debug|iPhone.Build.0 = Debug|Any CPU
- {6BEB95A6-0673-4AF5-892E-9146FF8B0948}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {6BEB95A6-0673-4AF5-892E-9146FF8B0948}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{6BEB95A6-0673-4AF5-892E-9146FF8B0948}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BEB95A6-0673-4AF5-892E-9146FF8B0948}.Release|Any CPU.Build.0 = Release|Any CPU
- {6BEB95A6-0673-4AF5-892E-9146FF8B0948}.Release|iPhone.ActiveCfg = Release|Any CPU
- {6BEB95A6-0673-4AF5-892E-9146FF8B0948}.Release|iPhone.Build.0 = Release|Any CPU
- {6BEB95A6-0673-4AF5-892E-9146FF8B0948}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {6BEB95A6-0673-4AF5-892E-9146FF8B0948}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{6E3EBF71-8664-49D7-BD0D-2B21B3EEC540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6E3EBF71-8664-49D7-BD0D-2B21B3EEC540}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6E3EBF71-8664-49D7-BD0D-2B21B3EEC540}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {6E3EBF71-8664-49D7-BD0D-2B21B3EEC540}.Debug|iPhone.Build.0 = Debug|Any CPU
- {6E3EBF71-8664-49D7-BD0D-2B21B3EEC540}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {6E3EBF71-8664-49D7-BD0D-2B21B3EEC540}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{6E3EBF71-8664-49D7-BD0D-2B21B3EEC540}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6E3EBF71-8664-49D7-BD0D-2B21B3EEC540}.Release|Any CPU.Build.0 = Release|Any CPU
- {6E3EBF71-8664-49D7-BD0D-2B21B3EEC540}.Release|iPhone.ActiveCfg = Release|Any CPU
- {6E3EBF71-8664-49D7-BD0D-2B21B3EEC540}.Release|iPhone.Build.0 = Release|Any CPU
- {6E3EBF71-8664-49D7-BD0D-2B21B3EEC540}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {6E3EBF71-8664-49D7-BD0D-2B21B3EEC540}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{55AB973D-ECA0-422B-B367-24BC47DA081B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{55AB973D-ECA0-422B-B367-24BC47DA081B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {55AB973D-ECA0-422B-B367-24BC47DA081B}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {55AB973D-ECA0-422B-B367-24BC47DA081B}.Debug|iPhone.Build.0 = Debug|Any CPU
- {55AB973D-ECA0-422B-B367-24BC47DA081B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {55AB973D-ECA0-422B-B367-24BC47DA081B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{55AB973D-ECA0-422B-B367-24BC47DA081B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55AB973D-ECA0-422B-B367-24BC47DA081B}.Release|Any CPU.Build.0 = Release|Any CPU
- {55AB973D-ECA0-422B-B367-24BC47DA081B}.Release|iPhone.ActiveCfg = Release|Any CPU
- {55AB973D-ECA0-422B-B367-24BC47DA081B}.Release|iPhone.Build.0 = Release|Any CPU
- {55AB973D-ECA0-422B-B367-24BC47DA081B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {55AB973D-ECA0-422B-B367-24BC47DA081B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{FC0A9040-7BAF-4524-B398-8C7A1C7DDEE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC0A9040-7BAF-4524-B398-8C7A1C7DDEE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FC0A9040-7BAF-4524-B398-8C7A1C7DDEE9}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {FC0A9040-7BAF-4524-B398-8C7A1C7DDEE9}.Debug|iPhone.Build.0 = Debug|Any CPU
- {FC0A9040-7BAF-4524-B398-8C7A1C7DDEE9}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {FC0A9040-7BAF-4524-B398-8C7A1C7DDEE9}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{FC0A9040-7BAF-4524-B398-8C7A1C7DDEE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC0A9040-7BAF-4524-B398-8C7A1C7DDEE9}.Release|Any CPU.Build.0 = Release|Any CPU
- {FC0A9040-7BAF-4524-B398-8C7A1C7DDEE9}.Release|iPhone.ActiveCfg = Release|Any CPU
- {FC0A9040-7BAF-4524-B398-8C7A1C7DDEE9}.Release|iPhone.Build.0 = Release|Any CPU
- {FC0A9040-7BAF-4524-B398-8C7A1C7DDEE9}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {FC0A9040-7BAF-4524-B398-8C7A1C7DDEE9}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{7809CB42-8FED-4BB7-8C68-7638357B94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7809CB42-8FED-4BB7-8C68-7638357B94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7809CB42-8FED-4BB7-8C68-7638357B94A6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {7809CB42-8FED-4BB7-8C68-7638357B94A6}.Debug|iPhone.Build.0 = Debug|Any CPU
- {7809CB42-8FED-4BB7-8C68-7638357B94A6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {7809CB42-8FED-4BB7-8C68-7638357B94A6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{7809CB42-8FED-4BB7-8C68-7638357B94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7809CB42-8FED-4BB7-8C68-7638357B94A6}.Release|Any CPU.Build.0 = Release|Any CPU
- {7809CB42-8FED-4BB7-8C68-7638357B94A6}.Release|iPhone.ActiveCfg = Release|Any CPU
- {7809CB42-8FED-4BB7-8C68-7638357B94A6}.Release|iPhone.Build.0 = Release|Any CPU
- {7809CB42-8FED-4BB7-8C68-7638357B94A6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {7809CB42-8FED-4BB7-8C68-7638357B94A6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{4C728441-4C3D-4AE4-9C0F-EA91E2C70965}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C728441-4C3D-4AE4-9C0F-EA91E2C70965}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4C728441-4C3D-4AE4-9C0F-EA91E2C70965}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {4C728441-4C3D-4AE4-9C0F-EA91E2C70965}.Debug|iPhone.Build.0 = Debug|Any CPU
- {4C728441-4C3D-4AE4-9C0F-EA91E2C70965}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {4C728441-4C3D-4AE4-9C0F-EA91E2C70965}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{4C728441-4C3D-4AE4-9C0F-EA91E2C70965}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C728441-4C3D-4AE4-9C0F-EA91E2C70965}.Release|Any CPU.Build.0 = Release|Any CPU
- {4C728441-4C3D-4AE4-9C0F-EA91E2C70965}.Release|iPhone.ActiveCfg = Release|Any CPU
- {4C728441-4C3D-4AE4-9C0F-EA91E2C70965}.Release|iPhone.Build.0 = Release|Any CPU
- {4C728441-4C3D-4AE4-9C0F-EA91E2C70965}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {4C728441-4C3D-4AE4-9C0F-EA91E2C70965}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{0309CF11-621A-4F23-8FBA-A583303A8531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0309CF11-621A-4F23-8FBA-A583303A8531}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0309CF11-621A-4F23-8FBA-A583303A8531}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {0309CF11-621A-4F23-8FBA-A583303A8531}.Debug|iPhone.Build.0 = Debug|Any CPU
- {0309CF11-621A-4F23-8FBA-A583303A8531}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {0309CF11-621A-4F23-8FBA-A583303A8531}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{0309CF11-621A-4F23-8FBA-A583303A8531}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0309CF11-621A-4F23-8FBA-A583303A8531}.Release|Any CPU.Build.0 = Release|Any CPU
- {0309CF11-621A-4F23-8FBA-A583303A8531}.Release|iPhone.ActiveCfg = Release|Any CPU
- {0309CF11-621A-4F23-8FBA-A583303A8531}.Release|iPhone.Build.0 = Release|Any CPU
- {0309CF11-621A-4F23-8FBA-A583303A8531}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {0309CF11-621A-4F23-8FBA-A583303A8531}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{9E4B69EE-34E6-47CF-8346-2A66D1714FCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E4B69EE-34E6-47CF-8346-2A66D1714FCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9E4B69EE-34E6-47CF-8346-2A66D1714FCD}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {9E4B69EE-34E6-47CF-8346-2A66D1714FCD}.Debug|iPhone.Build.0 = Debug|Any CPU
- {9E4B69EE-34E6-47CF-8346-2A66D1714FCD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {9E4B69EE-34E6-47CF-8346-2A66D1714FCD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{9E4B69EE-34E6-47CF-8346-2A66D1714FCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E4B69EE-34E6-47CF-8346-2A66D1714FCD}.Release|Any CPU.Build.0 = Release|Any CPU
- {9E4B69EE-34E6-47CF-8346-2A66D1714FCD}.Release|iPhone.ActiveCfg = Release|Any CPU
- {9E4B69EE-34E6-47CF-8346-2A66D1714FCD}.Release|iPhone.Build.0 = Release|Any CPU
- {9E4B69EE-34E6-47CF-8346-2A66D1714FCD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {9E4B69EE-34E6-47CF-8346-2A66D1714FCD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{BCFABDF9-AC1B-41B6-959E-04676F0C20F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BCFABDF9-AC1B-41B6-959E-04676F0C20F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BCFABDF9-AC1B-41B6-959E-04676F0C20F8}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {BCFABDF9-AC1B-41B6-959E-04676F0C20F8}.Debug|iPhone.Build.0 = Debug|Any CPU
- {BCFABDF9-AC1B-41B6-959E-04676F0C20F8}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {BCFABDF9-AC1B-41B6-959E-04676F0C20F8}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{BCFABDF9-AC1B-41B6-959E-04676F0C20F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BCFABDF9-AC1B-41B6-959E-04676F0C20F8}.Release|Any CPU.Build.0 = Release|Any CPU
- {BCFABDF9-AC1B-41B6-959E-04676F0C20F8}.Release|iPhone.ActiveCfg = Release|Any CPU
- {BCFABDF9-AC1B-41B6-959E-04676F0C20F8}.Release|iPhone.Build.0 = Release|Any CPU
- {BCFABDF9-AC1B-41B6-959E-04676F0C20F8}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {BCFABDF9-AC1B-41B6-959E-04676F0C20F8}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{A0DDCF0A-A352-4CC6-8E6E-0E16CAA50CDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0DDCF0A-A352-4CC6-8E6E-0E16CAA50CDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A0DDCF0A-A352-4CC6-8E6E-0E16CAA50CDD}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {A0DDCF0A-A352-4CC6-8E6E-0E16CAA50CDD}.Debug|iPhone.Build.0 = Debug|Any CPU
- {A0DDCF0A-A352-4CC6-8E6E-0E16CAA50CDD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {A0DDCF0A-A352-4CC6-8E6E-0E16CAA50CDD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{A0DDCF0A-A352-4CC6-8E6E-0E16CAA50CDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0DDCF0A-A352-4CC6-8E6E-0E16CAA50CDD}.Release|Any CPU.Build.0 = Release|Any CPU
- {A0DDCF0A-A352-4CC6-8E6E-0E16CAA50CDD}.Release|iPhone.ActiveCfg = Release|Any CPU
- {A0DDCF0A-A352-4CC6-8E6E-0E16CAA50CDD}.Release|iPhone.Build.0 = Release|Any CPU
- {A0DDCF0A-A352-4CC6-8E6E-0E16CAA50CDD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {A0DDCF0A-A352-4CC6-8E6E-0E16CAA50CDD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/osu-framework.sln.DotSettings b/osu-framework.sln.DotSettings
index 49383b498e..3f68824f12 100644
--- a/osu-framework.sln.DotSettings
+++ b/osu-framework.sln.DotSettings
@@ -20,6 +20,7 @@
WARNING
WARNING
True
+ DO_NOT_SHOW
WARNING
WARNING
HINT
@@ -848,6 +849,7 @@ See the LICENCE file in the repository root for full licence text.
True
True
True
+ True
True
True
True
@@ -999,4 +1001,5 @@ private void load()
True
True
True
- True
+ True
+ True
diff --git a/osu.Framework.Android/AndroidClipboard.cs b/osu.Framework.Android/AndroidClipboard.cs
deleted file mode 100644
index c6f3151340..0000000000
--- a/osu.Framework.Android/AndroidClipboard.cs
+++ /dev/null
@@ -1,31 +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 Android.Content;
-using osu.Framework.Platform;
-using SixLabors.ImageSharp;
-
-namespace osu.Framework.Android
-{
- public class AndroidClipboard : Clipboard
- {
- private readonly ClipboardManager? clipboardManager;
-
- public AndroidClipboard(AndroidGameView view)
- {
- clipboardManager = view.Activity.GetSystemService(Context.ClipboardService) as ClipboardManager;
- }
-
- public override string? GetText() => clipboardManager?.PrimaryClip?.GetItemAt(0)?.Text;
-
- public override void SetText(string text)
- {
- if (clipboardManager != null)
- clipboardManager.PrimaryClip = ClipData.NewPlainText(null, text);
- }
-
- public override Image? GetImage() => null;
-
- public override bool SetImage(Image image) => false;
- }
-}
diff --git a/osu.Framework.Android/AndroidGameActivity.cs b/osu.Framework.Android/AndroidGameActivity.cs
index 57130be1e4..41c69e69b4 100644
--- a/osu.Framework.Android/AndroidGameActivity.cs
+++ b/osu.Framework.Android/AndroidGameActivity.cs
@@ -1,15 +1,12 @@
// 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 Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
-using Android.Runtime;
-using Android.Views;
using ManagedBass;
-using osu.Framework.Bindables;
+using Org.Libsdl.App;
using osu.Framework.Extensions.ObjectExtensions;
using Debug = System.Diagnostics.Debug;
@@ -17,7 +14,7 @@ namespace osu.Framework.Android
{
// since `ActivityAttribute` can't be inherited, the below is only provided as an illustrative example of how to setup an activity for best compatibility.
[Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
- public abstract class AndroidGameActivity : Activity
+ public abstract class AndroidGameActivity : SDLActivity
{
protected const ConfigChanges DEFAULT_CONFIG_CHANGES = ConfigChanges.Keyboard
| ConfigChanges.KeyboardHidden
@@ -31,36 +28,18 @@ public abstract class AndroidGameActivity : Activity
protected const LaunchMode DEFAULT_LAUNCH_MODE = LaunchMode.SingleInstance;
- protected abstract Game CreateGame();
-
- ///
- /// Whether this is active (in the foreground).
- ///
- public BindableBool IsActive { get; } = new BindableBool();
+ internal static AndroidGameSurface Surface => (AndroidGameSurface)MSurface!;
- ///
- /// The visibility flags for the system UI (status and navigation bars)
- ///
- public SystemUiFlags UIVisibilityFlags
- {
-#pragma warning disable 618 // SystemUiVisibility is deprecated
- get => (SystemUiFlags)Window.AsNonNull().DecorView.SystemUiVisibility;
- set
- {
- systemUiFlags = value;
- Window.AsNonNull().DecorView.SystemUiVisibility = (StatusBarVisibility)value;
-#pragma warning restore 618
- }
- }
+ protected abstract Game CreateGame();
- private SystemUiFlags systemUiFlags;
+ protected override string[] GetLibraries() => new string[] { "SDL3" };
- private AndroidGameView gameView = null!;
+ protected override SDLSurface CreateSDLSurface(Context? context) => new AndroidGameSurface(this, context);
- public override void OnTrimMemory([GeneratedEnum] TrimMemory level)
+ protected override void Main()
{
- base.OnTrimMemory(level);
- gameView.Host?.Collect();
+ var host = new AndroidGameHost(this);
+ host.Run(CreateGame());
}
protected override void OnCreate(Bundle? savedInstanceState)
@@ -74,83 +53,18 @@ protected override void OnCreate(Bundle? savedInstanceState)
System.Environment.CurrentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile);
base.OnCreate(savedInstanceState);
-
- SetContentView(gameView = new AndroidGameView(this, CreateGame()));
-
- UIVisibilityFlags = SystemUiFlags.LayoutFlags | SystemUiFlags.ImmersiveSticky | SystemUiFlags.HideNavigation | SystemUiFlags.Fullscreen;
-
- // Firing up the on-screen keyboard (eg: interacting with textboxes) may cause the UI visibility flags to be altered thus showing the navigation bar and potentially the status bar
- // This sets back the UI flags to hidden once the interaction with the on-screen keyboard has finished.
- Window.AsNonNull().DecorView.SystemUiVisibilityChange += (_, e) =>
- {
- if ((SystemUiFlags)e.Visibility != systemUiFlags)
- {
- UIVisibilityFlags = systemUiFlags;
- }
- };
-
- if (OperatingSystem.IsAndroidVersionAtLeast(28))
- {
- Window.AsNonNull().Attributes.AsNonNull().LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
- }
-
- gameView.HostStarted += host =>
- {
- host.AllowScreenSuspension.Result.BindValueChanged(allow =>
- {
- RunOnUiThread(() =>
- {
- if (!allow.NewValue)
- Window?.AddFlags(WindowManagerFlags.KeepScreenOn);
- else
- Window?.ClearFlags(WindowManagerFlags.KeepScreenOn);
- });
- }, true);
- };
}
protected override void OnStop()
{
base.OnStop();
- gameView.Host?.Suspend();
Bass.Pause();
}
protected override void OnRestart()
{
base.OnRestart();
- gameView.Host?.Resume();
Bass.Start();
}
-
- public override void OnWindowFocusChanged(bool hasFocus)
- {
- base.OnWindowFocusChanged(hasFocus);
- IsActive.Value = hasFocus;
- }
-
- public override void OnBackPressed()
- {
- // Avoid the default implementation that does close the app.
- // This only happens when the back button could not be captured from OnKeyDown.
- }
-
- // On some devices and keyboard combinations the OnKeyDown event does not propagate the key event to the view.
- // Here it is done manually to ensure that the keys actually land in the view.
-
- public override bool OnKeyDown([GeneratedEnum] Keycode keyCode, KeyEvent? e)
- {
- return gameView.OnKeyDown(keyCode, e);
- }
-
- public override bool OnKeyUp([GeneratedEnum] Keycode keyCode, KeyEvent? e)
- {
- return gameView.OnKeyUp(keyCode, e);
- }
-
- public override bool OnKeyLongPress([GeneratedEnum] Keycode keyCode, KeyEvent? e)
- {
- return gameView.OnKeyLongPress(keyCode, e);
- }
}
}
diff --git a/osu.Framework.Android/AndroidGameHost.cs b/osu.Framework.Android/AndroidGameHost.cs
index 256b02f0f8..9754a275e0 100644
--- a/osu.Framework.Android/AndroidGameHost.cs
+++ b/osu.Framework.Android/AndroidGameHost.cs
@@ -8,16 +8,12 @@
using Android.Content;
using osu.Framework.Android.Graphics.Textures;
using osu.Framework.Android.Graphics.Video;
-using osu.Framework.Android.Input;
using osu.Framework.Configuration;
using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
-using osu.Framework.Input;
-using osu.Framework.Input.Handlers;
-using osu.Framework.Input.Handlers.Midi;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Framework.Platform;
@@ -25,13 +21,14 @@
namespace osu.Framework.Android
{
- public class AndroidGameHost : OsuTKGameHost
+ public class AndroidGameHost : SDLGameHost
{
- private readonly AndroidGameView gameView;
+ private readonly AndroidGameActivity activity;
- public AndroidGameHost(AndroidGameView gameView)
+ public AndroidGameHost(AndroidGameActivity activity)
+ : base(string.Empty)
{
- this.gameView = gameView;
+ this.activity = activity;
}
protected override void SetupConfig(IDictionary defaultOverrides)
@@ -42,9 +39,13 @@ protected override void SetupConfig(IDictionary defaul
base.SetupConfig(defaultOverrides);
}
- protected override IWindow CreateWindow(GraphicsSurfaceType preferredSurface) => new AndroidGameWindow(gameView);
+ protected override IWindow CreateWindow(GraphicsSurfaceType preferredSurface) => new AndroidGameWindow(preferredSurface, Options.FriendlyGameName);
- protected override Clipboard CreateClipboard() => new AndroidClipboard(gameView);
+ protected override void DrawFrame()
+ {
+ if (AndroidGameActivity.Surface.IsSurfaceReady)
+ base.DrawFrame();
+ }
public override bool CanExit => false;
@@ -52,18 +53,6 @@ protected override void SetupConfig(IDictionary defaul
public override bool OnScreenKeyboardOverlapsGameWindow => true;
- protected override TextInputSource CreateTextInput() => new AndroidTextInput(gameView);
-
- protected override IEnumerable CreateAvailableInputHandlers() =>
- new InputHandler[]
- {
- new AndroidMouseHandler(gameView),
- new AndroidKeyboardHandler(gameView),
- new AndroidTouchHandler(gameView),
- new AndroidJoystickHandler(gameView),
- new MidiHandler()
- };
-
public override string InitialFileSelectorPath => @"/sdcard";
public override Storage GetStorage(string path) => new AndroidStorage(path, this);
@@ -87,7 +76,7 @@ public override void OpenUrlExternally(string url)
{
// Recommended way to open URLs on Android 11+
// https://developer.android.com/training/package-visibility/use-cases#open-urls-browser-or-other-app
- gameView.Activity.StartActivity(intent);
+ activity.StartActivity(intent);
}
}
catch (Exception ex)
@@ -104,7 +93,7 @@ public override VideoDecoder CreateVideoDecoder(Stream stream)
public override bool SuspendToBackground()
{
- return gameView.Activity.MoveTaskToBack(true);
+ return activity.MoveTaskToBack(true);
}
}
}
diff --git a/osu.Framework.Android/AndroidGameSurface.cs b/osu.Framework.Android/AndroidGameSurface.cs
new file mode 100644
index 0000000000..8f913d1b74
--- /dev/null
+++ b/osu.Framework.Android/AndroidGameSurface.cs
@@ -0,0 +1,121 @@
+// 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 Android.Content;
+using Android.Runtime;
+using Org.Libsdl.App;
+using osu.Framework.Graphics;
+using osu.Framework.Platform;
+using osu.Framework.Bindables;
+using Android.Views;
+using AndroidX.Core.View;
+using AndroidX.Window.Layout;
+
+namespace osu.Framework.Android
+{
+ internal class AndroidGameSurface : SDLSurface
+ {
+ private AndroidGameActivity activity { get; } = null!;
+
+ public BindableSafeArea SafeAreaPadding { get; } = new BindableSafeArea();
+
+ public AndroidGameSurface(AndroidGameActivity activity, Context? context)
+ : base(context)
+ {
+ init();
+ this.activity = activity;
+ }
+
+ protected AndroidGameSurface(IntPtr javaReference, JniHandleOwnership transfer)
+ : base(javaReference, transfer)
+ {
+ init();
+ }
+
+ private void init()
+ {
+ if (OperatingSystem.IsAndroidVersionAtLeast(26))
+ {
+ // disable ugly green border when view is focused via hardware keyboard/mouse.
+ DefaultFocusHighlightEnabled = false;
+ }
+ }
+
+ private volatile bool isSurfaceReady;
+
+ public bool IsSurfaceReady => isSurfaceReady;
+
+ public override void HandlePause()
+ {
+ base.HandlePause();
+ isSurfaceReady = false;
+ }
+
+ public override void HandleResume()
+ {
+ base.HandleResume();
+ isSurfaceReady = true;
+ }
+
+ public override WindowInsets? OnApplyWindowInsets(View? view, WindowInsets? insets)
+ {
+ updateSafeArea(insets);
+ return base.OnApplyWindowInsets(view, insets);
+ }
+
+ ///
+ /// Updates the , taking into account screen insets that may be obstructing this .
+ ///
+ private void updateSafeArea(WindowInsets? windowInsets)
+ {
+ var metrics = WindowMetricsCalculator.Companion.OrCreate.ComputeCurrentWindowMetrics(activity);
+ var windowArea = metrics.Bounds.ToRectangleI();
+ var usableWindowArea = windowArea;
+
+ if (OperatingSystem.IsAndroidVersionAtLeast(28))
+ {
+ var cutout = windowInsets?.DisplayCutout;
+
+ if (cutout != null)
+ usableWindowArea = usableWindowArea.Shrink(cutout.SafeInsetLeft, cutout.SafeInsetRight, cutout.SafeInsetTop, cutout.SafeInsetBottom);
+ }
+
+ if (OperatingSystem.IsAndroidVersionAtLeast(31) && windowInsets != null)
+ {
+ var topLeftCorner = windowInsets.GetRoundedCorner((int)RoundedCornerPosition.TopLeft);
+ var topRightCorner = windowInsets.GetRoundedCorner((int)RoundedCornerPosition.TopRight);
+ var bottomLeftCorner = windowInsets.GetRoundedCorner((int)RoundedCornerPosition.BottomLeft);
+ var bottomRightCorner = windowInsets.GetRoundedCorner((int)RoundedCornerPosition.BottomRight);
+
+ int cornerInsetLeft = Math.Max(topLeftCorner?.Radius ?? 0, bottomLeftCorner?.Radius ?? 0);
+ int cornerInsetRight = Math.Max(topRightCorner?.Radius ?? 0, bottomRightCorner?.Radius ?? 0);
+ int cornerInsetTop = Math.Max(topLeftCorner?.Radius ?? 0, topRightCorner?.Radius ?? 0);
+ int cornerInsetBottom = Math.Max(bottomLeftCorner?.Radius ?? 0, bottomRightCorner?.Radius ?? 0);
+
+ var radiusInsetArea = windowArea.Width >= windowArea.Height
+ ? windowArea.Shrink(cornerInsetLeft, cornerInsetRight, 0, 0)
+ : windowArea.Shrink(0, 0, cornerInsetTop, cornerInsetBottom);
+
+ usableWindowArea = usableWindowArea.Intersect(radiusInsetArea);
+ }
+
+ if (OperatingSystem.IsAndroidVersionAtLeast(24) && activity.IsInMultiWindowMode && windowInsets != null)
+ {
+ // if we are in multi-window mode, the status bar is always visible (even if we request to hide it) and could be obstructing our view.
+ // if multi-window mode is not active, we can assume the status bar is hidden so we shouldn't consider it for safe area calculations.
+ var insetsCompat = WindowInsetsCompat.ToWindowInsetsCompat(windowInsets, this);
+ int statusBarHeight = insetsCompat.GetInsets(WindowInsetsCompat.Type.StatusBars()).Top;
+ usableWindowArea = usableWindowArea.Intersect(windowArea.Shrink(0, 0, statusBarHeight, 0));
+ }
+
+ SafeAreaPadding.Value = new MarginPadding
+ {
+ Left = usableWindowArea.Left - windowArea.Left,
+ Top = usableWindowArea.Top - windowArea.Top,
+ Right = windowArea.Right - usableWindowArea.Right,
+ Bottom = windowArea.Bottom - usableWindowArea.Bottom,
+ };
+ }
+ }
+}
diff --git a/osu.Framework.Android/AndroidGameView.cs b/osu.Framework.Android/AndroidGameView.cs
deleted file mode 100644
index a66f70a153..0000000000
--- a/osu.Framework.Android/AndroidGameView.cs
+++ /dev/null
@@ -1,359 +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;
-using System.Runtime.Versioning;
-using Android.Content;
-using Android.OS;
-using Android.Runtime;
-using Android.Text;
-using Android.Util;
-using Android.Views;
-using Android.Views.InputMethods;
-using AndroidX.Core.View;
-using AndroidX.Window.Layout;
-using osu.Framework.Android.Input;
-using osu.Framework.Logging;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Platform;
-using osuTK.Graphics;
-
-namespace osu.Framework.Android
-{
- public class AndroidGameView : osuTK.Android.AndroidGameView
- {
- public AndroidGameHost? Host { get; private set; }
-
- public AndroidGameActivity Activity { get; } = null!;
-
- public BindableSafeArea SafeAreaPadding { get; } = new BindableSafeArea();
-
- ///
- /// Represents whether the mouse pointer is captured, as reported by Android through .
- ///
- private bool pointerCaptured;
-
- ///
- /// Set Android's pointer capture.
- ///
- ///
- /// Only available in Android 8.0 Oreo () and up.
- ///
- public bool PointerCapture
- {
- get => pointerCaptured;
- set
- {
- if (!OperatingSystem.IsAndroidVersionAtLeast(26))
- {
- Logger.Log($"Tried to set {nameof(PointerCapture)} on an unsupported Android version.", level: LogLevel.Important);
- return;
- }
-
- if (pointerCaptured == value) return;
-
- if (value)
- RequestPointerCapture();
- else
- ReleasePointerCapture();
- }
- }
-
- private readonly Game game = null!;
-
- private InputMethodManager? inputMethodManager;
-
- ///
- /// Whether is active.
- ///
- private bool textInputActive;
-
- public AndroidGameView(AndroidGameActivity activity, Game game)
- : base(activity)
- {
- Activity = activity;
- this.game = game;
-
- init();
- }
-
- public AndroidGameView(Context context, IAttributeSet attrs)
- : base(context, attrs)
- {
- init();
- }
-
- public AndroidGameView(IntPtr handle, JniHandleOwnership transfer)
- : base(handle, transfer)
- {
- init();
- }
-
- private void init()
- {
- AutoSetContextOnRenderFrame = true;
- ContextRenderingApi = GLVersion.ES3;
-
- // enable soft and hardware keyboard
- // this needs to happen in the constructor
- Focusable = true;
- FocusableInTouchMode = true;
-
- if (OperatingSystem.IsAndroidVersionAtLeast(26))
- {
- // disable ugly green border when view is focused via hardware keyboard/mouse.
- DefaultFocusHighlightEnabled = false;
- }
-
- inputMethodManager = Activity.GetSystemService(Context.InputMethodService) as InputMethodManager;
- }
-
- protected override void CreateFrameBuffer()
- {
- try
- {
- base.CreateFrameBuffer();
- Log.Verbose("AndroidGameView", "Successfully created the framebuffer");
- }
- catch (Exception e)
- {
- Log.Verbose("AndroidGameView", "{0}", e);
- throw new InvalidOperationException("Can't load egl, aborting", e);
- }
- }
-
- public bool OnCommitText(string text)
- {
- CommitText?.Invoke(text);
- return false;
- }
-
- public override bool OnKeyDown([GeneratedEnum] Keycode keyCode, KeyEvent? e)
- {
- if (e == null) return base.OnKeyDown(keyCode, e);
-
- switch (keyCode)
- {
- // Do not consume Volume keys, so the system can handle them
- case Keycode.VolumeDown:
- case Keycode.VolumeUp:
- case Keycode.VolumeMute:
- return false;
-
- default:
- KeyDown?.Invoke(keyCode, e);
-
- // Releasing backspace on a physical keyboard when text input is active will not send a key up event.
- // Manually send one to prevent the key from getting stuck.
- // This does mean that key repeat is handled by the OS, instead of by the usual `InputManager` handling.
- if (keyCode == Keycode.Del && e.IsFromSource(InputSourceType.Keyboard) && textInputActive)
- KeyUp?.Invoke(Keycode.Del, new KeyEvent(e.DownTime, e.EventTime, KeyEventActions.Up, Keycode.Del, 0, e.MetaState, e.DeviceId, e.ScanCode, e.Flags, e.Source));
-
- return true;
- }
- }
-
- public override bool OnKeyLongPress([GeneratedEnum] Keycode keyCode, KeyEvent? e)
- {
- if (e == null) return base.OnKeyLongPress(keyCode, e);
-
- KeyLongPress?.Invoke(keyCode, e);
- return true;
- }
-
- public override bool OnKeyUp([GeneratedEnum] Keycode keyCode, KeyEvent? e)
- {
- if (e == null) return base.OnKeyUp(keyCode, e);
-
- KeyUp?.Invoke(keyCode, e);
- return true;
- }
-
- [SupportedOSPlatform("android26.0")]
- public override void OnPointerCaptureChange(bool hasCapture)
- {
- base.OnPointerCaptureChange(hasCapture);
- pointerCaptured = hasCapture;
- }
-
- protected override void OnLoad(EventArgs e)
- {
- base.OnLoad(e);
-
- // osuTK calls `OnLoad()` every time the application surface is created, which will also happen upon a resume,
- // at which point the host is already present and running, so there is no reason to create another one.
- if (Host == null)
- RenderGame();
- }
-
- public override WindowInsets? OnApplyWindowInsets(WindowInsets? insets)
- {
- updateSafeArea(insets);
- return base.OnApplyWindowInsets(insets);
- }
-
- [STAThread]
- public void RenderGame()
- {
- // request focus so that joystick input can immediately work.
- RequestFocus();
-
- Host = new AndroidGameHost(this);
- Host.ExceptionThrown += handleException;
- Host.Run(game);
- HostStarted?.Invoke(Host);
- }
-
- private bool handleException(Exception ex)
- {
- // suppress exceptions related to MobileAuthenticatedStream disposal
- // (see: https://github.com/ppy/osu/issues/6264 and linked related mono/xamarin issues)
- // to be removed when upstream fixes come in
- return ex is AggregateException ae
- && ae.InnerException is ObjectDisposedException ode
- && ode.ObjectName == "MobileAuthenticatedStream";
- }
-
- ///
- /// Updates the , taking into account screen insets that may be obstructing this .
- ///
- private void updateSafeArea(WindowInsets? windowInsets)
- {
- var metrics = WindowMetricsCalculator.Companion.OrCreate.ComputeCurrentWindowMetrics(Activity);
- var windowArea = metrics.Bounds.ToRectangleI();
- var usableWindowArea = windowArea;
-
- if (OperatingSystem.IsAndroidVersionAtLeast(28))
- {
- var cutout = windowInsets?.DisplayCutout;
-
- if (cutout != null)
- usableWindowArea = usableWindowArea.Shrink(cutout.SafeInsetLeft, cutout.SafeInsetRight, cutout.SafeInsetTop, cutout.SafeInsetBottom);
- }
-
- if (OperatingSystem.IsAndroidVersionAtLeast(31) && windowInsets != null)
- {
- var topLeftCorner = windowInsets.GetRoundedCorner((int)RoundedCornerPosition.TopLeft);
- var topRightCorner = windowInsets.GetRoundedCorner((int)RoundedCornerPosition.TopRight);
- var bottomLeftCorner = windowInsets.GetRoundedCorner((int)RoundedCornerPosition.BottomLeft);
- var bottomRightCorner = windowInsets.GetRoundedCorner((int)RoundedCornerPosition.BottomRight);
-
- int cornerInsetLeft = Math.Max(topLeftCorner?.Radius ?? 0, bottomLeftCorner?.Radius ?? 0);
- int cornerInsetRight = Math.Max(topRightCorner?.Radius ?? 0, bottomRightCorner?.Radius ?? 0);
- int cornerInsetTop = Math.Max(topLeftCorner?.Radius ?? 0, topRightCorner?.Radius ?? 0);
- int cornerInsetBottom = Math.Max(bottomLeftCorner?.Radius ?? 0, bottomRightCorner?.Radius ?? 0);
-
- var radiusInsetArea = windowArea.Width >= windowArea.Height
- ? windowArea.Shrink(cornerInsetLeft, cornerInsetRight, 0, 0)
- : windowArea.Shrink(0, 0, cornerInsetTop, cornerInsetBottom);
-
- usableWindowArea = usableWindowArea.Intersect(radiusInsetArea);
- }
-
- if (OperatingSystem.IsAndroidVersionAtLeast(24) && Activity.IsInMultiWindowMode && windowInsets != null)
- {
- // if we are in multi-window mode, the status bar is always visible (even if we request to hide it) and could be obstructing our view.
- // if multi-window mode is not active, we can assume the status bar is hidden so we shouldn't consider it for safe area calculations.
- var insetsCompat = WindowInsetsCompat.ToWindowInsetsCompat(windowInsets, this);
- int statusBarHeight = insetsCompat.GetInsets(WindowInsetsCompat.Type.StatusBars()).Top;
- usableWindowArea = usableWindowArea.Intersect(windowArea.Shrink(0, 0, statusBarHeight, 0));
- }
-
- SafeAreaPadding.Value = new MarginPadding
- {
- Left = usableWindowArea.Left - windowArea.Left,
- Top = usableWindowArea.Top - windowArea.Top,
- Right = windowArea.Right - usableWindowArea.Right,
- Bottom = windowArea.Bottom - usableWindowArea.Bottom,
- };
- }
-
- public override bool OnCheckIsTextEditor() => textInputActive;
-
- /// null to disable input methods
- public override IInputConnection? OnCreateInputConnection(EditorInfo? outAttrs)
- {
- ArgumentNullException.ThrowIfNull(outAttrs);
-
- // Properly disable native input methods so that the software keyboard doesn't unexpectedly open.
- // Eg. when pressing keys on a hardware keyboard.
- if (!textInputActive)
- return null;
-
- outAttrs.ImeOptions = ImeFlags.NoExtractUi | ImeFlags.NoFullscreen;
- outAttrs.InputType = InputTypes.TextVariationVisiblePassword | InputTypes.TextFlagNoSuggestions;
- return new AndroidInputConnection(this, true);
- }
-
- internal void StartTextInput()
- {
- textInputActive = true;
- Activity.RunOnUiThread(() =>
- {
- inputMethodManager?.RestartInput(this); // this syncs the Android input method state with `OnCreateInputConnection()`.
- RequestFocus();
- inputMethodManager?.ShowSoftInput(this, 0);
- });
- }
-
- internal void StopTextInput()
- {
- textInputActive = false;
- Activity.RunOnUiThread(() =>
- {
- inputMethodManager?.RestartInput(this);
- inputMethodManager?.HideSoftInputFromWindow(WindowToken, HideSoftInputFlags.None);
- });
- }
-
- public override void SwapBuffers()
- {
- try
- {
- base.SwapBuffers();
- }
- catch (GraphicsContextException ex)
- {
- // sometimes buffers will spontaneously fail to swap with BAD_SURFACE
- // just before the activity is suspended to background or just after it has been resumed,
- // but will continue operating correctly after that transitionary period.
- // despite some testing it is unclear which view callback can be used to tell whether it is safe to swap buffers,
- // so for now just catch and suppress these errors.
- if (ex.Message.Contains("BAD_SURFACE", StringComparison.Ordinal))
- Logger.Log($"BAD_SURFACE failure in {nameof(SwapBuffers)} suppressed");
- else
- throw;
- }
- }
-
- #region Events
-
- ///
- /// Invoked on a key down event.
- ///
- public new event Action? KeyDown;
-
- ///
- /// Invoked on a key up event.
- ///
- public new event Action? KeyUp;
-
- ///
- /// Invoked on a key long press event.
- ///
- public event Action? KeyLongPress;
-
- ///
- /// Invoked when text is committed by an .
- ///
- public event Action? CommitText;
-
- ///
- /// Invoked when the has been started on the .
- ///
- public event Action? HostStarted;
-
- #endregion
- }
-}
diff --git a/osu.Framework.Android/AndroidGameWindow.cs b/osu.Framework.Android/AndroidGameWindow.cs
index 2feee17adf..457d5e1ebe 100644
--- a/osu.Framework.Android/AndroidGameWindow.cs
+++ b/osu.Framework.Android/AndroidGameWindow.cs
@@ -2,71 +2,30 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using osu.Framework.Bindables;
-using osu.Framework.Configuration;
using osu.Framework.Platform;
-using osuTK;
-using osuTK.Graphics;
+using osu.Framework.Platform.SDL3;
namespace osu.Framework.Android
{
- internal class AndroidGameWindow : OsuTKWindow
+ internal class AndroidGameWindow : SDL3MobileWindow
{
- private readonly AndroidGameView view;
+ public override IntPtr SurfaceHandle => AndroidGameActivity.Surface.NativeSurface?.Handle ?? IntPtr.Zero;
- public override IGraphicsContext Context => view.GraphicsContext;
-
- public override bool Focused => IsActive.Value;
-
- public override IBindable IsActive { get; }
-
- public override Platform.WindowState WindowState
- {
- get => Platform.WindowState.Normal;
- set { }
- }
-
- public event Action? CursorStateChanged;
-
- public override CursorState CursorState
- {
- get => base.CursorState;
- set
- {
- // cursor should always be confined on mobile platforms, to have UserInputManager confine the cursor to window bounds
- base.CursorState = value | CursorState.Confined;
- CursorStateChanged?.Invoke();
- }
- }
-
- public AndroidGameWindow(AndroidGameView view)
- : base(view)
- {
- this.view = view;
- IsActive = view.Activity.IsActive.GetBoundCopy();
- }
-
- public override void SetupWindow(FrameworkConfigManager config)
+ public AndroidGameWindow(GraphicsSurfaceType surfaceType, string appName)
+ : base(surfaceType, appName)
{
- CursorState |= CursorState.Confined;
- SafeAreaPadding.BindTo(view.SafeAreaPadding);
}
- public override IEnumerable SupportedWindowModes => new[]
+ public override void Create()
{
- Configuration.WindowMode.Fullscreen,
- };
+ base.Create();
- public override void Run()
- {
- view.Run();
- }
+ SafeAreaPadding.BindTo(AndroidGameActivity.Surface.SafeAreaPadding);
- protected override DisplayDevice CurrentDisplayDevice
- {
- get => DisplayDevice.Default;
- set => throw new InvalidOperationException();
+ // Android SDL doesn't receive these events at start, so it never receives focus until it comes back from background
+ ((BindableBool)CursorInWindow).Value = true;
+ Focused = true;
}
}
}
diff --git a/osu.Framework.Android/Input/AndroidInputConnection.cs b/osu.Framework.Android/Input/AndroidInputConnection.cs
deleted file mode 100644
index 1bcdee1fb9..0000000000
--- a/osu.Framework.Android/Input/AndroidInputConnection.cs
+++ /dev/null
@@ -1,66 +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 Android.Views;
-using Android.Views.InputMethods;
-using Java.Lang;
-
-namespace osu.Framework.Android.Input
-{
- internal class AndroidInputConnection : BaseInputConnection
- {
- private readonly AndroidGameView targetView;
-
- public AndroidInputConnection(AndroidGameView targetView, bool fullEditor)
- : base(targetView, fullEditor)
- {
- this.targetView = targetView;
- }
-
- public override bool CommitText(ICharSequence? text, int newCursorPosition)
- {
- if (text?.Length() > 0)
- {
- targetView.OnCommitText(text.ToString());
- return true;
- }
-
- return base.CommitText(text, newCursorPosition);
- }
-
- public override bool SendKeyEvent(KeyEvent? e)
- {
- if (e == null)
- return base.SendKeyEvent(e);
-
- switch (e.Action)
- {
- case KeyEventActions.Down:
- targetView.OnKeyDown(e.KeyCode, e);
- return true;
-
- case KeyEventActions.Up:
- targetView.OnKeyUp(e.KeyCode, e);
- return true;
-
- case KeyEventActions.Multiple:
- targetView.OnKeyDown(e.KeyCode, e);
- targetView.OnKeyUp(e.KeyCode, e);
- return true;
- }
-
- return base.SendKeyEvent(e);
- }
-
- public override bool DeleteSurroundingText(int beforeLength, int afterLength)
- {
- for (int i = 0; i < beforeLength; i++)
- {
- KeyEvent ed = new KeyEvent(KeyEventActions.Multiple, Keycode.Del);
- SendKeyEvent(ed);
- }
-
- return true;
- }
- }
-}
diff --git a/osu.Framework.Android/Input/AndroidInputExtensions.cs b/osu.Framework.Android/Input/AndroidInputExtensions.cs
deleted file mode 100644
index 0cb1e4a63a..0000000000
--- a/osu.Framework.Android/Input/AndroidInputExtensions.cs
+++ /dev/null
@@ -1,370 +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;
-using System.Collections.Generic;
-using Android.Views;
-using osu.Framework.Extensions.EnumExtensions;
-using osu.Framework.Input;
-using osu.Framework.Logging;
-using osuTK;
-using osuTK.Input;
-
-namespace osu.Framework.Android.Input
-{
- public static class AndroidInputExtensions
- {
- ///
- /// Denotes the current (last) event in a 's history.
- ///
- public const int HISTORY_CURRENT = -1;
-
- public delegate void MotionEventHandler(MotionEvent motionEvent, int historyPosition);
-
- ///
- /// Handles all events in this 's history in a chronological fashion (up to and including ).
- ///
- /// The to handle the history of.
- /// The to handle the events.
- ///
- /// Used in lieu of when the input infrastructure can only handle one pointer
- /// and/or when the is expected to report only one pointer.
- ///
- public static void HandleHistorically(this MotionEvent motionEvent, MotionEventHandler handler)
- {
- if (motionEvent.PointerCount > 1)
- {
- Logger.Log($"{nameof(HandleHistorically)} was used when PointerCount ({motionEvent.PointerCount}) was greater than 1. Events for pointers other than the first have been dropped.");
- Logger.Log($"MotionEvent: {motionEvent}");
- }
-
- for (int h = 0; h < motionEvent.HistorySize; h++)
- {
- handler(motionEvent, h);
- }
-
- handler(motionEvent, HISTORY_CURRENT);
- }
-
- public delegate void MotionEventPerPointerHandler(MotionEvent motionEvent, int historyPosition, int pointerIndex);
-
- ///
- /// Handles all events in this 's history in a chronological fashion, sequentially calling for each pointer.
- ///
- /// The to handle the history of.
- /// The to handle the events.
- public static void HandleHistoricallyPerPointer(this MotionEvent motionEvent, MotionEventPerPointerHandler handler)
- {
- for (int h = 0; h < motionEvent.HistorySize; h++)
- {
- for (int p = 0; p < motionEvent.PointerCount; p++)
- {
- handler(motionEvent, h, p);
- }
- }
-
- for (int p = 0; p < motionEvent.PointerCount; p++)
- {
- handler(motionEvent, HISTORY_CURRENT, p);
- }
- }
-
- ///
- /// Returns the value of the requested axis.
- ///
- /// The to get the value from.
- /// The identifier for the axis value to retrieve.
- /// Which historical value to return; must be in range [0, ), or the constant .
- /// Raw index of pointer to retrieve; must be in range [0, ).
- /// The value of the axis, or 0 if the axis is not available.
- /// s different from are valid only for events.
- public static float Get(this MotionEvent motionEvent, Axis axis, int historyPosition = HISTORY_CURRENT, int pointerIndex = 0)
- => historyPosition == HISTORY_CURRENT
- ? motionEvent.GetAxisValue(axis, pointerIndex)
- : motionEvent.GetHistoricalAxisValue(axis, pointerIndex, historyPosition);
-
- ///
- /// Gets the of the requested axis, returning true if it's valid.
- ///
- /// The to get the value from.
- /// The identifier for the axis value to retrieve.
- /// The value of the axis, or 0 if the axis is not available.
- /// Which historical to return; must be in range [0, ),
- /// or the constant .
- /// Raw index of pointer to retrieve; must be in range [0, ).
- /// Whether the returned is valid.
- /// s different from are valid only for events.
- public static bool TryGet(this MotionEvent motionEvent, Axis axis, out float value, int historyPosition = HISTORY_CURRENT, int pointerIndex = 0)
- {
- value = historyPosition == HISTORY_CURRENT
- ? motionEvent.GetAxisValue(axis, pointerIndex)
- : motionEvent.GetHistoricalAxisValue(axis, pointerIndex, historyPosition);
-
- return float.IsFinite(value);
- }
-
- ///
- /// Gets the and axes of the event, returning true if they're valid.
- ///
- /// The to get the axes from.
- /// containing the and axes of the event.
- /// Which historical to return; must be in range [0, ),
- /// or the constant .
- /// Raw index of pointer to retrieve; must be in range [0, ).
- /// Whether the returned is valid.
- /// s different from are valid only for events.
- public static bool TryGetPosition(this MotionEvent motionEvent, out Vector2 position, int historyPosition = HISTORY_CURRENT, int pointerIndex = 0)
- {
- if (motionEvent.TryGet(Axis.X, out float x, historyPosition, pointerIndex)
- && motionEvent.TryGet(Axis.Y, out float y, historyPosition, pointerIndex))
- {
- position = new Vector2(x, y);
- return true;
- }
-
- position = Vector2.Zero;
- return false;
- }
-
- ///
- /// Returns the corresponding s for a mouse button given as a .
- ///
- /// The given button state. Must not be a raw state or a non-mouse button.
- /// The corresponding s.
- public static IEnumerable ToMouseButtons(this MotionEventButtonState motionEventMouseButton)
- {
- if (motionEventMouseButton.HasFlagFast(MotionEventButtonState.Primary))
- yield return MouseButton.Left;
-
- if (motionEventMouseButton.HasFlagFast(MotionEventButtonState.Secondary))
- yield return MouseButton.Right;
-
- if (motionEventMouseButton.HasFlagFast(MotionEventButtonState.Tertiary))
- yield return MouseButton.Middle;
-
- if (motionEventMouseButton.HasFlagFast(MotionEventButtonState.Back))
- yield return MouseButton.Button1;
-
- if (motionEventMouseButton.HasFlagFast(MotionEventButtonState.Forward))
- yield return MouseButton.Button2;
- }
-
- ///
- /// Returns the corresponding for a mouse button given as a .
- ///
- /// The given keycode. Should be or .
- /// The corresponding .
- /// true if this is a valid .
- public static bool TryGetMouseButton(this Keycode keycode, out MouseButton button)
- {
- switch (keycode)
- {
- case Keycode.Back:
- button = MouseButton.Button1;
- return true;
-
- case Keycode.Forward:
- button = MouseButton.Button2;
- return true;
- }
-
- button = MouseButton.LastButton;
- return false;
- }
-
- public static bool IsKeyboard(this InputSourceType source)
- {
- // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags
- return source is InputSourceType.Keyboard or (InputSourceType.Keyboard | InputSourceType.Dpad);
- }
-
- public static bool TryGetJoystickButton(this KeyEvent e, out JoystickButton button)
- {
- var keycode = e.KeyCode;
-
- if (keycode >= Keycode.Button1 && keycode <= Keycode.Button16)
- {
- // JoystickButtons 1-16 are used below.
- button = JoystickButton.Button17 + (keycode - Keycode.Button1);
- return true;
- }
-
- switch (keycode)
- {
- // Dpad keycodes are _not_ joystick buttons, but are instead used for arrow keys on a keyboard.
- // as evident from KeyEvent.isGamePadButton():
- // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/KeyEvent.java;l=1899-1936;drc=11e61ab1fd1f868ee8ddd6fc86662f4f09df1a6a
- case Keycode.DpadUp:
- case Keycode.DpadDown:
- case Keycode.DpadLeft:
- case Keycode.DpadRight:
- case Keycode.Back when e.Source.IsKeyboard():
- default:
- button = JoystickButton.FirstButton;
- return false;
-
- case Keycode.ButtonA:
- button = JoystickButton.GamePadA;
- return true;
-
- case Keycode.ButtonB:
- button = JoystickButton.GamePadB;
- return true;
-
- case Keycode.ButtonC:
- button = JoystickButton.Button14; // generic button
- return true;
-
- case Keycode.ButtonX:
- button = JoystickButton.GamePadX;
- return true;
-
- case Keycode.ButtonY:
- button = JoystickButton.GamePadY;
- return true;
-
- case Keycode.ButtonZ:
- button = JoystickButton.Button15; // generic button
- return true;
-
- case Keycode.ButtonL1:
- button = JoystickButton.GamePadLeftShoulder;
- return true;
-
- case Keycode.ButtonR1:
- button = JoystickButton.GamePadRightShoulder;
- return true;
-
- case Keycode.ButtonL2:
- button = JoystickButton.GamePadLeftTrigger;
- return true;
-
- case Keycode.ButtonR2:
- button = JoystickButton.GamePadRightTrigger;
- return true;
-
- case Keycode.ButtonThumbl:
- button = JoystickButton.GamePadLeftStick;
- return true;
-
- case Keycode.ButtonThumbr:
- button = JoystickButton.GamePadRightStick;
- return true;
-
- case Keycode.ButtonStart:
- button = JoystickButton.GamePadStart;
- return true;
-
- case Keycode.Back:
- case Keycode.ButtonSelect:
- button = JoystickButton.GamePadBack;
- return true;
-
- case Keycode.ButtonMode:
- button = JoystickButton.Button16; // generic button
- return true;
- }
- }
-
- ///
- /// All axes supported by .
- ///
- public static readonly IEnumerable ALL_AXES = new[]
- {
- Axis.X,
- Axis.Y,
- Axis.Ltrigger,
- Axis.Z,
- Axis.Rz,
- Axis.Rtrigger,
- Axis.Rx,
- Axis.Ry,
- Axis.Rudder,
- Axis.Wheel,
- };
-
- ///
- /// Returns the corresponding for an .
- ///
- /// true if provided maps to a .
- ///
- /// and are deliberately excluded as those axes are 1:1 mirrors of the and .
- ///
- public static bool TryGetJoystickAxisSource(this Axis axis, out JoystickAxisSource joystickAxis)
- {
- switch (axis)
- {
- case Axis.X:
- joystickAxis = JoystickAxisSource.GamePadLeftStickX;
- return true;
-
- case Axis.Y:
- joystickAxis = JoystickAxisSource.GamePadLeftStickY;
- return true;
-
- case Axis.Ltrigger:
- joystickAxis = JoystickAxisSource.GamePadLeftTrigger;
- return true;
-
- case Axis.Z:
- joystickAxis = JoystickAxisSource.GamePadRightStickX;
- return true;
-
- case Axis.Rz:
- joystickAxis = JoystickAxisSource.GamePadRightStickY;
- return true;
-
- case Axis.Rtrigger:
- joystickAxis = JoystickAxisSource.GamePadRightTrigger;
- return true;
-
- case Axis.Rx:
- joystickAxis = JoystickAxisSource.Axis7;
- return true;
-
- case Axis.Ry:
- joystickAxis = JoystickAxisSource.Axis8;
- return true;
-
- case Axis.Rudder:
- joystickAxis = JoystickAxisSource.Axis9;
- return true;
-
- case Axis.Wheel:
- joystickAxis = JoystickAxisSource.Axis10;
- return true;
- }
-
- joystickAxis = JoystickAxisSource.AxisCount;
- return false;
- }
-
- ///
- /// Whether this is a touch down action.
- ///
- /// The to check.
- ///
- /// true if this is a touch down action.
- /// false if this is a touch up action.
- ///
- /// If this action is not a touch action.
- public static bool IsTouchDownAction(this MotionEventActions action)
- {
- switch (action)
- {
- case MotionEventActions.Down:
- case MotionEventActions.PointerDown:
- case MotionEventActions.Move:
- return true;
-
- case MotionEventActions.PointerUp:
- case MotionEventActions.Up:
- case MotionEventActions.Cancel:
- return false;
-
- default:
- throw new ArgumentOutOfRangeException(nameof(action), action, "Motion event action is not a touch action.");
- }
- }
- }
-}
diff --git a/osu.Framework.Android/Input/AndroidInputHandler.cs b/osu.Framework.Android/Input/AndroidInputHandler.cs
deleted file mode 100644
index 277fafa4ea..0000000000
--- a/osu.Framework.Android/Input/AndroidInputHandler.cs
+++ /dev/null
@@ -1,258 +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;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using Android.Views;
-using osu.Framework.Extensions.EnumExtensions;
-using osu.Framework.Extensions.TypeExtensions;
-using osu.Framework.Input.Handlers;
-using osu.Framework.Platform;
-
-namespace osu.Framework.Android.Input
-{
- ///
- /// Base input handler for handling events dispatched by .
- /// Provides consistent and unified means for handling s only from specific s.
- ///
- public abstract class AndroidInputHandler : InputHandler
- {
- ///
- protected const int HISTORY_CURRENT = AndroidInputExtensions.HISTORY_CURRENT;
-
- ///
- /// The s that this will handle.
- ///
- protected abstract IEnumerable HandledEventSources { get; }
-
- ///
- /// The view that this is handling events from.
- ///
- protected readonly AndroidGameView View;
-
- ///
- /// Bitmask of all .
- ///
- private InputSourceType eventSourceBitmask;
-
- protected AndroidInputHandler(AndroidGameView view)
- {
- View = view;
- }
-
- public override bool Initialize(GameHost host)
- {
- if (!base.Initialize(host))
- return false;
-
- // compute the bitmask for later use.
- foreach (var eventSource in HandledEventSources)
- {
- // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags
- // (InputSourceType is a flags enum, but is not properly marked as such)
- eventSourceBitmask |= eventSource;
- }
-
- return true;
- }
-
-#pragma warning disable 1574 // unresolved cref attribute
-
- ///
- /// Invoked on every event that matches an from .
- ///
- ///
- /// Subscribe to . to receive events here.
- /// Whether the event was handled. Unhandled events are logged.
- ///
- protected virtual bool OnCapturedPointer(MotionEvent capturedPointerEvent)
- {
- throw new NotSupportedException($"{nameof(HandleCapturedPointer)} subscribed to {nameof(View.CapturedPointer)} but the relevant method was not overriden.");
- }
-
- ///
- /// Invoked on every event that matches an from .
- ///
- ///
- /// Subscribe to . to receive events here.
- /// Whether the event was handled. Unhandled events are logged.
- ///
- protected virtual bool OnGenericMotion(MotionEvent genericMotionEvent)
- {
- throw new NotSupportedException($"{nameof(HandleGenericMotion)} subscribed to {nameof(View.GenericMotion)} but the relevant method was not overriden.");
- }
-
- ///
- /// Invoked on every event that matches an from .
- ///
- ///
- /// Subscribe to . to receive events here.
- /// Whether the event was handled. Unhandled events are logged.
- ///
- protected virtual bool OnHover(MotionEvent hoverEvent)
- {
- throw new NotSupportedException($"{nameof(HandleHover)} subscribed to {nameof(View.Hover)} but the relevant method was not overriden.");
- }
-
- ///
- /// Invoked on every event that matches an from .
- ///
- ///
- /// Subscribe to . to receive events here.
- /// Whether the event was handled. Unhandled events are logged.
- ///
- protected virtual ReturnCode OnKeyDown(Keycode keycode, KeyEvent e)
- {
- throw new NotSupportedException($"{nameof(HandleKeyDown)} subscribed to {nameof(View.KeyDown)} but the relevant method was not overriden.");
- }
-
- ///
- /// Invoked on every event that matches an from .
- ///
- ///
- /// Subscribe to . to receive events here.
- /// Whether the event was handled. Unhandled events are logged.
- ///
- protected virtual ReturnCode OnKeyUp(Keycode keycode, KeyEvent e)
- {
- throw new NotSupportedException($"{nameof(HandleKeyUp)} subscribed to {nameof(View.KeyUp)} but the relevant method was not overriden.");
- }
-
- ///
- /// Invoked on every event that matches an from .
- ///
- ///
- /// Subscribe to . to receive events here.
- /// Whether the event was handled. Unhandled events are logged.
- ///
- protected virtual bool OnTouch(MotionEvent touchEvent)
- {
- throw new NotSupportedException($"{nameof(HandleTouch)} subscribed to {nameof(View.Touch)} but the relevant method was not overriden.");
- }
-
- #region Event handlers
-
- ///
- /// Checks whether the should be handled by this .
- ///
- /// The to check.
- /// true if the 's matches .
- /// Should be checked before handling events.
- protected virtual bool ShouldHandleEvent([NotNullWhen(true)] InputEvent? inputEvent)
- {
- return inputEvent != null && eventSourceBitmask.HasFlagFast(inputEvent.Source);
- }
-
- ///
- /// Handler for events.
- ///
- protected void HandleCapturedPointer(object sender, View.CapturedPointerEventArgs e)
- {
- if (ShouldHandleEvent(e.Event))
- {
- if (OnCapturedPointer(e.Event))
- e.Handled = true;
- else
- logUnhandledEvent(nameof(OnCapturedPointer), e.Event);
- }
- }
-
- ///
- /// Handler for events.
- ///
- protected void HandleGenericMotion(object sender, View.GenericMotionEventArgs e)
- {
- if (ShouldHandleEvent(e.Event))
- {
- if (OnGenericMotion(e.Event))
- e.Handled = true;
- else
- logUnhandledEvent(nameof(OnGenericMotion), e.Event);
- }
- }
-
- ///
- /// Handler for events.
- ///
- protected void HandleHover(object sender, View.HoverEventArgs e)
- {
- if (ShouldHandleEvent(e.Event))
- {
- if (OnHover(e.Event))
- e.Handled = true;
- else
- logUnhandledEvent(nameof(OnHover), e.Event);
- }
- }
-
- ///
- /// Handler for events.
- ///
- protected void HandleKeyDown(Keycode keycode, KeyEvent e)
- {
- if (ShouldHandleEvent(e))
- {
- if (OnKeyDown(keycode, e) == ReturnCode.Unhandled)
- logUnhandledEvent(nameof(OnKeyDown), e);
- }
- }
-
- ///
- /// Handler for events.
- ///
- protected void HandleKeyUp(Keycode keycode, KeyEvent e)
- {
- if (ShouldHandleEvent(e))
- {
- if (OnKeyUp(keycode, e) == ReturnCode.Unhandled)
- logUnhandledEvent(nameof(OnKeyUp), e);
- }
- }
-
- ///
- /// Handler for events.
- ///
- protected void HandleTouch(object sender, View.TouchEventArgs e)
- {
- if (ShouldHandleEvent(e.Event))
- {
- if (OnTouch(e.Event))
- e.Handled = true;
- else
- logUnhandledEvent(nameof(OnTouch), e.Event);
- }
- }
-
- #endregion
-
- private void logUnhandledEvent(string methodName, InputEvent inputEvent)
- {
- Log($"Unknown {GetType().ReadableName()}.{methodName} event: {inputEvent}");
- }
-
- protected enum ReturnCode
- {
- ///
- /// Denotes an event that was handled by this handler.
- ///
- Handled,
-
- ///
- /// Denotes an event that this handler did not handle.
- ///
- ///
- /// Since all events are first put through the filter, an unhandled event is considered a bug and is logged.
- ///
- Unhandled,
-
- ///
- /// Same as , but will not be logged.
- ///
- ///
- /// Used when an event might also be handled by another handler, but that cannot be determined purely on .
- ///
- UnhandledSuppressLogging,
- }
- }
-}
diff --git a/osu.Framework.Android/Input/AndroidJoystickHandler.cs b/osu.Framework.Android/Input/AndroidJoystickHandler.cs
deleted file mode 100644
index af7b63ddb4..0000000000
--- a/osu.Framework.Android/Input/AndroidJoystickHandler.cs
+++ /dev/null
@@ -1,231 +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 System.Linq;
-using Android.Views;
-using osu.Framework.Bindables;
-using osu.Framework.Input;
-using osu.Framework.Input.Handlers.Joystick;
-using osu.Framework.Input.StateChanges;
-using osu.Framework.Platform;
-using osu.Framework.Statistics;
-
-namespace osu.Framework.Android.Input
-{
- public class AndroidJoystickHandler : AndroidInputHandler
- {
- private static readonly GlobalStatistic statistic_total_events = GlobalStatistics.Get(StatisticGroupFor(), "Total events");
-
- public BindableFloat DeadzoneThreshold { get; } = new BindableFloat(0.1f)
- {
- MinValue = 0,
- MaxValue = 0.95f,
- Precision = 0.005f,
- };
-
- public override string Description => "Joystick / Gamepad";
-
- public override bool IsActive => true;
-
- protected override IEnumerable HandledEventSources => new[]
- {
- InputSourceType.Dpad,
- InputSourceType.Gamepad,
- InputSourceType.Joystick,
- // joysticks sometimes present themselves as a keyboard in OnKey{Up,Down} events.
- InputSourceType.Keyboard
- };
-
- public AndroidJoystickHandler(AndroidGameView view)
- : base(view)
- {
- }
-
- public override bool Initialize(GameHost host)
- {
- if (!base.Initialize(host))
- return false;
-
- Enabled.BindValueChanged(enabled =>
- {
-#nullable disable // Events misses nullable mark in .NET Android SDK (6.0.402)
- if (enabled.NewValue)
- {
- View.GenericMotion += HandleGenericMotion;
- View.KeyDown += HandleKeyDown;
- View.KeyUp += HandleKeyUp;
- }
- else
- {
- View.GenericMotion -= HandleGenericMotion;
- View.KeyDown -= HandleKeyDown;
- View.KeyUp -= HandleKeyUp;
- }
-#nullable restore
- }, true);
-
- return true;
- }
-
- private ReturnCode returnCodeForSource(InputSourceType source)
- {
- // keyboard only events are handled in AndroidKeyboardHandler
- return source.IsKeyboard()
- ? ReturnCode.UnhandledSuppressLogging
- : ReturnCode.Unhandled;
- }
-
- protected override ReturnCode OnKeyDown(Keycode keycode, KeyEvent e)
- {
- if (e.TryGetJoystickButton(out var button))
- {
- enqueueButtonDown(button);
- return ReturnCode.Handled;
- }
-
- return returnCodeForSource(e.Source);
- }
-
- protected override ReturnCode OnKeyUp(Keycode keycode, KeyEvent e)
- {
- if (e.TryGetJoystickButton(out var button))
- {
- enqueueButtonUp(button);
- return ReturnCode.Handled;
- }
-
- return returnCodeForSource(e.Source);
- }
-
- ///
- /// The for which the are valid.
- /// null iff the current device could not be determined, in that case, fall back to .
- ///
- private string? lastDeviceDescriptor;
-
- ///
- /// The axes that are reported as supported by the current ..
- /// if the current device doesn't report axes information.
- ///
- private IEnumerable availableAxes = AndroidInputExtensions.ALL_AXES;
-
- ///
- /// Updates to be appropriate for the current .
- ///
- private void updateAvailableAxesForDevice(InputDevice? device)
- {
- if (device?.Descriptor == null)
- {
- if (lastDeviceDescriptor == null)
- return;
-
- // use the default if this device is unknown.
- lastDeviceDescriptor = null;
- availableAxes = AndroidInputExtensions.ALL_AXES;
- return;
- }
-
- if (device.Descriptor == lastDeviceDescriptor)
- return;
-
- lastDeviceDescriptor = device.Descriptor;
-
- var motionRanges = device.MotionRanges;
-
- availableAxes = motionRanges != null && motionRanges.Count > 0
- ? motionRanges.Select(m => m.Axis).Where(isValid).Distinct().ToList()
- : AndroidInputExtensions.ALL_AXES;
-
- bool isValid(Axis axis)
- {
- switch (axis)
- {
- // D-pad axes are handled separately in `applyDpadInput`
- case Axis.HatX:
- case Axis.HatY:
- // Brake and Gas axes mirror the left and right trigger and are therefore ignored
- case Axis.Gas:
- case Axis.Brake:
- return false;
- }
-
- if (axis.TryGetJoystickAxisSource(out _))
- return true;
-
- Log($"Unknown joystick axis: {axis}");
- return false;
- }
- }
-
- protected override bool OnGenericMotion(MotionEvent genericMotionEvent)
- {
- switch (genericMotionEvent.Action)
- {
- case MotionEventActions.Move:
- updateAvailableAxesForDevice(genericMotionEvent.Device);
- genericMotionEvent.HandleHistorically(apply);
- return true;
-
- default:
- return false;
- }
- }
-
- private void apply(MotionEvent motionEvent, int historyPosition)
- {
- foreach (var axis in availableAxes)
- applyAxisInput(motionEvent, historyPosition, axis);
-
- applyDpadInput(motionEvent, historyPosition);
- }
-
- private void applyAxisInput(MotionEvent motionEvent, int historyPosition, Axis axis)
- {
- if (axis.TryGetJoystickAxisSource(out var joystickAxisSource)
- && motionEvent.TryGet(axis, out float value, historyPosition))
- {
- value = JoystickHandler.RescaleByDeadzone(value, DeadzoneThreshold.Value);
- enqueueInput(new JoystickAxisInput(new JoystickAxis(joystickAxisSource, value)));
- }
- }
-
- private float lastDpadX;
- private float lastDpadY;
-
- private void applyDpadInput(MotionEvent motionEvent, int historyPosition)
- {
- float x = motionEvent.Get(Axis.HatX, historyPosition);
-
- if (x != lastDpadX)
- {
- if (x == 0) enqueueButtonUp(lastDpadX > 0 ? JoystickButton.GamePadDPadRight : JoystickButton.GamePadDPadLeft);
- if (x > 0) enqueueButtonDown(JoystickButton.GamePadDPadRight);
- if (x < 0) enqueueButtonDown(JoystickButton.GamePadDPadLeft);
-
- lastDpadX = x;
- }
-
- float y = motionEvent.Get(Axis.HatY, historyPosition);
-
- if (y != lastDpadY)
- {
- if (y == 0) enqueueButtonUp(lastDpadY > 0 ? JoystickButton.GamePadDPadDown : JoystickButton.GamePadDPadUp);
- if (y > 0) enqueueButtonDown(JoystickButton.GamePadDPadDown);
- if (y < 0) enqueueButtonDown(JoystickButton.GamePadDPadUp);
-
- lastDpadY = y;
- }
- }
-
- private void enqueueButtonDown(JoystickButton button) => enqueueInput(new JoystickButtonInput(button, true));
- private void enqueueButtonUp(JoystickButton button) => enqueueInput(new JoystickButtonInput(button, false));
-
- private void enqueueInput(IInput input)
- {
- PendingInputs.Enqueue(input);
- FrameStatistics.Increment(StatisticsCounterType.JoystickEvents);
- statistic_total_events.Value++;
- }
- }
-}
diff --git a/osu.Framework.Android/Input/AndroidKeyboardHandler.cs b/osu.Framework.Android/Input/AndroidKeyboardHandler.cs
deleted file mode 100644
index 9eebefdb24..0000000000
--- a/osu.Framework.Android/Input/AndroidKeyboardHandler.cs
+++ /dev/null
@@ -1,217 +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;
-using System.Collections.Generic;
-using Android.Views;
-using osu.Framework.Input.StateChanges;
-using osu.Framework.Platform;
-using osu.Framework.Statistics;
-using osuTK.Input;
-
-namespace osu.Framework.Android.Input
-{
- public class AndroidKeyboardHandler : AndroidInputHandler
- {
- private static readonly GlobalStatistic statistic_total_events = GlobalStatistics.Get(StatisticGroupFor(), "Total events");
-
- protected override IEnumerable HandledEventSources => new[]
- {
- InputSourceType.Keyboard,
- // Some physical keyboards report as (Keyboard | Dpad)
- InputSourceType.Dpad,
- };
-
- public AndroidKeyboardHandler(AndroidGameView view)
- : base(view)
- {
- }
-
- public override bool Initialize(GameHost host)
- {
- if (!base.Initialize(host))
- return false;
-
- Enabled.BindValueChanged(enabled =>
- {
- if (enabled.NewValue)
- {
- View.KeyDown += HandleKeyDown;
- View.KeyUp += HandleKeyUp;
- }
- else
- {
- View.KeyDown -= HandleKeyDown;
- View.KeyUp -= HandleKeyUp;
- }
- }, true);
-
- return true;
- }
-
- public override bool IsActive => true;
-
- private ReturnCode returnCodeForKeycode(Keycode keycode)
- {
- // gamepad buttons are handled in AndroidJoystickHandler
- return KeyEvent.IsGamepadButton(keycode)
- ? ReturnCode.UnhandledSuppressLogging
- : ReturnCode.Unhandled;
- }
-
- protected override ReturnCode OnKeyDown(Keycode keycode, KeyEvent e)
- {
- var key = GetKeyCodeAsKey(keycode);
-
- if (key != Key.Unknown)
- {
- enqueueInput(new KeyboardKeyInput(key, true));
- return ReturnCode.Handled;
- }
-
- return returnCodeForKeycode(keycode);
- }
-
- protected override ReturnCode OnKeyUp(Keycode keycode, KeyEvent e)
- {
- var key = GetKeyCodeAsKey(keycode);
-
- if (key != Key.Unknown)
- {
- enqueueInput(new KeyboardKeyInput(key, false));
- return ReturnCode.Handled;
- }
-
- return returnCodeForKeycode(keycode);
- }
-
- ///
- /// This method maps the to from opentk.
- ///
- /// The to be converted into a .
- /// The that was converted from .
- public static Key GetKeyCodeAsKey(Keycode keyCode)
- {
- // number keys
- const Keycode first_num_key = Keycode.Num0;
- const Keycode last_num_key = Keycode.Num9;
- if (keyCode >= first_num_key && keyCode <= last_num_key)
- return Key.Number0 + (keyCode - first_num_key);
-
- // letters
- const Keycode first_letter_key = Keycode.A;
- const Keycode last_letter_key = Keycode.Z;
- if (keyCode >= first_letter_key && keyCode <= last_letter_key)
- return Key.A + (keyCode - first_letter_key);
-
- // function keys
- const Keycode first_function_key = Keycode.F1;
- const Keycode last_function_key = Keycode.F12;
- if (keyCode >= first_function_key && keyCode <= last_function_key)
- return Key.F1 + (keyCode - first_function_key);
-
- // keypad keys
- const Keycode first_keypad_key = Keycode.Numpad0;
- const Keycode last_key_pad_key = Keycode.NumpadDot;
- if (keyCode >= first_keypad_key && keyCode <= last_key_pad_key)
- return Key.Keypad0 + (keyCode - first_keypad_key);
-
- // direction keys
- const Keycode first_direction_key = Keycode.DpadUp;
- const Keycode last_direction_key = Keycode.DpadRight;
- if (keyCode >= first_direction_key && keyCode <= last_direction_key)
- return Key.Up + (keyCode - first_direction_key);
-
- // one to one mappings
- switch (keyCode)
- {
- case Keycode.Back:
- return Key.Escape;
-
- case Keycode.MediaPlayPause:
- return Key.PlayPause;
-
- case Keycode.SoftLeft:
- return Key.Left;
-
- case Keycode.SoftRight:
- return Key.Right;
-
- case Keycode.Star:
- return Key.KeypadMultiply;
-
- case Keycode.Backslash:
- case Keycode.Pound:
- return Key.BackSlash; // english keyboard layout
-
- case Keycode.Del:
- return Key.BackSpace;
-
- case Keycode.ForwardDel:
- return Key.Delete;
-
- case Keycode.Power:
- return Key.Sleep;
-
- case Keycode.MoveHome:
- return Key.Home;
-
- case Keycode.MoveEnd:
- return Key.End;
-
- case Keycode.MediaPause:
- return Key.Pause;
-
- case Keycode.MediaClose:
- return Key.Stop;
-
- case Keycode.LeftBracket:
- return Key.BracketLeft;
-
- case Keycode.RightBracket:
- return Key.BracketRight;
-
- case Keycode.MediaPrevious:
- return Key.TrackPrevious;
-
- case Keycode.MediaNext:
- return Key.TrackNext;
-
- case Keycode.CtrlLeft:
- return Key.ControlLeft;
-
- case Keycode.CtrlRight:
- return Key.ControlRight;
-
- case Keycode.MetaLeft:
- return Key.WinLeft;
-
- case Keycode.MetaRight:
- return Key.WinRight;
-
- case Keycode.Equals:
- return Key.Plus;
-
- case Keycode.At:
- case Keycode.Apostrophe:
- return Key.Quote;
-
- case Keycode.NumpadEnter:
- return Key.KeypadEnter;
- }
-
- if (Enum.TryParse(keyCode.ToString(), out Key key))
- return key;
-
- // this is the worst case scenario. Please note that the osu-framework keyboard handling cannot cope with Key.Unknown.
- return Key.Unknown;
- }
-
- private void enqueueInput(IInput input)
- {
- PendingInputs.Enqueue(input);
- FrameStatistics.Increment(StatisticsCounterType.KeyEvents);
- statistic_total_events.Value++;
- }
- }
-}
diff --git a/osu.Framework.Android/Input/AndroidMouseHandler.cs b/osu.Framework.Android/Input/AndroidMouseHandler.cs
deleted file mode 100644
index f2ebda89f5..0000000000
--- a/osu.Framework.Android/Input/AndroidMouseHandler.cs
+++ /dev/null
@@ -1,319 +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;
-using System.Collections.Generic;
-using Android.OS;
-using Android.Views;
-using osu.Framework.Bindables;
-using osu.Framework.Extensions.EnumExtensions;
-using osu.Framework.Input.StateChanges;
-using osu.Framework.Platform;
-using osu.Framework.Statistics;
-using osuTK;
-using osuTK.Input;
-
-namespace osu.Framework.Android.Input
-{
- ///
- /// Input handler for Android mouse-type devices: the and .
- ///
- public class AndroidMouseHandler : AndroidInputHandler
- {
- private static readonly GlobalStatistic statistic_total_events = GlobalStatistics.Get(StatisticGroupFor(), "Total events");
-
- ///
- /// Whether relative mode should be preferred when the window has focus, the cursor is contained and the OS cursor is not visible.
- ///
- ///
- /// Only available in Android 8.0 Oreo () and up.
- ///
- public BindableBool UseRelativeMode { get; } = new BindableBool(false)
- {
- Description = "Allows for sensitivity adjustment and tighter control of input",
- };
-
- public BindableDouble Sensitivity { get; } = new BindableDouble(1)
- {
- MinValue = 0.1,
- MaxValue = 10,
- Precision = 0.01,
- };
-
- public override string Description => "Mouse";
-
- public override bool IsActive => true;
-
- protected override IEnumerable HandledEventSources =>
- OperatingSystem.IsAndroidVersionAtLeast(26)
- ? new[] { InputSourceType.Mouse, InputSourceType.MouseRelative, InputSourceType.Touchpad }
- : new[] { InputSourceType.Mouse, InputSourceType.Touchpad };
-
- private AndroidGameWindow window = null!;
-
- ///
- /// Whether a non-relative mouse event has ever been received.
- /// This is used as a starting location for relative movement.
- ///
- private bool absolutePositionReceived;
-
- public AndroidMouseHandler(AndroidGameView view)
- : base(view)
- {
- }
-
- public override bool Initialize(GameHost host)
- {
- if (!base.Initialize(host))
- return false;
-
- if (!(host.Window is AndroidGameWindow androidWindow))
- return false;
-
- window = androidWindow;
-
- window.CursorStateChanged += updatePointerCapture;
-
- // it's possible that Android forcefully released capture if we were unfocused.
- // so we update here when we get focus again.
- View.FocusChange += (_, args) =>
- {
- if (args.HasFocus)
- updatePointerCapture();
- };
-
- UseRelativeMode.BindValueChanged(_ => updatePointerCapture());
-
- Enabled.BindValueChanged(enabled =>
- {
-#nullable disable // Events misses nullable mark in .NET Android SDK (6.0.402)
- if (enabled.NewValue)
- {
- View.GenericMotion += HandleGenericMotion;
- View.Hover += HandleHover;
- View.KeyDown += HandleKeyDown;
- View.KeyUp += HandleKeyUp;
- View.Touch += HandleTouch;
-
- // Pointer capture is only available on Android 8.0 and up
- if (OperatingSystem.IsAndroidVersionAtLeast(26))
- View.CapturedPointer += HandleCapturedPointer;
- }
- else
- {
- View.GenericMotion -= HandleGenericMotion;
- View.Hover -= HandleHover;
- View.KeyDown -= HandleKeyDown;
- View.KeyUp -= HandleKeyUp;
- View.Touch -= HandleTouch;
-
- // Pointer capture is only available on Android 8.0 and up
- if (OperatingSystem.IsAndroidVersionAtLeast(26))
- View.CapturedPointer -= HandleCapturedPointer;
- }
-#nullable restore
-
- updatePointerCapture();
- }, true);
-
- return true;
- }
-
- public override void Reset()
- {
- Sensitivity.SetDefault();
- base.Reset();
- }
-
- private void updatePointerCapture()
- {
- // Pointer capture is only available on Android 8.0 and up
- if (!OperatingSystem.IsAndroidVersionAtLeast(26))
- return;
-
- bool shouldCapture =
- // check whether this handler is actually enabled.
- Enabled.Value
- // check whether the consumer has requested to use relative mode when feasible.
- && UseRelativeMode.Value
- // relative mode requires at least one absolute input to arrive, to gain an additional position to work with.
- && absolutePositionReceived
- // relative mode shouldn't ever be enabled if the framework or a consumer has chosen not to hide the cursor.
- && window.CursorState.HasFlagFast(CursorState.Hidden);
-
- View.PointerCapture = shouldCapture;
- }
-
- protected override ReturnCode OnKeyDown(Keycode keycode, KeyEvent e)
- {
- // some implementations might send Mouse1 and Mouse2 as keyboard keycodes, so we handle those here.
- if (keycode.TryGetMouseButton(out var button))
- {
- handleMouseButton(button, true);
- return ReturnCode.Handled;
- }
-
- return ReturnCode.Unhandled;
- }
-
- protected override ReturnCode OnKeyUp(Keycode keycode, KeyEvent e)
- {
- if (keycode.TryGetMouseButton(out var button))
- {
- handleMouseButton(button, false);
- return ReturnCode.Handled;
- }
-
- return ReturnCode.Unhandled;
- }
-
- protected override bool OnHover(MotionEvent hoverEvent)
- {
- switch (hoverEvent.Action)
- {
- case MotionEventActions.HoverMove:
- handleMouseMoveEvent(hoverEvent);
- return true;
-
- // related to the mouse entering/exiting the view,
- // and the mouse "losing" hover state as the screen is touched (the mouse pointer disappears)
- // no need to log, and no need to handle them in any way here.
- case MotionEventActions.HoverEnter:
- case MotionEventActions.HoverExit:
- return true;
-
- default:
- return false;
- }
- }
-
- protected override bool OnTouch(MotionEvent touchEvent)
- {
- switch (touchEvent.Action)
- {
- case MotionEventActions.Move:
- handleMouseMoveEvent(touchEvent);
- return true;
-
- default:
- return tryHandleButtonEvent(touchEvent);
- }
- }
-
- protected override bool OnGenericMotion(MotionEvent genericMotionEvent)
- {
- switch (genericMotionEvent.Action)
- {
- case MotionEventActions.Scroll:
- handleScrollEvent(genericMotionEvent);
- return true;
-
- default:
- return tryHandleButtonEvent(genericMotionEvent);
- }
- }
-
- protected override bool OnCapturedPointer(MotionEvent capturedPointerEvent)
- {
- switch (capturedPointerEvent.Action)
- {
- case MotionEventActions.Move:
- handleMouseMoveRelativeEvent(capturedPointerEvent);
- return true;
-
- case MotionEventActions.Scroll:
- handleScrollEvent(capturedPointerEvent);
- return true;
-
- default:
- return tryHandleButtonEvent(capturedPointerEvent);
- }
- }
-
- ///
- /// Handles an event that could potentially be a mouse button event.
- ///
- private bool tryHandleButtonEvent(MotionEvent motionEvent)
- {
- if (OperatingSystem.IsAndroidVersionAtLeast(23))
- {
- switch (motionEvent.Action)
- {
- case MotionEventActions.ButtonPress:
- case MotionEventActions.ButtonRelease:
- bool pressed = motionEvent.Action == MotionEventActions.ButtonPress;
-
- foreach (var button in motionEvent.ActionButton.ToMouseButtons())
- handleMouseButton(button, pressed);
-
- return true;
-
- // fired when buttons are pressed, but these don't have reliable ActionButton information
- case MotionEventActions.Up:
- case MotionEventActions.Down:
- return true;
- }
- }
- else // on older android versions where button events are not supported
- {
- switch (motionEvent.Action)
- {
- case MotionEventActions.Up:
- case MotionEventActions.Down:
- bool pressed = motionEvent.Action == MotionEventActions.Down;
- handleMouseButton(MouseButton.Left, pressed);
- return true;
- }
- }
-
- return false;
- }
-
- private void handleScrollEvent(MotionEvent scrollEvent)
- {
- if (scrollEvent.TryGet(Axis.Hscroll, out float h)
- && scrollEvent.TryGet(Axis.Vscroll, out float v))
- {
- // Android reports horizontal scroll opposite of what framework expects.
- enqueueInput(new MouseScrollRelativeInput { Delta = new Vector2(-h, v) });
- }
- }
-
- private void handleMouseMoveEvent(MotionEvent mouseMoveEvent)
- {
- mouseMoveEvent.HandleHistorically(apply);
-
- absolutePositionReceived = true;
-
- // we may lose pointer capture if we lose focus / the app goes to the background,
- // so we use this opportunity to update capture if the user has requested it.
- updatePointerCapture();
-
- void apply(MotionEvent e, int historyPosition)
- {
- if (e.TryGetPosition(out var position, historyPosition))
- enqueueInput(new MousePositionAbsoluteInput { Position = position });
- }
- }
-
- private void handleMouseMoveRelativeEvent(MotionEvent capturedPointerEvent)
- {
- capturedPointerEvent.HandleHistorically(apply);
-
- void apply(MotionEvent e, int historyPosition)
- {
- if (e.TryGetPosition(out var delta, historyPosition))
- enqueueInput(new MousePositionRelativeInput { Delta = delta * (float)Sensitivity.Value });
- }
- }
-
- private void handleMouseButton(MouseButton button, bool pressed) => enqueueInput(new MouseButtonInput(button, pressed));
-
- private void enqueueInput(IInput input)
- {
- PendingInputs.Enqueue(input);
- FrameStatistics.Increment(StatisticsCounterType.MouseEvents);
- statistic_total_events.Value++;
- }
- }
-}
diff --git a/osu.Framework.Android/Input/AndroidTextInput.cs b/osu.Framework.Android/Input/AndroidTextInput.cs
deleted file mode 100644
index eab5a518d5..0000000000
--- a/osu.Framework.Android/Input/AndroidTextInput.cs
+++ /dev/null
@@ -1,48 +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 Android.Views;
-using osu.Framework.Input;
-
-namespace osu.Framework.Android.Input
-{
- internal class AndroidTextInput : TextInputSource
- {
- private readonly AndroidGameView view;
-
- public AndroidTextInput(AndroidGameView view)
- {
- this.view = view;
- }
-
- private void commitText(string text)
- {
- TriggerTextInput(text);
- }
-
- private void keyDown(Keycode arg, KeyEvent e)
- {
- if (e.UnicodeChar != 0)
- TriggerTextInput(((char)e.UnicodeChar).ToString());
- }
-
- protected override void ActivateTextInput(bool allowIme)
- {
- view.KeyDown += keyDown;
- view.CommitText += commitText;
- view.StartTextInput();
- }
-
- protected override void EnsureTextInputActivated(bool allowIme)
- {
- view.StartTextInput();
- }
-
- protected override void DeactivateTextInput()
- {
- view.KeyDown -= keyDown;
- view.CommitText -= commitText;
- view.StopTextInput();
- }
- }
-}
diff --git a/osu.Framework.Android/Input/AndroidTouchHandler.cs b/osu.Framework.Android/Input/AndroidTouchHandler.cs
deleted file mode 100644
index d3e01d2405..0000000000
--- a/osu.Framework.Android/Input/AndroidTouchHandler.cs
+++ /dev/null
@@ -1,156 +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;
-using System.Collections.Generic;
-using Android.Views;
-using osu.Framework.Input;
-using osu.Framework.Input.StateChanges;
-using osu.Framework.Platform;
-using osu.Framework.Statistics;
-using osuTK;
-using osuTK.Input;
-
-namespace osu.Framework.Android.Input
-{
- public class AndroidTouchHandler : AndroidInputHandler
- {
- private static readonly GlobalStatistic statistic_touch_events = GlobalStatistics.Get(StatisticGroupFor(), "Touch events");
- private static readonly GlobalStatistic statistic_hover_events = GlobalStatistics.Get(StatisticGroupFor(), "Hover events");
-
- public override bool IsActive => true;
-
- protected override IEnumerable HandledEventSources =>
- OperatingSystem.IsAndroidVersionAtLeast(23)
- ? new[] { InputSourceType.BluetoothStylus, InputSourceType.Stylus, InputSourceType.Touchscreen }
- : new[] { InputSourceType.Stylus, InputSourceType.Touchscreen };
-
- public AndroidTouchHandler(AndroidGameView view)
- : base(view)
- {
- }
-
- public override bool Initialize(GameHost host)
- {
- if (!base.Initialize(host))
- return false;
-
- Enabled.BindValueChanged(enabled =>
- {
-#nullable disable // Events misses nullable mark in .NET Android SDK (6.0.402)
- if (enabled.NewValue)
- {
- View.Hover += HandleHover;
- View.Touch += HandleTouch;
- }
- else
- {
- View.Hover -= HandleHover;
- View.Touch -= HandleTouch;
-#nullable restore
- }
- }, true);
-
- return true;
- }
-
- protected override bool OnTouch(MotionEvent touchEvent)
- {
- switch (touchEvent.ActionMasked)
- {
- // MotionEventActions.Down arrives at the beginning of a touch event chain and implies the 0th pointer is pressed.
- // ActionIndex is generally not valid here.
- case MotionEventActions.Down:
- applyTouchInput(touchEvent, HISTORY_CURRENT, 0);
- return true;
-
- // events that apply only to the ActionIndex pointer (other pointers' states remain unchanged)
- case MotionEventActions.PointerDown:
- case MotionEventActions.PointerUp:
- applyTouchInput(touchEvent, HISTORY_CURRENT, touchEvent.ActionIndex);
- return true;
-
- // events that apply to every pointer (up to PointerCount).
- case MotionEventActions.Move:
- case MotionEventActions.Up:
- case MotionEventActions.Cancel:
- touchEvent.HandleHistoricallyPerPointer(applyTouchInput);
- return true;
-
- default:
- return false;
- }
- }
-
- protected override bool OnHover(MotionEvent hoverEvent)
- {
- hoverEvent.HandleHistorically(apply);
-
- if (OperatingSystem.IsAndroidVersionAtLeast(23))
- enqueueInput(new MouseButtonInput(MouseButton.Right, hoverEvent.IsButtonPressed(MotionEventButtonState.StylusPrimary)));
-
- // TODO: handle stylus events based on hoverEvent.Action
- // stylus should probably have it's own handler.
- return true;
-
- void apply(MotionEvent e, int historyPosition)
- {
- if (tryGetEventPosition(e, historyPosition, 0, out var position))
- {
- enqueueInput(new MousePositionAbsoluteInput { Position = position });
- statistic_hover_events.Value++;
- }
- }
- }
-
- private void applyTouchInput(MotionEvent touchEvent, int historyPosition, int pointerIndex)
- {
- if (tryGetEventTouch(touchEvent, historyPosition, pointerIndex, out var touch))
- {
- enqueueInput(new TouchInput(touch, touchEvent.ActionMasked.IsTouchDownAction()));
- statistic_touch_events.Value++;
- }
- }
-
- private bool tryGetEventTouch(MotionEvent motionEvent, int historyPosition, int pointerIndex, out Touch touch)
- {
- if (tryGetTouchSource(motionEvent.GetPointerId(pointerIndex), out var touchSource)
- && tryGetEventPosition(motionEvent, historyPosition, pointerIndex, out var position))
- {
- touch = new Touch(touchSource, position);
- return true;
- }
-
- touch = new Touch();
- return false;
-
- bool tryGetTouchSource(int pointerId, out TouchSource source)
- {
- source = (TouchSource)pointerId;
- return source >= TouchSource.Touch1 && source <= TouchSource.Touch10;
- }
- }
-
- private bool tryGetEventPosition(MotionEvent motionEvent, int historyPosition, int pointerIndex, out Vector2 position)
- {
- if (motionEvent.TryGet(Axis.X, out float x, historyPosition, pointerIndex)
- && motionEvent.TryGet(Axis.Y, out float y, historyPosition, pointerIndex))
- {
- position = new Vector2(x * View.ScaleX, y * View.ScaleY);
- return true;
- }
-
- // in empirical testing, `MotionEvent.Get{X,Y}()` methods can return NaN positions early on in the android activity's lifetime.
- // these nonsensical inputs then cause issues later down the line when they are converted into framework inputs.
- // as there is really nothing to recover from such inputs, drop them entirely.
- position = Vector2.Zero;
- return false;
- }
-
- private void enqueueInput(IInput input)
- {
- PendingInputs.Enqueue(input);
- FrameStatistics.Increment(StatisticsCounterType.TouchEvents);
- }
- }
-}
diff --git a/osu.Framework.Android/arm64-v8a/libbass.so b/osu.Framework.Android/arm64-v8a/libbass.so
index cdfa700d6d..e33e7b7a8e 100644
Binary files a/osu.Framework.Android/arm64-v8a/libbass.so and b/osu.Framework.Android/arm64-v8a/libbass.so differ
diff --git a/osu.Framework.Android/arm64-v8a/libbassmix.so b/osu.Framework.Android/arm64-v8a/libbassmix.so
index cd1d6b4d5a..6bb2a1d10f 100644
Binary files a/osu.Framework.Android/arm64-v8a/libbassmix.so and b/osu.Framework.Android/arm64-v8a/libbassmix.so differ
diff --git a/osu.Framework.Android/armeabi-v7a/libbass.so b/osu.Framework.Android/armeabi-v7a/libbass.so
index 0e6a6bc6a8..cd1734f12a 100644
Binary files a/osu.Framework.Android/armeabi-v7a/libbass.so and b/osu.Framework.Android/armeabi-v7a/libbass.so differ
diff --git a/osu.Framework.Android/armeabi-v7a/libbassmix.so b/osu.Framework.Android/armeabi-v7a/libbassmix.so
index f75b9e206f..28733a0c04 100644
Binary files a/osu.Framework.Android/armeabi-v7a/libbassmix.so and b/osu.Framework.Android/armeabi-v7a/libbassmix.so differ
diff --git a/osu.Framework.Android/osu.Framework.Android.csproj b/osu.Framework.Android/osu.Framework.Android.csproj
index 1969356ced..54e6baf66a 100644
--- a/osu.Framework.Android/osu.Framework.Android.csproj
+++ b/osu.Framework.Android/osu.Framework.Android.csproj
@@ -18,7 +18,7 @@
-
-
+
+
diff --git a/osu.Framework.Android/x86/libbass.so b/osu.Framework.Android/x86/libbass.so
index a439bf1858..9a205f697c 100644
Binary files a/osu.Framework.Android/x86/libbass.so and b/osu.Framework.Android/x86/libbass.so differ
diff --git a/osu.Framework.Android/x86/libbassmix.so b/osu.Framework.Android/x86/libbassmix.so
index 645e1993ef..61ccd24375 100644
Binary files a/osu.Framework.Android/x86/libbassmix.so and b/osu.Framework.Android/x86/libbassmix.so differ
diff --git a/osu.Framework.Benchmarks/BenchmarkSRGBColourMultiplication.cs b/osu.Framework.Benchmarks/BenchmarkSRGBColourMultiplication.cs
new file mode 100644
index 0000000000..a3e9012a91
--- /dev/null
+++ b/osu.Framework.Benchmarks/BenchmarkSRGBColourMultiplication.cs
@@ -0,0 +1,62 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using BenchmarkDotNet.Attributes;
+using osu.Framework.Graphics.Colour;
+using osuTK.Graphics;
+
+namespace osu.Framework.Benchmarks
+{
+ public class BenchmarkSRGBColourMultiplication : BenchmarkTest
+ {
+ private static readonly SRGBColour white = new SRGBColour
+ {
+ SRGB = new Color4(1f, 1f, 1f, 1f)
+ };
+
+ private static readonly SRGBColour white_with_opacity = new SRGBColour
+ {
+ SRGB = new Color4(1f, 1f, 1f, 0.5f)
+ };
+
+ private static readonly SRGBColour gray = new SRGBColour
+ {
+ SRGB = Color4.Gray
+ };
+
+ private static readonly SRGBColour gray_light = new SRGBColour
+ {
+ SRGB = Color4.LightGray
+ };
+
+ [Benchmark]
+ public SRGBColour MultiplyNonWhite()
+ {
+ return gray * gray_light;
+ }
+
+ [Benchmark]
+ public SRGBColour MultiplyWhite()
+ {
+ return gray * white;
+ }
+
+ [Benchmark]
+ public SRGBColour MultiplyWhiteWithOpacity()
+ {
+ return gray * white_with_opacity;
+ }
+
+ [Benchmark]
+ public SRGBColour MultiplyConstOne()
+ {
+ return gray * 1;
+ }
+
+ [Benchmark]
+ public SRGBColour MultiplyConstNonOne()
+ {
+ return gray * 0.5f;
+ }
+ }
+}
diff --git a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libbass.so b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libbass.so
index e49c7740ef..3317ed4c64 100644
Binary files a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libbass.so and b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libbass.so differ
diff --git a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libbassmix.so b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libbassmix.so
index 1026b4a922..b90214f49b 100644
Binary files a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libbassmix.so and b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libbassmix.so differ
diff --git a/osu.Framework.NativeLibs/runtimes/linux-x86/native/libbass.so b/osu.Framework.NativeLibs/runtimes/linux-x86/native/libbass.so
index 175369aa7a..74a204d7ab 100644
Binary files a/osu.Framework.NativeLibs/runtimes/linux-x86/native/libbass.so and b/osu.Framework.NativeLibs/runtimes/linux-x86/native/libbass.so differ
diff --git a/osu.Framework.NativeLibs/runtimes/linux-x86/native/libbassmix.so b/osu.Framework.NativeLibs/runtimes/linux-x86/native/libbassmix.so
index 9ece95cfbd..81c8ecbe04 100644
Binary files a/osu.Framework.NativeLibs/runtimes/linux-x86/native/libbassmix.so and b/osu.Framework.NativeLibs/runtimes/linux-x86/native/libbassmix.so differ
diff --git a/osu.Framework.NativeLibs/runtimes/osx/native/libbass.dylib b/osu.Framework.NativeLibs/runtimes/osx/native/libbass.dylib
index 67782b93b4..9bf2a506cc 100644
Binary files a/osu.Framework.NativeLibs/runtimes/osx/native/libbass.dylib and b/osu.Framework.NativeLibs/runtimes/osx/native/libbass.dylib differ
diff --git a/osu.Framework.NativeLibs/runtimes/osx/native/libbass_fx.dylib b/osu.Framework.NativeLibs/runtimes/osx/native/libbass_fx.dylib
index 44df49e58c..e4ecd80332 100644
Binary files a/osu.Framework.NativeLibs/runtimes/osx/native/libbass_fx.dylib and b/osu.Framework.NativeLibs/runtimes/osx/native/libbass_fx.dylib differ
diff --git a/osu.Framework.NativeLibs/runtimes/osx/native/libbassmix.dylib b/osu.Framework.NativeLibs/runtimes/osx/native/libbassmix.dylib
index 763ad0d68b..7bebf7daf5 100644
Binary files a/osu.Framework.NativeLibs/runtimes/osx/native/libbassmix.dylib and b/osu.Framework.NativeLibs/runtimes/osx/native/libbassmix.dylib differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-arm64/native/bass.dll b/osu.Framework.NativeLibs/runtimes/win-arm64/native/bass.dll
index 08bba74756..a83988fccf 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-arm64/native/bass.dll and b/osu.Framework.NativeLibs/runtimes/win-arm64/native/bass.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-arm64/native/bassmix.dll b/osu.Framework.NativeLibs/runtimes/win-arm64/native/bassmix.dll
index b35f5f4c7b..6e440499ab 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-arm64/native/bassmix.dll and b/osu.Framework.NativeLibs/runtimes/win-arm64/native/bassmix.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x64/native/bass.dll b/osu.Framework.NativeLibs/runtimes/win-x64/native/bass.dll
index 84298be2d4..334093c659 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x64/native/bass.dll and b/osu.Framework.NativeLibs/runtimes/win-x64/native/bass.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x64/native/bass_fx.dll b/osu.Framework.NativeLibs/runtimes/win-x64/native/bass_fx.dll
index 23e3a6e1e5..339856160f 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x64/native/bass_fx.dll and b/osu.Framework.NativeLibs/runtimes/win-x64/native/bass_fx.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x64/native/bassmix.dll b/osu.Framework.NativeLibs/runtimes/win-x64/native/bassmix.dll
index 6647157949..52a3b791df 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x64/native/bassmix.dll and b/osu.Framework.NativeLibs/runtimes/win-x64/native/bassmix.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x86/native/bass.dll b/osu.Framework.NativeLibs/runtimes/win-x86/native/bass.dll
index 437a0b63e6..789d141634 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x86/native/bass.dll and b/osu.Framework.NativeLibs/runtimes/win-x86/native/bass.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x86/native/bass_fx.dll b/osu.Framework.NativeLibs/runtimes/win-x86/native/bass_fx.dll
index a31f20ffdd..18649fe6b4 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x86/native/bass_fx.dll and b/osu.Framework.NativeLibs/runtimes/win-x86/native/bass_fx.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x86/native/bassmix.dll b/osu.Framework.NativeLibs/runtimes/win-x86/native/bassmix.dll
index 9207cb1bfa..e55f25596b 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x86/native/bassmix.dll and b/osu.Framework.NativeLibs/runtimes/win-x86/native/bassmix.dll differ
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/build-android.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/build-android.sh
new file mode 100755
index 0000000000..73431886e6
--- /dev/null
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/build-android.sh
@@ -0,0 +1,96 @@
+#!/bin/bash
+set -eu
+
+# Android ABI level to target. 21 is the minimum supported by the NDK
+# See https://apilevels.com for info on what the API level means
+API_LEVEL="21"
+
+if [ -z "${ANDROID_NDK_ROOT:-}" ]; then
+ echo "ANDROID_NDK_ROOT must be set"
+ exit 1
+fi
+
+pushd "$(dirname "$0")" > /dev/null
+SCRIPT_PATH=$(pwd)
+popd > /dev/null
+source "$SCRIPT_PATH/common.sh"
+
+if [ -z "${arch-}" ]; then
+ PS3='Build for which arch? '
+ select arch in "armeabi-v7a" "arm64-v8a" "x86" "x86_64"; do
+ if [ -z "$arch" ]; then
+ echo "invalid option"
+ else
+ break
+ fi
+ done
+fi
+
+cpu=''
+cross_arch=''
+cc=''
+cflags=''
+asm_options=''
+
+case $arch in
+ armeabi-v7a)
+ cpu='armv7-a'
+ cross_arch='armv7-a'
+ cc="armv7a-linux-androideabi${API_LEVEL}-clang"
+ cflags='-mfpu=neon -mfloat-abi=softfp'
+ asm_options='--enable-neon --enable-asm --enable-inline-asm'
+ ;;
+
+ arm64-v8a)
+ cpu='armv8-a'
+ cross_arch='aarch64'
+ cc="aarch64-linux-android${API_LEVEL}-clang"
+ asm_options='--enable-neon --enable-asm --enable-inline-asm'
+ ;;
+
+ x86)
+ cpu='i686'
+ cross_arch='i686'
+ cc="i686-linux-android${API_LEVEL}-clang"
+ # ASM has text relocations
+ asm_options='--disable-asm'
+ ;;
+
+ x86_64)
+ cpu='x86-64'
+ cross_arch='x86_64'
+ cc="x86_64-linux-android${API_LEVEL}-clang"
+ asm_options='--enable-asm --enable-inline-asm'
+ ;;
+esac
+
+toolchain_path="$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64"
+bin_path="$toolchain_path/bin"
+
+FFMPEG_FLAGS+=(
+ --enable-jni
+
+ --enable-cross-compile
+ --target-os=android
+ --cpu=$cpu
+ --arch=$cross_arch
+ --sysroot="$toolchain_path/sysroot"
+ --cc="$bin_path/$cc"
+ --cxx="$bin_path/$cc++"
+ --ld="$bin_path/$cc"
+ --ar="$bin_path/llvm-ar"
+ --as="$bin_path/$cc"
+ --nm="$bin_path/llvm-nm"
+ --ranlib="$bin_path/llvm-ranlib"
+ --strip="$bin_path/llvm-strip"
+ --x86asmexe="$bin_path/yasm"
+ --extra-cflags="-fstrict-aliasing -fPIC -DANDROID -D__ANDROID__ $cflags"
+
+ $asm_options
+)
+
+pushd . > /dev/null
+prep_ffmpeg "android-$arch"
+build_ffmpeg
+popd > /dev/null
+
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/build-iOS.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/build-iOS.sh
new file mode 100755
index 0000000000..7b5daeb0ea
--- /dev/null
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/build-iOS.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+set -eu
+
+# Minimum iOS version. This should be the same as in osu.Framework.iOS.csproj
+DEPLOYMENT_TARGET="13.4"
+
+pushd "$(dirname "$0")" > /dev/null
+SCRIPT_PATH=$(pwd)
+popd > /dev/null
+source "$SCRIPT_PATH/common.sh"
+
+if [ -z "${GAS_PREPROCESSOR:-}" ]; then
+ echo "GAS_PREPROCESSOR must be set"
+ exit 1
+fi
+
+if [ -z "${arch-}" ]; then
+ PS3='Build for which arch? '
+ select arch in "arm64" "simulator-arm64" "simulator-x86_64"; do
+ if [ -z "$arch" ]; then
+ echo "invalid option"
+ else
+ break
+ fi
+ done
+fi
+
+cpu=''
+cross_arch=''
+cc=''
+as=''
+sysroot=''
+cflags=''
+
+case $arch in
+ arm64)
+ cpu='armv8-a'
+ cross_arch='arm64'
+ cc='xcrun -sdk iphoneos clang'
+ as="$GAS_PREPROCESSOR -arch arm64 -- $cc"
+ sysroot=$(xcrun -sdk iphoneos --show-sdk-path)
+ cflags="-mios-version-min=$DEPLOYMENT_TARGET"
+ ;;
+
+ simulator-arm64)
+ cpu='armv8-a'
+ cross_arch='arm64'
+ cc='xcrun -sdk iphonesimulator clang'
+ as="$GAS_PREPROCESSOR -arch arm64 -- $cc"
+ sysroot=$(xcrun -sdk iphonesimulator --show-sdk-path)
+ cflags="-mios-simulator-version-min=$DEPLOYMENT_TARGET"
+ ;;
+
+ simulator-x86_64)
+ cpu='x86-64'
+ cross_arch='x86_64'
+ cc='xcrun -sdk iphonesimulator clang'
+ as="$GAS_PREPROCESSOR -- $cc"
+ sysroot=$(xcrun -sdk iphonesimulator --show-sdk-path)
+ cflags="-mios-simulator-version-min=$DEPLOYMENT_TARGET"
+ ;;
+esac
+
+FFMPEG_FLAGS+=(
+ --enable-pic
+ --enable-videotoolbox
+ --enable-hwaccel=h264_videotoolbox
+ --enable-hwaccel=hevc_videotoolbox
+ --enable-hwaccel=vp9_videotoolbox
+
+ --enable-cross-compile
+ --target-os=darwin
+ --cpu=$cpu
+ --arch=$cross_arch
+ --cc="$cc"
+ --as="$as"
+ --extra-cflags="-isysroot $sysroot -arch $cross_arch $cflags"
+ --extra-ldflags="-isysroot $sysroot -arch $cross_arch $cflags"
+
+ --install-name-dir='@rpath'
+)
+
+pushd . > /dev/null
+prep_ffmpeg "iOS-$arch"
+# Change the `-install_name` from
+# "/libavcodec.dylib.61" to "/libavcodec.framework/libavcodec".
+# This is required for framework bundles and xcframeworks to load correctly.
+patch -p1 < "$SCRIPT_PATH/iOS-set-install-name-for-xcframework.patch"
+build_ffmpeg
+popd > /dev/null
+
+# Remove symlinks, keep only libraries with full version in their name
+find "iOS-$arch" -type l -delete
+
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/build-linux.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/build-linux.sh
index d3d52129b2..5044d4e061 100755
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/build-linux.sh
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/build-linux.sh
@@ -7,22 +7,11 @@ popd > /dev/null
source "$SCRIPT_PATH/common.sh"
FFMPEG_FLAGS+=(
- # --enable-vaapi
- # --enable-vdpau
- # --enable-hwaccel='h264_vaapi,h264_vdpau'
- # --enable-hwaccel='hevc_vaapi,hevc_vdpau'
- # --enable-hwaccel='vp8_vaapi,vp8_vdpau'
- # --enable-hwaccel='vp9_vaapi,vp9_vdpau'
-
--target-os=linux
)
pushd . > /dev/null
prep_ffmpeg linux-x64
-# Apply patch from upstream to fix errors with new binutils versions:
-# Ticket: https://fftrac-bg.ffmpeg.org/ticket/10405
-# This patch should be removed when FFmpeg is updated to >=6.1
-patch -p1 < "$SCRIPT_PATH/fix-binutils-2.41.patch"
build_ffmpeg
popd > /dev/null
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/build-win.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/build-win.sh
index ded8520dab..8dff16f670 100755
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/build-win.sh
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/build-win.sh
@@ -54,15 +54,6 @@ FFMPEG_FLAGS+=(
pushd . > /dev/null
prep_ffmpeg "win-$arch"
-
-# FFmpeg doesn't do this correctly when building, so we do it instead.
-# A newer FFmpeg release might make this unnecessary.
-echo '-> Creating resource objects...'
-make .version
-for res in lib*/*res.rc; do
- "${cross_prefix}windres" -I. "$res" "${res%.rc}.o"
-done
-
build_ffmpeg
popd > /dev/null
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/combine_dylibs.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/combine_dylibs.sh
index 9eb2ae0372..6965a54499 100755
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/combine_dylibs.sh
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/combine_dylibs.sh
@@ -1,13 +1,24 @@
#!/bin/bash
set -eu
+if [ -z "${platform-}" ]; then
+ PS3='Combine binaries for which platform? '
+ select platform in "macOS" "iOS"; do
+ if [ -z "$platform" ]; then
+ echo "invalid option"
+ else
+ break
+ fi
+ done
+fi
+
pushd . > /dev/null
-mkdir -p macOS-universal
-cd macOS-arm64
+mkdir -p $platform-universal
+cd $platform-arm64
for lib_arm in *.dylib; do
- lib_x86="../macOS-x86_64/$lib_arm"
+ lib_x86="../$platform-x86_64/$lib_arm"
echo "-> Creating universal $lib_arm..."
- lipo -create "$lib_arm" "$lib_x86" -output "../macOS-universal/$lib_arm"
+ lipo -create "$lib_arm" "$lib_x86" -output "../$platform-universal/$lib_arm"
done
popd > /dev/null
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/common.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/common.sh
index 7753867dca..522b177112 100755
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/common.sh
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/common.sh
@@ -1,12 +1,13 @@
#!/bin/bash
set -eu
-FFMPEG_VERSION=4.3.3
+FFMPEG_VERSION="7.0"
FFMPEG_FILE="ffmpeg-$FFMPEG_VERSION.tar.gz"
FFMPEG_FLAGS=(
# General options
--disable-static
--enable-shared
+ --disable-debug
--disable-all
--disable-autodetect
--enable-lto
@@ -50,14 +51,14 @@ function prep_ffmpeg() {
echo "-> $build_dir already exists, skipping unpacking."
fi
- echo "-> Configuring..."
cd "$build_dir"
- ./configure "${FFMPEG_FLAGS[@]}"
}
function build_ffmpeg() {
- echo "-> Building using $CORES threads..."
+ echo "-> Configuring..."
+ ./configure "${FFMPEG_FLAGS[@]}"
+ echo "-> Building using $CORES threads..."
make -j$CORES
make install-libs
}
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/create-xcframeworks.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/create-xcframeworks.sh
new file mode 100755
index 0000000000..68629e4463
--- /dev/null
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/create-xcframeworks.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+set -eu
+
+# See build-iOS.sh
+DEPLOYMENT_TARGET="13.4"
+
+for arch in "arm64" "simulator-universal"; do
+ pushd . > /dev/null
+ cd "iOS-$arch"
+ for f in *.*.*.*.dylib; do
+ [ -f "$f" ] || continue
+
+ # [avcodec].58.10.72.dylib
+ lib_name="${f%.*.*.*.*}"
+
+ # avcodec.[58.10.72].dylib
+ tmp=${f#*.}
+ version_string="${tmp%.*}"
+
+ framework_dir="$lib_name.framework"
+ mkdir "$framework_dir"
+
+ mv -v "$f" "$framework_dir/$lib_name"
+
+ plist_file="$framework_dir/Info.plist"
+
+ plutil -create xml1 "$plist_file"
+ plutil -insert CFBundleDevelopmentRegion -string en "$plist_file"
+ plutil -insert CFBundleExecutable -string "$lib_name" "$plist_file"
+ plutil -insert CFBundleIdentifier -string "sh.ppy.osu.Framework.iOS.$lib_name" "$plist_file"
+ plutil -insert CFBundleInfoDictionaryVersion -string '6.0' "$plist_file"
+ plutil -insert CFBundleName -string "$lib_name" "$plist_file"
+ plutil -insert CFBundlePackageType -string FMWK "$plist_file"
+ plutil -insert CFBundleShortVersionString -string "$version_string" "$plist_file"
+ plutil -insert CFBundleVersion -string "$version_string" "$plist_file"
+ plutil -insert MinimumOSVersion -string "$DEPLOYMENT_TARGET" "$plist_file"
+ plutil -insert CFBundleSupportedPlatforms -array "$plist_file"
+ plutil -insert CFBundleSupportedPlatforms -string iPhoneOS -append "$plist_file"
+
+ done
+ popd > /dev/null
+done
+
+pushd . > /dev/null
+mkdir -p iOS-xcframework
+cd iOS-arm64
+for framework_arm in *.framework; do
+ xcodebuild -create-xcframework \
+ -framework "$framework_arm" \
+ -framework "../iOS-simulator-universal/$framework_arm" \
+ -output "../iOS-xcframework/${framework_arm%.framework}.xcframework"
+done
+popd > /dev/null
+
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/fix-binutils-2.41.patch b/osu.Framework.NativeLibs/scripts/ffmpeg/fix-binutils-2.41.patch
deleted file mode 100644
index 33fd3d484f..0000000000
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/fix-binutils-2.41.patch
+++ /dev/null
@@ -1,76 +0,0 @@
-From effadce6c756247ea8bae32dc13bb3e6f464f0eb Mon Sep 17 00:00:00 2001
-From: =?utf8?q?R=C3=A9mi=20Denis-Courmont?=
-Date: Sun, 16 Jul 2023 18:18:02 +0300
-Subject: [PATCH] avcodec/x86/mathops: clip constants used with shift
- instructions within inline assembly
-
-Fixes assembling with binutil as >= 2.41
-
-Signed-off-by: James Almer
----
- libavcodec/x86/mathops.h | 26 +++++++++++++++++++++++---
- 1 file changed, 23 insertions(+), 3 deletions(-)
-
-diff --git a/libavcodec/x86/mathops.h b/libavcodec/x86/mathops.h
-index 6298f5ed19..ca7e2dffc1 100644
---- a/libavcodec/x86/mathops.h
-+++ b/libavcodec/x86/mathops.h
-@@ -35,12 +35,20 @@
- static av_always_inline av_const int MULL(int a, int b, unsigned shift)
- {
- int rt, dummy;
-+ if (__builtin_constant_p(shift))
- __asm__ (
- "imull %3 \n\t"
- "shrdl %4, %%edx, %%eax \n\t"
- :"=a"(rt), "=d"(dummy)
-- :"a"(a), "rm"(b), "ci"((uint8_t)shift)
-+ :"a"(a), "rm"(b), "i"(shift & 0x1F)
- );
-+ else
-+ __asm__ (
-+ "imull %3 \n\t"
-+ "shrdl %4, %%edx, %%eax \n\t"
-+ :"=a"(rt), "=d"(dummy)
-+ :"a"(a), "rm"(b), "c"((uint8_t)shift)
-+ );
- return rt;
- }
-
-@@ -113,19 +121,31 @@ __asm__ volatile(\
- // avoid +32 for shift optimization (gcc should do that ...)
- #define NEG_SSR32 NEG_SSR32
- static inline int32_t NEG_SSR32( int32_t a, int8_t s){
-+ if (__builtin_constant_p(s))
- __asm__ ("sarl %1, %0\n\t"
- : "+r" (a)
-- : "ic" ((uint8_t)(-s))
-+ : "i" (-s & 0x1F)
- );
-+ else
-+ __asm__ ("sarl %1, %0\n\t"
-+ : "+r" (a)
-+ : "c" ((uint8_t)(-s))
-+ );
- return a;
- }
-
- #define NEG_USR32 NEG_USR32
- static inline uint32_t NEG_USR32(uint32_t a, int8_t s){
-+ if (__builtin_constant_p(s))
- __asm__ ("shrl %1, %0\n\t"
- : "+r" (a)
-- : "ic" ((uint8_t)(-s))
-+ : "i" (-s & 0x1F)
- );
-+ else
-+ __asm__ ("shrl %1, %0\n\t"
-+ : "+r" (a)
-+ : "c" ((uint8_t)(-s))
-+ );
- return a;
- }
-
---
-2.30.2
-
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/iOS-set-install-name-for-xcframework.patch b/osu.Framework.NativeLibs/scripts/ffmpeg/iOS-set-install-name-for-xcframework.patch
new file mode 100644
index 0000000000..9a34485807
--- /dev/null
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/iOS-set-install-name-for-xcframework.patch
@@ -0,0 +1,13 @@
+diff --git a/configure b/configure
+index 4f5353f84b..dfddd13c9d 100755
+--- a/configure
++++ b/configure
+@@ -5738,7 +5738,7 @@ case $target_os in
+ darwin)
+ enabled ppc && add_asflags -force_cpusubtype_ALL
+ install_name_dir_default='$(SHLIBDIR)'
+- SHFLAGS='-dynamiclib -Wl,-single_module -Wl,-install_name,$(INSTALL_NAME_DIR)/$(SLIBNAME_WITH_MAJOR),-current_version,$(LIBVERSION),-compatibility_version,$(LIBMAJOR)'
++ SHFLAGS='-dynamiclib -Wl,-single_module -Wl,-install_name,$(INSTALL_NAME_DIR)/$(SLIBPREF)$(FULLNAME).framework/$(SLIBPREF)$(FULLNAME),-current_version,$(LIBVERSION),-compatibility_version,$(LIBMAJOR)'
+ enabled x86_32 && append SHFLAGS -Wl,-read_only_relocs,suppress
+ strip="${strip} -x"
+ add_ldflags -Wl,-dynamic,-search_paths_first
diff --git a/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/TemplateGame.iOS.csproj b/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/TemplateGame.iOS.csproj
index 2cca8636a2..dc86d4e4d3 100644
--- a/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/TemplateGame.iOS.csproj
+++ b/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/TemplateGame.iOS.csproj
@@ -10,12 +10,6 @@
so there's nothing to be worried about. -->
MT7091
-
- ios-arm64
-
-
- iossimulator-x64
-
diff --git a/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/FlappyDon.iOS.csproj b/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/FlappyDon.iOS.csproj
index a53d5b0248..ff0034760a 100644
--- a/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/FlappyDon.iOS.csproj
+++ b/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/FlappyDon.iOS.csproj
@@ -10,12 +10,6 @@
so there's nothing to be worried about. -->
MT7091
-
- ios-arm64
-
-
- iossimulator-x64
-
diff --git a/osu.Framework.Tests/Audio/BassAudioMixerTest.cs b/osu.Framework.Tests/Audio/BassAudioMixerTest.cs
index cc9a5fdbb2..23b5a10a75 100644
--- a/osu.Framework.Tests/Audio/BassAudioMixerTest.cs
+++ b/osu.Framework.Tests/Audio/BassAudioMixerTest.cs
@@ -6,7 +6,6 @@
using System;
using System.Threading;
using ManagedBass;
-using ManagedBass.Fx;
using ManagedBass.Mix;
using NUnit.Framework;
using osu.Framework.Audio.Mixing.Bass;
@@ -226,95 +225,6 @@ static WeakReference runTest(SampleBass sample)
}
}
- [Test]
- public void TestAddEffect()
- {
- bass.Mixer.Effects.Add(new BQFParameters());
- assertEffectParameters();
-
- bass.Mixer.Effects.AddRange(new[]
- {
- new BQFParameters(),
- new BQFParameters(),
- new BQFParameters()
- });
- assertEffectParameters();
- }
-
- [Test]
- public void TestRemoveEffect()
- {
- bass.Mixer.Effects.Add(new BQFParameters());
- assertEffectParameters();
-
- bass.Mixer.Effects.RemoveAt(0);
- assertEffectParameters();
-
- bass.Mixer.Effects.AddRange(new[]
- {
- new BQFParameters(),
- new BQFParameters(),
- new BQFParameters()
- });
- assertEffectParameters();
-
- bass.Mixer.Effects.RemoveAt(1);
- assertEffectParameters();
-
- bass.Mixer.Effects.RemoveAt(1);
- assertEffectParameters();
- }
-
- [Test]
- public void TestMoveEffect()
- {
- bass.Mixer.Effects.AddRange(new[]
- {
- new BQFParameters(),
- new BQFParameters(),
- new BQFParameters()
- });
- assertEffectParameters();
-
- bass.Mixer.Effects.Move(0, 1);
- assertEffectParameters();
-
- bass.Mixer.Effects.Move(2, 0);
- assertEffectParameters();
- }
-
- [Test]
- public void TestReplaceEffect()
- {
- bass.Mixer.Effects.AddRange(new[]
- {
- new BQFParameters(),
- new BQFParameters(),
- new BQFParameters()
- });
- assertEffectParameters();
-
- bass.Mixer.Effects[1] = new BQFParameters();
- assertEffectParameters();
- }
-
- [Test]
- public void TestInsertEffect()
- {
- bass.Mixer.Effects.AddRange(new[]
- {
- new BQFParameters(),
- new BQFParameters()
- });
- assertEffectParameters();
-
- bass.Mixer.Effects.Insert(1, new BQFParameters());
- assertEffectParameters();
-
- bass.Mixer.Effects.Insert(3, new BQFParameters());
- assertEffectParameters();
- }
-
[Test]
public void TestChannelDoesNotPlayIfReachedEndAndSeekedBackwards()
{
@@ -356,22 +266,6 @@ public void TestChannelDoesNotPlayIfReachedEndAndMovedMixers()
Assert.That(secondMixer.ChannelIsActive(track), Is.Not.EqualTo(PlaybackState.Playing));
}
- private void assertEffectParameters()
- {
- bass.Update();
-
- Assert.That(bass.Mixer.ActiveEffects.Count, Is.EqualTo(bass.Mixer.Effects.Count));
-
- Assert.Multiple(() =>
- {
- for (int i = 0; i < bass.Mixer.ActiveEffects.Count; i++)
- {
- Assert.That(bass.Mixer.ActiveEffects[i].Effect, Is.EqualTo(bass.Mixer.Effects[i]));
- Assert.That(bass.Mixer.ActiveEffects[i].Priority, Is.EqualTo(-i));
- }
- });
- }
-
private int getHandle() => ((IBassAudioChannel)track).Handle;
}
}
diff --git a/osu.Framework.Tests/Clocks/DecouplingFramedClockTest.cs b/osu.Framework.Tests/Clocks/DecouplingFramedClockTest.cs
index ba135aa60d..b5b645904e 100644
--- a/osu.Framework.Tests/Clocks/DecouplingFramedClockTest.cs
+++ b/osu.Framework.Tests/Clocks/DecouplingFramedClockTest.cs
@@ -502,6 +502,26 @@ public void TestForwardPlaybackOverLengthBoundary()
Assert.That(source.IsRunning, Is.False);
}
+ [Test]
+ public void TestPlayDifferentSourceAfterSeekFailure()
+ {
+ decouplingClock.AllowDecoupling = true;
+
+ var firstSource = (TestClockWithRange)source;
+ firstSource.MaxTime = 100;
+
+ decouplingClock.Seek(1000);
+
+ Assert.That(firstSource.IsRunning, Is.False);
+
+ var secondSource = new TestClockWithRange();
+
+ decouplingClock.ChangeSource(secondSource);
+ decouplingClock.Start();
+
+ Assert.That(secondSource.IsRunning, Is.True);
+ }
+
#endregion
private class TestClockWithRange : TestClock
diff --git a/osu.Framework.Tests/Dependencies/Reflection/CachedModelDependenciesTest.cs b/osu.Framework.Tests/Dependencies/Reflection/CachedModelDependenciesTest.cs
index 8c27434c91..02297e5bde 100644
--- a/osu.Framework.Tests/Dependencies/Reflection/CachedModelDependenciesTest.cs
+++ b/osu.Framework.Tests/Dependencies/Reflection/CachedModelDependenciesTest.cs
@@ -1,9 +1,6 @@
// 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 System.Diagnostics.CodeAnalysis;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -15,24 +12,6 @@ namespace osu.Framework.Tests.Dependencies.Reflection
[SuppressMessage("Performance", "OFSG001:Class contributes to dependency injection and should be partial")]
public class CachedModelDependenciesTest
{
- [Test]
- public void TestModelWithNonBindableFieldsFails()
- {
- IReadOnlyDependencyContainer unused;
-
- Assert.Throws(() => unused = new CachedModelDependencyContainer(null));
- Assert.Throws(() => unused = new CachedModelDependencyContainer(null));
- }
-
- [Test]
- public void TestModelWithNonReadOnlyFieldsFails()
- {
- IReadOnlyDependencyContainer unused;
-
- Assert.Throws(() => unused = new CachedModelDependencyContainer(null));
- Assert.Throws(() => unused = new CachedModelDependencyContainer(null));
- }
-
[Test]
public void TestSettingNoModelResolvesDefault()
{
@@ -195,7 +174,7 @@ public void TestSetModelToNullAfterResolved()
var model = new FieldModel { Bindable = { Value = 2 } };
- var dependencies = new CachedModelDependencyContainer(null)
+ var dependencies = new CachedModelDependencyContainer(null)
{
Model = { Value = model }
};
@@ -248,7 +227,7 @@ public void TestResolveIndividualProperties()
BindableString = { Value = "3" }
};
- var dependencies = new CachedModelDependencyContainer(null)
+ var dependencies = new CachedModelDependencyContainer(null)
{
Model = { Value = model1 }
};
@@ -269,33 +248,6 @@ public void TestResolveIndividualProperties()
Assert.AreEqual(null, resolver.BindableString.Value);
}
- private class NonBindablePublicFieldModel : IDependencyInjectionCandidate
- {
-#pragma warning disable 649
- public readonly int FailingField;
-#pragma warning restore 649
- }
-
- private class NonBindablePrivateFieldModel : IDependencyInjectionCandidate
- {
-#pragma warning disable 169
- private readonly int failingField;
-#pragma warning restore 169
- }
-
- private class NonReadOnlyFieldModel : IDependencyInjectionCandidate
- {
-#pragma warning disable 649
- public Bindable Bindable;
-#pragma warning restore 649
- }
-
- private class PropertyModel : IDependencyInjectionCandidate
- {
- // ReSharper disable once UnusedMember.Local
- public Bindable Bindable { get; private set; }
- }
-
private class FieldModel : IDependencyInjectionCandidate
{
[Cached]
@@ -311,22 +263,22 @@ private class DerivedFieldModel : FieldModel
private class FieldModelResolver : IDependencyInjectionCandidate
{
[Resolved]
- public FieldModel Model { get; private set; }
+ public FieldModel Model { get; private set; } = null!;
}
private class DerivedFieldModelResolver : IDependencyInjectionCandidate
{
[Resolved]
- public DerivedFieldModel Model { get; private set; }
+ public DerivedFieldModel Model { get; private set; } = null!;
}
private class DerivedFieldModelPropertyResolver : IDependencyInjectionCandidate
{
[Resolved(typeof(DerivedFieldModel))]
- public Bindable Bindable { get; private set; }
+ public Bindable Bindable { get; private set; } = null!;
[Resolved(typeof(DerivedFieldModel))]
- public Bindable BindableString { get; private set; }
+ public Bindable BindableString { get; private set; } = null!;
}
}
}
diff --git a/osu.Framework.Tests/Dependencies/SourceGeneration/CachedModelDependenciesTest.cs b/osu.Framework.Tests/Dependencies/SourceGeneration/CachedModelDependenciesTest.cs
index 8555380a72..84529a4ae5 100644
--- a/osu.Framework.Tests/Dependencies/SourceGeneration/CachedModelDependenciesTest.cs
+++ b/osu.Framework.Tests/Dependencies/SourceGeneration/CachedModelDependenciesTest.cs
@@ -1,9 +1,6 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -13,24 +10,6 @@ namespace osu.Framework.Tests.Dependencies.SourceGeneration
[TestFixture]
public partial class CachedModelDependenciesTest
{
- [Test]
- public void TestModelWithNonBindableFieldsFails()
- {
- IReadOnlyDependencyContainer unused;
-
- Assert.Throws(() => unused = new CachedModelDependencyContainer(null));
- Assert.Throws(() => unused = new CachedModelDependencyContainer(null));
- }
-
- [Test]
- public void TestModelWithNonReadOnlyFieldsFails()
- {
- IReadOnlyDependencyContainer unused;
-
- Assert.Throws(() => unused = new CachedModelDependencyContainer(null));
- Assert.Throws(() => unused = new CachedModelDependencyContainer(null));
- }
-
[Test]
public void TestSettingNoModelResolvesDefault()
{
@@ -193,7 +172,7 @@ public void TestSetModelToNullAfterResolved()
var model = new FieldModel { Bindable = { Value = 2 } };
- var dependencies = new CachedModelDependencyContainer(null)
+ var dependencies = new CachedModelDependencyContainer(null)
{
Model = { Value = model }
};
@@ -246,7 +225,7 @@ public void TestResolveIndividualProperties()
BindableString = { Value = "3" }
};
- var dependencies = new CachedModelDependencyContainer(null)
+ var dependencies = new CachedModelDependencyContainer(null)
{
Model = { Value = model1 }
};
@@ -267,33 +246,6 @@ public void TestResolveIndividualProperties()
Assert.AreEqual(null, resolver.BindableString.Value);
}
- private partial class NonBindablePublicFieldModel : IDependencyInjectionCandidate
- {
-#pragma warning disable 649
- public readonly int FailingField;
-#pragma warning restore 649
- }
-
- private partial class NonBindablePrivateFieldModel : IDependencyInjectionCandidate
- {
-#pragma warning disable 169
- private readonly int failingField;
-#pragma warning restore 169
- }
-
- private partial class NonReadOnlyFieldModel : IDependencyInjectionCandidate
- {
-#pragma warning disable 649
- public Bindable Bindable;
-#pragma warning restore 649
- }
-
- private partial class PropertyModel : IDependencyInjectionCandidate
- {
- // ReSharper disable once UnusedMember.Local
- public Bindable Bindable { get; private set; }
- }
-
private partial class FieldModel : IDependencyInjectionCandidate
{
[Cached]
@@ -309,22 +261,22 @@ private partial class DerivedFieldModel : FieldModel
private partial class FieldModelResolver : IDependencyInjectionCandidate
{
[Resolved]
- public FieldModel Model { get; private set; }
+ public FieldModel Model { get; private set; } = null!;
}
private partial class DerivedFieldModelResolver : IDependencyInjectionCandidate
{
[Resolved]
- public DerivedFieldModel Model { get; private set; }
+ public DerivedFieldModel Model { get; private set; } = null!;
}
private partial class DerivedFieldModelPropertyResolver : IDependencyInjectionCandidate
{
[Resolved(typeof(DerivedFieldModel))]
- public Bindable Bindable { get; private set; }
+ public Bindable Bindable { get; private set; } = null!;
[Resolved(typeof(DerivedFieldModel))]
- public Bindable BindableString { get; private set; }
+ public Bindable BindableString { get; private set; } = null!;
}
}
}
diff --git a/osu.Framework.Tests/FlakyTestAttribute.cs b/osu.Framework.Tests/FlakyTestAttribute.cs
index 62e8914901..e4eb0c685a 100644
--- a/osu.Framework.Tests/FlakyTestAttribute.cs
+++ b/osu.Framework.Tests/FlakyTestAttribute.cs
@@ -1,7 +1,11 @@
// 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 NUnit.Framework;
+using NUnit.Framework.Interfaces;
+using NUnit.Framework.Internal;
+using NUnit.Framework.Internal.Commands;
namespace osu.Framework.Tests
{
@@ -9,16 +13,63 @@ namespace osu.Framework.Tests
/// An attribute to mark any flaky tests.
/// Will add a retry count unless environment variable `FAIL_FLAKY_TESTS` is set to `1`.
///
- public class FlakyTestAttribute : RetryAttribute
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ public class FlakyTestAttribute : NUnitAttribute, IRepeatTest
{
+ private readonly int tryCount;
+
public FlakyTestAttribute()
: this(10)
{
}
public FlakyTestAttribute(int tryCount)
- : base(FrameworkEnvironment.FailFlakyTests ? 1 : tryCount)
{
+ this.tryCount = tryCount;
+ }
+
+ public TestCommand Wrap(TestCommand command) => new FlakyTestCommand(command, tryCount);
+
+ // Adapted from https://github.com/nunit/nunit/blob/4eaab2eef3713907ca37bfb2f7f47e3fc2785214/src/NUnitFramework/framework/Attributes/RetryAttribute.cs
+ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt
+ public class FlakyTestCommand : DelegatingTestCommand
+ {
+ private readonly int tryCount;
+
+ public FlakyTestCommand(TestCommand innerCommand, int tryCount)
+ : base(innerCommand)
+ {
+ this.tryCount = tryCount;
+ }
+
+ public override TestResult Execute(TestExecutionContext context)
+ {
+ int count = FrameworkEnvironment.FailFlakyTests ? 1 : tryCount;
+
+ while (count-- > 0)
+ {
+ try
+ {
+ context.CurrentResult = innerCommand.Execute(context);
+ }
+ catch (Exception ex)
+ {
+ context.CurrentResult ??= context.CurrentTest.MakeTestResult();
+ context.CurrentResult.RecordException(ex);
+ }
+
+ if (context.CurrentResult.ResultState != ResultState.Failure)
+ break;
+
+ if (count > 0)
+ {
+ context.CurrentResult = context.CurrentTest.MakeTestResult();
+ context.CurrentRepeatCount++;
+ }
+ }
+
+ return context.CurrentResult;
+ }
}
}
}
diff --git a/osu.Framework.Tests/Graphics/RendererTest.cs b/osu.Framework.Tests/Graphics/RendererTest.cs
new file mode 100644
index 0000000000..fd84554e2c
--- /dev/null
+++ b/osu.Framework.Tests/Graphics/RendererTest.cs
@@ -0,0 +1,53 @@
+// 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.Rendering.Dummy;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Framework.Tests.Graphics
+{
+ public class RendererTest
+ {
+ [Test]
+ public void TestWhitePixelReuseUpdatesTextureWrapping()
+ {
+ DummyRenderer renderer = new DummyRenderer();
+
+ renderer.BindTexture(renderer.WhitePixel, 0, WrapMode.None, WrapMode.None);
+ Assert.That(renderer.CurrentWrapModeS, Is.EqualTo(WrapMode.None));
+ Assert.That(renderer.CurrentWrapModeS, Is.EqualTo(WrapMode.None));
+
+ renderer.BindTexture(renderer.WhitePixel, 0, WrapMode.ClampToEdge, WrapMode.ClampToEdge);
+ Assert.That(renderer.CurrentWrapModeS, Is.EqualTo(WrapMode.ClampToEdge));
+ Assert.That(renderer.CurrentWrapModeS, Is.EqualTo(WrapMode.ClampToEdge));
+
+ renderer.BindTexture(renderer.WhitePixel, 0, WrapMode.None, WrapMode.None);
+ Assert.That(renderer.CurrentWrapModeS, Is.EqualTo(WrapMode.None));
+ Assert.That(renderer.CurrentWrapModeS, Is.EqualTo(WrapMode.None));
+ }
+
+ [Test]
+ public void TestTextureAtlasReuseUpdatesTextureWrapping()
+ {
+ DummyRenderer renderer = new DummyRenderer();
+
+ TextureAtlas atlas = new TextureAtlas(renderer, 1024, 1024);
+
+ Texture textureWrapNone = atlas.Add(100, 100, WrapMode.None, WrapMode.None)!;
+ Texture textureWrapClamp = atlas.Add(100, 100, WrapMode.ClampToEdge, WrapMode.ClampToEdge)!;
+
+ renderer.BindTexture(textureWrapNone, 0, null, null);
+ Assert.That(renderer.CurrentWrapModeS, Is.EqualTo(WrapMode.None));
+ Assert.That(renderer.CurrentWrapModeT, Is.EqualTo(WrapMode.None));
+
+ renderer.BindTexture(textureWrapClamp, 0, null, null);
+ Assert.That(renderer.CurrentWrapModeS, Is.EqualTo(WrapMode.ClampToEdge));
+ Assert.That(renderer.CurrentWrapModeT, Is.EqualTo(WrapMode.ClampToEdge));
+
+ renderer.BindTexture(textureWrapNone, 0, null, null);
+ Assert.That(renderer.CurrentWrapModeS, Is.EqualTo(WrapMode.None));
+ Assert.That(renderer.CurrentWrapModeT, Is.EqualTo(WrapMode.None));
+ }
+ }
+}
diff --git a/osu.Framework.Tests/Graphics/ShaderStorageBufferObjectStackTest.cs b/osu.Framework.Tests/Graphics/ShaderStorageBufferObjectStackTest.cs
index a8561e3cc7..545dc7c8c2 100644
--- a/osu.Framework.Tests/Graphics/ShaderStorageBufferObjectStackTest.cs
+++ b/osu.Framework.Tests/Graphics/ShaderStorageBufferObjectStackTest.cs
@@ -2,9 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Runtime.InteropServices;
using NUnit.Framework;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Rendering.Dummy;
+using osu.Framework.Graphics.Shaders.Types;
namespace osu.Framework.Tests.Graphics
{
@@ -12,21 +14,21 @@ public class ShaderStorageBufferObjectStackTest
{
private const int size = 10;
- private ShaderStorageBufferObjectStack stack = null!;
+ private ShaderStorageBufferObjectStack stack = null!;
[SetUp]
public void Setup()
{
- stack = new ShaderStorageBufferObjectStack(new DummyRenderer(), 2, size);
+ stack = new ShaderStorageBufferObjectStack(new DummyRenderer(), 2, size);
}
[Test]
public void TestBufferMustBeAtLeast2Elements()
{
- Assert.Throws(() => _ = new ShaderStorageBufferObjectStack(new DummyRenderer(), 1, 100));
- Assert.Throws(() => _ = new ShaderStorageBufferObjectStack(new DummyRenderer(), 100, 1));
- Assert.DoesNotThrow(() => _ = new ShaderStorageBufferObjectStack(new DummyRenderer(), 2, 100));
- Assert.DoesNotThrow(() => _ = new ShaderStorageBufferObjectStack(new DummyRenderer(), 100, 2));
+ Assert.Throws(() => _ = new ShaderStorageBufferObjectStack(new DummyRenderer(), 1, 100));
+ Assert.Throws(() => _ = new ShaderStorageBufferObjectStack(new DummyRenderer(), 100, 1));
+ Assert.DoesNotThrow(() => _ = new ShaderStorageBufferObjectStack(new DummyRenderer(), 2, 100));
+ Assert.DoesNotThrow(() => _ = new ShaderStorageBufferObjectStack(new DummyRenderer(), 100, 2));
}
[Test]
@@ -34,7 +36,7 @@ public void TestInitialState()
{
Assert.That(stack.CurrentOffset, Is.Zero);
Assert.That(stack.CurrentBuffer, Is.Not.Null);
- Assert.That(stack.CurrentBuffer[stack.CurrentOffset], Is.EqualTo(0));
+ Assert.That(stack.CurrentBuffer[stack.CurrentOffset].Int.Value, Is.EqualTo(0));
}
[Test]
@@ -48,11 +50,11 @@ public void TestAddInitialItem()
{
var firstBuffer = stack.CurrentBuffer;
- stack.Push(1);
+ stack.Push(new TestUniformData { Int = 1 });
Assert.That(stack.CurrentOffset, Is.Zero);
Assert.That(stack.CurrentBuffer, Is.EqualTo(firstBuffer));
- Assert.That(stack.CurrentBuffer[stack.CurrentOffset], Is.EqualTo(1));
+ Assert.That(stack.CurrentBuffer[stack.CurrentOffset].Int.Value, Is.EqualTo(1));
}
[Test]
@@ -63,10 +65,10 @@ public void TestPushToFillOneBuffer()
for (int i = 0; i < size; i++)
{
- stack.Push(i);
+ stack.Push(new TestUniformData { Int = i });
Assert.That(stack.CurrentOffset, Is.EqualTo(expectedIndex++));
Assert.That(stack.CurrentBuffer, Is.EqualTo(firstBuffer));
- Assert.That(stack.CurrentBuffer[stack.CurrentOffset], Is.EqualTo(i));
+ Assert.That(stack.CurrentBuffer[stack.CurrentOffset].Int.Value, Is.EqualTo(i));
}
}
@@ -74,7 +76,7 @@ public void TestPushToFillOneBuffer()
public void TestPopEntireBuffer()
{
for (int i = 0; i < size; i++)
- stack.Push(i);
+ stack.Push(new TestUniformData { Int = i });
var firstBuffer = stack.CurrentBuffer;
@@ -82,7 +84,7 @@ public void TestPopEntireBuffer()
{
Assert.That(stack.CurrentOffset, Is.EqualTo(i));
Assert.That(stack.CurrentBuffer, Is.EqualTo(firstBuffer));
- Assert.That(stack.CurrentBuffer[stack.CurrentOffset], Is.EqualTo(i));
+ Assert.That(stack.CurrentBuffer[stack.CurrentOffset].Int.Value, Is.EqualTo(i));
stack.Pop();
}
}
@@ -91,47 +93,47 @@ public void TestPopEntireBuffer()
public void TestTransitionToBufferOnPush()
{
for (int i = 0; i < size; i++)
- stack.Push(i);
+ stack.Push(new TestUniformData { Int = i });
var firstBuffer = stack.CurrentBuffer;
- int copiedItem = stack.CurrentBuffer[stack.CurrentOffset];
+ int copiedItem = stack.CurrentBuffer[stack.CurrentOffset].Int.Value;
// Transition to a new buffer...
- stack.Push(size);
+ stack.Push(new TestUniformData { Int = size });
Assert.That(stack.CurrentBuffer, Is.Not.EqualTo(firstBuffer));
// ... where the "hack" employed by the queue means that after a transition, the new item is added at index 1...
Assert.That(stack.CurrentOffset, Is.EqualTo(1));
- Assert.That(stack.CurrentBuffer[1], Is.EqualTo(size));
+ Assert.That(stack.CurrentBuffer[1].Int.Value, Is.EqualTo(size));
// ... and the first item in the new buffer is a copy of the last referenced item before the push.
- Assert.That(stack.CurrentBuffer[0], Is.EqualTo(copiedItem));
+ Assert.That(stack.CurrentBuffer[0].Int.Value, Is.EqualTo(copiedItem));
}
[Test]
public void TestTransitionToBufferOnPop()
{
for (int i = 0; i < size; i++)
- stack.Push(i);
+ stack.Push(new TestUniformData { Int = i });
var firstBuffer = stack.CurrentBuffer;
- int copiedItem = stack.CurrentBuffer[stack.CurrentOffset];
+ int copiedItem = stack.CurrentBuffer[stack.CurrentOffset].Int.Value;
// Transition to the new buffer.
- stack.Push(size);
+ stack.Push(new TestUniformData { Int = size });
// The "hack" employed means that on the first pop, the index moves to the 0th index in the new buffer.
stack.Pop();
Assert.That(stack.CurrentBuffer, Is.Not.EqualTo(firstBuffer));
Assert.That(stack.CurrentOffset, Is.Zero);
- Assert.That(stack.CurrentBuffer[stack.CurrentOffset], Is.EqualTo(copiedItem));
+ Assert.That(stack.CurrentBuffer[stack.CurrentOffset].Int.Value, Is.EqualTo(copiedItem));
// After a subsequent pop, we transition to the previous buffer and move to the index prior to the copied item.
// We've already seen the copied item in the new buffer with the above pop, so we should not see it again here.
stack.Pop();
Assert.That(stack.CurrentBuffer, Is.EqualTo(firstBuffer));
Assert.That(stack.CurrentOffset, Is.EqualTo(copiedItem - 1));
- Assert.That(stack.CurrentBuffer[stack.CurrentOffset], Is.EqualTo(copiedItem - 1));
+ Assert.That(stack.CurrentBuffer[stack.CurrentOffset].Int.Value, Is.EqualTo(copiedItem - 1));
// Popping once again should move the index further backwards.
stack.Pop();
@@ -143,7 +145,7 @@ public void TestTransitionToBufferOnPop()
public void TestTransitionToAndFromNewBufferFromMiddle()
{
for (int i = 0; i < size; i++)
- stack.Push(i);
+ stack.Push(new TestUniformData { Int = i });
// Move to the middle of the current buffer (it can not take up any new items at this point).
stack.Pop();
@@ -153,13 +155,13 @@ public void TestTransitionToAndFromNewBufferFromMiddle()
int copiedItem = stack.CurrentOffset;
// Transition to the new buffer...
- stack.Push(size);
+ stack.Push(new TestUniformData { Int = size });
// ... and as above, we arrive at index 1 in the new buffer.
Assert.That(stack.CurrentBuffer, Is.Not.EqualTo(firstBuffer));
Assert.That(stack.CurrentOffset, Is.EqualTo(1));
- Assert.That(stack.CurrentBuffer[1], Is.EqualTo(size));
- Assert.That(stack.CurrentBuffer[0], Is.EqualTo(copiedItem));
+ Assert.That(stack.CurrentBuffer[1].Int.Value, Is.EqualTo(size));
+ Assert.That(stack.CurrentBuffer[0].Int.Value, Is.EqualTo(copiedItem));
// Transition to the previous buffer...
stack.Pop();
@@ -168,7 +170,7 @@ public void TestTransitionToAndFromNewBufferFromMiddle()
// ... noting that this is the same as the above "normal" pop case, except that item arrived at is in the middle of the previous buffer.
Assert.That(stack.CurrentBuffer, Is.EqualTo(firstBuffer));
Assert.That(stack.CurrentOffset, Is.EqualTo(copiedItem - 1));
- Assert.That(stack.CurrentBuffer[stack.CurrentOffset], Is.EqualTo(copiedItem - 1));
+ Assert.That(stack.CurrentBuffer[stack.CurrentOffset].Int.Value, Is.EqualTo(copiedItem - 1));
// Popping once again from this state should move further backwards.
stack.Pop();
@@ -180,19 +182,19 @@ public void TestTransitionToAndFromNewBufferFromMiddle()
public void TestMoveToAndFromMiddleOfNewBuffer()
{
for (int i = 0; i < size; i++)
- stack.Push(i);
+ stack.Push(new TestUniformData { Int = i });
var lastBuffer = stack.CurrentBuffer;
- int copiedItem1 = stack.CurrentBuffer[stack.CurrentOffset];
+ int copiedItem1 = stack.CurrentBuffer[stack.CurrentOffset].Int.Value;
// Transition to the middle of the new buffer.
- stack.Push(size);
- stack.Push(size + 1);
+ stack.Push(new TestUniformData { Int = size });
+ stack.Push(new TestUniformData { Int = size + 1 });
Assert.That(stack.CurrentBuffer, Is.Not.EqualTo(lastBuffer));
Assert.That(stack.CurrentOffset, Is.EqualTo(2));
- Assert.That(stack.CurrentBuffer[2], Is.EqualTo(size + 1));
- Assert.That(stack.CurrentBuffer[1], Is.EqualTo(size));
- Assert.That(stack.CurrentBuffer[0], Is.EqualTo(copiedItem1));
+ Assert.That(stack.CurrentBuffer[2].Int.Value, Is.EqualTo(size + 1));
+ Assert.That(stack.CurrentBuffer[1].Int.Value, Is.EqualTo(size));
+ Assert.That(stack.CurrentBuffer[0].Int.Value, Is.EqualTo(copiedItem1));
// Transition to the previous buffer.
stack.Pop();
@@ -201,23 +203,23 @@ public void TestMoveToAndFromMiddleOfNewBuffer()
Assert.That(stack.CurrentBuffer, Is.EqualTo(lastBuffer));
// The item that will be copied into the new buffer.
- int copiedItem2 = stack.CurrentBuffer[stack.CurrentOffset];
+ int copiedItem2 = stack.CurrentBuffer[stack.CurrentOffset].Int.Value;
// Transition to the new buffer...
- stack.Push(size + 2);
+ stack.Push(new TestUniformData { Int = size + 2 });
Assert.That(stack.CurrentBuffer, Is.Not.EqualTo(lastBuffer));
// ... noting that this is the same as the normal case of transitioning to a new buffer, except arriving in the middle of it...
Assert.That(stack.CurrentOffset, Is.EqualTo(4));
- Assert.That(stack.CurrentBuffer[4], Is.EqualTo(size + 2));
+ Assert.That(stack.CurrentBuffer[4].Int.Value, Is.EqualTo(size + 2));
// ... where this is the copied item as a result of the immediate push...
- Assert.That(stack.CurrentBuffer[3], Is.EqualTo(copiedItem2));
+ Assert.That(stack.CurrentBuffer[3].Int.Value, Is.EqualTo(copiedItem2));
// ... and these are the same items from the first pushes above.
- Assert.That(stack.CurrentBuffer[2], Is.EqualTo(size + 1));
- Assert.That(stack.CurrentBuffer[1], Is.EqualTo(size));
- Assert.That(stack.CurrentBuffer[0], Is.EqualTo(copiedItem1));
+ Assert.That(stack.CurrentBuffer[2].Int.Value, Is.EqualTo(size + 1));
+ Assert.That(stack.CurrentBuffer[1].Int.Value, Is.EqualTo(size));
+ Assert.That(stack.CurrentBuffer[0].Int.Value, Is.EqualTo(copiedItem1));
// Transition to the previous buffer...
stack.Pop();
@@ -230,7 +232,7 @@ public void TestMoveToAndFromMiddleOfNewBuffer()
// 3. From index N-2 -> transition to new buffer.
// 4. Transition to old buffer, arrive at index N-3 (N-2 was copied into the new buffer).
Assert.That(stack.CurrentOffset, Is.EqualTo(size - 3));
- Assert.That(stack.CurrentBuffer[stack.CurrentOffset], Is.EqualTo(size - 3));
+ Assert.That(stack.CurrentBuffer[stack.CurrentOffset].Int.Value, Is.EqualTo(size - 3));
}
[Test]
@@ -241,18 +243,25 @@ public void TestTransitionFromEmptyStack()
var lastBuffer = stack.CurrentBuffer;
// Push one item.
- stack.Push(i);
+ stack.Push(new TestUniformData { Int = i });
// On a buffer transition, test that the item at the 0-th index of the first buffer was correct copied to the new buffer.
if (stack.CurrentBuffer != lastBuffer)
- Assert.That(stack.CurrentBuffer[stack.CurrentOffset - 1], Is.EqualTo(0));
+ Assert.That(stack.CurrentBuffer[stack.CurrentOffset - 1].Int.Value, Is.EqualTo(0));
// Test that the item was correctly placed in the new buffer
- Assert.That(stack.CurrentBuffer[stack.CurrentOffset], Is.EqualTo(i));
+ Assert.That(stack.CurrentBuffer[stack.CurrentOffset].Int.Value, Is.EqualTo(i));
// Return to an empty stack.
stack.Pop();
}
}
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ private record struct TestUniformData
+ {
+ public UniformInt Int;
+ private UniformPadding12 pad;
+ }
}
}
diff --git a/osu.Framework.Tests/Graphics/TripleBufferTest.cs b/osu.Framework.Tests/Graphics/TripleBufferTest.cs
index 3dce0e5eb0..d14e0faec1 100644
--- a/osu.Framework.Tests/Graphics/TripleBufferTest.cs
+++ b/osu.Framework.Tests/Graphics/TripleBufferTest.cs
@@ -40,196 +40,49 @@ public void TestSameBufferIsNotWrittenTwiceInRowNoContestation()
{
var tripleBuffer = createWithIDsMatchingIndices();
- using (var write = tripleBuffer.GetForWrite())
- Assert.That(write.Object?.ID, Is.EqualTo(0));
+ int? lastWrite = null;
- // buffer 0: waiting for read
- // buffer 1: old
- // buffer 2: old
-
- using (var buffer = tripleBuffer.GetForRead())
- Assert.That(buffer?.Object?.ID, Is.EqualTo(0));
-
- // buffer 0: last read
- // buffer 1: old
- // buffer 2: old
-
- using (var write = tripleBuffer.GetForWrite())
- Assert.That(write.Object?.ID, Is.EqualTo(1));
-
- // buffer 0: last read
- // buffer 1: waiting for read
- // buffer 2: old
-
- using (var write = tripleBuffer.GetForWrite())
- Assert.That(write.Object?.ID, Is.EqualTo(2));
-
- // buffer 0: last read
- // buffer 1: old
- // buffer 2: waiting for read
-
- using (var write = tripleBuffer.GetForWrite())
- Assert.That(write.Object?.ID, Is.EqualTo(1));
-
- // buffer 0: last read
- // buffer 1: waiting for read
- // buffer 2: old
-
- using (var buffer = tripleBuffer.GetForRead())
- Assert.That(buffer?.Object?.ID, Is.EqualTo(1));
-
- // buffer 0: old
- // buffer 1: last read
- // buffer 2: old
- }
-
- [Test]
- public void TestSameBufferIsNotWrittenTwiceInRowContestation()
- {
- var tripleBuffer = createWithIDsMatchingIndices();
-
- // Test with first write in use during second.
- using (var write = tripleBuffer.GetForWrite())
- Assert.That(write.Object?.ID, Is.EqualTo(0));
-
- // buffer 0: waiting for read
- // buffer 1: old
- // buffer 2: old
-
- using (var read = tripleBuffer.GetForRead())
- {
- Assert.That(read?.Object?.ID, Is.EqualTo(0));
-
- // buffer 0: reading
- // buffer 1: old
- // buffer 2: old
-
- using (var write = tripleBuffer.GetForWrite())
- Assert.That(write.Object?.ID, Is.EqualTo(1));
-
- // buffer 0: reading
- // buffer 1: waiting for read
- // buffer 2: old
-
- using (var write = tripleBuffer.GetForWrite())
- Assert.That(write.Object?.ID, Is.EqualTo(2));
-
- // buffer 0: reading
- // buffer 1: old
- // buffer 2: waiting for read
- }
-
- using (var read = tripleBuffer.GetForRead())
+ for (int i = 0; i < 3; i++)
{
- Assert.That(read?.Object?.ID, Is.EqualTo(2));
-
- // buffer 0: old
- // buffer 1: old
- // buffer 2: reading
-
- using (var write = tripleBuffer.GetForWrite())
- Assert.That(write.Object?.ID, Is.EqualTo(0));
-
- // buffer 0: waiting for read
- // buffer 1: old
- // buffer 2: reading
-
using (var write = tripleBuffer.GetForWrite())
- Assert.That(write.Object?.ID, Is.EqualTo(1));
-
- // buffer 0: old
- // buffer 1: waiting for read
- // buffer 2: reading
-
- using (var write = tripleBuffer.GetForWrite())
- Assert.That(write.Object?.ID, Is.EqualTo(0));
-
- // buffer 0: waiting for read
- // buffer 1: old
- // buffer 2: reading
- }
+ {
+ Assert.That(write.Object!.ID, Is.Not.EqualTo(lastWrite));
+ lastWrite = write.Object!.ID;
+ }
- using (var read = tripleBuffer.GetForRead())
- {
- Assert.That(read?.Object?.ID, Is.EqualTo(0));
- // buffer 0: reading
- // buffer 1: old
- // buffer 2: old
+ using (var buffer = tripleBuffer.GetForRead())
+ Assert.That(buffer!.Object!.ID, Is.EqualTo(lastWrite));
}
}
[Test]
- public void TestSameBufferIsNotWrittenTwiceInRowContestation2()
+ public void TestSameBufferIsNotWrittenTwiceInRowContestation()
{
var tripleBuffer = createWithIDsMatchingIndices();
- using (var write = tripleBuffer.GetForWrite())
- Assert.That(write.Object?.ID, Is.EqualTo(0));
-
- // buffer 0: waiting for read
- // buffer 1: old
- // buffer 2: old
-
- using (var read = tripleBuffer.GetForRead())
- {
- Assert.That(read?.Object?.ID, Is.EqualTo(0));
-
- // buffer 0: reading
- // buffer 1: old
- // buffer 2: old
-
- using (var write = tripleBuffer.GetForWrite())
- {
- Assert.That(write.Object?.ID, Is.EqualTo(1));
-
- // buffer 0: reading
- // buffer 1: writing
- // buffer 2: old
- }
- }
-
- using (var read = tripleBuffer.GetForRead())
+ // Test with first write in use during second.
+ using (tripleBuffer.GetForWrite())
{
- Assert.That(read?.Object?.ID, Is.EqualTo(1));
-
- // buffer 0: old
- // buffer 1: reading
- // buffer 2: old
}
- using (var write = tripleBuffer.GetForWrite())
- {
- Assert.That(write.Object?.ID, Is.EqualTo(0));
-
- // buffer 0: writing
- // buffer 1: last read
- // buffer 2: old
- }
+ int? lastRead = null;
+ int? lastWrite = null;
- using (var read = tripleBuffer.GetForRead())
+ for (int i = 0; i < 3; i++)
{
- Assert.That(read?.Object?.ID, Is.EqualTo(0));
-
- // buffer 0: reading
- // buffer 1: old
- // buffer 2: old
-
- using (var write = tripleBuffer.GetForWrite())
+ using (var read = tripleBuffer.GetForRead())
{
- Assert.That(write.Object?.ID, Is.EqualTo(1));
-
- // buffer 0: reading
- // buffer 1: writing
- // buffer 2: old
- }
-
- using (var write = tripleBuffer.GetForWrite())
- {
- Assert.That(write.Object?.ID, Is.EqualTo(2));
-
- // buffer 0: reading
- // buffer 1: waiting for read
- // buffer 2: writing
+ Assert.That(read!.Object!.ID, Is.Not.EqualTo(lastRead));
+
+ for (int j = 0; j < 3; j++)
+ {
+ using (var write = tripleBuffer.GetForWrite())
+ {
+ Assert.That(write.Object!.ID, Is.Not.EqualTo(lastWrite));
+ Assert.That(write.Object!.ID, Is.Not.EqualTo(read.Object?.ID));
+ lastWrite = write.Object!.ID;
+ }
+ }
}
}
}
diff --git a/osu.Framework.Tests/IO/TestOnlineStore.cs b/osu.Framework.Tests/IO/TestOnlineStore.cs
new file mode 100644
index 0000000000..f673909ba9
--- /dev/null
+++ b/osu.Framework.Tests/IO/TestOnlineStore.cs
@@ -0,0 +1,121 @@
+// 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 System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Extensions;
+using osu.Framework.IO.Stores;
+
+namespace osu.Framework.Tests.IO
+{
+ [TestFixture]
+ [Category("httpbin")]
+ public class TestOnlineStore
+ {
+ private const string default_protocol = "http";
+
+ private static readonly string host;
+ private static readonly IEnumerable protocols;
+
+ private bool oldAllowInsecureRequests;
+ private OnlineStore store = null!;
+
+ static TestOnlineStore()
+ {
+ bool localHttpBin = Environment.GetEnvironmentVariable("OSU_TESTS_LOCAL_HTTPBIN") == "1";
+
+ if (localHttpBin)
+ {
+ // httpbin very frequently falls over and causes random tests to fail
+ // Thus github actions builds rely on a local httpbin instance to run the tests
+
+ host = "127.0.0.1:8080";
+ protocols = new[] { default_protocol };
+ }
+ else
+ {
+ host = "httpbin.org";
+ protocols = new[] { default_protocol, "https" };
+ }
+ }
+
+ [OneTimeSetUp]
+ public void GlobalSetup()
+ {
+ oldAllowInsecureRequests = FrameworkEnvironment.AllowInsecureRequests;
+ FrameworkEnvironment.AllowInsecureRequests = true;
+ }
+
+ [OneTimeTearDown]
+ public void GlobalTeardown()
+ {
+ FrameworkEnvironment.AllowInsecureRequests = oldAllowInsecureRequests;
+ }
+
+ [SetUp]
+ public void Setup()
+ {
+ store = new OnlineStore();
+ }
+
+ [Test, Retry(5)]
+ public void TestValidUrlReturnsData([ValueSource(nameof(protocols))] string protocol, [Values(true, false)] bool async)
+ {
+ byte[]? result = async
+ ? store.GetAsync($"{protocol}://{host}/image/png").GetResultSafely()
+ : store.Get($"{protocol}://{host}/image/png");
+
+ Assert.That(result, Is.Not.Null);
+ Assert.That(result, Has.Length.GreaterThan(0));
+ }
+
+ [Test]
+ public void TestMissingSchemeReturnsNull([Values(true, false)] bool async)
+ {
+ byte[]? result = async
+ ? store.GetAsync($"{host}/image/png").GetResultSafely()
+ : store.Get($"{host}/image/png");
+
+ Assert.That(result, Is.Null);
+ }
+
+ [Test]
+ public void TestInvalidUrlReturnsNull()
+ {
+ byte[]? result = store.Get("this is not a valid url");
+ Assert.That(result, Is.Null);
+ }
+
+ [Test]
+ public void TestNullUrlReturnsNull()
+ {
+ // Not sure if this store should accept a null URL, but let's test it anyway.
+ byte[]? result = store.Get(null);
+ Assert.That(result, Is.Null);
+ }
+
+ [Test]
+ public void TestFileUrlFails([Values(true, false)] bool async)
+ {
+ // Known, guaranteed file path.
+ string path = new Uri(AppContext.BaseDirectory).AbsoluteUri;
+
+ byte[]? result = async
+ ? store.GetAsync(path).GetResultSafely()
+ : store.Get(path);
+
+ Assert.That(result, Is.Null);
+ }
+
+ [Test]
+ public void TestBadWebRequest([ValueSource(nameof(protocols))] string protocol, [Values(true, false)] bool async)
+ {
+ byte[]? result = async
+ ? store.GetAsync($"{protocol}://{host}/status/500").GetResultSafely()
+ : store.Get($"{protocol}://{host}/status/500");
+
+ Assert.That(result, Is.Null);
+ }
+ }
+}
diff --git a/osu.Framework.Tests/IO/TestWebRequest.cs b/osu.Framework.Tests/IO/TestWebRequest.cs
index 263fa788de..5dc898de18 100644
--- a/osu.Framework.Tests/IO/TestWebRequest.cs
+++ b/osu.Framework.Tests/IO/TestWebRequest.cs
@@ -32,6 +32,8 @@ public class TestWebRequest
private static readonly string host;
private static readonly IEnumerable protocols;
+ private bool oldAllowInsecureRequests;
+
static TestWebRequest()
{
bool localHttpBin = Environment.GetEnvironmentVariable("OSU_TESTS_LOCAL_HTTPBIN") == "1";
@@ -51,14 +53,26 @@ static TestWebRequest()
}
}
+ [SetUp]
+ public void Setup()
+ {
+ oldAllowInsecureRequests = FrameworkEnvironment.AllowInsecureRequests;
+ FrameworkEnvironment.AllowInsecureRequests = true;
+ }
+
+ [TearDown]
+ public void Teardown()
+ {
+ FrameworkEnvironment.AllowInsecureRequests = oldAllowInsecureRequests;
+ }
+
[Test, Retry(5)]
public void TestValidGet([ValueSource(nameof(protocols))] string protocol, [Values(true, false)] bool async)
{
string url = $"{protocol}://{host}/get";
var request = new JsonWebRequest(url)
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true
+ Method = HttpMethod.Get
};
testValidGetInternal(async, request, "osu-framework");
@@ -74,8 +88,7 @@ public void TestValidGetFromTask([ValueSource(nameof(protocols))] string protoco
string url = $"{protocol}://{host}/get";
var request = new JsonWebRequest(url)
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true
+ Method = HttpMethod.Get
};
Task.Run(() => testValidGetInternal(false, request, "osu-framework")).WaitSafely();
@@ -87,8 +100,7 @@ public void TestCustomUserAgent([ValueSource(nameof(protocols))] string protocol
string url = $"{protocol}://{host}/get";
var request = new CustomUserAgentWebRequest(url)
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true
+ Method = HttpMethod.Get
};
testValidGetInternal(async, request, "custom-ua");
@@ -143,7 +155,6 @@ public void TestConcurrency()
var request = new DelayedWebRequest
{
Method = HttpMethod.Get,
- AllowInsecureRequests = true,
Delay = induced_delay
};
@@ -186,8 +197,7 @@ public void TestInvalidGetExceptions([ValueSource(nameof(protocols))] string pro
{
var request = new WebRequest($"{protocol}://{invalid_get_url}")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true
+ Method = HttpMethod.Get
};
Exception finishedException = null;
@@ -208,10 +218,7 @@ public void TestInvalidGetExceptions([ValueSource(nameof(protocols))] string pro
[Test, Retry(5)]
public void TestBadStatusCode([Values(true, false)] bool async)
{
- var request = new WebRequest($"{default_protocol}://{host}/hidden-basic-auth/user/passwd")
- {
- AllowInsecureRequests = true,
- };
+ var request = new WebRequest($"{default_protocol}://{host}/hidden-basic-auth/user/passwd");
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
@@ -230,10 +237,7 @@ public void TestBadStatusCode([Values(true, false)] bool async)
[Test, Retry(5)]
public void TestJsonWebRequestThrowsCorrectlyOnMultipleErrors([Values(true, false)] bool async)
{
- var request = new JsonWebRequest("badrequest://www.google.com")
- {
- AllowInsecureRequests = true,
- };
+ var request = new JsonWebRequest("badrequest://www.google.com");
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
@@ -261,8 +265,7 @@ public void TestAbortReceive([Values(true, false)] bool async)
{
var request = new JsonWebRequest($"{default_protocol}://{host}/get")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Get
};
bool hasThrown = false;
@@ -290,8 +293,7 @@ public void TestAbortRequest()
{
var request = new JsonWebRequest($"{default_protocol}://{host}/get")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Get
};
bool hasThrown = false;
@@ -317,8 +319,7 @@ public void TestStartAfterAbort([Values(true, false)] bool async)
{
var request = new JsonWebRequest($"{default_protocol}://{host}/get")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Get
};
bool hasThrown = false;
@@ -348,8 +349,7 @@ public void TestRestartAfterAbort([Values(true, false)] bool async)
{
var request = new JsonWebRequest($"{default_protocol}://{host}/delay/10")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Get
};
bool hasThrown = false;
@@ -383,8 +383,7 @@ public void TestCancelReceive()
var cancellationSource = new CancellationTokenSource();
var request = new JsonWebRequest($"{default_protocol}://{host}/get")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Get
};
bool hasThrown = false;
@@ -409,8 +408,7 @@ public async Task TestCancelRequest()
var cancellationSource = new CancellationTokenSource();
var request = new JsonWebRequest($"{default_protocol}://{host}/get")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Get
};
bool hasThrown = false;
@@ -436,8 +434,7 @@ public void TestRestartAfterAbortViaCancellationToken()
var cancellationSource = new CancellationTokenSource();
var request = new JsonWebRequest($"{default_protocol}://{host}/get")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Get
};
bool hasThrown = false;
@@ -466,7 +463,6 @@ public void TestOneTimeout()
var request = new DelayedWebRequest
{
Method = HttpMethod.Get,
- AllowInsecureRequests = true,
Timeout = 1000,
Delay = 2
};
@@ -493,7 +489,6 @@ public void TestFailTimeout()
var request = new WebRequest($"{default_protocol}://{host}/delay/4")
{
Method = HttpMethod.Get,
- AllowInsecureRequests = true,
Timeout = 1000
};
@@ -518,8 +513,7 @@ public void TestEventUnbindOnCompletion([Values(true, false)] bool async)
{
var request = new JsonWebRequest($"{default_protocol}://{host}/get")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Get
};
request.Started += () => { };
@@ -546,8 +540,7 @@ public void TestUnbindOnDispose([Values(true, false)] bool async)
{
var request = new JsonWebRequest($"{default_protocol}://{host}/get")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Get
};
using (request)
@@ -580,8 +573,7 @@ public void TestGetWithQueryStringParameters()
var request = new JsonWebRequest($@"{default_protocol}://{host}/get")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true
+ Method = HttpMethod.Get
};
request.AddParameter(test_key_1, test_val_1);
@@ -608,8 +600,7 @@ public void TestPostWithJsonResponse([Values(true, false)] bool async)
{
var request = new JsonWebRequest($"{default_protocol}://{host}/post")
{
- Method = HttpMethod.Post,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Post
};
request.AddParameter("testkey1", "testval1");
@@ -645,7 +636,6 @@ public void TestPostWithJsonRequest([Values(true, false)] bool async)
var request = new JsonWebRequest($"{default_protocol}://{host}/post")
{
Method = HttpMethod.Post,
- AllowInsecureRequests = true,
ContentType = "application/json"
};
@@ -672,8 +662,7 @@ public void TestNoContentPost([Values(true, false)] bool async)
{
var request = new WebRequest($"{default_protocol}://{host}/post")
{
- Method = HttpMethod.Post,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Post
};
if (async)
@@ -702,8 +691,7 @@ public void TestPutWithQueryAndFormParams()
var request = new JsonWebRequest($"{default_protocol}://{host}/put")
{
- Method = HttpMethod.Put,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Put
};
request.AddParameter(test_key_1, test_val_1, RequestParameterType.Query);
@@ -735,8 +723,7 @@ public void TestFormParamsNotSupportedForGet()
{
var request = new JsonWebRequest($"{default_protocol}://{host}/get")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Get
};
Assert.Throws(() => request.AddParameter("cannot", "work", RequestParameterType.Form));
@@ -777,7 +764,6 @@ Also note that the TPL thread pool generally gets much higher values than this (
var request = new DelayedWebRequest
{
Method = HttpMethod.Get,
- AllowInsecureRequests = true,
Timeout = 1000,
Delay = 2
};
@@ -804,8 +790,7 @@ public void TestGetBinaryData([Values(true, false)] bool async, [Values(true, fa
WebRequest request = new WebRequest($"{default_protocol}://{host}/{endpoint}/{bytes_count}")
{
- Method = HttpMethod.Get,
- AllowInsecureRequests = true,
+ Method = HttpMethod.Get
};
if (chunked)
request.AddParameter("chunk_size", chunk_size.ToString());
@@ -821,6 +806,21 @@ public void TestGetBinaryData([Values(true, false)] bool async, [Values(true, fa
Assert.AreEqual(bytes_count, request.ResponseStream.Length);
}
+ [Test, Retry(5)]
+ public void TestAllowInsecureRequestsOverride([ValueSource(nameof(protocols))] string protocol, [Values(true, false)] bool async)
+ {
+ FrameworkEnvironment.AllowInsecureRequests = false;
+
+ string url = $"{protocol}://{host}/get";
+ var request = new JsonWebRequest(url)
+ {
+ Method = HttpMethod.Get,
+ AllowInsecureRequests = true
+ };
+
+ testValidGetInternal(async, request, "osu-framework");
+ }
+
private static Dictionary convertDictionary(Dictionary dict)
{
var result = new Dictionary();
diff --git a/osu.Framework.Tests/Input/KeyCombinationTest.cs b/osu.Framework.Tests/Input/KeyCombinationTest.cs
index 618c260ce3..37a6caff3d 100644
--- a/osu.Framework.Tests/Input/KeyCombinationTest.cs
+++ b/osu.Framework.Tests/Input/KeyCombinationTest.cs
@@ -1,8 +1,11 @@
// 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 NUnit.Framework;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.States;
+using osuTK.Input;
namespace osu.Framework.Tests.Input
{
@@ -14,37 +17,34 @@ public class KeyCombinationTest
// test single combination matches.
new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.LShift), KeyCombinationMatchingMode.Any, true },
new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.RShift), KeyCombinationMatchingMode.Any, true },
- new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.Shift), KeyCombinationMatchingMode.Any, true },
new object[] { new KeyCombination(InputKey.LShift), new KeyCombination(InputKey.RShift), KeyCombinationMatchingMode.Any, false },
new object[] { new KeyCombination(InputKey.RShift), new KeyCombination(InputKey.RShift), KeyCombinationMatchingMode.Any, true },
new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.LShift), KeyCombinationMatchingMode.Exact, true },
new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.RShift), KeyCombinationMatchingMode.Exact, true },
- new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.Shift), KeyCombinationMatchingMode.Exact, true },
new object[] { new KeyCombination(InputKey.LShift), new KeyCombination(InputKey.RShift), KeyCombinationMatchingMode.Exact, false },
new object[] { new KeyCombination(InputKey.RShift), new KeyCombination(InputKey.RShift), KeyCombinationMatchingMode.Exact, true },
new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.LShift), KeyCombinationMatchingMode.Modifiers, true },
new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.RShift), KeyCombinationMatchingMode.Modifiers, true },
- new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.Shift), KeyCombinationMatchingMode.Modifiers, true },
new object[] { new KeyCombination(InputKey.LShift), new KeyCombination(InputKey.RShift), KeyCombinationMatchingMode.Modifiers, false },
new object[] { new KeyCombination(InputKey.RShift), new KeyCombination(InputKey.RShift), KeyCombinationMatchingMode.Modifiers, true },
// test multiple combination matches.
- new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.Shift, InputKey.LShift), KeyCombinationMatchingMode.Any, true },
- new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.Shift, InputKey.RShift), KeyCombinationMatchingMode.Any, true },
- new object[] { new KeyCombination(InputKey.LShift), new KeyCombination(InputKey.Shift, InputKey.RShift), KeyCombinationMatchingMode.Any, false },
- new object[] { new KeyCombination(InputKey.RShift), new KeyCombination(InputKey.Shift, InputKey.RShift), KeyCombinationMatchingMode.Any, true },
+ new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.LShift, InputKey.RShift), KeyCombinationMatchingMode.Any, true },
+ new object[] { new KeyCombination(InputKey.LShift), new KeyCombination(InputKey.LShift, InputKey.RShift), KeyCombinationMatchingMode.Any, true },
+ new object[] { new KeyCombination(InputKey.RShift), new KeyCombination(InputKey.LShift, InputKey.RShift), KeyCombinationMatchingMode.Any, true },
+ new object[] { new KeyCombination(InputKey.RShift), new KeyCombination(InputKey.RShift, InputKey.A), KeyCombinationMatchingMode.Any, true },
- new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.Shift, InputKey.LShift), KeyCombinationMatchingMode.Exact, true },
- new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.Shift, InputKey.RShift), KeyCombinationMatchingMode.Exact, true },
- new object[] { new KeyCombination(InputKey.LShift), new KeyCombination(InputKey.Shift, InputKey.RShift), KeyCombinationMatchingMode.Exact, false },
- new object[] { new KeyCombination(InputKey.RShift), new KeyCombination(InputKey.Shift, InputKey.RShift), KeyCombinationMatchingMode.Exact, true },
+ new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.LShift, InputKey.RShift), KeyCombinationMatchingMode.Exact, true },
+ new object[] { new KeyCombination(InputKey.LShift), new KeyCombination(InputKey.LShift, InputKey.RShift), KeyCombinationMatchingMode.Exact, false },
+ new object[] { new KeyCombination(InputKey.RShift), new KeyCombination(InputKey.LShift, InputKey.RShift), KeyCombinationMatchingMode.Exact, false },
+ new object[] { new KeyCombination(InputKey.RShift), new KeyCombination(InputKey.RShift, InputKey.A), KeyCombinationMatchingMode.Exact, false },
- new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.Shift, InputKey.LShift), KeyCombinationMatchingMode.Modifiers, true },
- new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.Shift, InputKey.RShift), KeyCombinationMatchingMode.Modifiers, true },
- new object[] { new KeyCombination(InputKey.LShift), new KeyCombination(InputKey.Shift, InputKey.RShift), KeyCombinationMatchingMode.Modifiers, false },
- new object[] { new KeyCombination(InputKey.RShift), new KeyCombination(InputKey.Shift, InputKey.RShift), KeyCombinationMatchingMode.Modifiers, true },
+ new object[] { new KeyCombination(InputKey.Shift), new KeyCombination(InputKey.LShift, InputKey.RShift), KeyCombinationMatchingMode.Modifiers, true },
+ new object[] { new KeyCombination(InputKey.LShift), new KeyCombination(InputKey.LShift, InputKey.RShift), KeyCombinationMatchingMode.Modifiers, false },
+ new object[] { new KeyCombination(InputKey.RShift), new KeyCombination(InputKey.LShift, InputKey.RShift), KeyCombinationMatchingMode.Modifiers, false },
+ new object[] { new KeyCombination(InputKey.RShift), new KeyCombination(InputKey.RShift, InputKey.A), KeyCombinationMatchingMode.Modifiers, true },
};
[TestCaseSource(nameof(key_combination_display_test_cases))]
@@ -68,5 +68,16 @@ public void TestCreationWithDuplicates()
Assert.That(keyCombination.Keys[0], Is.EqualTo(InputKey.Control));
Assert.That(keyCombination.Keys[1], Is.EqualTo(InputKey.A));
}
+
+ [Test]
+ public void TestEmptyCombinationIsNeverPressed()
+ {
+ var keyCombination = new KeyCombination(Array.Empty());
+
+ var state = new InputState();
+ state.Keyboard.Keys.SetPressed(Key.X, true);
+
+ Assert.That(keyCombination.IsPressed(new KeyCombination(InputKey.X), state, KeyCombinationMatchingMode.Any), Is.False);
+ }
}
}
diff --git a/osu.Framework.Tests/Layout/TestSceneContainerLayout.cs b/osu.Framework.Tests/Layout/TestSceneContainerLayout.cs
index 754f84eb5e..81cd0577d0 100644
--- a/osu.Framework.Tests/Layout/TestSceneContainerLayout.cs
+++ b/osu.Framework.Tests/Layout/TestSceneContainerLayout.cs
@@ -7,6 +7,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Layout;
using osu.Framework.Testing;
@@ -498,6 +499,42 @@ public void TestNonAutoSizingParentDoesNotInvalidateSizeDependenciesFromChild()
AddAssert("still valid", () => isValid);
}
+ ///
+ /// Tests that changing Masking property will invalidate child masking bounds.
+ ///
+ [Test]
+ public void TestChildMaskingInvalidationOnMaskingChange()
+ {
+ Container parent = null;
+ Container child = null;
+ RectangleF childMaskingBounds = new RectangleF();
+ RectangleF actualChildMaskingBounds = new RectangleF();
+
+ AddStep("createTest", () =>
+ {
+ Child = parent = new Container
+ {
+ Size = new Vector2(100),
+ Child = child = new Container
+ {
+ RelativeSizeAxes = Axes.Both
+ }
+ };
+ });
+
+ AddAssert("Parent's masking is off", () => parent.Masking == false);
+ AddStep("Save masking bounds", () =>
+ {
+ childMaskingBounds = parent.ChildMaskingBounds;
+ actualChildMaskingBounds = child.ChildMaskingBounds;
+ });
+ AddAssert("Parent and child have the same masking bounds", () => childMaskingBounds == actualChildMaskingBounds);
+ AddStep("Enable parent masking", () => parent.Masking = true);
+ AddAssert("Parent's ChildMaskingBounds has changed", () => childMaskingBounds != parent.ChildMaskingBounds);
+ AddAssert("Child's masking bounds has changed", () => actualChildMaskingBounds != child.ChildMaskingBounds);
+ AddAssert("Parent and child have the same masking bounds", () => parent.ChildMaskingBounds == child.ChildMaskingBounds);
+ }
+
private partial class TestBox1 : Box
{
public override bool RemoveWhenNotAlive => false;
diff --git a/osu.Framework.Tests/Localisation/LocalisableStringTest.cs b/osu.Framework.Tests/Localisation/LocalisableStringTest.cs
index a026a68c04..f6195c9548 100644
--- a/osu.Framework.Tests/Localisation/LocalisableStringTest.cs
+++ b/osu.Framework.Tests/Localisation/LocalisableStringTest.cs
@@ -99,6 +99,7 @@ public void TestNullEqualsNull()
public void TestLocalisableStringDoesNotEqualNull()
{
testEquals(false, new LocalisableString(), new RomanisableString(makeStringA, makeStringB));
+ testEquals(false, new RomanisableString(makeStringA, makeStringB), new LocalisableString());
}
[Test]
diff --git a/osu.Framework.Tests/Resources/Textures/sample-nine-slice-texture.png b/osu.Framework.Tests/Resources/Textures/sample-nine-slice-texture.png
new file mode 100644
index 0000000000..cb881832df
Binary files /dev/null and b/osu.Framework.Tests/Resources/Textures/sample-nine-slice-texture.png differ
diff --git a/osu.Framework.Tests/Visual/Audio/TestSceneAudioMixer.cs b/osu.Framework.Tests/Visual/Audio/TestSceneAudioMixer.cs
index 3234d0a852..0fa2481396 100644
--- a/osu.Framework.Tests/Visual/Audio/TestSceneAudioMixer.cs
+++ b/osu.Framework.Tests/Visual/Audio/TestSceneAudioMixer.cs
@@ -4,10 +4,8 @@
using System.Linq;
using ManagedBass;
using ManagedBass.Fx;
-using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
@@ -21,64 +19,88 @@ namespace osu.Framework.Tests.Visual.Audio
{
public partial class TestSceneAudioMixer : FrameworkTestScene
{
- [SetUp]
- public void Setup() => Schedule(() =>
- {
- ContainerWithEffect noEffectContainer;
- FillFlowContainer effectContainers;
+ private readonly DragHandle dragHandle;
+ private readonly AudioPlayingDrawable audioDrawable;
+ private readonly ContainerWithEffect noEffectContainer;
+ private readonly FillFlowContainer effectContainers;
- Child = noEffectContainer = new ContainerWithEffect("no effect", Color4.Black)
+ public TestSceneAudioMixer()
+ {
+ AddRange(new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Size = new Vector2(1),
- Child = new Container
+ noEffectContainer = new ContainerWithEffect("no effect", Color4.Black, null)
{
RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding(20),
- Child = effectContainers = new FillFlowContainer
+ Size = new Vector2(1),
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
+ effectContainers = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(20)
+ },
+ audioDrawable = new AudioPlayingDrawable { Origin = Anchor.Centre }
}
+ },
+ dragHandle = new DragHandle
+ {
+ Origin = Anchor.Centre,
+ Position = new Vector2(50)
}
- };
+ });
for (int i = 0; i < 50; i++)
{
float centre = 150 + 50 * i;
- effectContainers.Add(new ContainerWithEffect($"<{centre}Hz", Color4.Blue)
+ effectContainers.Add(new ContainerWithEffect($"<{centre}Hz", Color4.Blue, new BQFParameters
+ {
+ lFilter = BQFType.LowPass,
+ fCenter = centre
+ })
{
Size = new Vector2(100),
- Effects =
- {
- new BQFParameters
- {
- lFilter = BQFType.LowPass,
- fCenter = centre
- }
- }
});
}
+ }
- AudioBox audioBox;
- noEffectContainer.Add(audioBox = new AudioBox(noEffectContainer, effectContainers));
+ protected override void Update()
+ {
+ base.Update();
- Add(audioBox.CreateProxy());
- });
+ Vector2 pos = dragHandle.ScreenSpaceDrawQuad.Centre;
+ Container container = effectContainers.SingleOrDefault(c => c.ScreenSpaceDrawQuad.Contains(pos)) ?? noEffectContainer;
- private partial class AudioBox : CompositeDrawable
- {
- private readonly Container defaultParent;
- private readonly Container effectContainers;
+ if (audioDrawable.Parent != container)
+ {
+ audioDrawable.Parent!.RemoveInternal(audioDrawable, false);
+ container.Add(audioDrawable);
+ }
+ }
- public AudioBox(Container defaultParent, Container effectContainers)
+ private partial class AudioPlayingDrawable : CompositeDrawable
+ {
+ [BackgroundDependencyLoader]
+ private void load(ISampleStore samples)
{
- this.defaultParent = defaultParent;
- this.effectContainers = effectContainers;
+ DrawableSample sample;
+
+ AddInternal(new AudioContainer
+ {
+ Volume = { Value = 0.5f },
+ Child = sample = new DrawableSample(samples.Get("long.mp3"))
+ });
- currentContainer = defaultParent;
+ var channel = sample.GetChannel();
+ channel.Looping = true;
+ channel.Play();
+ }
+ }
- Origin = Anchor.Centre;
+ private partial class DragHandle : CompositeDrawable
+ {
+ public DragHandle()
+ {
Size = new Vector2(50);
InternalChild = new CircularContainer
@@ -104,64 +126,24 @@ public AudioBox(Container defaultParent, Container true;
- protected override void OnDrag(DragEvent e)
- {
- Position += e.Delta;
- }
-
- private Container currentContainer;
-
- protected override void Update()
- {
- base.Update();
-
- Vector2 centre = ScreenSpaceDrawQuad.Centre;
-
- Container targetContainer = effectContainers.FirstOrDefault(c => c.Contains(centre)) ?? defaultParent;
- if (targetContainer == currentContainer)
- return;
-
- currentContainer.Remove(this, false);
- targetContainer.Add(this);
-
- Position = Parent!.ToLocalSpace(centre);
-
- currentContainer = targetContainer;
- }
+ protected override void OnDrag(DragEvent e) => Position = e.MousePosition;
}
private partial class ContainerWithEffect : Container
{
protected override Container Content => content;
- private readonly DrawableAudioMixer mixer;
private readonly Container content;
-
private readonly Drawable background;
- public ContainerWithEffect(string name, Color4 colour)
+ public ContainerWithEffect(string name, Color4 colour, IEffectParameter? effect)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
+ DrawableAudioMixer mixer;
InternalChild = mixer = new DrawableAudioMixer
{
RelativeSizeAxes = Axes.Both,
@@ -186,14 +168,14 @@ public ContainerWithEffect(string name, Color4 colour)
}
}
};
- }
- public BindableList Effects => mixer.Effects;
+ if (effect != null)
+ mixer.AddEffect(effect);
+ }
protected override void Update()
{
base.Update();
-
background.Alpha = content.Count > 0 ? 1 : 0.2f;
}
}
diff --git a/osu.Framework.Tests/Visual/Containers/TestSceneVirtualisedListContainer.cs b/osu.Framework.Tests/Visual/Containers/TestSceneVirtualisedListContainer.cs
new file mode 100644
index 0000000000..b137b68886
--- /dev/null
+++ b/osu.Framework.Tests/Visual/Containers/TestSceneVirtualisedListContainer.cs
@@ -0,0 +1,173 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Pooling;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Events;
+
+namespace osu.Framework.Tests.Visual.Containers
+{
+ [TestFixture]
+ public partial class TestSceneVirtualisedListContainer : FrameworkTestScene
+ {
+ [Test]
+ public void TestNaiveList()
+ {
+ AddStep("create list", () => Child = new BasicScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ ChildrenEnumerable = Enumerable.Range(1, 10000).Select(i => new DrawableItem { Current = { Value = $"Item #{i}" } })
+ }
+ });
+ }
+
+ [Test]
+ public void TestVirtualisedList()
+ {
+ ExampleVirtualisedList list = null!;
+ AddStep("create list", () =>
+ {
+ Child = list = new ExampleVirtualisedList
+ {
+ RelativeSizeAxes = Axes.Both,
+ };
+ list.RowData.AddRange(Enumerable.Range(1, 10000).Select(i => $"Item #{i}"));
+ });
+ AddStep("replace items", () =>
+ {
+ list.RowData.Clear();
+ list.RowData.AddRange(Enumerable.Range(10001, 10000).Select(i => $"Item #{i}"));
+ });
+ }
+
+ [Test]
+ public void TestVirtualisedListDisposal()
+ {
+ ExampleVirtualisedList list = null!;
+ AddStep("create list nested in container", () =>
+ {
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = list = new ExampleVirtualisedList
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ }
+ };
+ list.RowData.AddRange(Enumerable.Range(1, 10000).Select(i => $"Item #{i}"));
+ });
+ AddStep("clear", Clear);
+ AddUntilStep("wait for async disposal", () => list.IsDisposed);
+ }
+
+ [Test]
+ public void TestCollectionChangeHandling()
+ {
+ ExampleVirtualisedList list = null!;
+
+ AddStep("create list", () =>
+ {
+ Child = list = new ExampleVirtualisedList
+ {
+ RelativeSizeAxes = Axes.Both,
+ };
+ list.RowData.AddRange(Enumerable.Range(1, 10).Select(i => $"Item #{i}"));
+ });
+
+ AddStep("insert at start", () => list.RowData.Insert(0, "first"));
+ AddStep("insert at end", () => list.RowData.Add("last"));
+ AddStep("remove from middle", () => list.RowData.RemoveAt(5));
+ AddStep("move forward", () => list.RowData.Move(0, 4));
+ AddStep("move back", () => list.RowData.Move(5, 2));
+ AddStep("replace", () => list.RowData[3] = "replacing");
+ AddStep("clear", () => list.RowData.Clear());
+ }
+
+ private partial class DrawableItem : PoolableDrawable, IHasCurrentValue
+ {
+ public const int HEIGHT = 25;
+
+ private readonly BindableWithCurrent current = new BindableWithCurrent();
+
+ private Box background = null!;
+ private SpriteText text = null!;
+
+ public Bindable Current
+ {
+ get => current.Current;
+ set => current.Current = value;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = HEIGHT;
+ InternalChildren = new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = FrameworkColour.GreenDark,
+ },
+ text = new SpriteText
+ {
+ RelativeSizeAxes = Axes.X,
+ Margin = new MarginPadding { Left = 10, },
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Current.BindValueChanged(_ => text.Text = Current.Value, true);
+ updateState();
+ FinishTransforms(true);
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ updateState();
+ return true;
+ }
+
+ private void updateState() => background.FadeTo(IsHovered ? 1 : 0, 300, Easing.OutQuint);
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ updateState();
+ }
+ }
+
+ private partial class ExampleVirtualisedList : VirtualisedListContainer
+ {
+ public ExampleVirtualisedList()
+ : base(DrawableItem.HEIGHT, 50)
+ {
+ }
+
+ protected override ScrollContainer CreateScrollContainer() => new BasicScrollContainer();
+ }
+ }
+}
diff --git a/osu.Framework.Tests/Visual/Drawables/TestSceneFastCircle.cs b/osu.Framework.Tests/Visual/Drawables/TestSceneFastCircle.cs
new file mode 100644
index 0000000000..b0e464e289
--- /dev/null
+++ b/osu.Framework.Tests/Visual/Drawables/TestSceneFastCircle.cs
@@ -0,0 +1,188 @@
+// 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 NUnit.Framework;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Events;
+using osu.Framework.Testing;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Framework.Tests.Visual.Drawables
+{
+ public partial class TestSceneFastCircle : ManualInputManagerTestScene
+ {
+ private TestCircle fastCircle = null!;
+ private Circle circle = null!;
+ private CircularContainer fastCircleMask = null!;
+ private CircularContainer circleMask = null!;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Child = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ RowDimensions = new[]
+ {
+ new Dimension(GridSizeMode.Absolute, 100),
+ new Dimension(),
+ },
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.Relative, 0.5f),
+ new Dimension()
+ },
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new SpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = "FastCircle"
+ },
+ new SpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = "Circle"
+ }
+ },
+ new Drawable[]
+ {
+ fastCircleMask = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.TopRight,
+ Size = new Vector2(200),
+ Child = fastCircle = new TestCircle
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.Centre,
+ Size = new Vector2(200),
+ Clicked = onClick
+ }
+ },
+ circleMask = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.TopRight,
+ Size = new Vector2(200),
+ Child = circle = new Circle
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.Centre,
+ Size = new Vector2(200)
+ }
+ },
+ }
+ }
+ };
+ });
+
+ [Test]
+ public void TestInput()
+ {
+ testInput(new Vector2(200, 100));
+ testInput(new Vector2(100, 200));
+ testInput(new Vector2(200, 200));
+ }
+
+ [Test]
+ public void TestSmoothness()
+ {
+ AddStep("Change smoothness to 0", () => fastCircle.EdgeSmoothness = circle.MaskingSmoothness = 0);
+ AddStep("Change smoothness to 1", () => fastCircle.EdgeSmoothness = circle.MaskingSmoothness = 1);
+ AddStep("Change smoothness to 5", () => fastCircle.EdgeSmoothness = circle.MaskingSmoothness = 5);
+ }
+
+ [Test]
+ public void TestNestedMasking()
+ {
+ AddToggleStep("Toggle parent masking", m => fastCircleMask.Masking = circleMask.Masking = m);
+ }
+
+ [Test]
+ public void TestRotation()
+ {
+ resize(new Vector2(200, 100));
+ AddToggleStep("Toggle rotation", rotate =>
+ {
+ fastCircle.ClearTransforms();
+ circle.ClearTransforms();
+
+ if (rotate)
+ {
+ fastCircle.Spin(2000, RotationDirection.Clockwise);
+ circle.Spin(2000, RotationDirection.Clockwise);
+ }
+ });
+ }
+
+ [Test]
+ public void TestShear()
+ {
+ resize(new Vector2(200, 100));
+ AddToggleStep("Toggle shear", shear =>
+ {
+ fastCircle.Shear = circle.Shear = shear ? new Vector2(0.5f, 0) : Vector2.Zero;
+ });
+ }
+
+ [Test]
+ public void TestScale()
+ {
+ resize(new Vector2(200, 100));
+ AddToggleStep("Toggle scale", scale =>
+ {
+ fastCircle.Scale = circle.Scale = scale ? new Vector2(2f, 1f) : Vector2.One;
+ });
+ }
+
+ private void testInput(Vector2 size)
+ {
+ resize(size);
+ AddStep("Click outside the corner", () => clickNearCorner(-Vector2.One));
+ AddAssert("input not received", () => clicked == false);
+ AddStep("Click inside the corner", () => clickNearCorner(Vector2.One));
+ AddAssert("input received", () => clicked);
+ }
+
+ private void resize(Vector2 size)
+ {
+ AddStep($"Resize to {size}", () =>
+ {
+ fastCircle.Size = circle.Size = size;
+ });
+ }
+
+ private void clickNearCorner(Vector2 offset)
+ {
+ clicked = false;
+ InputManager.MoveMouseTo(fastCircle.ToScreenSpace(new Vector2(fastCircle.Radius * (1f - MathF.Sqrt(0.5f))) + offset));
+ InputManager.Click(MouseButton.Left);
+ }
+
+ private bool clicked;
+
+ private void onClick() => clicked = true;
+
+ private partial class TestCircle : FastCircle
+ {
+ public Action? Clicked;
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ base.OnClick(e);
+ Clicked?.Invoke();
+ return true;
+ }
+ }
+ }
+}
diff --git a/osu.Framework.Tests/Visual/Drawables/TestSceneFocus.cs b/osu.Framework.Tests/Visual/Drawables/TestSceneFocus.cs
index 64e5701afa..1da78d6cad 100644
--- a/osu.Framework.Tests/Visual/Drawables/TestSceneFocus.cs
+++ b/osu.Framework.Tests/Visual/Drawables/TestSceneFocus.cs
@@ -10,6 +10,7 @@
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Framework.Testing;
@@ -135,7 +136,7 @@ public void RequestsFocusLosesFocusOnClickingFocused()
}
///
- /// Ensures that performing to a drawable with disabled returns .
+ /// Ensures that performing to a drawable with disabled returns .
///
[Test]
public void DisabledFocusDrawableCannotReceiveFocusViaChangeFocus()
@@ -143,13 +144,13 @@ public void DisabledFocusDrawableCannotReceiveFocusViaChangeFocus()
checkFocused(() => requestingFocus);
AddStep("disable focus from top left", () => focusTopLeft.AllowAcceptingFocus = false);
- AddAssert("cannot switch focus to top left", () => !InputManager.ChangeFocus(focusTopLeft));
+ AddAssert("cannot switch focus to top left", () => !((IFocusManager)InputManager).ChangeFocus(focusTopLeft));
checkFocused(() => requestingFocus);
}
///
- /// Ensures that performing to a non-present drawable returns .
+ /// Ensures that performing to a non-present drawable returns .
///
[Test]
public void NotPresentDrawableCannotReceiveFocusViaChangeFocus()
@@ -157,13 +158,13 @@ public void NotPresentDrawableCannotReceiveFocusViaChangeFocus()
checkFocused(() => requestingFocus);
AddStep("hide top left", () => focusTopLeft.Alpha = 0);
- AddAssert("cannot switch focus to top left", () => !InputManager.ChangeFocus(focusTopLeft));
+ AddAssert("cannot switch focus to top left", () => !((IFocusManager)InputManager).ChangeFocus(focusTopLeft));
checkFocused(() => requestingFocus);
}
///
- /// Ensures that performing to a drawable of a non-present parent returns .
+ /// Ensures that performing to a drawable of a non-present parent returns .
///
[Test]
public void DrawableOfNotPresentParentCannotReceiveFocusViaChangeFocus()
@@ -183,7 +184,7 @@ public void DrawableOfNotPresentParentCannotReceiveFocusViaChangeFocus()
Remove(focusTopLeft, false);
container.Add(focusTopLeft);
});
- AddAssert("cannot switch focus to top left", () => !InputManager.ChangeFocus(focusTopLeft));
+ AddAssert("cannot switch focus to top left", () => !((IFocusManager)InputManager).ChangeFocus(focusTopLeft));
checkFocused(() => requestingFocus);
}
@@ -243,6 +244,91 @@ public void InputPropagation()
focusBottomRight.JoystickPressCount == 1 && focusBottomRight.JoystickReleaseCount == 1);
}
+ [Test]
+ public void TestDrawableWithNoFocusChangeOnClick()
+ {
+ FocusBox focusableBox = null!;
+ FocusBox noFocusChangeBox = null!;
+
+ AddStep("setup", () =>
+ {
+ Children = new Drawable[]
+ {
+ focusableBox = new FocusBox
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ },
+ noFocusChangeBox = new NoFocusChangeBox
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ AllowAcceptingFocus = false
+ }
+ };
+ });
+
+ AddStep("click focusable box", () =>
+ {
+ InputManager.MoveMouseTo(focusableBox);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ checkFocused(() => focusableBox);
+
+ AddStep("click no focus change box", () =>
+ {
+ InputManager.MoveMouseTo(noFocusChangeBox);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ checkFocused(() => focusableBox);
+ checkNotFocused(() => noFocusChangeBox);
+ AddAssert("no focus change box received click", () => noFocusChangeBox.ClickCount, () => Is.GreaterThan(0));
+ }
+
+ [Test]
+ public void TestChangeFocusDuringInputHandling_ShouldRetainFocus()
+ {
+ BasicButton button = null!;
+ FocusBox box = null!;
+
+ AddStep("setup", () =>
+ {
+ FocusBox b = new FocusBox
+ {
+ Position = new Vector2(0, 75)
+ };
+
+ Children =
+ [
+ button = new BasicButton
+ {
+ Size = new Vector2(150, 50),
+ Text = "Focus the box",
+ Action = () => b.GetContainingFocusManager()!.ChangeFocus(b)
+ },
+ box = b
+ ];
+ });
+
+ AddStep("click button", () =>
+ {
+ InputManager.MoveMouseTo(button);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddAssert("box is focused", () => box.HasFocus, () => Is.True);
+
+ AddStep("click button again", () =>
+ {
+ InputManager.MoveMouseTo(button);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddAssert("box is still focused", () => box.HasFocus, () => Is.True);
+ }
+
private void checkFocused(Func d) => AddAssert("check focus", () => d().HasFocus);
private void checkNotFocused(Func d) => AddAssert("check not focus", () => !d().HasFocus);
@@ -343,7 +429,7 @@ public RequestingFocusBox()
public partial class FocusBox : CompositeDrawable
{
protected Box Box;
- public int KeyDownCount, KeyUpCount, JoystickPressCount, JoystickReleaseCount;
+ public int KeyDownCount, KeyUpCount, JoystickPressCount, JoystickReleaseCount, ClickCount;
public FocusBox()
{
@@ -358,7 +444,11 @@ public FocusBox()
Size = new Vector2(0.4f);
}
- protected override bool OnClick(ClickEvent e) => true;
+ protected override bool OnClick(ClickEvent e)
+ {
+ ++ClickCount;
+ return true;
+ }
public bool AllowAcceptingFocus = true;
@@ -401,5 +491,22 @@ protected override void OnJoystickRelease(JoystickReleaseEvent e)
base.OnJoystickRelease(e);
}
}
+
+ public partial class NoFocusChangeBox : FocusBox
+ {
+ public NoFocusChangeBox()
+ {
+ Box.Colour = Color4.Green;
+
+ AddInternal(new SpriteText
+ {
+ Text = "ChangeFocusOnClick = false",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+ }
+
+ public override bool ChangeFocusOnClick => false;
+ }
}
}
diff --git a/osu.Framework.Tests/Visual/Graphics/TestSceneTripleBufferOccupancy.cs b/osu.Framework.Tests/Visual/Graphics/TestSceneTripleBufferOccupancy.cs
new file mode 100644
index 0000000000..8b119c6de1
--- /dev/null
+++ b/osu.Framework.Tests/Visual/Graphics/TestSceneTripleBufferOccupancy.cs
@@ -0,0 +1,124 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+
+namespace osu.Framework.Tests.Visual.Graphics
+{
+ public partial class TestSceneTripleBufferOccupancy : FrameworkTestScene
+ {
+ private readonly CancellationTokenSource cts = new CancellationTokenSource();
+
+ private readonly TextFlowContainer text;
+
+ private long[] writes = new long[3];
+ private long[] reads = new long[3];
+ private Stopwatch stopwatch = Stopwatch.StartNew();
+
+ private int writeLag;
+ private int readLag;
+
+ public TestSceneTripleBufferOccupancy()
+ {
+ Add(text = new TextFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both
+ });
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ TripleBuffer