diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 87a8faa814..2199c2cb81 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -9,7 +9,7 @@
]
},
"jetbrains.resharper.globaltools": {
- "version": "2022.2.3",
+ "version": "2023.3.3",
"commands": [
"jb"
]
diff --git a/.editorconfig b/.editorconfig
index 6468252b6c..c341ed3647 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -9,6 +9,9 @@ indent_style = space
indent_size = 2
trim_trailing_whitespace = true
+[g_*.cs]
+generated_code = true
+
[*.cs]
end_of_line = crlf
insert_final_newline = true
@@ -197,4 +200,4 @@ csharp_style_namespace_declarations = block_scoped:warning
insert_final_newline = true
indent_style = space
indent_size = 2
-trim_trailing_whitespace = true
\ No newline at end of file
+trim_trailing_whitespace = true
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index b276fb97f3..ffd1c2e4eb 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -4,3 +4,5 @@
ba1385330cc501f34937e08257e586c84e35d772
# Make everything partial
9bbc6a81a2b8f035e2b043d3718d2db6e4f1d868
+# Mass NRT enabling
+75ed421f60228920abd819cebc5982cb7799bbbb
diff --git a/.github/workflows/build-ffmpeg.yml b/.github/workflows/build-ffmpeg.yml
index b4de4b3bc3..c8812e0a5e 100644
--- a/.github/workflows/build-ffmpeg.yml
+++ b/.github/workflows/build-ffmpeg.yml
@@ -4,7 +4,7 @@ on: workflow_dispatch
jobs:
build-macos:
name: Build macOS
- runs-on: macos-latest
+ runs-on: macos-12
strategy:
fail-fast: false
matrix:
@@ -13,50 +13,117 @@ jobs:
- arm64
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- uses: ilammy/setup-nasm@v1
- name: Build
- run: osu.Framework.NativeLibs/scripts/ffmpeg/macOS/fetch_and_build.sh
+ run: osu.Framework.NativeLibs/scripts/ffmpeg/build-macOS.sh
env:
arch: ${{ matrix.arch }}
- name: Upload
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: macOS-${{ matrix.arch }}
- path: build-${{ matrix.arch }}
+ path: macOS-${{ matrix.arch }}
combine-macos:
name: Combine macOS libs
- runs-on: macos-latest
+ runs-on: macos-12
needs: build-macos
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
name: macOS-x86_64
path: macOS-x86_64
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
name: macOS-arm64
path: macOS-arm64
- name: Combine
- run: osu.Framework.NativeLibs/scripts/ffmpeg/macOS/combine_universal.sh
+ run: osu.Framework.NativeLibs/scripts/ffmpeg/combine_dylibs.sh
+ env:
+ platform: macOS
- name: Upload
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
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-latest
+ runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
@@ -65,106 +132,154 @@ jobs:
- x64
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
- sudo apt-get install make nasm gcc mingw-w64
+ sudo apt-get install nasm mingw-w64
- name: Build
- run: osu.Framework.NativeLibs/scripts/ffmpeg/win/fetch_and_build.sh
+ run: osu.Framework.NativeLibs/scripts/ffmpeg/build-win.sh
env:
arch: ${{ matrix.arch }}
- name: Upload
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: win-${{ matrix.arch }}
- path: build-${{ matrix.arch }}
+ path: win-${{ matrix.arch }}
# The win-arm64 build runs in a special MinGW container to cross-compile successfully.
build-win-arm64:
name: Build Windows (arm64)
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
container:
image: mstorsjo/llvm-mingw:latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Build
- run: osu.Framework.NativeLibs/scripts/ffmpeg/win/fetch_and_build.sh
+ run: osu.Framework.NativeLibs/scripts/ffmpeg/build-win.sh
env:
arch: arm64
- name: Upload
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: win-arm64
- path: build-arm64
+ path: win-arm64
build-linux:
name: Build Linux (x64)
- runs-on: ubuntu-latest
- container:
- image: ubuntu:16.04
+ # Use 20.04 to target glibc 2.31 like the other native libs
+ runs-on: ubuntu-20.04
steps:
- name: Install dependencies
run: |
- apt-get update
- apt-get install -y git curl gcc make nasm
+ sudo apt-get update
+ sudo apt-get install nasm
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Build
- run: osu.Framework.NativeLibs/scripts/ffmpeg/linux/fetch_and_build.sh
+ run: osu.Framework.NativeLibs/scripts/ffmpeg/build-linux.sh
- name: Upload
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: linux-x64
- path: build-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-latest
+ 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@v3
+ uses: actions/checkout@v4
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
name: macOS-universal
path: osu.Framework.NativeLibs/runtimes/osx/native
- - uses: actions/download-artifact@v3
+ - 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
path: osu.Framework.NativeLibs/runtimes/linux-x64/native
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
name: win-arm64
path: osu.Framework.NativeLibs/runtimes/win-arm64/native
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
name: win-x64
path: osu.Framework.NativeLibs/runtimes/win-x64/native
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
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@v4
+ - uses: peter-evans/create-pull-request@v6
with:
commit-message: Update FFmpeg binaries
title: Update FFmpeg binaries
body: This PR has been auto-generated to update the FFmpeg binaries.
branch: update-ffmpeg-binaries
+ delete-branch: true
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4302dceff6..5905850e62 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,12 +7,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- - name: Install .NET 6.0.x
- uses: actions/setup-dotnet@v3
+ - name: Install .NET 8.0.x
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: "6.0.x"
+ dotnet-version: "8.0.x"
- name: Restore Tools
run: dotnet tool restore
@@ -21,7 +21,7 @@ jobs:
run: dotnet restore osu-framework.Desktop.slnf
- name: Restore inspectcode cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ${{ github.workspace }}/inspectcode
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu-framework.sln*', 'osu-framework*.slnf', '.editorconfig', '.globalconfig', 'CodeAnalysis/*', '**/*.csproj', '**/*.props') }}
@@ -58,23 +58,26 @@ jobs:
fail-fast: false
matrix:
os:
- - { prettyname: Windows, fullname: windows-latest }
- - { prettyname: macOS, fullname: macos-latest }
- - { prettyname: Linux, fullname: ubuntu-latest }
+ - { prettyname: Windows, fullname: windows-latest, configuration: Debug }
+ - { prettyname: macOS, fullname: macos-latest, configuration: Debug }
+ - { prettyname: Linux, fullname: ubuntu-latest, configuration: Debug }
+ - { prettyname: Linux, fullname: ubuntu-latest, configuration: Release }
threadingMode: ['SingleThread', 'MultiThreaded']
timeout-minutes: 60
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- - name: Install .NET 6.0.x
- uses: actions/setup-dotnet@v3
+ - name: Install .NET 8.0.x
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: "6.0.x"
+ dotnet-version: "8.0.x"
- name: Setup Go
- uses: actions/setup-go@v3
+ 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
@@ -84,20 +87,20 @@ jobs:
shell: bash
- name: Compile
- run: dotnet build -c Debug -warnaserror osu-framework.Desktop.slnf
+ run: dotnet build -c ${{matrix.os.configuration}} -warnaserror osu-framework.Desktop.slnf
- name: Test
- run: dotnet test $pwd/**/*.Tests/bin/Debug/*/*.Tests.dll --settings $pwd/build/vstestconfig.runsettings --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.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}}
- path: ${{github.workspace}}/TestResults/TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx
+ name: osu-framework-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}-${{matrix.os.configuration}}
+ path: ${{github.workspace}}/TestResults/TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}-${{matrix.os.configuration}}.trx
build-only-android:
name: Build only (Android)
@@ -105,12 +108,18 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
+
+ - name: Setup JDK 11
+ uses: actions/setup-java@v4
+ with:
+ distribution: microsoft
+ java-version: 11
- - name: Install .NET 6.0.x
- uses: actions/setup-dotnet@v3
+ - name: Install .NET 8.0.x
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: "6.0.x"
+ dotnet-version: "8.0.x"
- name: Restore .NET workloads
run: dotnet workload install android
@@ -124,17 +133,16 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- - name: Install .NET 6.0.x
- uses: actions/setup-dotnet@v3
+ - name: Install .NET 8.0.x
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: "6.0.x"
+ 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-nativelibs.yml b/.github/workflows/deploy-nativelibs.yml
index b037de3a7d..2b54f12150 100644
--- a/.github/workflows/deploy-nativelibs.yml
+++ b/.github/workflows/deploy-nativelibs.yml
@@ -40,22 +40,22 @@ jobs:
needs: check-if-tag
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Set Artifacts Directory
id: artifactsPath
run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}/artifacts"
- - name: Setup .NET 6.0.x
- uses: actions/setup-dotnet@v3
+ - name: Setup .NET 8.0.x
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: "6.0.x"
+ dotnet-version: "8.0.x"
- name: Build NativeLibs
run: dotnet pack -c Release osu.Framework.NativeLibs /p:Configuration=Release /p:Version=${{needs.check-if-tag.outputs.version}} /p:GenerateDocumentationFile=true -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
- name: Upload Artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: osu-framework-nativelibs
path: ${{steps.artifactsPath.outputs.nuget_artifacts}}/*.nupkg
diff --git a/.github/workflows/deploy-pack.yml b/.github/workflows/deploy-pack.yml
index d9c85bcbd1..88bc62e7ac 100644
--- a/.github/workflows/deploy-pack.yml
+++ b/.github/workflows/deploy-pack.yml
@@ -1,64 +1,68 @@
-# There is no manual way to call this out to run this on tags via UI.
-# See: https://github.community/t/workflow-dispatch-from-a-tag-in-actions-tab/130561
-on: workflow_dispatch
-name: Deploy - Framework
+name: Pack and nuget
+
+on:
+ push:
+ tags:
+ - '*'
jobs:
- check-if-tag:
- name: Set Package Version
+ notify_pending_production_deploy:
runs-on: ubuntu-latest
- outputs:
- version: ${{steps.deployment.outputs.version}}
steps:
- - name: Checkout
- run: |
- REPOSITORY="https://${{ github.actor }}:${{ github.token }}@github.com/${{ github.repository }}.git"
- BRANCH="${GITHUB_REF/#refs\/heads\//}"
-
- git version
- git clone --no-checkout ${REPOSITORY} .
- git config --local gc.auto 0
-
- git -c protocol.version=2 fetch --no-tags --prune --progress --depth=2 origin +${GITHUB_SHA}:refs/remotes/origin/${BRANCH}
- git checkout --progress --force -B $BRANCH refs/remotes/origin/$BRANCH
-
-
- - name: Set Variables
- id: deployment
- shell: bash
+ -
+ name: Submit pending deployment notification
run: |
- if [ $(git describe --exact-match --tags HEAD &> /dev/null; echo $?) == 0 ]; then
- echo "::set-output name=VERSION::$(git describe --exact-match --tags HEAD)"
- else
- echo "fatal: no tag detected for HEAD. Workflow will now stop."
- exit 128;
- fi
+ export TITLE="Pending osu-framework Production Deployment: $GITHUB_REF_NAME"
+ export URL="https://github.com/ppy/osu-framework/actions/runs/$GITHUB_RUN_ID"
+ export DESCRIPTION="Awaiting approval for building NuGet packages for tag $GITHUB_REF_NAME:
+ [View Workflow Run]($URL)"
+ export ACTOR_ICON="https://avatars.githubusercontent.com/u/$GITHUB_ACTOR_ID"
+
+ BODY="$(jq --null-input '{
+ "embeds": [
+ {
+ "title": env.TITLE,
+ "color": 15098112,
+ "description": env.DESCRIPTION,
+ "url": env.URL,
+ "author": {
+ "name": env.GITHUB_ACTOR,
+ "icon_url": env.ACTOR_ICON
+ }
+ }
+ ]
+ }')"
+
+ curl \
+ -H "Content-Type: application/json" \
+ -d "$BODY" \
+ "${{ secrets.DISCORD_INFRA_WEBHOOK_URL }}"
pack-framework:
name: Pack (Framework)
runs-on: windows-latest
- needs: [check-if-tag]
+ environment: production
defaults:
run:
shell: powershell
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- - name: Set Artifacts Directory
+ - name: Set artifacts directory
id: artifactsPath
run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}\artifacts"
- - name: Install .NET 6.0.x
- uses: actions/setup-dotnet@v3
+ - name: Install .NET 8.0.x
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: "6.0.x"
+ dotnet-version: "8.0.x"
- name: Pack (Framework)
- run: dotnet pack -c Release osu.Framework /p:Version=${{needs.check-if-tag.outputs.version}} /p:GenerateDocumentationFile=true /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
+ run: dotnet pack -c Release osu.Framework /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
- - name: Upload Artifacts
- uses: actions/upload-artifact@v3
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
with:
name: osu-framework
path: |
@@ -71,28 +75,28 @@ jobs:
pack-template:
name: Pack (Templates)
runs-on: ubuntu-latest
- needs: [check-if-tag]
+ environment: production
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- - name: Set Artifacts Directory
+ - name: Set artifacts Directory
id: artifactsPath
run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}/artifacts"
- - name: Install .NET 6.0.x
- uses: actions/setup-dotnet@v3
+ - name: Install .NET 8.0.x
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: "6.0.x"
+ dotnet-version: "8.0.x"
- - name: Replace Project References
- run: osu.Framework.Templates/replace-references.sh ${{needs.check-if-tag.outputs.version}}
+ - name: Replace project references
+ run: osu.Framework.Templates/replace-references.sh ${{ github.ref_name }}
- name: Pack (Template)
- run: dotnet pack -c Release osu.Framework.Templates /p:Configuration=Release /p:Version=${{needs.check-if-tag.outputs.version}} /p:GenerateDocumentationFile=true /p:NoDefaultExcludes=true -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
+ run: dotnet pack -c Release osu.Framework.Templates /p:Configuration=Release /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true /p:NoDefaultExcludes=true -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
- - name: Upload Artifacts
- uses: actions/upload-artifact@v3
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
with:
name: osu-framework-templates
path: ${{steps.artifactsPath.outputs.nuget_artifacts}}/*.nupkg
@@ -103,31 +107,37 @@ jobs:
pack-android:
name: Pack (Android)
runs-on: windows-latest
- needs: [check-if-tag]
+ environment: production
defaults:
run:
shell: powershell
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- - name: Set Artifacts Directory
+ - name: Set artifacts directory
id: artifactsPath
run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}\artifacts"
- - name: Install .NET 6.0.x
- uses: actions/setup-dotnet@v3
+ - name: Install .NET 8.0.x
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: "8.0.x"
+
+ - name: Setup JDK 11
+ uses: actions/setup-java@v4
with:
- dotnet-version: "6.0.x"
+ distribution: microsoft
+ java-version: 11
- name: Restore .NET workloads
run: dotnet workload install android
- name: Pack (Android Framework)
- run: dotnet pack -c Release osu.Framework.Android /p:Version=${{needs.check-if-tag.outputs.version}} /p:GenerateDocumentationFile=true -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
+ run: dotnet pack -c Release osu.Framework.Android /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
- - name: Upload Artifacts
- uses: actions/upload-artifact@v3
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
with:
name: osu-framework-android
path: ${{steps.artifactsPath.outputs.nuget_artifacts}}\*.nupkg
@@ -138,33 +148,31 @@ jobs:
pack-ios:
name: Pack (iOS)
runs-on: macos-latest
- needs: [check-if-tag]
+ environment: production
defaults:
run:
shell: bash
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- - name: Set Artifacts Directory
+ - name: Set artifacts directory
id: artifactsPath
run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}/artifacts"
- - name: Install .NET 6.0.x
- uses: actions/setup-dotnet@v3
+ - name: Install .NET 8.0.x
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: "6.0.x"
+ 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=${{needs.check-if-tag.outputs.version}} /p:GenerateDocumentationFile=true -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
+ run: dotnet pack -c Release osu.Framework.iOS /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
- - name: Upload Artifacts
- uses: actions/upload-artifact@v3
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
with:
name: osu-framework-ios
path: ${{steps.artifactsPath.outputs.nuget_artifacts}}/*.nupkg
diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml
index a60028f30b..476000a223 100644
--- a/.github/workflows/report-nunit.yml
+++ b/.github/workflows/report-nunit.yml
@@ -5,29 +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 }
- - { prettyname: macOS }
- - { prettyname: Linux }
- 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.6.0
+ uses: dorny/test-reporter@v1.8.0
with:
- artifact: osu-framework-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
- name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
+ name: Results
path: "*.trx"
reporter: dotnet-trx
list-suites: 'failed'
diff --git a/.gitignore b/.gitignore
index 9e77922eb8..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/
@@ -337,4 +340,10 @@ fabric.properties
inspectcodereport.xml
inspectcode
-.idea/.idea.osu-framework.Desktop/.idea/misc.xml
\ No newline at end of file
+.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/.globalconfig b/.globalconfig
index 607798492c..0a2727ecd4 100644
--- a/.globalconfig
+++ b/.globalconfig
@@ -1,5 +1,3 @@
-is_global = true
-
# .NET Code Style
# IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/
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.Desktop/.idea/runConfigurations/Benchmarks.xml b/.idea/.idea.osu-framework.Desktop/.idea/runConfigurations/Benchmarks.xml
index e3413de811..11482fa08b 100644
--- a/.idea/.idea.osu-framework.Desktop/.idea/runConfigurations/Benchmarks.xml
+++ b/.idea/.idea.osu-framework.Desktop/.idea/runConfigurations/Benchmarks.xml
@@ -1,8 +1,8 @@
-
+
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu-framework.Desktop/.idea/runConfigurations/SampleGame.xml b/.idea/.idea.osu-framework.Desktop/.idea/runConfigurations/SampleGame.xml
index 238408e062..ff8c845450 100644
--- a/.idea/.idea.osu-framework.Desktop/.idea/runConfigurations/SampleGame.xml
+++ b/.idea/.idea.osu-framework.Desktop/.idea/runConfigurations/SampleGame.xml
@@ -1,8 +1,8 @@
-
+
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu-framework.Desktop/.idea/runConfigurations/VisualTests.xml b/.idea/.idea.osu-framework.Desktop/.idea/runConfigurations/VisualTests.xml
index 00492a9695..e8ebfd0859 100644
--- a/.idea/.idea.osu-framework.Desktop/.idea/runConfigurations/VisualTests.xml
+++ b/.idea/.idea.osu-framework.Desktop/.idea/runConfigurations/VisualTests.xml
@@ -1,8 +1,8 @@
-
+
-
+
@@ -12,7 +12,7 @@
-
+
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 41d621b0f1..dc9e9161c4 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -21,13 +21,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Framework.Tests/bin/Debug/net6.0/osu.Framework.Tests.dll",
+ "${workspaceRoot}/osu.Framework.Tests/bin/Debug/net8.0/osu.Framework.Tests.dll",
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Framework.Tests/bin/Debug/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Framework.Tests/bin/Debug/net8.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -38,13 +38,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Framework.Tests/bin/Release/net6.0/osu.Framework.Tests.dll",
+ "${workspaceRoot}/osu.Framework.Tests/bin/Release/net8.0/osu.Framework.Tests.dll",
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Framework.Tests/bin/Release/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Framework.Tests/bin/Release/net8.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -55,13 +55,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/SampleGame.Desktop/bin/Debug/net6.0/SampleGame.Desktop.dll",
+ "${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/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/SampleGame.Desktop/bin/Debug/net8.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -72,13 +72,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/SampleGame.Desktop/bin/Release/net6.0/SampleGame.Desktop.dll",
+ "${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/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/SampleGame.Desktop/bin/Release/net8.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -89,7 +89,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Framework.Benchmarks/bin/Release/net6.0/osu.Framework.Benchmarks.dll",
+ "${workspaceRoot}/osu.Framework.Benchmarks/bin/Release/net8.0/osu.Framework.Benchmarks.dll",
"--filter",
"*",
],
@@ -97,7 +97,7 @@
"preLaunchTask": "Build Benchmarks",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Framework.Benchmarks/bin/Release/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Framework.Benchmarks/bin/Release/net8.0:${env:LD_LIBRARY_PATH}"
}
},
}
diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt
index ca752d71de..a0a86a2fe5 100644
--- a/CodeAnalysis/BannedSymbols.txt
+++ b/CodeAnalysis/BannedSymbols.txt
@@ -12,3 +12,4 @@ M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on
M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
+M:System.Reflection.Assembly.GetEntryAssembly();Use osu.Framework.RuntimeInfo.EntryAssembly instead
diff --git a/Directory.Build.props b/Directory.Build.props
index 9a1bc7834f..fb2120b7ad 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,10 +1,14 @@
- 10.0
+ 12.0
true
enable
+
+ true
+ $(NoWarn);CS1591
+
osu-framework.licenseheader
@@ -29,7 +33,7 @@
ppy Pty Ltd
ppy Pty Ltd
- Copyright (c) 2021 ppy Pty Ltd
+ Copyright (c) 2024 ppy Pty Ltd
osu!framework
Automated release.
MIT
diff --git a/LICENCE b/LICENCE
index b5962ad3b2..3bb8b62d5d 100644
--- a/LICENCE
+++ b/LICENCE
@@ -1,4 +1,4 @@
-Copyright (c) 2021 ppy Pty Ltd .
+Copyright (c) 2024 ppy Pty Ltd .
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 7ac0a4e4c1..b392ce6289 100644
--- a/README.md
+++ b/README.md
@@ -27,10 +27,10 @@ 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 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/).
+- 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
@@ -70,6 +70,10 @@ The BASS audio library (a dependency of this framework) is a commercial product.
[IWBTM](https://github.com/EVAST9919/iwbtm) - A platform game with level editor based off of "I Wanna..." games
+[DeltaDash](https://deltada.sh/) - A multi-direction, lane-based scroller rhythm game
+
+[fluXis](https://github.com/TeamFluXis/fluXis) - A community-driven rhythm game with a focus on creativity and expression
+
true
true
+ $(NoWarn);XA4218
diff --git a/osu.Framework.Android/AndroidExtensions.cs b/osu.Framework.Android/AndroidExtensions.cs
new file mode 100644
index 0000000000..d97378b0ca
--- /dev/null
+++ b/osu.Framework.Android/AndroidExtensions.cs
@@ -0,0 +1,13 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Android.Graphics;
+using osu.Framework.Graphics.Primitives;
+
+namespace osu.Framework.Android
+{
+ public static class AndroidExtensions
+ {
+ public static RectangleI ToRectangleI(this Rect rect) => new RectangleI(rect.Left, rect.Top, rect.Width(), rect.Height());
+ }
+}
diff --git a/osu.Framework.Android/AndroidGameActivity.cs b/osu.Framework.Android/AndroidGameActivity.cs
index 35dc044d0c..41c69e69b4 100644
--- a/osu.Framework.Android/AndroidGameActivity.cs
+++ b/osu.Framework.Android/AndroidGameActivity.cs
@@ -1,22 +1,20 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using 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;
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
@@ -30,123 +28,43 @@ 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)
{
+ Debug.Assert(RuntimeInfo.EntryAssembly.IsNull(), "RuntimeInfo.EntryAssembly should be null on Android and therefore needs to be manually updated.");
+ RuntimeInfo.EntryAssembly = GetType().Assembly;
+
// The default current directory on android is '/'.
// On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage.
// In order to have a consistent current directory on all devices the full path of the app data directory is set as the current directory.
- System.Environment.CurrentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
+ 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 466f12ad3a..9754a275e0 100644
--- a/osu.Framework.Android/AndroidGameHost.cs
+++ b/osu.Framework.Android/AndroidGameHost.cs
@@ -8,15 +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;
@@ -24,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)
@@ -41,7 +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 void DrawFrame()
+ {
+ if (AndroidGameActivity.Surface.IsSurfaceReady)
+ base.DrawFrame();
+ }
public override bool CanExit => false;
@@ -49,27 +53,13 @@ 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);
- public override IEnumerable UserStoragePaths => new[]
- {
+ public override IEnumerable UserStoragePaths
// not null as internal "external storage" is always available.
- Application.Context.GetExternalFilesDir(string.Empty).AsNonNull().ToString(),
- };
+ => Application.Context.GetExternalFilesDir(string.Empty).AsNonNull().ToString().Yield();
public override bool OpenFileExternally(string filename) => false;
@@ -80,19 +70,19 @@ public override void OpenUrlExternally(string url)
if (!url.CheckIsValidUrl())
throw new ArgumentException("The provided URL must be one of either http://, https:// or mailto: protocols.", nameof(url));
- using (var intent = new Intent(Intent.ActionView, Uri.Parse(url)))
+ try
{
- // Recommended way to open URLs on Android 11+
- // https://developer.android.com/training/package-visibility/use-cases#open-urls-browser-or-other-app
- try
- {
- gameView.Activity.StartActivity(intent);
- }
- catch (ActivityNotFoundException e)
+ using (var intent = new Intent(Intent.ActionView, Uri.Parse(url)))
{
- Logger.Error(e, $"Failed to start intent: {intent}");
+ // Recommended way to open URLs on Android 11+
+ // https://developer.android.com/training/package-visibility/use-cases#open-urls-browser-or-other-app
+ activity.StartActivity(intent);
}
}
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Unable to open external link.");
+ }
}
public override IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore)
@@ -103,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 c05cd28869..0000000000
--- a/osu.Framework.Android/AndroidGameView.cs
+++ /dev/null
@@ -1,358 +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.Graphics;
-using Android.OS;
-using Android.Runtime;
-using Android.Text;
-using Android.Util;
-using Android.Views;
-using Android.Views.InputMethods;
-using osu.Framework.Android.Input;
-using osu.Framework.Logging;
-using osu.Framework.Bindables;
-using osu.Framework.Extensions.ObjectExtensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Primitives;
-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();
- }
-
- [STAThread]
- public void RenderGame()
- {
- // request focus so that joystick input can immediately work.
- RequestFocus();
-
- LayoutChange += (_, _) => updateSafeArea();
- updateSafeArea();
-
- 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()
- {
- // compute the usable screen area.
-
- var screenSize = new Point();
-#pragma warning disable 618 // GetRealSize is deprecated
- Display.AsNonNull().GetRealSize(screenSize);
-#pragma warning restore 618
- var screenArea = new RectangleI(0, 0, screenSize.X, screenSize.Y);
- var usableScreenArea = screenArea;
-
- if (OperatingSystem.IsAndroidVersionAtLeast(28))
- {
- var cutout = RootWindowInsets?.DisplayCutout;
-
- if (cutout != null)
- usableScreenArea = usableScreenArea.Shrink(cutout.SafeInsetLeft, cutout.SafeInsetRight, cutout.SafeInsetTop, cutout.SafeInsetBottom);
- }
-
- if (OperatingSystem.IsAndroidVersionAtLeast(24) && Activity.IsInMultiWindowMode)
- {
- // 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.
-
- // `SystemWindowInsetTop` should be the correct inset here, but it doesn't correctly work (gives `0` even if the view is obstructed).
-#pragma warning disable 618 // StableInsetTop is deprecated
- int statusBarHeight = RootWindowInsets?.StableInsetTop ?? 0;
-#pragma warning restore 618 //
- usableScreenArea = usableScreenArea.Intersect(screenArea.Shrink(0, 0, statusBarHeight, 0));
- }
-
- // TODO: add rounded corners support (Android 12): https://developer.android.com/guide/topics/ui/look-and-feel/rounded-corners
-
- // compute the location/area of this view on the screen.
-
- int[] location = new int[2];
- GetLocationOnScreen(location);
- var viewArea = new RectangleI(location[0], location[1], ((View)this).Width, ((View)this).Height);
-
- // intersect with the usable area and treat the the difference as unsafe.
-
- var usableViewArea = viewArea.Intersect(usableScreenArea);
-
- SafeAreaPadding.Value = new MarginPadding
- {
- Left = usableViewArea.Left - viewArea.Left,
- Top = usableViewArea.Top - viewArea.Top,
- Right = viewArea.Right - usableViewArea.Right,
- Bottom = viewArea.Bottom - usableViewArea.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 f272bee4a1..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
{
- public 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/Graphics/Video/AndroidVideoDecoder.cs b/osu.Framework.Android/Graphics/Video/AndroidVideoDecoder.cs
index 372abf4277..1c26d7cc8d 100644
--- a/osu.Framework.Android/Graphics/Video/AndroidVideoDecoder.cs
+++ b/osu.Framework.Android/Graphics/Video/AndroidVideoDecoder.cs
@@ -21,7 +21,6 @@ public unsafe class AndroidVideoDecoder : VideoDecoder
private const string lib_avcodec = "libavcodec.so";
private const string lib_avformat = "libavformat.so";
private const string lib_swscale = "libswscale.so";
- private const string lib_avfilter = "libavfilter.so";
[DllImport(lib_avutil)]
private static extern AVFrame* av_frame_alloc();
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 c8375f0460..0000000000
--- a/osu.Framework.Android/Input/AndroidJoystickHandler.cs
+++ /dev/null
@@ -1,228 +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
- {
- 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);
- }
- }
-}
diff --git a/osu.Framework.Android/Input/AndroidKeyboardHandler.cs b/osu.Framework.Android/Input/AndroidKeyboardHandler.cs
deleted file mode 100644
index 163bbe8cd9..0000000000
--- a/osu.Framework.Android/Input/AndroidKeyboardHandler.cs
+++ /dev/null
@@ -1,214 +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
- {
- 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);
- }
- }
-}
diff --git a/osu.Framework.Android/Input/AndroidMouseHandler.cs b/osu.Framework.Android/Input/AndroidMouseHandler.cs
deleted file mode 100644
index 0eaf7e1b4b..0000000000
--- a/osu.Framework.Android/Input/AndroidMouseHandler.cs
+++ /dev/null
@@ -1,313 +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
- {
- ///
- /// 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(true)
- {
- 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 => new[] { InputSourceType.Mouse, InputSourceType.MouseRelative, 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;
-
- // fired when buttons are pressed, but these don't have reliable ActionButton information
- case MotionEventActions.Up:
- case MotionEventActions.Down:
- return true;
-
- default:
- return false;
- }
- }
-
- protected override bool OnGenericMotion(MotionEvent genericMotionEvent)
- {
- switch (genericMotionEvent.Action)
- {
- case MotionEventActions.ButtonPress:
- case MotionEventActions.ButtonRelease:
- handleButtonEvent(genericMotionEvent);
- return true;
-
- case MotionEventActions.Scroll:
- handleScrollEvent(genericMotionEvent);
- return true;
-
- // fired when buttons are pressed, but these don't have reliable ActionButton information
- case MotionEventActions.Up:
- case MotionEventActions.Down:
- return true;
-
- default:
- return false;
- }
- }
-
- protected override bool OnCapturedPointer(MotionEvent capturedPointerEvent)
- {
- switch (capturedPointerEvent.Action)
- {
- case MotionEventActions.Move:
- handleMouseMoveRelativeEvent(capturedPointerEvent);
- return true;
-
- case MotionEventActions.Scroll:
- handleScrollEvent(capturedPointerEvent);
- return true;
-
- case MotionEventActions.ButtonPress:
- case MotionEventActions.ButtonRelease:
- handleButtonEvent(capturedPointerEvent);
- return true;
-
- // fired when buttons are pressed, but these don't have reliable ActionButton information
- case MotionEventActions.Up:
- case MotionEventActions.Down:
- return true;
-
- default:
- return false;
- }
- }
-
- private void handleButtonEvent(MotionEvent buttonEvent)
- {
- bool pressed = buttonEvent.Action == MotionEventActions.ButtonPress;
-
- // ActionButton is not available before API 23
- // https://developer.android.com/reference/android/view/MotionEvent#getActionButton()
-
- if (OperatingSystem.IsAndroidVersionAtLeast(23))
- {
- foreach (var button in buttonEvent.ActionButton.ToMouseButtons())
- handleMouseButton(button, pressed);
- }
- }
-
- 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);
- }
- }
-}
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 d7aaf32b56..0000000000
--- a/osu.Framework.Android/Input/AndroidTouchHandler.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-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
- {
- public override bool IsActive => true;
-
- protected override IEnumerable HandledEventSources => new[] { InputSourceType.BluetoothStylus, 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);
- 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 });
- }
- }
-
- private void applyTouchInput(MotionEvent touchEvent, int historyPosition, int pointerIndex)
- {
- if (tryGetEventTouch(touchEvent, historyPosition, pointerIndex, out var touch))
- enqueueInput(new TouchInput(touch, touchEvent.ActionMasked.IsTouchDownAction()));
- }
-
- 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/Properties/AssemblyInfo.cs b/osu.Framework.Android/Properties/AssemblyInfo.cs
index 16db49e197..18c9e57544 100644
--- a/osu.Framework.Android/Properties/AssemblyInfo.cs
+++ b/osu.Framework.Android/Properties/AssemblyInfo.cs
@@ -17,8 +17,6 @@
ResizeableActivity = true,
Theme = "@android:style/Theme.Black.NoTitleBar"
)]
-[assembly: UsesPermission(Manifest.Permission.ReadExternalStorage)]
-[assembly: UsesPermission(Manifest.Permission.WriteExternalStorage)]
[assembly: UsesPermission(Manifest.Permission.WakeLock)]
[assembly: UsesPermission(Manifest.Permission.ReadFrameBuffer)]
[assembly: UsesPermission(Manifest.Permission.Internet)]
diff --git a/osu.Framework.Android/arm64-v8a/libavfilter.so b/osu.Framework.Android/arm64-v8a/libavfilter.so
deleted file mode 100644
index 001ea9e41a..0000000000
Binary files a/osu.Framework.Android/arm64-v8a/libavfilter.so and /dev/null differ
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/libavfilter-neon.so b/osu.Framework.Android/armeabi-v7a/libavfilter-neon.so
deleted file mode 100644
index d3eb0d9553..0000000000
Binary files a/osu.Framework.Android/armeabi-v7a/libavfilter-neon.so and /dev/null differ
diff --git a/osu.Framework.Android/armeabi-v7a/libavfilter.so b/osu.Framework.Android/armeabi-v7a/libavfilter.so
deleted file mode 100644
index 49e873c805..0000000000
Binary files a/osu.Framework.Android/armeabi-v7a/libavfilter.so and /dev/null 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 7457146e36..54e6baf66a 100644
--- a/osu.Framework.Android/osu.Framework.Android.csproj
+++ b/osu.Framework.Android/osu.Framework.Android.csproj
@@ -1,6 +1,6 @@
- net6.0-android
+ net8.0-android
21.0
Library
true
@@ -18,6 +18,7 @@
-
+
+
diff --git a/osu.Framework.Android/x86/libavfilter.so b/osu.Framework.Android/x86/libavfilter.so
deleted file mode 100644
index eafb0b215f..0000000000
Binary files a/osu.Framework.Android/x86/libavfilter.so and /dev/null differ
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/BenchmarkAggregateBindable.cs b/osu.Framework.Benchmarks/BenchmarkAggregateBindable.cs
index 3e01b96c66..bb6039a658 100644
--- a/osu.Framework.Benchmarks/BenchmarkAggregateBindable.cs
+++ b/osu.Framework.Benchmarks/BenchmarkAggregateBindable.cs
@@ -11,15 +11,27 @@ public class BenchmarkAggregateBindable
{
private readonly BindableInt source1 = new BindableInt();
private readonly BindableInt source2 = new BindableInt();
+ private readonly AggregateBindable boundAggregate = new AggregateBindable(((i, j) => i + j));
+ private readonly AggregateBindable aggregate = new AggregateBindable(((i, j) => i + j));
- [Benchmark]
- public void AggregateRecalculation()
+ [GlobalSetup]
+ public void GlobalSetup()
{
- var aggregate = new AggregateBindable(((i, j) => i + j));
+ boundAggregate.AddSource(source1);
+ boundAggregate.AddSource(source2);
+ }
+ [Benchmark]
+ public void AddRemoveSource()
+ {
aggregate.AddSource(source1);
aggregate.AddSource(source2);
+ aggregate.RemoveAllSources();
+ }
+ [Benchmark]
+ public void SetValue()
+ {
for (int i = 0; i < 100; i++)
{
source1.Value = i;
diff --git a/osu.Framework.Benchmarks/BenchmarkBindableList.cs b/osu.Framework.Benchmarks/BenchmarkBindableList.cs
index 6154505ffa..9cb5cb6f6a 100644
--- a/osu.Framework.Benchmarks/BenchmarkBindableList.cs
+++ b/osu.Framework.Benchmarks/BenchmarkBindableList.cs
@@ -10,6 +10,7 @@ namespace osu.Framework.Benchmarks
public class BenchmarkBindableList
{
private readonly BindableList list = new BindableList();
+ private IBindableList iList => list;
[GlobalSetup]
public void GlobalSetup()
@@ -31,5 +32,19 @@ public int Enumerate()
return result;
}
+
+ [Benchmark]
+ public int EnumerateInterface()
+ {
+ int result = 0;
+
+ for (int i = 0; i < 100; i++)
+ {
+ foreach (int val in iList)
+ result += val;
+ }
+
+ return result;
+ }
}
}
diff --git a/osu.Framework.Benchmarks/BenchmarkBindableNumber.cs b/osu.Framework.Benchmarks/BenchmarkBindableNumber.cs
new file mode 100644
index 0000000000..d7b174d9c1
--- /dev/null
+++ b/osu.Framework.Benchmarks/BenchmarkBindableNumber.cs
@@ -0,0 +1,29 @@
+// 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.Bindables;
+
+namespace osu.Framework.Benchmarks
+{
+ [MemoryDiagnoser]
+ public class BenchmarkBindableNumber
+ {
+ private readonly BindableInt bindableNoPrecision = new BindableInt();
+ private readonly BindableInt bindableWithPrecision = new BindableInt { Precision = 5 };
+
+ [Benchmark]
+ public void SetValueNoPrecision()
+ {
+ for (int i = 0; i < 1000; i++)
+ bindableNoPrecision.Value = i;
+ }
+
+ [Benchmark]
+ public void SetValueWithPrecision()
+ {
+ for (int i = 0; i < 1000; i++)
+ bindableWithPrecision.Value = i;
+ }
+ }
+}
diff --git a/osu.Framework.Benchmarks/BenchmarkDrawableTypeAllocations.cs b/osu.Framework.Benchmarks/BenchmarkDrawableTypeAllocations.cs
index f3e986cb80..b8c69d74d0 100644
--- a/osu.Framework.Benchmarks/BenchmarkDrawableTypeAllocations.cs
+++ b/osu.Framework.Benchmarks/BenchmarkDrawableTypeAllocations.cs
@@ -22,28 +22,28 @@ public void TestBaseline()
[Benchmark]
public void TestSprite()
{
- var _ = new Sprite();
+ _ = new Sprite();
}
[Test]
[Benchmark]
public void TestCompositeDrawable()
{
- var _ = new SimpleComposite();
+ _ = new SimpleComposite();
}
[Test]
[Benchmark]
public void TestContainer()
{
- var _ = new Container();
+ _ = new Container();
}
[Test]
[Benchmark]
public void TestSpriteText()
{
- var _ = new SpriteText();
+ _ = new SpriteText();
}
public partial class SimpleComposite : CompositeDrawable
diff --git a/osu.Framework.Benchmarks/BenchmarkEventList.cs b/osu.Framework.Benchmarks/BenchmarkEventList.cs
new file mode 100644
index 0000000000..c31407047a
--- /dev/null
+++ b/osu.Framework.Benchmarks/BenchmarkEventList.cs
@@ -0,0 +1,131 @@
+// 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 BenchmarkDotNet.Attributes;
+using osu.Framework.Graphics.Rendering.Deferred.Allocation;
+using osu.Framework.Graphics.Rendering.Deferred.Events;
+
+namespace osu.Framework.Benchmarks
+{
+ public class BenchmarkEventList
+ {
+ // Used for benchmark-local testing.
+ private ResourceAllocator localAllocator = null!;
+ private List localEventList = null!;
+
+ // Used for benchmark-static testing.
+ // 0: Basic events
+ // 1: Events with data
+ // 2: Mixed events
+ private readonly (ResourceAllocator allocator, List list)[] staticItems = new (ResourceAllocator allocator, List list)[3];
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ localAllocator = new ResourceAllocator();
+ localEventList = new List();
+
+ for (int i = 0; i < staticItems.Length; i++)
+ {
+ ResourceAllocator allocator = new ResourceAllocator();
+ staticItems[i] = (allocator, new List());
+ }
+
+ for (int i = 0; i < 10000; i++)
+ {
+ staticItems[0].list.Add(RenderEvent.Init(new FlushEvent(new ResourceReference(1), 10)));
+ staticItems[1].list.Add(RenderEvent.Init(new AddPrimitiveToBatchEvent(new ResourceReference(0), staticItems[1].allocator.AllocateRegion(1024))));
+ staticItems[2].list.Add(i % 2 == 0
+ ? RenderEvent.Init(new FlushEvent(new ResourceReference(1), 10))
+ : RenderEvent.Init(new AddPrimitiveToBatchEvent(new ResourceReference(0), staticItems[2].allocator.AllocateRegion(1024))));
+ }
+ }
+
+ [Benchmark]
+ public void Write()
+ {
+ localAllocator.NewFrame();
+
+ for (int i = 0; i < 10000; i++)
+ localEventList.Add(RenderEvent.Init(new FlushEvent()));
+ }
+
+ [Benchmark]
+ public void WriteWithData()
+ {
+ localAllocator.NewFrame();
+
+ for (int i = 0; i < 10000; i++)
+ localEventList.Add(RenderEvent.Init(new AddPrimitiveToBatchEvent(new ResourceReference(0), localAllocator.AllocateRegion(1024))));
+ }
+
+ [Benchmark]
+ public int Read()
+ {
+ int totalVertices = 0;
+
+ foreach (var renderEvent in staticItems[0].list)
+ {
+ switch (renderEvent.Type)
+ {
+ case RenderEventType.Flush:
+ FlushEvent e = (FlushEvent)renderEvent;
+ totalVertices += e.VertexCount;
+ break;
+ }
+ }
+
+ return totalVertices;
+ }
+
+ [Benchmark]
+ public int ReadWithData()
+ {
+ int data = 0;
+
+ foreach (var renderEvent in staticItems[1].list)
+ {
+ switch (renderEvent.Type)
+ {
+ case RenderEventType.AddPrimitiveToBatch:
+ AddPrimitiveToBatchEvent e = (AddPrimitiveToBatchEvent)renderEvent;
+ foreach (byte b in staticItems[1].allocator.GetRegion(e.Memory))
+ data += b;
+ break;
+ }
+ }
+
+ return data;
+ }
+
+ [Benchmark]
+ public int ReadMixed()
+ {
+ int data = 0;
+
+ foreach (var renderEvent in staticItems[2].list)
+ {
+ switch (renderEvent.Type)
+ {
+ case RenderEventType.Flush:
+ {
+ FlushEvent e = (FlushEvent)renderEvent;
+ data += e.VertexCount;
+ break;
+ }
+
+ case RenderEventType.AddPrimitiveToBatch:
+ {
+ AddPrimitiveToBatchEvent e = (AddPrimitiveToBatchEvent)renderEvent;
+ foreach (byte b in staticItems[2].allocator.GetRegion(e.Memory))
+ data += b;
+ break;
+ }
+ }
+ }
+
+ return data;
+ }
+ }
+}
diff --git a/osu.Framework.Benchmarks/BenchmarkFontLoading.cs b/osu.Framework.Benchmarks/BenchmarkFontLoading.cs
index a98a26fbb4..696be54804 100644
--- a/osu.Framework.Benchmarks/BenchmarkFontLoading.cs
+++ b/osu.Framework.Benchmarks/BenchmarkFontLoading.cs
@@ -41,16 +41,22 @@ public void TearDown()
[Benchmark]
public void BenchmarkRawCachingReuse()
{
- using (var store = new RawCachingGlyphStore(baseResources, font_name) { CacheStorage = sharedTemp })
+ using (var store = new RawCachingGlyphStore(baseResources, font_name))
+ {
+ store.CacheStorage = sharedTemp;
runFor(store);
+ }
}
[Benchmark(Baseline = true)]
public void BenchmarkRawCaching()
{
using (var temp = new TemporaryNativeStorage("fontstore-test" + Guid.NewGuid()))
- using (var store = new RawCachingGlyphStore(baseResources, font_name) { CacheStorage = temp })
+ using (var store = new RawCachingGlyphStore(baseResources, font_name))
+ {
+ store.CacheStorage = temp;
runFor(store);
+ }
}
[Benchmark]
diff --git a/osu.Framework.Benchmarks/BenchmarkLongRunningLoad.cs b/osu.Framework.Benchmarks/BenchmarkLongRunningLoad.cs
new file mode 100644
index 0000000000..811c022216
--- /dev/null
+++ b/osu.Framework.Benchmarks/BenchmarkLongRunningLoad.cs
@@ -0,0 +1,60 @@
+// 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.Allocation;
+using osu.Framework.Graphics;
+
+namespace osu.Framework.Benchmarks
+{
+ [MemoryDiagnoser]
+ public partial class BenchmarkLongRunningLoad
+ {
+ private Drawable nonLongRunningReflectionDrawable = null!;
+ private Drawable longRunningReflectionDrawable = null!;
+ private Drawable nonLongRunningSourceGenerationDrawable = null!;
+ private Drawable longRunningSourceGenerationDrawable = null!;
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ nonLongRunningReflectionDrawable = new NonLongRunningReflectionDrawable();
+ longRunningReflectionDrawable = new LongRunningReflectionDrawable();
+
+ nonLongRunningSourceGenerationDrawable = new NonLongRunningSourceGenerationDrawable();
+ longRunningSourceGenerationDrawable = new LongRunningSourceGenerationDrawable();
+ }
+
+ [Benchmark]
+ public bool QueryNonLongRunningViaReflection() => nonLongRunningReflectionDrawable.IsLongRunning;
+
+ [Benchmark]
+ public bool QueryLongRunningViaReflection() => longRunningReflectionDrawable.IsLongRunning;
+
+ [Benchmark]
+ public bool QueryNonLongRunningViaSourceGeneration() => nonLongRunningSourceGenerationDrawable.IsLongRunning;
+
+ [Benchmark]
+ public bool QueryLongRunningViaSourceGeneration() => longRunningSourceGenerationDrawable.IsLongRunning;
+
+#pragma warning disable OFSG001
+ private class NonLongRunningReflectionDrawable : Drawable
+ {
+ }
+
+ [LongRunningLoad]
+ private class LongRunningReflectionDrawable : Drawable
+ {
+ }
+#pragma warning restore OFSG001
+
+ private partial class NonLongRunningSourceGenerationDrawable : Drawable
+ {
+ }
+
+ [LongRunningLoad]
+ private partial class LongRunningSourceGenerationDrawable : Drawable
+ {
+ }
+ }
+}
diff --git a/osu.Framework.Benchmarks/BenchmarkPiecewiseLinearToBezier.cs b/osu.Framework.Benchmarks/BenchmarkPiecewiseLinearToBezier.cs
new file mode 100644
index 0000000000..918f537d39
--- /dev/null
+++ b/osu.Framework.Benchmarks/BenchmarkPiecewiseLinearToBezier.cs
@@ -0,0 +1,43 @@
+// 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 BenchmarkDotNet.Attributes;
+using osu.Framework.Utils;
+using osuTK;
+
+namespace osu.Framework.Benchmarks
+{
+ public class BenchmarkPiecewiseLinearToBezier : BenchmarkTest
+ {
+ private Vector2[] inputPath = null!;
+
+ [Params(5, 25)]
+ public int NumControlPoints;
+
+ [Params(5, 200)]
+ public int NumTestPoints;
+
+ [Params(0, 100, 200)]
+ public int MaxIterations;
+
+ public override void SetUp()
+ {
+ base.SetUp();
+
+ Vector2[] points = new Vector2[5];
+ points[0] = new Vector2(50, 250);
+ points[1] = new Vector2(150, 230);
+ points[2] = new Vector2(100, 150);
+ points[3] = new Vector2(200, 80);
+ points[4] = new Vector2(250, 50);
+ inputPath = PathApproximator.LagrangePolynomialToPiecewiseLinear(points).ToArray();
+ }
+
+ [Benchmark]
+ public List PiecewiseLinearToBezier()
+ {
+ return PathApproximator.PiecewiseLinearToBezier(inputPath, NumControlPoints, NumTestPoints, MaxIterations);
+ }
+ }
+}
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.Benchmarks/BenchmarkSlimReadOnlyDictionary.cs b/osu.Framework.Benchmarks/BenchmarkSlimReadOnlyDictionary.cs
new file mode 100644
index 0000000000..749d1c4105
--- /dev/null
+++ b/osu.Framework.Benchmarks/BenchmarkSlimReadOnlyDictionary.cs
@@ -0,0 +1,153 @@
+// 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.Collections.ObjectModel;
+using BenchmarkDotNet.Attributes;
+using osu.Framework.Extensions.ListExtensions;
+
+namespace osu.Framework.Benchmarks
+{
+ [MemoryDiagnoser]
+ public class BenchmarkSlimReadOnlyDictionary
+ {
+ private readonly Dictionary dictionary = new Dictionary();
+ private ReadOnlyDictionary readOnlyDictionary = null!;
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ readOnlyDictionary = new ReadOnlyDictionary(dictionary);
+
+ int[] values = { 0, 1, 2, 3, 4, 5, 3, 2, 3, 1, 4, 5, -1 };
+ for (int i = 0; i < values.Length; i++)
+ dictionary[i] = values[i];
+ }
+
+ [Benchmark]
+ public int Dictionary()
+ {
+ int sum = 0;
+
+ for (int i = 0; i < 1000; i++)
+ {
+ foreach ((_, int v) in dictionary)
+ sum += v;
+ }
+
+ return sum;
+ }
+
+ [Benchmark]
+ public int DictionaryAsReadOnly()
+ {
+ int sum = 0;
+
+ for (int i = 0; i < 1000; i++)
+ {
+ foreach ((_, int v) in readOnlyDictionary)
+ sum += v;
+ }
+
+ return sum;
+ }
+
+ [Benchmark]
+ public int DictionaryAsSlimReadOnly()
+ {
+ int sum = 0;
+
+ for (int i = 0; i < 1000; i++)
+ {
+ foreach ((_, int v) in dictionary.AsSlimReadOnly())
+ sum += v;
+ }
+
+ return sum;
+ }
+
+ [Benchmark]
+ public int Keys()
+ {
+ int sum = 0;
+
+ for (int i = 0; i < 1000; i++)
+ {
+ foreach (int v in dictionary.Keys)
+ sum += v;
+ }
+
+ return sum;
+ }
+
+ [Benchmark]
+ public int KeysAsReadOnly()
+ {
+ int sum = 0;
+
+ for (int i = 0; i < 1000; i++)
+ {
+ foreach (int v in readOnlyDictionary.Keys)
+ sum += v;
+ }
+
+ return sum;
+ }
+
+ [Benchmark]
+ public int KeysAsSlimReadOnly()
+ {
+ int sum = 0;
+
+ for (int i = 0; i < 1000; i++)
+ {
+ foreach (int v in dictionary.AsSlimReadOnly().Keys)
+ sum += v;
+ }
+
+ return sum;
+ }
+
+ [Benchmark]
+ public int Values()
+ {
+ int sum = 0;
+
+ for (int i = 0; i < 1000; i++)
+ {
+ foreach (int v in dictionary.Values)
+ sum += v;
+ }
+
+ return sum;
+ }
+
+ [Benchmark]
+ public int ValuesAsReadOnly()
+ {
+ int sum = 0;
+
+ for (int i = 0; i < 1000; i++)
+ {
+ foreach (int v in readOnlyDictionary.Values)
+ sum += v;
+ }
+
+ return sum;
+ }
+
+ [Benchmark]
+ public int ValuesAsSlimReadOnly()
+ {
+ int sum = 0;
+
+ for (int i = 0; i < 1000; i++)
+ {
+ foreach (int v in dictionary.AsSlimReadOnly().Values)
+ sum += v;
+ }
+
+ return sum;
+ }
+ }
+}
diff --git a/osu.Framework.Benchmarks/BenchmarkSlimReadOnlyCollection.cs b/osu.Framework.Benchmarks/BenchmarkSlimReadOnlyList.cs
similarity index 96%
rename from osu.Framework.Benchmarks/BenchmarkSlimReadOnlyCollection.cs
rename to osu.Framework.Benchmarks/BenchmarkSlimReadOnlyList.cs
index db94774d36..54a2d985a2 100644
--- a/osu.Framework.Benchmarks/BenchmarkSlimReadOnlyCollection.cs
+++ b/osu.Framework.Benchmarks/BenchmarkSlimReadOnlyList.cs
@@ -8,7 +8,7 @@
namespace osu.Framework.Benchmarks
{
[MemoryDiagnoser]
- public class BenchmarkSlimReadOnlyCollection
+ public class BenchmarkSlimReadOnlyList
{
private readonly List list = new List { 0, 1, 2, 3, 4, 5, 3, 2, 3, 1, 4, 5, -1 };
diff --git a/osu.Framework.Benchmarks/BenchmarkSpriteText.cs b/osu.Framework.Benchmarks/BenchmarkSpriteText.cs
new file mode 100644
index 0000000000..7f36993551
--- /dev/null
+++ b/osu.Framework.Benchmarks/BenchmarkSpriteText.cs
@@ -0,0 +1,96 @@
+// 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.Linq;
+using BenchmarkDotNet.Attributes;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Utils;
+using osuTK;
+
+namespace osu.Framework.Benchmarks
+{
+ public partial class BenchmarkSpriteText : GameBenchmark
+ {
+ private TestGame game = null!;
+
+ [Test]
+ [Benchmark]
+ public void TestStaticText()
+ {
+ game.Mode = 0;
+ RunSingleFrame();
+ }
+
+ [Test]
+ [Benchmark]
+ public void TestMovingText()
+ {
+ game.Mode = 1;
+ RunSingleFrame();
+ }
+
+ [Test]
+ [Benchmark]
+ public void TestChangingText()
+ {
+ game.Mode = 2;
+ RunSingleFrame();
+ }
+
+ protected override Game CreateGame() => game = new TestGame();
+
+ private partial class TestGame : Game
+ {
+ public int Mode;
+
+ private readonly string text1 = Guid.NewGuid().ToString();
+ private readonly string text2 = Guid.NewGuid().ToString();
+
+ private long frame;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ for (int i = 0; i < 1000; i++)
+ {
+ Add(new SpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = "i am some relatively long text",
+ });
+ }
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ switch (Mode)
+ {
+ case 0:
+ break;
+
+ case 1:
+ var pos = new Vector2(RNG.NextSingle(100));
+
+ foreach (var text in Children.OfType())
+ text.Position = pos;
+
+ break;
+
+ case 2:
+ foreach (var text in Children.OfType())
+ text.Text = frame % 2 == 0 ? text1 : text2;
+ break;
+ }
+
+ frame++;
+ }
+ }
+ }
+}
diff --git a/osu.Framework.Benchmarks/BenchmarkTextBuilder.cs b/osu.Framework.Benchmarks/BenchmarkTextBuilder.cs
index 9a3523f9b3..73cd45f0fc 100644
--- a/osu.Framework.Benchmarks/BenchmarkTextBuilder.cs
+++ b/osu.Framework.Benchmarks/BenchmarkTextBuilder.cs
@@ -47,11 +47,11 @@ private void initialiseBuilder(bool withDifferentBaselines)
private class TestStore : ITexturedGlyphLookupStore
{
- public ITexturedCharacterGlyph Get(string fontName, char character) => new TexturedCharacterGlyph(
+ public ITexturedCharacterGlyph Get(string? fontName, char character) => new TexturedCharacterGlyph(
new CharacterGlyph(character, character, character, character, character, null),
new DummyRenderer().CreateTexture(1, 1));
- public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character));
+ public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character));
}
}
}
diff --git a/osu.Framework.Benchmarks/BenchmarkTransform.cs b/osu.Framework.Benchmarks/BenchmarkTransform.cs
index 860e96b35f..7a2ff75dc3 100644
--- a/osu.Framework.Benchmarks/BenchmarkTransform.cs
+++ b/osu.Framework.Benchmarks/BenchmarkTransform.cs
@@ -4,7 +4,7 @@
using System;
using BenchmarkDotNet.Attributes;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Timing;
using osuTK;
@@ -13,7 +13,7 @@ namespace osu.Framework.Benchmarks
{
public partial class BenchmarkTransform : BenchmarkTest
{
- private Drawable target = null!;
+ private Container target = null!;
public override void SetUp()
{
@@ -25,6 +25,60 @@ public override void SetUp()
[Benchmark]
public Transform CreateSingleBlank() => new TestTransform();
+ [Benchmark]
+ public void CreateSequenceThenRewind()
+ {
+ target.FadeIn(1000, Easing.OutQuint)
+ .Then().FadeOut(1000)
+ .Then().FadeOut(1000)
+ .Then().FadeOut(1000)
+ .Then().FadeOut(1000)
+ .Then().FadeOut(1000)
+ .Then().FadeOut(1000)
+ .Then().FadeOut(1000)
+ .Then().FadeOut(1000)
+ .Then().FadeOut(1000)
+ .Then().FadeOut(1000);
+
+ for (int i = 0; i < 1000; i++)
+ {
+ target.ApplyTransformsAt(50000);
+ target.ApplyTransformsAt(0);
+ }
+
+ target.ClearTransforms(true);
+ }
+
+ [Benchmark]
+ public void CreateSequenceThenRewindManyChildren()
+ {
+ var nested = target;
+
+ for (int i = 0; i < 5; i++)
+ {
+ nested.Add(new TestBox());
+ nested.FadeOutFromOne(1000)
+ .Then().FadeOutFromOne(1000)
+ .Then().FadeOutFromOne(1000)
+ .Then().FadeOutFromOne(1000);
+
+ nested.Add(nested = new TestBox());
+ nested.FadeOutFromOne(1000)
+ .Then().FadeOutFromOne(1000)
+ .Then().FadeOutFromOne(1000)
+ .Then().FadeOutFromOne(1000);
+ }
+
+ for (int i = 0; i < 1000; i++)
+ {
+ target.ApplyTransformsAt(50000, true);
+ target.ApplyTransformsAt(0, true);
+ }
+
+ target.ClearTransforms(true);
+ target.Clear();
+ }
+
[Benchmark]
public void CreateSequenceThenClearAfter()
{
@@ -117,21 +171,21 @@ private class ReferenceEasingFunction : IEasingFunction
public double ApplyEasing(double time) => 0;
}
- private partial class TestBox : Box
+ private partial class TestBox : Container
{
public override bool RemoveCompletedTransforms => false;
}
- private class TestTransform : Transform
+ private class TestTransform : Transform
{
public override string TargetMember => throw new NotImplementedException();
- protected override void Apply(Box d, double time)
+ protected override void Apply(TestBox d, double time)
{
throw new NotImplementedException();
}
- protected override void ReadIntoStartValue(Box d)
+ protected override void ReadIntoStartValue(TestBox d)
{
throw new NotImplementedException();
}
diff --git a/osu.Framework.Benchmarks/BenchmarkWaveform.cs b/osu.Framework.Benchmarks/BenchmarkWaveform.cs
index 677abe47fe..f569ac6f04 100644
--- a/osu.Framework.Benchmarks/BenchmarkWaveform.cs
+++ b/osu.Framework.Benchmarks/BenchmarkWaveform.cs
@@ -16,7 +16,7 @@ namespace osu.Framework.Benchmarks
[MemoryDiagnoser]
public class BenchmarkWaveform : BenchmarkTest
{
- private Stream data = null!;
+ private byte[] data = null!;
private Waveform preloadedWaveform = null!;
public override void SetUp()
@@ -27,11 +27,11 @@ public override void SetUp()
var store = new NamespacedResourceStore(new DllResourceStore(typeof(FrameworkTestScene).Assembly), "Resources");
- data = store.GetStream("Tracks/sample-track.mp3");
+ data = store.Get("Tracks/sample-track.mp3");
Debug.Assert(data != null);
- preloadedWaveform = new Waveform(data);
+ preloadedWaveform = new Waveform(new MemoryStream(data));
// wait for load
preloadedWaveform.GetPoints();
}
@@ -40,7 +40,7 @@ public override void SetUp()
[Test]
public async Task TestCreate()
{
- var waveform = new Waveform(data);
+ var waveform = new Waveform(new MemoryStream(data));
var originalPoints = await waveform.GetPointsAsync().ConfigureAwait(false);
Debug.Assert(originalPoints.Length > 0);
diff --git a/osu.Framework.Benchmarks/GameBenchmark.cs b/osu.Framework.Benchmarks/GameBenchmark.cs
index 3c90cbfe62..2a5725f43e 100644
--- a/osu.Framework.Benchmarks/GameBenchmark.cs
+++ b/osu.Framework.Benchmarks/GameBenchmark.cs
@@ -1,7 +1,6 @@
// 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.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
@@ -105,13 +104,17 @@ public ManualThreadRunner(InputThread mainThread)
public void RunSingleFrame()
{
ExecutionMode = ExecutionMode.SingleThread;
+
+ // Importantly, this calls the base method, bypassing the custom wait logic below
+ // (which is blocking execution by thread runner while the benchmark runs).
base.RunMainLoop();
}
public override void RunMainLoop()
{
- if (!RunOnce.Wait(10000))
- throw new TimeoutException("Run request didn't arrive for a long time");
+#pragma warning disable RS0030
+ RunOnce.Wait();
+#pragma warning restore RS0030
RunSingleFrame();
RunOnce.Reset();
diff --git a/osu.Framework.Benchmarks/osu.Framework.Benchmarks.csproj b/osu.Framework.Benchmarks/osu.Framework.Benchmarks.csproj
index 5de346abd4..d57bbc2401 100644
--- a/osu.Framework.Benchmarks/osu.Framework.Benchmarks.csproj
+++ b/osu.Framework.Benchmarks/osu.Framework.Benchmarks.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
Exe
false
diff --git a/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj b/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj
index 30eafe42b2..60a90fac55 100644
--- a/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj
+++ b/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj
@@ -1,6 +1,6 @@
- net6.0
+ net8.0
Library
osu!framework Libraries
osu.Framework.NativeLibs
diff --git a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavcodec.so b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavcodec.so
deleted file mode 100644
index 1607979688..0000000000
Binary files a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavcodec.so and /dev/null differ
diff --git a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavcodec.so.58 b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavcodec.so.58
new file mode 100644
index 0000000000..9ed8f7dbe1
Binary files /dev/null and b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavcodec.so.58 differ
diff --git a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavfilter.so b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavfilter.so
deleted file mode 100644
index b4372bba84..0000000000
Binary files a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavfilter.so and /dev/null differ
diff --git a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavformat.so b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavformat.so
deleted file mode 100644
index df7c558932..0000000000
Binary files a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavformat.so and /dev/null differ
diff --git a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavformat.so.58 b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavformat.so.58
new file mode 100644
index 0000000000..33c6f329e4
Binary files /dev/null and b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavformat.so.58 differ
diff --git a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavutil.so b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavutil.so
deleted file mode 100644
index 267b124b8c..0000000000
Binary files a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavutil.so and /dev/null differ
diff --git a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavutil.so.56 b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavutil.so.56
new file mode 100644
index 0000000000..f34510bc68
Binary files /dev/null and b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libavutil.so.56 differ
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-x64/native/libswscale.so b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libswscale.so
deleted file mode 100644
index 73b4b028ee..0000000000
Binary files a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libswscale.so and /dev/null differ
diff --git a/osu.Framework.NativeLibs/runtimes/linux-x64/native/libswscale.so.5 b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libswscale.so.5
new file mode 100644
index 0000000000..757bebfd0d
Binary files /dev/null and b/osu.Framework.NativeLibs/runtimes/linux-x64/native/libswscale.so.5 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/libavcodec.58.dylib b/osu.Framework.NativeLibs/runtimes/osx/native/libavcodec.58.dylib
index 13971bdd54..4351c58b4f 100755
Binary files a/osu.Framework.NativeLibs/runtimes/osx/native/libavcodec.58.dylib and b/osu.Framework.NativeLibs/runtimes/osx/native/libavcodec.58.dylib differ
diff --git a/osu.Framework.NativeLibs/runtimes/osx/native/libavfilter.7.dylib b/osu.Framework.NativeLibs/runtimes/osx/native/libavfilter.7.dylib
deleted file mode 100755
index 6c7360f585..0000000000
Binary files a/osu.Framework.NativeLibs/runtimes/osx/native/libavfilter.7.dylib and /dev/null differ
diff --git a/osu.Framework.NativeLibs/runtimes/osx/native/libavformat.58.dylib b/osu.Framework.NativeLibs/runtimes/osx/native/libavformat.58.dylib
index c637f3536f..2d57c9e62d 100755
Binary files a/osu.Framework.NativeLibs/runtimes/osx/native/libavformat.58.dylib and b/osu.Framework.NativeLibs/runtimes/osx/native/libavformat.58.dylib differ
diff --git a/osu.Framework.NativeLibs/runtimes/osx/native/libavutil.56.dylib b/osu.Framework.NativeLibs/runtimes/osx/native/libavutil.56.dylib
index f0c9d71fc0..910340c0d0 100755
Binary files a/osu.Framework.NativeLibs/runtimes/osx/native/libavutil.56.dylib and b/osu.Framework.NativeLibs/runtimes/osx/native/libavutil.56.dylib 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/osx/native/libswscale.5.dylib b/osu.Framework.NativeLibs/runtimes/osx/native/libswscale.5.dylib
index 2ea2b72fe5..b256e25cc3 100755
Binary files a/osu.Framework.NativeLibs/runtimes/osx/native/libswscale.5.dylib and b/osu.Framework.NativeLibs/runtimes/osx/native/libswscale.5.dylib differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-arm64/native/avcodec-58.dll b/osu.Framework.NativeLibs/runtimes/win-arm64/native/avcodec-58.dll
index 5c549eb898..3b002d2847 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-arm64/native/avcodec-58.dll and b/osu.Framework.NativeLibs/runtimes/win-arm64/native/avcodec-58.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-arm64/native/avfilter-7.dll b/osu.Framework.NativeLibs/runtimes/win-arm64/native/avfilter-7.dll
deleted file mode 100644
index 5a061cacad..0000000000
Binary files a/osu.Framework.NativeLibs/runtimes/win-arm64/native/avfilter-7.dll and /dev/null differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-arm64/native/avformat-58.dll b/osu.Framework.NativeLibs/runtimes/win-arm64/native/avformat-58.dll
index 263fa6cdcf..4c5d524c61 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-arm64/native/avformat-58.dll and b/osu.Framework.NativeLibs/runtimes/win-arm64/native/avformat-58.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-arm64/native/avutil-56.dll b/osu.Framework.NativeLibs/runtimes/win-arm64/native/avutil-56.dll
index 44eb2b8f54..d02080ddb5 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-arm64/native/avutil-56.dll and b/osu.Framework.NativeLibs/runtimes/win-arm64/native/avutil-56.dll 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-arm64/native/swscale-5.dll b/osu.Framework.NativeLibs/runtimes/win-arm64/native/swscale-5.dll
index 10f4217f53..368162a813 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-arm64/native/swscale-5.dll and b/osu.Framework.NativeLibs/runtimes/win-arm64/native/swscale-5.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x64/native/avcodec-58.dll b/osu.Framework.NativeLibs/runtimes/win-x64/native/avcodec-58.dll
index 2cfe80e9e4..4119628587 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x64/native/avcodec-58.dll and b/osu.Framework.NativeLibs/runtimes/win-x64/native/avcodec-58.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x64/native/avfilter-7.dll b/osu.Framework.NativeLibs/runtimes/win-x64/native/avfilter-7.dll
deleted file mode 100644
index dbac50102d..0000000000
Binary files a/osu.Framework.NativeLibs/runtimes/win-x64/native/avfilter-7.dll and /dev/null differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x64/native/avformat-58.dll b/osu.Framework.NativeLibs/runtimes/win-x64/native/avformat-58.dll
index 4da1e28faf..6c68a838c5 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x64/native/avformat-58.dll and b/osu.Framework.NativeLibs/runtimes/win-x64/native/avformat-58.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x64/native/avutil-56.dll b/osu.Framework.NativeLibs/runtimes/win-x64/native/avutil-56.dll
index 0390cbb251..8b2c26b280 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x64/native/avutil-56.dll and b/osu.Framework.NativeLibs/runtimes/win-x64/native/avutil-56.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-x64/native/basswasapi.dll b/osu.Framework.NativeLibs/runtimes/win-x64/native/basswasapi.dll
new file mode 100755
index 0000000000..0cf465d7bd
Binary files /dev/null and b/osu.Framework.NativeLibs/runtimes/win-x64/native/basswasapi.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x64/native/swscale-5.dll b/osu.Framework.NativeLibs/runtimes/win-x64/native/swscale-5.dll
index 2111d69035..7161b3dd9c 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x64/native/swscale-5.dll and b/osu.Framework.NativeLibs/runtimes/win-x64/native/swscale-5.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x86/native/avcodec-58.dll b/osu.Framework.NativeLibs/runtimes/win-x86/native/avcodec-58.dll
index c74db8adff..dba390167c 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x86/native/avcodec-58.dll and b/osu.Framework.NativeLibs/runtimes/win-x86/native/avcodec-58.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x86/native/avfilter-7.dll b/osu.Framework.NativeLibs/runtimes/win-x86/native/avfilter-7.dll
deleted file mode 100644
index 88d485bc57..0000000000
Binary files a/osu.Framework.NativeLibs/runtimes/win-x86/native/avfilter-7.dll and /dev/null differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x86/native/avformat-58.dll b/osu.Framework.NativeLibs/runtimes/win-x86/native/avformat-58.dll
index ddb51e11dc..e20af62e69 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x86/native/avformat-58.dll and b/osu.Framework.NativeLibs/runtimes/win-x86/native/avformat-58.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x86/native/avutil-56.dll b/osu.Framework.NativeLibs/runtimes/win-x86/native/avutil-56.dll
index 681ef53965..b7c83c637c 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x86/native/avutil-56.dll and b/osu.Framework.NativeLibs/runtimes/win-x86/native/avutil-56.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/runtimes/win-x86/native/basswasapi.dll b/osu.Framework.NativeLibs/runtimes/win-x86/native/basswasapi.dll
new file mode 100755
index 0000000000..77b65163af
Binary files /dev/null and b/osu.Framework.NativeLibs/runtimes/win-x86/native/basswasapi.dll differ
diff --git a/osu.Framework.NativeLibs/runtimes/win-x86/native/swscale-5.dll b/osu.Framework.NativeLibs/runtimes/win-x86/native/swscale-5.dll
index 1e233c3484..af3e3bf264 100644
Binary files a/osu.Framework.NativeLibs/runtimes/win-x86/native/swscale-5.dll and b/osu.Framework.NativeLibs/runtimes/win-x86/native/swscale-5.dll differ
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/BUILDING.md b/osu.Framework.NativeLibs/scripts/ffmpeg/BUILDING.md
new file mode 100644
index 0000000000..531ff3030e
--- /dev/null
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/BUILDING.md
@@ -0,0 +1,63 @@
+# Build Instructions
+
+1. Install the dependencies for your platform(s)
+2. Run the build script for your platform(s)
+3. (macOS only) Run `combine_dylibs.sh` to create universal dylibs
+
+## Build dependencies
+
+In general, you need `gcc`, `make`, and `nasm`.
+If external libraries need to be included in the build, `pkg-config` is also required.
+
+### Windows Dependencies (compiling on Ubuntu/Debian)
+
+Targetting x86 and x86_64:
+
+```sh
+sudo apt install make nasm gcc mingw-w64 mingw-w64-tools
+```
+
+Targetting aarch64:
+
+```sh
+sudo apt install make nasm
+
+# Downloading a llvm-mingw release and adding it to PATH #
+# If host is x86_64:
+url="https://github.com/mstorsjo/llvm-mingw/releases/download/20230614/llvm-mingw-20230614-ucrt-ubuntu-20.04-x86_64.tar.xz"
+# If host is aarch64:
+#url="https://github.com/mstorsjo/llvm-mingw/releases/download/20230614/llvm-mingw-20230614-ucrt-ubuntu-20.04-aarch64.tar.xz"
+curl -Lo llvm-mingw.tar.xz "$url"
+mkdir llvm-mingw
+tar xfJ llvm-mingw.tar.xz --strip 1 -C llvm-mingw
+export PATH="$PATH:$PWD/llvm-mingw/bin"
+```
+
+### macOS Dependencies
+
+Check [this page](https://trac.ffmpeg.org/wiki/CompilationGuide/macOS#CompilingFFmpegyourself) for instructions on how to install macOS dependencies.
+Note that you don't need packages like `x264` or `libvpx`, it is enough to install these packages in addition to Xcode:
+
+```zsh
+brew install gcc make nasm
+```
+
+### Linux Dependencies (Ubuntu/Debian)
+
+```sh
+sudo apt install make nasm gcc pkg-config libva-dev libvdpau-dev
+```
+
+## Output files
+
+For each `-` combination, two directories will be created. The directory called `-` will
+contain the resulting shared libraries, and the directory with a `-build` suffix will contain the respective build files.
+
+### macOS only: Combine arch-specific dylib files into universal dylib files
+
+The `combine_universal.sh` script will combine the `x86_64` and `arm64` dylibs into universal dylibs.
+The universal dylibs are output into a folder named `macOS-universal` and should be copied into `osu.Framework.NativeLibs/runtimes/osx`.
+
+## Cleanup
+
+Files are left around for debugging purposes, manually delete the directories to clean up.
\ No newline at end of file
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
new file mode 100755
index 0000000000..5044d4e061
--- /dev/null
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/build-linux.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+set -eu
+
+pushd "$(dirname "$0")" > /dev/null
+SCRIPT_PATH=$(pwd)
+popd > /dev/null
+source "$SCRIPT_PATH/common.sh"
+
+FFMPEG_FLAGS+=(
+ --target-os=linux
+)
+
+pushd . > /dev/null
+prep_ffmpeg linux-x64
+build_ffmpeg
+popd > /dev/null
+
+# gcc creates multiple symlinks per .so file for versioning.
+# We delete the symlinks and rename the real files to include the major library version
+rm linux-x64/*.so
+for f in linux-x64/*.so.*.*.*; do
+ mv -vf "$f" "${f%.*.*}"
+done
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/build-macOS.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/build-macOS.sh
new file mode 100755
index 0000000000..c6978bf011
--- /dev/null
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/build-macOS.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+set -eu
+
+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 "arm64" "x86_64"; do
+ if [ -z "$arch" ]; then
+ echo "invalid option"
+ else
+ break
+ fi
+ done
+fi
+
+FFMPEG_FLAGS+=(
+ --enable-videotoolbox
+ --enable-hwaccel=h264_videotoolbox
+ --enable-hwaccel=hevc_videotoolbox
+ --enable-hwaccel=vp9_videotoolbox
+
+ --enable-cross-compile
+ --target-os=darwin
+ --arch=$arch
+ --extra-cflags="-arch $arch"
+ --extra-ldflags="-arch $arch"
+
+ --install-name-dir='@loader_path'
+)
+
+pushd . > /dev/null
+prep_ffmpeg "macOS-$arch"
+build_ffmpeg
+popd > /dev/null
+
+# Rename dylibs that have a full version string (A.B.C) in their filename
+# Example: avcodec.58.10.72.dylib -> avcodec.58.dylib
+pushd . > /dev/null
+cd "macOS-$arch"
+for f in *.*.*.*.dylib; do
+ [ -f "$f" ] || continue
+ mv -v "$f" "${f%.*.*.*}.dylib"
+done
+popd > /dev/null
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/build-win.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/build-win.sh
new file mode 100755
index 0000000000..8dff16f670
--- /dev/null
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/build-win.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+set -eu
+
+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 "x86" "x64" "arm64"; do
+ if [ -z "$arch" ]; then
+ echo "invalid option"
+ else
+ break
+ fi
+ done
+fi
+
+cross_arch=''
+cross_prefix=''
+
+case $arch in
+ x86)
+ cross_arch='x86'
+ cross_prefix='i686-w64-mingw32-'
+ ;;
+
+ x64)
+ cross_arch='x86_64'
+ cross_prefix='x86_64-w64-mingw32-'
+ ;;
+
+ arm64)
+ cross_arch='aarch64'
+ cross_prefix='aarch64-w64-mingw32-'
+ ;;
+esac
+
+FFMPEG_FLAGS+=(
+ --enable-w32threads
+
+ --enable-dxva2
+ --enable-d3d11va
+ --enable-hwaccel='h264_dxva2,h264_d3d11va,h264_d3d11va2'
+ --enable-hwaccel='hevc_dxva2,hevc_d3d11va,hevc_d3d11va2'
+ --enable-hwaccel='vp9_dxva2,vp9_d3d11va,vp9_d3d11va2'
+
+ --enable-cross-compile
+ --target-os=mingw32
+ --arch=$cross_arch
+ --cross-prefix=$cross_prefix
+)
+
+pushd . > /dev/null
+prep_ffmpeg "win-$arch"
+build_ffmpeg
+popd > /dev/null
+
+find "win-$arch" -not -name "win-$arch" -not -name '*.dll' -delete
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/combine_dylibs.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/combine_dylibs.sh
new file mode 100755
index 0000000000..6965a54499
--- /dev/null
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/combine_dylibs.sh
@@ -0,0 +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 $platform-universal
+cd $platform-arm64
+for lib_arm in *.dylib; do
+ lib_x86="../$platform-x86_64/$lib_arm"
+
+ echo "-> Creating 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 36e5cf2e6d..522b177112 100755
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/common.sh
+++ b/osu.Framework.NativeLibs/scripts/ffmpeg/common.sh
@@ -1,54 +1,71 @@
#!/bin/bash
+set -eu
-FFMPEG_VERSION=4.3.3
-
+FFMPEG_VERSION="7.0"
+FFMPEG_FILE="ffmpeg-$FFMPEG_VERSION.tar.gz"
FFMPEG_FLAGS=(
- --disable-programs
- --disable-doc
+ # General options
--disable-static
- --disable-debug
- --disable-ffplay
- --disable-ffprobe
- --disable-avdevice
- --disable-swresample
- --disable-librtmp
- --disable-alsa
- --disable-iconv
- --disable-libxcb
- --disable-libxcb-shm
- --disable-libxcb-xfixes
- --disable-libxcb-shape
- --disable-sdl2
- --disable-zlib
- --disable-bzlib
- --disable-lzma
- --disable-xlib
- --disable-schannel
--enable-shared
+ --disable-debug
+ --disable-all
+ --disable-autodetect
+ --enable-lto
+
+ # Libraries
+ --enable-avcodec
+ --enable-avformat
+ --enable-swscale
+
+ # Legacy video formats
+ --enable-demuxer='avi,flv,asf'
+ --enable-parser='mpeg4video'
+ --enable-decoder='flv,msmpeg4v1,msmpeg4v2,msmpeg4v3,mpeg4,vp6,vp6f,wmv2'
+
+ # Modern video formats
+ --enable-demuxer='mov,matroska' # mov = mp4, matroska = mkv & webm
+ --enable-parser='h264,hevc,vp8,vp9'
+ --enable-decoder='h264,hevc,vp8,vp9'
+ --enable-protocol=pipe
)
-function build_ffmpeg() {
- if [ ! -d "ffmpeg-$FFMPEG_VERSION" ]; then
- echo "-> Downloading source..."
- curl https://ffmpeg.org/releases/ffmpeg-$FFMPEG_VERSION.tar.gz | tar zxf -
+function prep_ffmpeg() {
+ FFMPEG_FLAGS+=(
+ --prefix="$PWD/$1"
+ --shlibdir="$PWD/$1"
+ )
+
+ local build_dir="$1-build"
+ if [ ! -e "$FFMPEG_FILE" ]; then
+ echo "-> Downloading $FFMPEG_FILE..."
+ curl -o "$FFMPEG_FILE" "https://ffmpeg.org/releases/$FFMPEG_FILE"
else
- echo "-> ffmpeg-$FFMPEG_VERSION already exists, not re-downloading."
+ echo "-> $FFMPEG_FILE already exists, not re-downloading."
fi
- echo "-> Configuring..."
-
- cd ffmpeg-$FFMPEG_VERSION
- ./configure "${FFMPEG_FLAGS[@]}"
-
- CORES=0
- if [[ "$OSTYPE" == "darwin"* ]]; then
- CORES=$(sysctl -n hw.ncpu)
+ if [ ! -d "$build_dir" ]; then
+ echo "-> Unpacking source to $build_dir..."
+ mkdir "$build_dir"
+ tar xzf "$FFMPEG_FILE" --strip 1 -C "$build_dir"
else
- CORES=$(nproc)
+ echo "-> $build_dir already exists, skipping unpacking."
fi
- echo "-> Building using $CORES threads..."
+ cd "$build_dir"
+}
+function build_ffmpeg() {
+ echo "-> Configuring..."
+ ./configure "${FFMPEG_FLAGS[@]}"
+
+ echo "-> Building using $CORES threads..."
make -j$CORES
- make install
-}
\ No newline at end of file
+ make install-libs
+}
+
+CORES=0
+if [[ "$OSTYPE" == "darwin"* ]]; then
+ CORES=$(sysctl -n hw.ncpu)
+else
+ CORES=$(nproc)
+fi
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/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.NativeLibs/scripts/ffmpeg/linux/BUILDING.md b/osu.Framework.NativeLibs/scripts/ffmpeg/linux/BUILDING.md
deleted file mode 100644
index d9c98bd52b..0000000000
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/linux/BUILDING.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# Build Instructions
-
-## Dependencies
-
-```
-sudo apt-get update
-sudo apt-get install make nasm gcc
-```
-
-## `fetch_and_build.sh`
-This script downloads ffmpeg 4.3.3 from ffmpeg.org and compiles it.
-
-It outputs libraries into a folder named `linux-x64`.
-
-## Cleanup
-Files are left around for debugging purposes, manually delete the `linux-x64` and `ffmpeg-4.3.3` folders to cleanup.
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/linux/fetch_and_build.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/linux/fetch_and_build.sh
deleted file mode 100755
index 51a34617a3..0000000000
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/linux/fetch_and_build.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-
-pushd $(dirname $0) > /dev/null
-SCRIPT_PATH=$(pwd)
-popd > /dev/null
-source $SCRIPT_PATH/../common.sh
-
-FFMPEG_FLAGS+=(
- --target-os=linux
- --prefix=build-x64
-)
-
-build_ffmpeg
-
-mkdir -p ../build-x64
-cp build-x64/lib/*.so ../build-x64/
\ No newline at end of file
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/macOS/BUILDING.md b/osu.Framework.NativeLibs/scripts/ffmpeg/macOS/BUILDING.md
deleted file mode 100644
index 59b0aef343..0000000000
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/macOS/BUILDING.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# Build Instructions
-
-## Dependencies
-
-See: https://trac.ffmpeg.org/wiki/CompilationGuide/macOS#CompilingFFmpegyourself
-
-## `fetch_and_build.sh`
-This script downloads ffmpeg 4.3.3 from ffmpeg.org and compiles it. You should run this twice - once for `x86_64` and for `arm64`.
-
-It outputs dylibs into folders named `macOS-$arch` depending on the arch that was built.
-
-## `combine_universal.sh`
-Use this script to combine the `x86_64` and `arm64` dylibs built by the previous script into universal dylibs. The universal dylibs are output into a folder named `macOS-universal` and should be copied into `osu.Framework.NativeLibs/runtimes/osx`.
-
-## Cleanup
-Files are left around for debugging purposes, manually delete the `macOS-$arch` and `ffmpeg-4.3.3` folders to cleanup.
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/macOS/combine_universal.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/macOS/combine_universal.sh
deleted file mode 100755
index dab8b5a6ea..0000000000
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/macOS/combine_universal.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-
-mkdir -p macOS-universal
-
-lipo -create macOS-arm64/libavcodec.58.dylib macOS-x86_64/libavcodec.58.dylib -output macOS-universal/libavcodec.58.dylib
-lipo -create macOS-arm64/libavfilter.7.dylib macOS-x86_64/libavfilter.7.dylib -output macOS-universal/libavfilter.7.dylib
-lipo -create macOS-arm64/libavformat.58.dylib macOS-x86_64/libavformat.58.dylib -output macOS-universal/libavformat.58.dylib
-lipo -create macOS-arm64/libavutil.56.dylib macOS-x86_64/libavutil.56.dylib -output macOS-universal/libavutil.56.dylib
-lipo -create macOS-arm64/libswscale.5.dylib macOS-x86_64/libswscale.5.dylib -output macOS-universal/libswscale.5.dylib
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/macOS/fetch_and_build.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/macOS/fetch_and_build.sh
deleted file mode 100755
index 74f80082b8..0000000000
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/macOS/fetch_and_build.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/bin/bash
-
-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? '
- archs=("arm64" "x86_64")
- select arch in "${archs[@]}"; do
- case $arch in
- "arm64")
- break;;
- "x86_64")
- break;;
- *) echo "invalid option";;
- esac
- done
-fi
-
-FFMPEG_FLAGS+=(
- --target-os=darwin
- --arch=$arch
- --enable-cross-compile
- --extra-cflags='-arch $arch'
- --extra-ldflags='-arch $arch'
- --prefix=build-$arch
- --libdir=build-$arch/lib
-)
-
-build_ffmpeg
-
-mv build-$arch/lib/libavcodec.58.91.100.dylib build-$arch/lib/libavcodec.58.dylib
-mv build-$arch/lib/libavfilter.7.85.100.dylib build-$arch/lib/libavfilter.7.dylib
-mv build-$arch/lib/libavformat.58.45.100.dylib build-$arch/lib/libavformat.58.dylib
-mv build-$arch/lib/libavutil.56.51.100.dylib build-$arch/lib/libavutil.56.dylib
-mv build-$arch/lib/libswscale.5.7.100.dylib build-$arch/lib/libswscale.5.dylib
-
-echo "-> Fixing dylibs paths..."
-BUILDPATH=build-$arch/lib
-LIBS="libavcodec.58.dylib libavdevice.58.dylib libavfilter.7.dylib libavformat.58.dylib libavutil.56.dylib libswresample.3.dylib libswscale.5.dylib"
-for f in $LIBS; do
- install_name_tool $BUILDPATH/$f -id @loader_path/$f \
- -change $BUILDPATH/libavcodec.58.dylib @loader_path/libavcodec.58.dylib \
- -change $BUILDPATH/libavfilter.7.dylib @loader_path/libavfilter.7.dylib \
- -change $BUILDPATH/libavformat.58.dylib @loader_path/libavformat.58.dylib \
- -change $BUILDPATH/libavutil.56.dylib @loader_path/libavutil.56.dylib \
- -change $BUILDPATH/libswscale.5.dylib @loader_path/libswscale.5.dylib
-
- mkdir -p ../build-$arch
- cp $BUILDPATH/$f ../build-$arch/$f
-done
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/win/BUILDING.md b/osu.Framework.NativeLibs/scripts/ffmpeg/win/BUILDING.md
deleted file mode 100644
index 7833c7a451..0000000000
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/win/BUILDING.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# Build Instructions
-
-## Dependencies
-
-```
-sudo apt-get update
-sudo apt-get install make nasm gcc mingw-w64
-```
-
-## `fetch_and_build.sh`
-This script downloads ffmpeg 4.3.3 from ffmpeg.org and compiles it. You should run this three times - once for `x86`, `x64`, and `arm64`.
-
-It outputs libraries into folders named `win-$arch` depending on the arch that was built.
-
-## Cleanup
-Files are left around for debugging purposes, manually delete the `win-$arch` and `ffmpeg-4.3.3` folders to cleanup.
diff --git a/osu.Framework.NativeLibs/scripts/ffmpeg/win/fetch_and_build.sh b/osu.Framework.NativeLibs/scripts/ffmpeg/win/fetch_and_build.sh
deleted file mode 100755
index a0cd697bb8..0000000000
--- a/osu.Framework.NativeLibs/scripts/ffmpeg/win/fetch_and_build.sh
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/bin/bash
-
-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? '
- archs=("x86" "x64" "arm64")
- select arch in "${archs[@]}"; do
- case $arch in
- "x86")
- break;;
- "x64")
- break;;
- "arm64")
- break;;
- *) echo "invalid option";;
- esac
- done
-fi
-
-cross_arch=''
-cross_prefix=''
-
-case $arch in
- x86)
- cross_arch='x86'
- cross_prefix='i686-w64-mingw32-'
- ;;
-
- x64)
- cross_arch='x86'
- cross_prefix='x86_64-w64-mingw32-'
- ;;
-
- arm64)
- cross_arch='aarch64'
- cross_prefix='aarch64-w64-mingw32-'
- ;;
-esac
-
-FFMPEG_FLAGS+=(
- --arch=$cross_arch
- --target-os=mingw32
- --cross-prefix=$cross_prefix
- --prefix=build-$arch
-)
-
-build_ffmpeg
-
-mkdir -p ../build-$arch
-cp build-$arch/bin/*.dll ../build-$arch/
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/GeneratorTestHelper.cs b/osu.Framework.SourceGeneration.Tests/GeneratorTestHelper.cs
index 7f73013d19..36542417de 100644
--- a/osu.Framework.SourceGeneration.Tests/GeneratorTestHelper.cs
+++ b/osu.Framework.SourceGeneration.Tests/GeneratorTestHelper.cs
@@ -90,7 +90,7 @@ public void AddOrUpdateSource(string filename, string content)
{
var newTree = CSharpSyntaxTree.ParseText(content, path: filename);
- if (sources.ContainsKey(filename))
+ if (!sources.TryAdd(filename, newTree))
{
var oldTree = sources[filename];
sources[filename] = newTree;
@@ -104,7 +104,6 @@ public void AddOrUpdateSource(string filename, string content)
}
else
{
- sources.Add(filename, newTree);
Compilation = Compilation.AddSyntaxTrees(newTree);
}
}
diff --git a/osu.Framework.SourceGeneration.Tests/HandleInput/HandleInputSourceGeneratorTests.cs b/osu.Framework.SourceGeneration.Tests/HandleInput/HandleInputSourceGeneratorTests.cs
new file mode 100644
index 0000000000..dce93bc56c
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/HandleInput/HandleInputSourceGeneratorTests.cs
@@ -0,0 +1,64 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Threading.Tasks;
+using Xunit;
+using VerifyCS = osu.Framework.SourceGeneration.Tests.Verifiers.CSharpSourceGeneratorVerifier;
+
+namespace osu.Framework.SourceGeneration.Tests.HandleInput
+{
+ public class HandleInputSourceGeneratorTests : AbstractGeneratorTests
+ {
+ protected override string ResourceNamespace => "HandleInput";
+
+ [Theory]
+ [InlineData("HandleMethod")]
+ [InlineData("OnMouseMoveMethod")]
+ [InlineData("OnHoverMethod")]
+ [InlineData("OnHoverLostMethod")]
+ [InlineData("OnMouseDownMethod")]
+ [InlineData("OnMouseUpMethod")]
+ [InlineData("OnClickMethod")]
+ [InlineData("OnDoubleClickMethod")]
+ [InlineData("OnDragStartMethod")]
+ [InlineData("OnDragMethod")]
+ [InlineData("OnDragEndMethod")]
+ [InlineData("OnScrollMethod")]
+ [InlineData("OnFocusMethod")]
+ [InlineData("OnFocusLostMethod")]
+ [InlineData("OnTouchDownMethod")]
+ [InlineData("OnTouchMoveMethod")]
+ [InlineData("OnTouchUpMethod")]
+ [InlineData("OnTabletPenButtonPressMethod")]
+ [InlineData("OnTabletPenButtonReleaseMethod")]
+ [InlineData("OnKeyDownMethod")]
+ [InlineData("OnKeyUpMethod")]
+ [InlineData("OnJoystickPressMethod")]
+ [InlineData("OnJoystickReleaseMethod")]
+ [InlineData("OnJoystickAxisMoveMethod")]
+ [InlineData("OnTabletAuxiliaryButtonPressMethod")]
+ [InlineData("OnTabletAuxiliaryButtonReleaseMethod")]
+ [InlineData("OnMidiDownMethod")]
+ [InlineData("OnMidiUpMethod")]
+ [InlineData("HandlePositionalInputProperty")]
+ [InlineData("HandleNonPositionalInputProperty")]
+ [InlineData("AcceptsFocusProperty")]
+ [InlineData("IHasTooltipInterface")]
+ [InlineData("IHasCustomTooltipInterface")]
+ [InlineData("IHasContextMenuInterface")]
+ [InlineData("IHasPopoverInterface")]
+ [InlineData("IKeyBindingHandlerInterface")]
+ [InlineData("IntermediateNonPartial")]
+ public Task Check(string name)
+ {
+ GetTestSources(name,
+ out (string filename, string content)[] commonSourceFiles,
+ out (string filename, string content)[] sourceFiles,
+ out (string filename, string content)[] commonGeneratedFiles,
+ out (string filename, string content)[] generatedFiles
+ );
+
+ return VerifyCS.VerifyAsync(commonSourceFiles, sourceFiles, commonGeneratedFiles, generatedFiles);
+ }
+ }
+}
diff --git a/osu.Framework.SourceGeneration.Tests/LongRunningLoad/LongRunningLoadSourceGeneratorTests.cs b/osu.Framework.SourceGeneration.Tests/LongRunningLoad/LongRunningLoadSourceGeneratorTests.cs
new file mode 100644
index 0000000000..f2a6a61028
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/LongRunningLoad/LongRunningLoadSourceGeneratorTests.cs
@@ -0,0 +1,28 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Threading.Tasks;
+using Xunit;
+using VerifyCS = osu.Framework.SourceGeneration.Tests.Verifiers.CSharpSourceGeneratorVerifier;
+
+namespace osu.Framework.SourceGeneration.Tests.LongRunningLoad
+{
+ public class LongRunningLoadSourceGeneratorTests : AbstractGeneratorTests
+ {
+ protected override string ResourceNamespace => "LongRunningLoad";
+
+ [Theory]
+ [InlineData("LongRunningType")]
+ public Task Check(string name)
+ {
+ GetTestSources(name,
+ out (string filename, string content)[] commonSourceFiles,
+ out (string filename, string content)[] sourceFiles,
+ out (string filename, string content)[] commonGeneratedFiles,
+ out (string filename, string content)[] generatedFiles
+ );
+
+ return VerifyCS.VerifyAsync(commonSourceFiles, sourceFiles, commonGeneratedFiles, generatedFiles);
+ }
+ }
+}
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/AcceptsFocusProperty/Generated/g_AcceptsFocusProperty_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/AcceptsFocusProperty/Generated/g_AcceptsFocusProperty_HandleInput.txt
new file mode 100644
index 0000000000..0e2e16b27b
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/AcceptsFocusProperty/Generated/g_AcceptsFocusProperty_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class AcceptsFocusProperty : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::AcceptsFocusProperty);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/AcceptsFocusProperty/Sources/AcceptsFocusProperty.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/AcceptsFocusProperty/Sources/AcceptsFocusProperty.txt
new file mode 100644
index 0000000000..6f99c2444f
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/AcceptsFocusProperty/Sources/AcceptsFocusProperty.txt
@@ -0,0 +1,4 @@
+public partial class AcceptsFocusProperty : osu.Framework.Graphics.Drawable
+{
+ protected override bool AcceptsFocus => false;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonGenerated/g_osu.Framework.Graphics.Drawable_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonGenerated/g_osu.Framework.Graphics.Drawable_HandleInput.txt
new file mode 100644
index 0000000000..5d641826e9
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonGenerated/g_osu.Framework.Graphics.Drawable_HandleInput.txt
@@ -0,0 +1,13 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+namespace osu.Framework.Graphics
+{
+ partial class Drawable : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+ {
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::osu.Framework.Graphics.Drawable);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => false;
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => false;
+ }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/Drawable.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/Drawable.txt
new file mode 100644
index 0000000000..89092e7dc2
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/Drawable.txt
@@ -0,0 +1,39 @@
+namespace osu.Framework.Graphics
+{
+ public partial class Drawable
+ {
+ protected virtual void Handle(object e) { }
+
+ protected virtual void OnMouseMove(object e) { }
+ protected virtual void OnHover(object e) { }
+ protected virtual void OnHoverLost(object e) { }
+ protected virtual void OnMouseDown(object e) { }
+ protected virtual void OnMouseUp(object e) { }
+ protected virtual void OnClick(object e) { }
+ protected virtual void OnDoubleClick(object e) { }
+ protected virtual void OnDragStart(object e) { }
+ protected virtual void OnDrag(object e) { }
+ protected virtual void OnDragEnd(object e) { }
+ protected virtual void OnScroll(object e) { }
+ protected virtual void OnFocus(object e) { }
+ protected virtual void OnFocusLost(object e) { }
+ protected virtual void OnKeyDown(object e) { }
+ protected virtual void OnKeyUp(object e) { }
+ protected virtual void OnTouchDown(object e) { }
+ protected virtual void OnTouchMove(object e) { }
+ protected virtual void OnTouchUp(object e) { }
+ protected virtual void OnJoystickPress(object e) { }
+ protected virtual void OnJoystickRelease(object e) { }
+ protected virtual void OnJoystickAxisMove(object e) { }
+ protected virtual void OnMidiDown(object e) { }
+ protected virtual void OnMidiUp(object e) { }
+ protected virtual void OnTabletPenButtonPress(object e) { }
+ protected virtual void OnTabletPenButtonRelease(object e) { }
+ protected virtual void OnTabletAuxiliaryButtonPress(object e) { }
+ protected virtual void OnTabletAuxiliaryButtonRelease(object e) { }
+
+ protected virtual bool HandlePositionalInput => false;
+ protected virtual bool HandleNonPositionalInput => false;
+ protected virtual bool AcceptsFocus => false;
+ }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IHasContextMenu.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IHasContextMenu.txt
new file mode 100644
index 0000000000..fa66d19351
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IHasContextMenu.txt
@@ -0,0 +1,6 @@
+namespace osu.Framework.Graphics.Cursor
+{
+ public interface IHasContextMenu
+ {
+ }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IHasCustomTooltip.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IHasCustomTooltip.txt
new file mode 100644
index 0000000000..2ebbd1e45c
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IHasCustomTooltip.txt
@@ -0,0 +1,6 @@
+namespace osu.Framework.Graphics.Cursor
+{
+ public interface IHasCustomTooltip
+ {
+ }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IHasPopover.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IHasPopover.txt
new file mode 100644
index 0000000000..84787486e9
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IHasPopover.txt
@@ -0,0 +1,6 @@
+namespace osu.Framework.Graphics.Cursor
+{
+ public interface IHasPopover
+ {
+ }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IHasTooltip.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IHasTooltip.txt
new file mode 100644
index 0000000000..e165891ece
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IHasTooltip.txt
@@ -0,0 +1,6 @@
+namespace osu.Framework.Graphics.Cursor
+{
+ public interface IHasTooltip
+ {
+ }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IKeyBindingHandler.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IKeyBindingHandler.txt
new file mode 100644
index 0000000000..242d309e77
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/IKeyBindingHandler.txt
@@ -0,0 +1,6 @@
+namespace osu.Framework.Input.Bindings
+{
+ public interface IKeyBindingHandler
+ {
+ }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/ISourceGeneratedHandleInputCache.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/ISourceGeneratedHandleInputCache.txt
new file mode 100644
index 0000000000..0b72a81c60
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/CommonSources/ISourceGeneratedHandleInputCache.txt
@@ -0,0 +1,9 @@
+namespace osu.Framework.Input
+{
+ public interface ISourceGeneratedHandleInputCache
+ {
+ protected internal System.Type KnownType { get; }
+ protected internal bool RequestsPositionalInput { get; }
+ protected internal bool RequestsNonPositionalInput { get; }
+ }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandleMethod/Generated/g_HandleMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandleMethod/Generated/g_HandleMethod_HandleInput.txt
new file mode 100644
index 0000000000..9a867cecf8
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandleMethod/Generated/g_HandleMethod_HandleInput.txt
@@ -0,0 +1,10 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class HandleMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::HandleMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandleMethod/Sources/HandleMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandleMethod/Sources/HandleMethod.txt
new file mode 100644
index 0000000000..ff01ecc7ad
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandleMethod/Sources/HandleMethod.txt
@@ -0,0 +1,4 @@
+public partial class HandleMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void Handle(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandleNonPositionalInputProperty/Generated/g_HandleNonPositionalInputProperty_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandleNonPositionalInputProperty/Generated/g_HandleNonPositionalInputProperty_HandleInput.txt
new file mode 100644
index 0000000000..2fec5a4f63
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandleNonPositionalInputProperty/Generated/g_HandleNonPositionalInputProperty_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class HandleNonPositionalInputProperty : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::HandleNonPositionalInputProperty);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandleNonPositionalInputProperty/Sources/HandleNonPositionalInputProperty.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandleNonPositionalInputProperty/Sources/HandleNonPositionalInputProperty.txt
new file mode 100644
index 0000000000..097025254d
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandleNonPositionalInputProperty/Sources/HandleNonPositionalInputProperty.txt
@@ -0,0 +1,4 @@
+public partial class HandleNonPositionalInputProperty : osu.Framework.Graphics.Drawable
+{
+ protected override bool HandleNonPositionalInput => false;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandlePositionalInputProperty/Generated/g_HandlePositionalInputProperty_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandlePositionalInputProperty/Generated/g_HandlePositionalInputProperty_HandleInput.txt
new file mode 100644
index 0000000000..e6140fbf90
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandlePositionalInputProperty/Generated/g_HandlePositionalInputProperty_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class HandlePositionalInputProperty : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::HandlePositionalInputProperty);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandlePositionalInputProperty/Sources/HandlePositionalInputProperty.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandlePositionalInputProperty/Sources/HandlePositionalInputProperty.txt
new file mode 100644
index 0000000000..254b7661b6
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/HandlePositionalInputProperty/Sources/HandlePositionalInputProperty.txt
@@ -0,0 +1,4 @@
+public partial class HandlePositionalInputProperty : osu.Framework.Graphics.Drawable
+{
+ protected override bool HandlePositionalInput => false;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasContextMenuInterface/Generated/g_IHasContextMenuInterface_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasContextMenuInterface/Generated/g_IHasContextMenuInterface_HandleInput.txt
new file mode 100644
index 0000000000..b34773253d
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasContextMenuInterface/Generated/g_IHasContextMenuInterface_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class IHasContextMenuInterface : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::IHasContextMenuInterface);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasContextMenuInterface/Sources/IHasContextMenuInterface.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasContextMenuInterface/Sources/IHasContextMenuInterface.txt
new file mode 100644
index 0000000000..75ec5d452b
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasContextMenuInterface/Sources/IHasContextMenuInterface.txt
@@ -0,0 +1,5 @@
+using osu.Framework.Graphics.Cursor;
+
+public partial class IHasContextMenuInterface : osu.Framework.Graphics.Drawable, IHasContextMenu
+{
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasCustomTooltipInterface/Generated/g_IHasCustomTooltipInterface_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasCustomTooltipInterface/Generated/g_IHasCustomTooltipInterface_HandleInput.txt
new file mode 100644
index 0000000000..d312abdc0a
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasCustomTooltipInterface/Generated/g_IHasCustomTooltipInterface_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class IHasCustomTooltipInterface : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::IHasCustomTooltipInterface);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasCustomTooltipInterface/Sources/IHasCustomTooltipInterface.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasCustomTooltipInterface/Sources/IHasCustomTooltipInterface.txt
new file mode 100644
index 0000000000..bf5d9c59db
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasCustomTooltipInterface/Sources/IHasCustomTooltipInterface.txt
@@ -0,0 +1,5 @@
+using osu.Framework.Graphics.Cursor;
+
+public partial class IHasCustomTooltipInterface : osu.Framework.Graphics.Drawable, IHasCustomTooltip
+{
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasPopoverInterface/Generated/g_IHasPopoverInterface_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasPopoverInterface/Generated/g_IHasPopoverInterface_HandleInput.txt
new file mode 100644
index 0000000000..f529f436d0
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasPopoverInterface/Generated/g_IHasPopoverInterface_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class IHasPopoverInterface : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::IHasPopoverInterface);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasPopoverInterface/Sources/IHasPopoverInterface.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasPopoverInterface/Sources/IHasPopoverInterface.txt
new file mode 100644
index 0000000000..536677b80d
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasPopoverInterface/Sources/IHasPopoverInterface.txt
@@ -0,0 +1,5 @@
+using osu.Framework.Graphics.Cursor;
+
+public partial class IHasPopoverInterface : osu.Framework.Graphics.Drawable, IHasPopover
+{
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasTooltipInterface/Generated/g_IHasTooltipInterface_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasTooltipInterface/Generated/g_IHasTooltipInterface_HandleInput.txt
new file mode 100644
index 0000000000..452c059595
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasTooltipInterface/Generated/g_IHasTooltipInterface_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class IHasTooltipInterface : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::IHasTooltipInterface);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasTooltipInterface/Sources/IHasTooltipInterface.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasTooltipInterface/Sources/IHasTooltipInterface.txt
new file mode 100644
index 0000000000..bd449d953b
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IHasTooltipInterface/Sources/IHasTooltipInterface.txt
@@ -0,0 +1,5 @@
+using osu.Framework.Graphics.Cursor;
+
+public partial class IHasTooltipInterface : osu.Framework.Graphics.Drawable, IHasTooltip
+{
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IKeyBindingHandlerInterface/Generated/g_IKeyBindingHandlerInterface_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IKeyBindingHandlerInterface/Generated/g_IKeyBindingHandlerInterface_HandleInput.txt
new file mode 100644
index 0000000000..72c5d7de77
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IKeyBindingHandlerInterface/Generated/g_IKeyBindingHandlerInterface_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class IKeyBindingHandlerInterface : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::IKeyBindingHandlerInterface);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IKeyBindingHandlerInterface/Sources/IKeyBindingHandlerInterface.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IKeyBindingHandlerInterface/Sources/IKeyBindingHandlerInterface.txt
new file mode 100644
index 0000000000..b457f5990d
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IKeyBindingHandlerInterface/Sources/IKeyBindingHandlerInterface.txt
@@ -0,0 +1,5 @@
+using osu.Framework.Input.Bindings;
+
+public partial class IKeyBindingHandlerInterface : osu.Framework.Graphics.Drawable, IKeyBindingHandler
+{
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IntermediateNonPartial/Generated/g_PartialClass_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IntermediateNonPartial/Generated/g_PartialClass_HandleInput.txt
new file mode 100644
index 0000000000..dab954aec0
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IntermediateNonPartial/Generated/g_PartialClass_HandleInput.txt
@@ -0,0 +1,10 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class PartialClass : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::PartialClass);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IntermediateNonPartial/Sources/NonPartialClass.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IntermediateNonPartial/Sources/NonPartialClass.txt
new file mode 100644
index 0000000000..b58e975bea
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IntermediateNonPartial/Sources/NonPartialClass.txt
@@ -0,0 +1,4 @@
+public class NonPartialClass : osu.Framework.Graphics.Drawable
+{
+ protected override void Handle(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IntermediateNonPartial/Sources/PartialClass.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IntermediateNonPartial/Sources/PartialClass.txt
new file mode 100644
index 0000000000..f52a611655
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/IntermediateNonPartial/Sources/PartialClass.txt
@@ -0,0 +1,3 @@
+public partial class PartialClass : NonPartialClass
+{
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnClickMethod/Generated/g_OnClickMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnClickMethod/Generated/g_OnClickMethod_HandleInput.txt
new file mode 100644
index 0000000000..3ffb370ae8
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnClickMethod/Generated/g_OnClickMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnClickMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnClickMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnClickMethod/Sources/OnClickMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnClickMethod/Sources/OnClickMethod.txt
new file mode 100644
index 0000000000..e463dd2b02
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnClickMethod/Sources/OnClickMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnClickMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnClick(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDoubleClickMethod/Generated/g_OnDoubleClickMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDoubleClickMethod/Generated/g_OnDoubleClickMethod_HandleInput.txt
new file mode 100644
index 0000000000..cd9602e9ac
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDoubleClickMethod/Generated/g_OnDoubleClickMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnDoubleClickMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnDoubleClickMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDoubleClickMethod/Sources/OnDoubleClickMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDoubleClickMethod/Sources/OnDoubleClickMethod.txt
new file mode 100644
index 0000000000..6bbab3a8e0
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDoubleClickMethod/Sources/OnDoubleClickMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnDoubleClickMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnDoubleClick(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragEndMethod/Generated/g_OnDragEndMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragEndMethod/Generated/g_OnDragEndMethod_HandleInput.txt
new file mode 100644
index 0000000000..c5ec735477
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragEndMethod/Generated/g_OnDragEndMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnDragEndMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnDragEndMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragEndMethod/Sources/OnDragEndMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragEndMethod/Sources/OnDragEndMethod.txt
new file mode 100644
index 0000000000..091146dec1
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragEndMethod/Sources/OnDragEndMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnDragEndMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnDragEnd(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragMethod/Generated/g_OnDragMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragMethod/Generated/g_OnDragMethod_HandleInput.txt
new file mode 100644
index 0000000000..0215339f90
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragMethod/Generated/g_OnDragMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnDragMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnDragMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragMethod/Sources/OnDragMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragMethod/Sources/OnDragMethod.txt
new file mode 100644
index 0000000000..e921baf2c6
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragMethod/Sources/OnDragMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnDragMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnDrag(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragStartMethod/Generated/g_OnDragStartMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragStartMethod/Generated/g_OnDragStartMethod_HandleInput.txt
new file mode 100644
index 0000000000..cc10baafce
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragStartMethod/Generated/g_OnDragStartMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnDragStartMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnDragStartMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragStartMethod/Sources/OnDragStartMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragStartMethod/Sources/OnDragStartMethod.txt
new file mode 100644
index 0000000000..7ff470e6fb
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnDragStartMethod/Sources/OnDragStartMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnDragStartMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnDragStart(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnFocusLostMethod/Generated/g_OnFocusLostMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnFocusLostMethod/Generated/g_OnFocusLostMethod_HandleInput.txt
new file mode 100644
index 0000000000..4aa301a6d4
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnFocusLostMethod/Generated/g_OnFocusLostMethod_HandleInput.txt
@@ -0,0 +1,10 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnFocusLostMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnFocusLostMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnFocusLostMethod/Sources/OnFocusLostMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnFocusLostMethod/Sources/OnFocusLostMethod.txt
new file mode 100644
index 0000000000..c457fc98d6
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnFocusLostMethod/Sources/OnFocusLostMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnFocusLostMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnFocusLost(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnFocusMethod/Generated/g_OnFocusMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnFocusMethod/Generated/g_OnFocusMethod_HandleInput.txt
new file mode 100644
index 0000000000..8e48c314f0
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnFocusMethod/Generated/g_OnFocusMethod_HandleInput.txt
@@ -0,0 +1,10 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnFocusMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnFocusMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnFocusMethod/Sources/OnFocusMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnFocusMethod/Sources/OnFocusMethod.txt
new file mode 100644
index 0000000000..185229d9cd
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnFocusMethod/Sources/OnFocusMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnFocusMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnFocus(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnHoverLostMethod/Generated/g_OnHoverLostMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnHoverLostMethod/Generated/g_OnHoverLostMethod_HandleInput.txt
new file mode 100644
index 0000000000..bbbe7585c8
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnHoverLostMethod/Generated/g_OnHoverLostMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnHoverLostMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnHoverLostMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnHoverLostMethod/Sources/OnHoverLostMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnHoverLostMethod/Sources/OnHoverLostMethod.txt
new file mode 100644
index 0000000000..5fc813320f
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnHoverLostMethod/Sources/OnHoverLostMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnHoverLostMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnHoverLost(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnHoverMethod/Generated/g_OnHoverMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnHoverMethod/Generated/g_OnHoverMethod_HandleInput.txt
new file mode 100644
index 0000000000..7dc5351d6e
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnHoverMethod/Generated/g_OnHoverMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnHoverMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnHoverMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnHoverMethod/Sources/OnHoverMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnHoverMethod/Sources/OnHoverMethod.txt
new file mode 100644
index 0000000000..2a729f7beb
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnHoverMethod/Sources/OnHoverMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnHoverMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnHover(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickAxisMoveMethod/Generated/g_OnJoystickAxisMoveMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickAxisMoveMethod/Generated/g_OnJoystickAxisMoveMethod_HandleInput.txt
new file mode 100644
index 0000000000..b3251e113e
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickAxisMoveMethod/Generated/g_OnJoystickAxisMoveMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnJoystickAxisMoveMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnJoystickAxisMoveMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickAxisMoveMethod/Sources/OnJoystickAxisMoveMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickAxisMoveMethod/Sources/OnJoystickAxisMoveMethod.txt
new file mode 100644
index 0000000000..9c5febb1f9
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickAxisMoveMethod/Sources/OnJoystickAxisMoveMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnJoystickAxisMoveMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnJoystickAxisMove(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickPressMethod/Generated/g_OnJoystickPressMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickPressMethod/Generated/g_OnJoystickPressMethod_HandleInput.txt
new file mode 100644
index 0000000000..be8a54303d
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickPressMethod/Generated/g_OnJoystickPressMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnJoystickPressMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnJoystickPressMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickPressMethod/Sources/OnJoystickPressMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickPressMethod/Sources/OnJoystickPressMethod.txt
new file mode 100644
index 0000000000..829c4efbfe
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickPressMethod/Sources/OnJoystickPressMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnJoystickPressMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnJoystickPress(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickReleaseMethod/Generated/g_OnJoystickReleaseMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickReleaseMethod/Generated/g_OnJoystickReleaseMethod_HandleInput.txt
new file mode 100644
index 0000000000..82767a3d4b
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickReleaseMethod/Generated/g_OnJoystickReleaseMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnJoystickReleaseMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnJoystickReleaseMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickReleaseMethod/Sources/OnJoystickReleaseMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickReleaseMethod/Sources/OnJoystickReleaseMethod.txt
new file mode 100644
index 0000000000..7df6873be3
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnJoystickReleaseMethod/Sources/OnJoystickReleaseMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnJoystickReleaseMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnJoystickRelease(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnKeyDownMethod/Generated/g_OnKeyDownMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnKeyDownMethod/Generated/g_OnKeyDownMethod_HandleInput.txt
new file mode 100644
index 0000000000..ab892cc0ad
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnKeyDownMethod/Generated/g_OnKeyDownMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnKeyDownMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnKeyDownMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnKeyDownMethod/Sources/OnKeyDownMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnKeyDownMethod/Sources/OnKeyDownMethod.txt
new file mode 100644
index 0000000000..3a772b4499
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnKeyDownMethod/Sources/OnKeyDownMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnKeyDownMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnKeyDown(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnKeyUpMethod/Generated/g_OnKeyUpMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnKeyUpMethod/Generated/g_OnKeyUpMethod_HandleInput.txt
new file mode 100644
index 0000000000..c59b62cbe9
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnKeyUpMethod/Generated/g_OnKeyUpMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnKeyUpMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnKeyUpMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnKeyUpMethod/Sources/OnKeyUpMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnKeyUpMethod/Sources/OnKeyUpMethod.txt
new file mode 100644
index 0000000000..b894f4fa52
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnKeyUpMethod/Sources/OnKeyUpMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnKeyUpMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnKeyUp(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMidiDownMethod/Generated/g_OnMidiDownMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMidiDownMethod/Generated/g_OnMidiDownMethod_HandleInput.txt
new file mode 100644
index 0000000000..1d891b2c04
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMidiDownMethod/Generated/g_OnMidiDownMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnMidiDownMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnMidiDownMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMidiDownMethod/Sources/OnMidiDownMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMidiDownMethod/Sources/OnMidiDownMethod.txt
new file mode 100644
index 0000000000..e239ad491f
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMidiDownMethod/Sources/OnMidiDownMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnMidiDownMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnMidiDown(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMidiUpMethod/Generated/g_OnMidiUpMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMidiUpMethod/Generated/g_OnMidiUpMethod_HandleInput.txt
new file mode 100644
index 0000000000..e03a548caf
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMidiUpMethod/Generated/g_OnMidiUpMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnMidiUpMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnMidiUpMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMidiUpMethod/Sources/OnMidiUpMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMidiUpMethod/Sources/OnMidiUpMethod.txt
new file mode 100644
index 0000000000..5f9f36fc5d
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMidiUpMethod/Sources/OnMidiUpMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnMidiUpMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnMidiUp(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseDownMethod/Generated/g_OnMouseDownMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseDownMethod/Generated/g_OnMouseDownMethod_HandleInput.txt
new file mode 100644
index 0000000000..01a8f10e4d
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseDownMethod/Generated/g_OnMouseDownMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnMouseDownMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnMouseDownMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseDownMethod/Sources/OnMouseDownMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseDownMethod/Sources/OnMouseDownMethod.txt
new file mode 100644
index 0000000000..588c73bb8c
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseDownMethod/Sources/OnMouseDownMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnMouseDownMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnMouseDown(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseMoveMethod/Generated/g_OnMouseMoveMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseMoveMethod/Generated/g_OnMouseMoveMethod_HandleInput.txt
new file mode 100644
index 0000000000..0c0c5984f6
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseMoveMethod/Generated/g_OnMouseMoveMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnMouseMoveMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnMouseMoveMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseMoveMethod/Sources/OnMouseMoveMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseMoveMethod/Sources/OnMouseMoveMethod.txt
new file mode 100644
index 0000000000..8e7aa35f8c
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseMoveMethod/Sources/OnMouseMoveMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnMouseMoveMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnMouseMove(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseUpMethod/Generated/g_OnMouseUpMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseUpMethod/Generated/g_OnMouseUpMethod_HandleInput.txt
new file mode 100644
index 0000000000..aa3c2e7a0f
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseUpMethod/Generated/g_OnMouseUpMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnMouseUpMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnMouseUpMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseUpMethod/Sources/OnMouseUpMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseUpMethod/Sources/OnMouseUpMethod.txt
new file mode 100644
index 0000000000..6cac8c925c
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnMouseUpMethod/Sources/OnMouseUpMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnMouseUpMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnMouseUp(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnScrollMethod/Generated/g_OnScrollMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnScrollMethod/Generated/g_OnScrollMethod_HandleInput.txt
new file mode 100644
index 0000000000..d32f8ebf54
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnScrollMethod/Generated/g_OnScrollMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnScrollMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnScrollMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnScrollMethod/Sources/OnScrollMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnScrollMethod/Sources/OnScrollMethod.txt
new file mode 100644
index 0000000000..081e7911c4
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnScrollMethod/Sources/OnScrollMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnScrollMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnScroll(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletAuxiliaryButtonPressMethod/Generated/g_OnTabletAuxiliaryButtonPressMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletAuxiliaryButtonPressMethod/Generated/g_OnTabletAuxiliaryButtonPressMethod_HandleInput.txt
new file mode 100644
index 0000000000..fc01beb600
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletAuxiliaryButtonPressMethod/Generated/g_OnTabletAuxiliaryButtonPressMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnTabletAuxiliaryButtonPressMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnTabletAuxiliaryButtonPressMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletAuxiliaryButtonPressMethod/Sources/OnTabletAuxiliaryButtonPressMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletAuxiliaryButtonPressMethod/Sources/OnTabletAuxiliaryButtonPressMethod.txt
new file mode 100644
index 0000000000..7f66542213
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletAuxiliaryButtonPressMethod/Sources/OnTabletAuxiliaryButtonPressMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnTabletAuxiliaryButtonPressMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnTabletAuxiliaryButtonPress(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletAuxiliaryButtonReleaseMethod/Generated/g_OnTabletAuxiliaryButtonReleaseMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletAuxiliaryButtonReleaseMethod/Generated/g_OnTabletAuxiliaryButtonReleaseMethod_HandleInput.txt
new file mode 100644
index 0000000000..6844d61957
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletAuxiliaryButtonReleaseMethod/Generated/g_OnTabletAuxiliaryButtonReleaseMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnTabletAuxiliaryButtonReleaseMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnTabletAuxiliaryButtonReleaseMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsNonPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletAuxiliaryButtonReleaseMethod/Sources/OnTabletAuxiliaryButtonReleaseMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletAuxiliaryButtonReleaseMethod/Sources/OnTabletAuxiliaryButtonReleaseMethod.txt
new file mode 100644
index 0000000000..80b55bd41a
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletAuxiliaryButtonReleaseMethod/Sources/OnTabletAuxiliaryButtonReleaseMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnTabletAuxiliaryButtonReleaseMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnTabletAuxiliaryButtonRelease(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletPenButtonPressMethod/Generated/g_OnTabletPenButtonPressMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletPenButtonPressMethod/Generated/g_OnTabletPenButtonPressMethod_HandleInput.txt
new file mode 100644
index 0000000000..84c24b4c19
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletPenButtonPressMethod/Generated/g_OnTabletPenButtonPressMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnTabletPenButtonPressMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnTabletPenButtonPressMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletPenButtonPressMethod/Sources/OnTabletPenButtonPressMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletPenButtonPressMethod/Sources/OnTabletPenButtonPressMethod.txt
new file mode 100644
index 0000000000..84876ff885
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletPenButtonPressMethod/Sources/OnTabletPenButtonPressMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnTabletPenButtonPressMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnTabletPenButtonPress(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletPenButtonReleaseMethod/Generated/g_OnTabletPenButtonReleaseMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletPenButtonReleaseMethod/Generated/g_OnTabletPenButtonReleaseMethod_HandleInput.txt
new file mode 100644
index 0000000000..27e232ed8d
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletPenButtonReleaseMethod/Generated/g_OnTabletPenButtonReleaseMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnTabletPenButtonReleaseMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnTabletPenButtonReleaseMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletPenButtonReleaseMethod/Sources/OnTabletPenButtonReleaseMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletPenButtonReleaseMethod/Sources/OnTabletPenButtonReleaseMethod.txt
new file mode 100644
index 0000000000..6dd07709c4
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTabletPenButtonReleaseMethod/Sources/OnTabletPenButtonReleaseMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnTabletPenButtonReleaseMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnTabletPenButtonRelease(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchDownMethod/Generated/g_OnTouchDownMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchDownMethod/Generated/g_OnTouchDownMethod_HandleInput.txt
new file mode 100644
index 0000000000..208a411a93
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchDownMethod/Generated/g_OnTouchDownMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnTouchDownMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnTouchDownMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchDownMethod/Sources/OnTouchDownMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchDownMethod/Sources/OnTouchDownMethod.txt
new file mode 100644
index 0000000000..05b84f3429
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchDownMethod/Sources/OnTouchDownMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnTouchDownMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnTouchDown(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchMoveMethod/Generated/g_OnTouchMoveMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchMoveMethod/Generated/g_OnTouchMoveMethod_HandleInput.txt
new file mode 100644
index 0000000000..eab84c556c
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchMoveMethod/Generated/g_OnTouchMoveMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnTouchMoveMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnTouchMoveMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchMoveMethod/Sources/OnTouchMoveMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchMoveMethod/Sources/OnTouchMoveMethod.txt
new file mode 100644
index 0000000000..8029fe4f46
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchMoveMethod/Sources/OnTouchMoveMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnTouchMoveMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnTouchMove(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchUpMethod/Generated/g_OnTouchUpMethod_HandleInput.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchUpMethod/Generated/g_OnTouchUpMethod_HandleInput.txt
new file mode 100644
index 0000000000..d2317c800c
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchUpMethod/Generated/g_OnTouchUpMethod_HandleInput.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class OnTouchUpMethod : global::osu.Framework.Input.ISourceGeneratedHandleInputCache
+{
+ global::System.Type global::osu.Framework.Input.ISourceGeneratedHandleInputCache.KnownType => typeof(global::OnTouchUpMethod);
+ bool global::osu.Framework.Input.ISourceGeneratedHandleInputCache.RequestsPositionalInput => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchUpMethod/Sources/OnTouchUpMethod.txt b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchUpMethod/Sources/OnTouchUpMethod.txt
new file mode 100644
index 0000000000..14ab67225b
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/HandleInput/OnTouchUpMethod/Sources/OnTouchUpMethod.txt
@@ -0,0 +1,4 @@
+public partial class OnTouchUpMethod : osu.Framework.Graphics.Drawable
+{
+ protected override void OnTouchUp(object e) { }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonGenerated/g_osu.Framework.Graphics.Drawable_LongRunningLoad.txt b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonGenerated/g_osu.Framework.Graphics.Drawable_LongRunningLoad.txt
new file mode 100644
index 0000000000..c3c0ec0238
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonGenerated/g_osu.Framework.Graphics.Drawable_LongRunningLoad.txt
@@ -0,0 +1,12 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+namespace osu.Framework.Graphics
+{
+ partial class Drawable : global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache
+ {
+ global::System.Type global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache.KnownType => typeof(global::osu.Framework.Graphics.Drawable);
+ bool global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache.IsLongRunning => false;
+ }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/Drawable.txt b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/Drawable.txt
new file mode 100644
index 0000000000..e5cde97c34
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/Drawable.txt
@@ -0,0 +1,6 @@
+namespace osu.Framework.Graphics
+{
+ public partial class Drawable
+ {
+ }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/ISourceGeneratedLongRunningLoadCache.txt b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/ISourceGeneratedLongRunningLoadCache.txt
new file mode 100644
index 0000000000..04456fa084
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/ISourceGeneratedLongRunningLoadCache.txt
@@ -0,0 +1,8 @@
+namespace osu.Framework.Allocation
+{
+ public interface ISourceGeneratedLongRunningLoadCache
+ {
+ protected internal System.Type KnownType { get; }
+ protected internal bool IsLongRunning { get; }
+ }
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/LongRunningLoadAttribute.txt b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/LongRunningLoadAttribute.txt
new file mode 100644
index 0000000000..93215725f3
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/LongRunningLoadAttribute.txt
@@ -0,0 +1,9 @@
+using System;
+
+namespace osu.Framework.Allocation
+{
+ [AttributeUsage(AttributeTargets.Class)]
+ public class LongRunningLoadAttribute : Attribute
+ {
+ }
+}
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/LongRunningType/Generated/g_LongRunningType_LongRunningLoad.txt b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/LongRunningType/Generated/g_LongRunningType_LongRunningLoad.txt
new file mode 100644
index 0000000000..65a489d3ea
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/LongRunningType/Generated/g_LongRunningType_LongRunningLoad.txt
@@ -0,0 +1,9 @@
+//
+#nullable enable
+#pragma warning disable CS4014
+
+partial class LongRunningType : global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache
+{
+ global::System.Type global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache.KnownType => typeof(global::LongRunningType);
+ bool global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache.IsLongRunning => true;
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/LongRunningType/Sources/LongRunningType.txt b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/LongRunningType/Sources/LongRunningType.txt
new file mode 100644
index 0000000000..95c5effa62
--- /dev/null
+++ b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/LongRunningType/Sources/LongRunningType.txt
@@ -0,0 +1,6 @@
+using osu.Framework.Allocation;
+
+[LongRunningLoad]
+public partial class LongRunningType : osu.Framework.Graphics.Drawable
+{
+}
\ No newline at end of file
diff --git a/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpMultiPhaseSourceGeneratorVerifier_Test.cs b/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpMultiPhaseSourceGeneratorVerifier_Test.cs
index 64371fe38d..6b4e071cd3 100644
--- a/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpMultiPhaseSourceGeneratorVerifier_Test.cs
+++ b/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpMultiPhaseSourceGeneratorVerifier_Test.cs
@@ -34,6 +34,9 @@ public Test(
{
driver = CSharpGeneratorDriver.Create(generator = new TSourceGenerator());
driver = driver.WithUpdatedParseOptions(CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion));
+
+ generator.ForceRun = true;
+
this.commonSources = commonSources;
this.commonGenerated = commonGenerated;
this.multiPhaseSources = multiPhaseSources;
diff --git a/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier.cs b/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier.cs
index 1c989832eb..b92dfc04fb 100644
--- a/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier.cs
+++ b/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier.cs
@@ -4,13 +4,13 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
+using osu.Framework.SourceGeneration.Generators;
namespace osu.Framework.SourceGeneration.Tests.Verifiers
{
public partial class CSharpSourceGeneratorVerifier
- where TSourceGenerator : IIncrementalGenerator, new()
+ where TSourceGenerator : AbstractIncrementalGenerator, new()
{
public static async Task VerifyAsync(
(string filename, string content)[] commonSources,
diff --git a/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier_Test.cs b/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier_Test.cs
index 2adda0e5fc..b45a96e678 100644
--- a/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier_Test.cs
+++ b/osu.Framework.SourceGeneration.Tests/Verifiers/CSharpSourceGeneratorVerifier_Test.cs
@@ -7,11 +7,12 @@
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
+using osu.Framework.SourceGeneration.Generators;
namespace osu.Framework.SourceGeneration.Tests.Verifiers
{
public partial class CSharpSourceGeneratorVerifier
- where TSourceGenerator : IIncrementalGenerator, new()
+ where TSourceGenerator : AbstractIncrementalGenerator, new()
{
public class Test : CSharpSourceGeneratorTest
{
@@ -19,7 +20,7 @@ public class Test : CSharpSourceGeneratorTest GetSourceGenerators() => new[]
{
- new TSourceGenerator().AsSourceGenerator()
+ new TSourceGenerator { ForceRun = true }.AsSourceGenerator()
};
protected override ParseOptions CreateParseOptions()
diff --git a/osu.Framework.SourceGeneration.Tests/osu.Framework.SourceGeneration.Tests.csproj b/osu.Framework.SourceGeneration.Tests/osu.Framework.SourceGeneration.Tests.csproj
index 113a17463b..80822f343b 100644
--- a/osu.Framework.SourceGeneration.Tests/osu.Framework.SourceGeneration.Tests.csproj
+++ b/osu.Framework.SourceGeneration.Tests/osu.Framework.SourceGeneration.Tests.csproj
@@ -1,6 +1,6 @@
- net6.0
+ net8.0
diff --git a/osu.Framework.SourceGeneration/Analysers/DiagnosticRules.cs b/osu.Framework.SourceGeneration/Analysers/DiagnosticRules.cs
index dc6a827867..c1af3c327e 100644
--- a/osu.Framework.SourceGeneration/Analysers/DiagnosticRules.cs
+++ b/osu.Framework.SourceGeneration/Analysers/DiagnosticRules.cs
@@ -13,12 +13,12 @@ public class DiagnosticRules
public static readonly DiagnosticDescriptor MAKE_DI_CLASS_PARTIAL = new DiagnosticDescriptor(
"OFSG001",
- "This class is a candidate for dependency injection and should be partial",
- "This class is a candidate for dependency injection and should be partial",
+ "This type, or a nested type, is a candidate for dependency injection and should be partial",
+ "This type, or a nested type, is a candidate for dependency injection and should be partial",
"Performance",
DiagnosticSeverity.Warning,
true,
- "Classes that are candidates for dependency injection should be made partial to benefit from compile-time optimisations.");
+ "Types that are candidates for dependency injection should be made partial to benefit from compile-time optimisations.");
#pragma warning restore RS2008
}
diff --git a/osu.Framework.SourceGeneration/Analysers/DrawableAnalyser.cs b/osu.Framework.SourceGeneration/Analysers/DrawableAnalyser.cs
index ae43c90abf..ec18e51940 100644
--- a/osu.Framework.SourceGeneration/Analysers/DrawableAnalyser.cs
+++ b/osu.Framework.SourceGeneration/Analysers/DrawableAnalyser.cs
@@ -24,19 +24,38 @@ public override void Initialize(AnalysisContext context)
}
///
- /// Analyses class definitions for implementations of IDrawable, ISourceGeneratedDependencyActivator, and Transformable.
+ /// Analyses class definitions for implementations of IDependencyInjectionCandidateInterface.
///
private void analyseClass(SyntaxNodeAnalysisContext context)
{
var classSyntax = (ClassDeclarationSyntax)context.Node;
- if (classSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
+ if (classSyntax.Ancestors().OfType().Any())
return;
- INamedTypeSymbol? type = context.SemanticModel.GetDeclaredSymbol(classSyntax);
+ analyseRecursively(context, classSyntax);
- if (type?.AllInterfaces.Any(SyntaxHelpers.IsIDependencyInjectionCandidateInterface) == true)
- context.ReportDiagnostic(Diagnostic.Create(DiagnosticRules.MAKE_DI_CLASS_PARTIAL, context.Node.GetLocation(), context.Node));
+ static bool analyseRecursively(SyntaxNodeAnalysisContext context, ClassDeclarationSyntax node)
+ {
+ bool requiresPartial = false;
+
+ // Child nodes always have to be analysed to provide diagnostics.
+ foreach (var nested in node.DescendantNodes().OfType())
+ requiresPartial |= analyseRecursively(context, nested);
+
+ // - If at least one child requires partial, then this node also needs to be partial regardless of its own type (optimisation).
+ // - If no child requires partial, we need to check if this node is a DI candidate (e.g. If the node has no nested types).
+ if (!requiresPartial)
+ requiresPartial = context.SemanticModel.GetDeclaredSymbol(node)?.AllInterfaces.Any(SyntaxHelpers.IsIDependencyInjectionCandidateInterface) == true;
+
+ // Whether the node is already partial.
+ bool isPartial = node.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword));
+
+ if (requiresPartial && !isPartial)
+ context.ReportDiagnostic(Diagnostic.Create(DiagnosticRules.MAKE_DI_CLASS_PARTIAL, node.GetLocation(), node));
+
+ return requiresPartial;
+ }
}
}
}
diff --git a/osu.Framework.SourceGeneration/Generators/AbstractIncrementalGenerator.cs b/osu.Framework.SourceGeneration/Generators/AbstractIncrementalGenerator.cs
index d2613efd31..bb88eaf0df 100644
--- a/osu.Framework.SourceGeneration/Generators/AbstractIncrementalGenerator.cs
+++ b/osu.Framework.SourceGeneration/Generators/AbstractIncrementalGenerator.cs
@@ -14,6 +14,11 @@ public abstract class AbstractIncrementalGenerator : IIncrementalGenerator
{
public readonly GeneratorEventDriver EventDriver = new GeneratorEventDriver();
+ ///
+ /// Whether the generator should be forcefully run, even if building as debug.
+ ///
+ public bool ForceRun;
+
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Stage 1: Create SyntaxTarget objects for all classes.
@@ -22,6 +27,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
(n, _) => isSyntaxTarget(n),
(ctx, _) => returnWithEvent(new IncrementalSyntaxTarget((ClassDeclarationSyntax)ctx.Node, ctx.SemanticModel), EventDriver.OnSyntaxTargetCreated))
.Select((t, _) => t.WithName())
+ .Combine(context.CompilationProvider)
+ .Where(c => ForceRun || c.Right.Options.OptimizationLevel == OptimizationLevel.Release)
+ .Select((t, _) => t.Item1)
.Select((t, _) => returnWithEvent(t.WithSemanticTarget(CreateSemanticTarget), EventDriver.OnSemanticTargetCreated));
// Stage 2: Separate out the old and new syntax targets for the same class object.
diff --git a/osu.Framework.SourceGeneration/Generators/GeneratorEventDriver.cs b/osu.Framework.SourceGeneration/Generators/GeneratorEventDriver.cs
index f76c5613f3..6409e789e1 100644
--- a/osu.Framework.SourceGeneration/Generators/GeneratorEventDriver.cs
+++ b/osu.Framework.SourceGeneration/Generators/GeneratorEventDriver.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
-using System.Diagnostics;
namespace osu.Framework.SourceGeneration.Generators
{
@@ -47,7 +46,10 @@ public void OnEmit(IncrementalSemanticTarget candidate)
conditionalInvoke(Emit, candidate);
}
- [Conditional("DEBUG")]
+ // Since we're running source generators in release configuration along with tests,
+ // we need this to always fire. Because we're not really worried about the compile
+ // overhead (due to only incurring on release builds) this isn't seen as a huge issue.
+ // [Conditional("DEBUG")]
private void conditionalInvoke(Action? @event, T arg)
{
@event?.Invoke(arg);
diff --git a/osu.Framework.SourceGeneration/Generators/HandleInput/HandleInputSemanticTarget.cs b/osu.Framework.SourceGeneration/Generators/HandleInput/HandleInputSemanticTarget.cs
new file mode 100644
index 0000000000..962930b4f8
--- /dev/null
+++ b/osu.Framework.SourceGeneration/Generators/HandleInput/HandleInputSemanticTarget.cs
@@ -0,0 +1,179 @@
+// 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.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace osu.Framework.SourceGeneration.Generators.HandleInput
+{
+ public class HandleInputSemanticTarget : IncrementalSemanticTarget
+ {
+ public bool RequestsPositionalInput { get; private set; }
+ public bool RequestsNonPositionalInput { get; private set; }
+
+ public HandleInputSemanticTarget(ClassDeclarationSyntax classSyntax, SemanticModel semanticModel)
+ : base(classSyntax, semanticModel)
+ {
+ }
+
+ protected override bool CheckValid(INamedTypeSymbol symbol)
+ {
+ INamedTypeSymbol? s = symbol;
+
+ while (s != null)
+ {
+ if (isDrawableType(s))
+ return true;
+
+ s = s.BaseType;
+ }
+
+ return false;
+ }
+
+ // This source generator never overrides.
+ protected override bool CheckNeedsOverride(INamedTypeSymbol symbol) => false;
+
+ protected override void Process(INamedTypeSymbol symbol)
+ {
+ if (FullyQualifiedTypeName == "osu.Framework.Graphics.Drawable")
+ return;
+
+ RequestsPositionalInput =
+ checkMethods(positional_input_methods, symbol)
+ || checkInterfaces(positional_input_interfaces, symbol)
+ || checkProperties(positional_input_properties, symbol);
+
+ RequestsNonPositionalInput =
+ checkMethods(non_positional_input_methods, symbol)
+ || checkInterfaces(non_positional_input_interfaces, symbol)
+ || checkProperties(non_positional_input_properties, symbol);
+ }
+
+ private static bool checkMethods(IEnumerable methods, INamedTypeSymbol symbol)
+ {
+ return runForTypeHierarchy(symbol, methods, static (symbol, methods) =>
+ {
+ return methods.SelectMany(name => symbol.GetMembers(name).OfType()).Any(isDrawableMethod);
+ });
+ }
+
+ private static bool checkProperties(IEnumerable properties, INamedTypeSymbol symbol)
+ {
+ return runForTypeHierarchy(symbol, properties, static (symbol, properties) =>
+ {
+ return properties.SelectMany(name => symbol.GetMembers(name).OfType()).Any(isDrawableProperty);
+ });
+ }
+
+ private static bool checkInterfaces(ImmutableHashSet interfaces, INamedTypeSymbol symbol)
+ {
+ return symbol.AllInterfaces.Select(SyntaxHelpers.GetFullyQualifiedTypeName).Any(interfaces.Contains);
+ }
+
+ private static bool runForTypeHierarchy(INamedTypeSymbol symbol, T items, Func func)
+ {
+ INamedTypeSymbol? type = symbol;
+
+ while (type != null && !isDrawableType(type))
+ {
+ if (func(type, items))
+ return true;
+
+ type = type.BaseType;
+ }
+
+ return false;
+ }
+
+ private static bool isDrawableMethod(IMethodSymbol method)
+ {
+ while (method.OverriddenMethod != null)
+ method = method.OverriddenMethod;
+
+ return isDrawableType(method.ContainingType);
+ }
+
+ private static bool isDrawableProperty(IPropertySymbol property)
+ {
+ while (property.OverriddenProperty != null)
+ property = property.OverriddenProperty;
+
+ return isDrawableType(property.ContainingType);
+ }
+
+ private static bool isDrawableType(INamedTypeSymbol type)
+ => SyntaxHelpers.GetFullyQualifiedTypeName(type) == "osu.Framework.Graphics.Drawable";
+
+ // HandleInputCache.positional_input_methods
+ private static readonly string[] positional_input_methods =
+ {
+ "Handle",
+ "OnMouseMove",
+ "OnHover",
+ "OnHoverLost",
+ "OnMouseDown",
+ "OnMouseUp",
+ "OnClick",
+ "OnDoubleClick",
+ "OnDragStart",
+ "OnDrag",
+ "OnDragEnd",
+ "OnScroll",
+ "OnFocus",
+ "OnFocusLost",
+ "OnTouchDown",
+ "OnTouchMove",
+ "OnTouchUp",
+ "OnTabletPenButtonPress",
+ "OnTabletPenButtonRelease"
+ };
+
+ // HandleInputCache.non_positional_input_methods
+ private static readonly string[] non_positional_input_methods =
+ {
+ "Handle",
+ "OnFocus",
+ "OnFocusLost",
+ "OnKeyDown",
+ "OnKeyUp",
+ "OnJoystickPress",
+ "OnJoystickRelease",
+ "OnJoystickAxisMove",
+ "OnTabletAuxiliaryButtonPress",
+ "OnTabletAuxiliaryButtonRelease",
+ "OnMidiDown",
+ "OnMidiUp"
+ };
+
+ // HandleInputCache.positional_input_interfaces
+ private static readonly ImmutableHashSet positional_input_interfaces = ImmutableHashSet.Create(
+ "osu.Framework.Graphics.Cursor.IHasTooltip",
+ "osu.Framework.Graphics.Cursor.IHasCustomTooltip",
+ "osu.Framework.Graphics.Cursor.IHasContextMenu",
+ "osu.Framework.Graphics.Cursor.IHasPopover"
+ );
+
+ // HandleInputCache.non_positional_input_interfaces
+ private static readonly ImmutableHashSet non_positional_input_interfaces = ImmutableHashSet.Create(
+ "osu.Framework.Input.Bindings.IKeyBindingHandler"
+ );
+
+ // HandleInputCache.positional_input_properties
+ private static readonly string[] positional_input_properties =
+ {
+ "HandlePositionalInput"
+ };
+
+ // HandleInputCache.non_positional_input_properties
+ private static readonly string[] non_positional_input_properties =
+ {
+ "HandleNonPositionalInput",
+ "AcceptsFocus"
+ };
+ }
+}
diff --git a/osu.Framework.SourceGeneration/Generators/HandleInput/HandleInputSourceEmitter.cs b/osu.Framework.SourceGeneration/Generators/HandleInput/HandleInputSourceEmitter.cs
new file mode 100644
index 0000000000..bfa1c54e1e
--- /dev/null
+++ b/osu.Framework.SourceGeneration/Generators/HandleInput/HandleInputSourceEmitter.cs
@@ -0,0 +1,74 @@
+// 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 Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace osu.Framework.SourceGeneration.Generators.HandleInput
+{
+ public class HandleInputSourceEmitter : IncrementalSourceEmitter
+ {
+ private const string interface_name = "global::osu.Framework.Input.ISourceGeneratedHandleInputCache";
+
+ protected override string FileSuffix => "HandleInput";
+
+ public new HandleInputSemanticTarget Target => (HandleInputSemanticTarget)base.Target;
+
+ public HandleInputSourceEmitter(IncrementalSemanticTarget target)
+ : base(target)
+ {
+ }
+
+ protected override ClassDeclarationSyntax ConstructClass(ClassDeclarationSyntax initialClass)
+ {
+ return initialClass.WithBaseList(
+ SyntaxFactory.BaseList(
+ SyntaxFactory.SingletonSeparatedList(
+ SyntaxFactory.SimpleBaseType(
+ SyntaxFactory.ParseTypeName(interface_name)))))
+ .WithMembers(
+ SyntaxFactory.List(createProperties()));
+ }
+
+ private IEnumerable createProperties()
+ {
+ // Drawable is the base type which always needs to have the members defined.
+ bool isDrawable = Target.FullyQualifiedTypeName == "osu.Framework.Graphics.Drawable";
+
+ yield return SyntaxFactory.PropertyDeclaration(
+ SyntaxFactory.ParseTypeName("global::System.Type"),
+ SyntaxFactory.Identifier("KnownType"))
+ .WithExplicitInterfaceSpecifier(
+ SyntaxFactory.ExplicitInterfaceSpecifier(
+ SyntaxFactory.IdentifierName(interface_name)))
+ .WithExpressionBody(
+ SyntaxFactory.ArrowExpressionClause(
+ SyntaxFactory.TypeOfExpression(
+ SyntaxFactory.ParseTypeName(Target.GlobalPrefixedTypeName))))
+ .WithSemicolonToken(
+ SyntaxFactory.Token(SyntaxKind.SemicolonToken));
+
+ if (Target.RequestsPositionalInput || isDrawable)
+ yield return createInputMember("RequestsPositionalInput", Target.RequestsPositionalInput);
+
+ if (Target.RequestsNonPositionalInput || isDrawable)
+ yield return createInputMember("RequestsNonPositionalInput", Target.RequestsNonPositionalInput);
+
+ MemberDeclarationSyntax createInputMember(string name, bool value) =>
+ SyntaxFactory.PropertyDeclaration(
+ SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)),
+ SyntaxFactory.Identifier(name))
+ .WithExplicitInterfaceSpecifier(
+ SyntaxFactory.ExplicitInterfaceSpecifier(
+ SyntaxFactory.IdentifierName(interface_name)))
+ .WithExpressionBody(
+ SyntaxFactory.ArrowExpressionClause(
+ SyntaxFactory.LiteralExpression(
+ value
+ ? SyntaxKind.TrueLiteralExpression
+ : SyntaxKind.FalseLiteralExpression)))
+ .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
+ }
+ }
+}
diff --git a/osu.Framework.SourceGeneration/Generators/HandleInput/HandleInputSourceGenerator.cs b/osu.Framework.SourceGeneration/Generators/HandleInput/HandleInputSourceGenerator.cs
new file mode 100644
index 0000000000..c6bf6145b5
--- /dev/null
+++ b/osu.Framework.SourceGeneration/Generators/HandleInput/HandleInputSourceGenerator.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace osu.Framework.SourceGeneration.Generators.HandleInput
+{
+ [Generator]
+ public class HandleInputSourceGenerator : AbstractIncrementalGenerator
+ {
+ protected override IncrementalSemanticTarget CreateSemanticTarget(ClassDeclarationSyntax node, SemanticModel semanticModel)
+ => new HandleInputSemanticTarget(node, semanticModel);
+
+ protected override IncrementalSourceEmitter CreateSourceEmitter(IncrementalSemanticTarget target)
+ => new HandleInputSourceEmitter(target);
+ }
+}
diff --git a/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSemanticTarget.cs b/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSemanticTarget.cs
new file mode 100644
index 0000000000..0835cf74dd
--- /dev/null
+++ b/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSemanticTarget.cs
@@ -0,0 +1,48 @@
+// 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 Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace osu.Framework.SourceGeneration.Generators.LongRunningLoad
+{
+ public class LongRunningLoadSemanticTarget : IncrementalSemanticTarget
+ {
+ public bool IsLongRunning { get; private set; }
+
+ public LongRunningLoadSemanticTarget(ClassDeclarationSyntax classSyntax, SemanticModel semanticModel)
+ : base(classSyntax, semanticModel)
+ {
+ }
+
+ protected override bool CheckValid(INamedTypeSymbol symbol)
+ {
+ INamedTypeSymbol? s = symbol;
+
+ while (s != null)
+ {
+ if (isDrawableType(s))
+ return true;
+
+ s = s.BaseType;
+ }
+
+ return false;
+ }
+
+ // This source generator never overrides.
+ protected override bool CheckNeedsOverride(INamedTypeSymbol symbol) => false;
+
+ protected override void Process(INamedTypeSymbol symbol)
+ {
+ if (FullyQualifiedTypeName == "osu.Framework.Graphics.Drawable")
+ return;
+
+ IsLongRunning = symbol.GetAttributes().Any(SyntaxHelpers.IsLongRunningLoadAttribute);
+ }
+
+ private static bool isDrawableType(INamedTypeSymbol type)
+ => SyntaxHelpers.GetFullyQualifiedTypeName(type) == "osu.Framework.Graphics.Drawable";
+ }
+}
diff --git a/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSourceEmitter.cs b/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSourceEmitter.cs
new file mode 100644
index 0000000000..b18d60caef
--- /dev/null
+++ b/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSourceEmitter.cs
@@ -0,0 +1,72 @@
+// 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 Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace osu.Framework.SourceGeneration.Generators.LongRunningLoad
+{
+ public class LongRunningLoadSourceEmitter : IncrementalSourceEmitter
+ {
+ private const string interface_name = "global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache";
+
+ protected override string FileSuffix => "LongRunningLoad";
+
+ public new LongRunningLoadSemanticTarget Target => (LongRunningLoadSemanticTarget)base.Target;
+
+ public LongRunningLoadSourceEmitter(IncrementalSemanticTarget target)
+ : base(target)
+ {
+ }
+
+ protected override ClassDeclarationSyntax ConstructClass(ClassDeclarationSyntax initialClass)
+ {
+ return initialClass.WithBaseList(
+ SyntaxFactory.BaseList(
+ SyntaxFactory.SingletonSeparatedList(
+ SyntaxFactory.SimpleBaseType(
+ SyntaxFactory.ParseTypeName(interface_name)))))
+ .WithMembers(
+ SyntaxFactory.List(createProperties()));
+ }
+
+ private IEnumerable createProperties()
+ {
+ // Drawable is the base type which always needs to have the members defined.
+ bool isDrawable = Target.FullyQualifiedTypeName == "osu.Framework.Graphics.Drawable";
+
+ yield return SyntaxFactory.PropertyDeclaration(
+ SyntaxFactory.ParseTypeName("global::System.Type"),
+ SyntaxFactory.Identifier("KnownType"))
+ .WithExplicitInterfaceSpecifier(
+ SyntaxFactory.ExplicitInterfaceSpecifier(
+ SyntaxFactory.IdentifierName(interface_name)))
+ .WithExpressionBody(
+ SyntaxFactory.ArrowExpressionClause(
+ SyntaxFactory.TypeOfExpression(
+ SyntaxFactory.ParseTypeName(Target.GlobalPrefixedTypeName))))
+ .WithSemicolonToken(
+ SyntaxFactory.Token(SyntaxKind.SemicolonToken));
+
+ if (Target.IsLongRunning || isDrawable)
+ {
+ yield return SyntaxFactory.PropertyDeclaration(
+ SyntaxFactory.PredefinedType(
+ SyntaxFactory.Token(SyntaxKind.BoolKeyword)),
+ SyntaxFactory.Identifier("IsLongRunning"))
+ .WithExplicitInterfaceSpecifier(
+ SyntaxFactory.ExplicitInterfaceSpecifier(
+ SyntaxFactory.IdentifierName(interface_name)))
+ .WithExpressionBody(
+ SyntaxFactory.ArrowExpressionClause(
+ SyntaxFactory.LiteralExpression(
+ Target.IsLongRunning
+ ? SyntaxKind.TrueLiteralExpression
+ : SyntaxKind.FalseLiteralExpression)))
+ .WithSemicolonToken(
+ SyntaxFactory.Token(SyntaxKind.SemicolonToken));
+ }
+ }
+ }
+}
diff --git a/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSourceGenerator.cs b/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSourceGenerator.cs
new file mode 100644
index 0000000000..62ebb782ce
--- /dev/null
+++ b/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSourceGenerator.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace osu.Framework.SourceGeneration.Generators.LongRunningLoad
+{
+ [Generator]
+ public class LongRunningLoadSourceGenerator : AbstractIncrementalGenerator
+ {
+ protected override IncrementalSemanticTarget CreateSemanticTarget(ClassDeclarationSyntax node, SemanticModel semanticModel)
+ => new LongRunningLoadSemanticTarget(node, semanticModel);
+
+ protected override IncrementalSourceEmitter CreateSourceEmitter(IncrementalSemanticTarget target)
+ => new LongRunningLoadSourceEmitter(target);
+ }
+}
diff --git a/osu.Framework.SourceGeneration/SyntaxHelpers.cs b/osu.Framework.SourceGeneration/SyntaxHelpers.cs
index 31b22f9f29..3959d9fa67 100644
--- a/osu.Framework.SourceGeneration/SyntaxHelpers.cs
+++ b/osu.Framework.SourceGeneration/SyntaxHelpers.cs
@@ -100,6 +100,9 @@ public static bool IsResolvedAttribute(AttributeData? attribute)
public static bool IsCachedAttribute(AttributeData? attribute)
=> IsCachedAttribute(attribute?.AttributeClass);
+ public static bool IsLongRunningLoadAttribute(AttributeData? attribute)
+ => IsLongRunningLoadAttribute(attribute?.AttributeClass);
+
public static bool IsBackgroundDependencyLoaderAttribute(ITypeSymbol? type)
=> type != null && GetFullyQualifiedTypeName(type) == "osu.Framework.Allocation.BackgroundDependencyLoaderAttribute";
@@ -109,6 +112,9 @@ public static bool IsResolvedAttribute(ITypeSymbol? type)
public static bool IsCachedAttribute(ITypeSymbol? type)
=> type != null && GetFullyQualifiedTypeName(type) == "osu.Framework.Allocation.CachedAttribute";
+ public static bool IsLongRunningLoadAttribute(ITypeSymbol? type)
+ => type != null && GetFullyQualifiedTypeName(type) == "osu.Framework.Allocation.LongRunningLoadAttribute";
+
public static bool IsIDependencyInjectionCandidateInterface(ITypeSymbol? type)
=> type != null && GetFullyQualifiedTypeName(type) == "osu.Framework.Allocation.IDependencyInjectionCandidate";
diff --git a/osu.Framework.Templates/README.md b/osu.Framework.Templates/README.md
index 59fff03f61..67702f4ffe 100644
--- a/osu.Framework.Templates/README.md
+++ b/osu.Framework.Templates/README.md
@@ -7,7 +7,7 @@ Templates to use when starting off with osu!framework. Create a fully-testable,
```bash
# install (or update) template package.
# this only needs to be done once
-dotnet new install ppy.osu.Framework.Templates
+dotnet new -i ppy.osu.Framework.Templates
## IMPORTANT: Do not use spaces or hyphens in your project name for the following commands.
## This does not play nice with the templating system.
diff --git a/osu.Framework.Templates/osu.Framework.Templates.csproj b/osu.Framework.Templates/osu.Framework.Templates.csproj
index 16374258bc..2bf5b6c6af 100644
--- a/osu.Framework.Templates/osu.Framework.Templates.csproj
+++ b/osu.Framework.Templates/osu.Framework.Templates.csproj
@@ -5,7 +5,7 @@
osu! framework templates
Templates that can be used as starting points for new games, built with of osu! framework.
dotnet-new;templates;osu;framework
- net6.0
+ net8.0
true
false
content
diff --git a/osu.Framework.Templates/templates/template-empty/.idea/.idea.TemplateGame.Desktop/.idea/runConfigurations/TemplateGame_Desktop.xml b/osu.Framework.Templates/templates/template-empty/.idea/.idea.TemplateGame.Desktop/.idea/runConfigurations/TemplateGame_Desktop.xml
index c2bfc6ee4e..2439312ed9 100644
--- a/osu.Framework.Templates/templates/template-empty/.idea/.idea.TemplateGame.Desktop/.idea/runConfigurations/TemplateGame_Desktop.xml
+++ b/osu.Framework.Templates/templates/template-empty/.idea/.idea.TemplateGame.Desktop/.idea/runConfigurations/TemplateGame_Desktop.xml
@@ -1,8 +1,8 @@
-
+
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/osu.Framework.Templates/templates/template-empty/.idea/.idea.TemplateGame.Desktop/.idea/runConfigurations/TemplateGame_Tests.xml b/osu.Framework.Templates/templates/template-empty/.idea/.idea.TemplateGame.Desktop/.idea/runConfigurations/TemplateGame_Tests.xml
index f0e126c8d2..7f9d026e84 100644
--- a/osu.Framework.Templates/templates/template-empty/.idea/.idea.TemplateGame.Desktop/.idea/runConfigurations/TemplateGame_Tests.xml
+++ b/osu.Framework.Templates/templates/template-empty/.idea/.idea.TemplateGame.Desktop/.idea/runConfigurations/TemplateGame_Tests.xml
@@ -1,8 +1,8 @@
-
+
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/osu.Framework.Templates/templates/template-empty/.vscode/launch.json b/osu.Framework.Templates/templates/template-empty/.vscode/launch.json
index a1296262dc..822c3b7eac 100644
--- a/osu.Framework.Templates/templates/template-empty/.vscode/launch.json
+++ b/osu.Framework.Templates/templates/template-empty/.vscode/launch.json
@@ -9,13 +9,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/TemplateGame.Game.Tests/bin/Debug/net6.0/TemplateGame.Game.Tests.dll",
+ "${workspaceRoot}/TemplateGame.Game.Tests/bin/Debug/net8.0/TemplateGame.Game.Tests.dll",
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Tests, Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/TemplateGame.Game.Tests/bin/Debug/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/TemplateGame.Game.Tests/bin/Debug/net8.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -26,13 +26,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/TemplateGame.Game.Tests/bin/Release/net6.0/TemplateGame.Game.Tests.dll",
+ "${workspaceRoot}/TemplateGame.Game.Tests/bin/Release/net8.0/TemplateGame.Game.Tests.dll",
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Tests, Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/TemplateGame.Game.Tests/bin/Release/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/TemplateGame.Game.Tests/bin/Release/net8.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -43,13 +43,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/TemplateGame.Desktop/bin/Debug/net6.0/TemplateGame.dll",
+ "${workspaceRoot}/TemplateGame.Desktop/bin/Debug/net8.0/TemplateGame.dll",
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Desktop, Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/TemplateGame.Desktop/bin/Debug/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/TemplateGame.Desktop/bin/Debug/net8.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -60,13 +60,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/TemplateGame.Desktop/bin/Debug/net6.0/TemplateGame.dll",
+ "${workspaceRoot}/TemplateGame.Desktop/bin/Debug/net8.0/TemplateGame.dll",
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Desktop, Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/TemplateGame.Desktop/bin/Debug/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/TemplateGame.Desktop/bin/Debug/net8.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
diff --git a/osu.Framework.Templates/templates/template-empty/Directory.Build.props b/osu.Framework.Templates/templates/template-empty/Directory.Build.props
index 8824b1f46e..74d05ff690 100644
--- a/osu.Framework.Templates/templates/template-empty/Directory.Build.props
+++ b/osu.Framework.Templates/templates/template-empty/Directory.Build.props
@@ -3,4 +3,8 @@
$(MSBuildThisFileDirectory)app.manifest
+
+ true
+ $(NoWarn);CS1591
+
diff --git a/osu.Framework.Templates/templates/template-empty/TemplateGame.Desktop/TemplateGame.Desktop.csproj b/osu.Framework.Templates/templates/template-empty/TemplateGame.Desktop/TemplateGame.Desktop.csproj
index 712149e3f0..c9c5471a47 100644
--- a/osu.Framework.Templates/templates/template-empty/TemplateGame.Desktop/TemplateGame.Desktop.csproj
+++ b/osu.Framework.Templates/templates/template-empty/TemplateGame.Desktop/TemplateGame.Desktop.csproj
@@ -1,6 +1,6 @@
- net6.0
+ net8.0
WinExe
TemplateGame
game.ico
diff --git a/osu.Framework.Templates/templates/template-empty/TemplateGame.Game.Tests/TemplateGame.Game.Tests.csproj b/osu.Framework.Templates/templates/template-empty/TemplateGame.Game.Tests/TemplateGame.Game.Tests.csproj
index dc05f72bb3..ed71b6365d 100644
--- a/osu.Framework.Templates/templates/template-empty/TemplateGame.Game.Tests/TemplateGame.Game.Tests.csproj
+++ b/osu.Framework.Templates/templates/template-empty/TemplateGame.Game.Tests/TemplateGame.Game.Tests.csproj
@@ -1,7 +1,7 @@
WinExe
- net6.0
+ net8.0
false
diff --git a/osu.Framework.Templates/templates/template-empty/TemplateGame.Game.Tests/Visual/TemplateGameTestScene.cs b/osu.Framework.Templates/templates/template-empty/TemplateGame.Game.Tests/Visual/TemplateGameTestScene.cs
index 3284a2dedd..53c1fd2067 100644
--- a/osu.Framework.Templates/templates/template-empty/TemplateGame.Game.Tests/Visual/TemplateGameTestScene.cs
+++ b/osu.Framework.Templates/templates/template-empty/TemplateGame.Game.Tests/Visual/TemplateGameTestScene.cs
@@ -2,7 +2,7 @@
namespace TemplateGame.Game.Tests.Visual
{
- public partial class TemplateGameTestScene : TestScene
+ public abstract partial class TemplateGameTestScene : TestScene
{
protected override ITestSceneTestRunner CreateRunner() => new TemplateGameTestSceneTestRunner();
diff --git a/osu.Framework.Templates/templates/template-empty/TemplateGame.Game/TemplateGame.Game.csproj b/osu.Framework.Templates/templates/template-empty/TemplateGame.Game/TemplateGame.Game.csproj
index 483e947fef..31dd2699f0 100644
--- a/osu.Framework.Templates/templates/template-empty/TemplateGame.Game/TemplateGame.Game.csproj
+++ b/osu.Framework.Templates/templates/template-empty/TemplateGame.Game/TemplateGame.Game.csproj
@@ -1,6 +1,6 @@
- net6.0
+ net8.0
diff --git a/osu.Framework.Templates/templates/template-empty/TemplateGame.Resources/TemplateGame.Resources.csproj b/osu.Framework.Templates/templates/template-empty/TemplateGame.Resources/TemplateGame.Resources.csproj
index 12d1268ea5..cbe37760e8 100644
--- a/osu.Framework.Templates/templates/template-empty/TemplateGame.Resources/TemplateGame.Resources.csproj
+++ b/osu.Framework.Templates/templates/template-empty/TemplateGame.Resources/TemplateGame.Resources.csproj
@@ -1,6 +1,6 @@
- net6.0
+ net8.0
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 7a9a4298fa..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
@@ -1,7 +1,7 @@
Exe
- net6.0-ios
+ net8.0-ios
13.4
iPhone Developer
true
@@ -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/.idea/.idea.FlappyDon.Desktop/.idea/runConfigurations/FlappyDon_Desktop.xml b/osu.Framework.Templates/templates/template-flappy/.idea/.idea.FlappyDon.Desktop/.idea/runConfigurations/FlappyDon_Desktop.xml
index 2eba99bb48..a4e5666264 100644
--- a/osu.Framework.Templates/templates/template-flappy/.idea/.idea.FlappyDon.Desktop/.idea/runConfigurations/FlappyDon_Desktop.xml
+++ b/osu.Framework.Templates/templates/template-flappy/.idea/.idea.FlappyDon.Desktop/.idea/runConfigurations/FlappyDon_Desktop.xml
@@ -1,8 +1,8 @@
-
+
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/osu.Framework.Templates/templates/template-flappy/.idea/.idea.FlappyDon.Desktop/.idea/runConfigurations/FlappyDon_Tests.xml b/osu.Framework.Templates/templates/template-flappy/.idea/.idea.FlappyDon.Desktop/.idea/runConfigurations/FlappyDon_Tests.xml
index 48ecc2127b..f4e2d64846 100644
--- a/osu.Framework.Templates/templates/template-flappy/.idea/.idea.FlappyDon.Desktop/.idea/runConfigurations/FlappyDon_Tests.xml
+++ b/osu.Framework.Templates/templates/template-flappy/.idea/.idea.FlappyDon.Desktop/.idea/runConfigurations/FlappyDon_Tests.xml
@@ -1,8 +1,8 @@
-
+
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/osu.Framework.Templates/templates/template-flappy/.vscode/launch.json b/osu.Framework.Templates/templates/template-flappy/.vscode/launch.json
index ca64678242..1346cacfcf 100644
--- a/osu.Framework.Templates/templates/template-flappy/.vscode/launch.json
+++ b/osu.Framework.Templates/templates/template-flappy/.vscode/launch.json
@@ -9,13 +9,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/FlappyDon.Game.Tests/bin/Debug/net6.0/FlappyDon.Game.Tests.dll",
+ "${workspaceRoot}/FlappyDon.Game.Tests/bin/Debug/net8.0/FlappyDon.Game.Tests.dll",
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Tests, Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/FlappyDon.Game.Tests/bin/Debug/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/FlappyDon.Game.Tests/bin/Debug/net8.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -26,13 +26,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/FlappyDon.Game.Tests/bin/Release/net6.0/FlappyDon.Game.Tests.dll",
+ "${workspaceRoot}/FlappyDon.Game.Tests/bin/Release/net8.0/FlappyDon.Game.Tests.dll",
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Tests, Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/FlappyDon.Game.Tests/bin/Release/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/FlappyDon.Game.Tests/bin/Release/net8.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -43,13 +43,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/FlappyDon.Desktop/bin/Debug/net6.0/FlappyDon.dll",
+ "${workspaceRoot}/FlappyDon.Desktop/bin/Debug/net8.0/FlappyDon.dll",
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Desktop, Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/FlappyDon.Desktop/bin/Debug/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/FlappyDon.Desktop/bin/Debug/net8.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -60,13 +60,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/FlappyDon.Desktop/bin/Debug/net6.0/FlappyDon.dll",
+ "${workspaceRoot}/FlappyDon.Desktop/bin/Debug/net8.0/FlappyDon.dll",
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Desktop, Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/FlappyDon.Desktop/bin/Debug/net6.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/FlappyDon.Desktop/bin/Debug/net8.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
diff --git a/osu.Framework.Templates/templates/template-flappy/Directory.Build.props b/osu.Framework.Templates/templates/template-flappy/Directory.Build.props
index 8824b1f46e..74d05ff690 100644
--- a/osu.Framework.Templates/templates/template-flappy/Directory.Build.props
+++ b/osu.Framework.Templates/templates/template-flappy/Directory.Build.props
@@ -3,4 +3,8 @@
$(MSBuildThisFileDirectory)app.manifest
+
+ true
+ $(NoWarn);CS1591
+
diff --git a/osu.Framework.Templates/templates/template-flappy/FlappyDon.Desktop/FlappyDon.Desktop.csproj b/osu.Framework.Templates/templates/template-flappy/FlappyDon.Desktop/FlappyDon.Desktop.csproj
index c3a28513f5..d45fb64239 100644
--- a/osu.Framework.Templates/templates/template-flappy/FlappyDon.Desktop/FlappyDon.Desktop.csproj
+++ b/osu.Framework.Templates/templates/template-flappy/FlappyDon.Desktop/FlappyDon.Desktop.csproj
@@ -1,6 +1,6 @@
- net6.0
+ net8.0
WinExe
FlappyDon
game.ico
diff --git a/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game.Tests/FlappyDon.Game.Tests.csproj b/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game.Tests/FlappyDon.Game.Tests.csproj
index cf94079149..a64227e2c0 100644
--- a/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game.Tests/FlappyDon.Game.Tests.csproj
+++ b/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game.Tests/FlappyDon.Game.Tests.csproj
@@ -1,7 +1,7 @@
WinExe
- net6.0
+ net8.0
false
diff --git a/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game.Tests/Visual/FlappyDonTestScene.cs b/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game.Tests/Visual/FlappyDonTestScene.cs
index cb720c2e25..c1c7fb00f2 100644
--- a/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game.Tests/Visual/FlappyDonTestScene.cs
+++ b/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game.Tests/Visual/FlappyDonTestScene.cs
@@ -2,7 +2,7 @@
namespace FlappyDon.Game.Tests.Visual
{
- public partial class FlappyDonTestScene : TestScene
+ public abstract partial class FlappyDonTestScene : TestScene
{
protected override ITestSceneTestRunner CreateRunner() => new FlappyDonTestSceneTestRunner();
diff --git a/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game/Elements/Bird.cs b/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game/Elements/Bird.cs
index 8edc416670..7c482d6bf3 100644
--- a/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game/Elements/Bird.cs
+++ b/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game/Elements/Bird.cs
@@ -144,7 +144,7 @@ protected override void Update()
if (GroundY > 0.0f)
groundPlane = GroundY / 2.0f;
else
- groundPlane = Parent.DrawHeight - DrawHeight;
+ groundPlane = Parent!.DrawHeight - DrawHeight;
Y = Math.Min(Y, groundPlane);
diff --git a/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game/FlappyDon.Game.csproj b/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game/FlappyDon.Game.csproj
index 4f82d5f67f..38222c9881 100644
--- a/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game/FlappyDon.Game.csproj
+++ b/osu.Framework.Templates/templates/template-flappy/FlappyDon.Game/FlappyDon.Game.csproj
@@ -1,6 +1,6 @@
- net6.0
+ net8.0
diff --git a/osu.Framework.Templates/templates/template-flappy/FlappyDon.Resources/FlappyDon.Resources.csproj b/osu.Framework.Templates/templates/template-flappy/FlappyDon.Resources/FlappyDon.Resources.csproj
index 3c455c3d1a..a4125eccbf 100644
--- a/osu.Framework.Templates/templates/template-flappy/FlappyDon.Resources/FlappyDon.Resources.csproj
+++ b/osu.Framework.Templates/templates/template-flappy/FlappyDon.Resources/FlappyDon.Resources.csproj
@@ -1,6 +1,6 @@
- net6.0
+ net8.0
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 9f55b0b6a2..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
@@ -1,7 +1,7 @@
Exe
- net6.0-ios
+ net8.0-ios
13.4
iPhone Developer
true
@@ -10,12 +10,6 @@
so there's nothing to be worried about. -->
MT7091
-
- ios-arm64
-
-
- iossimulator-x64
-
diff --git a/osu.Framework.Tests.Android/AndroidManifest.xml b/osu.Framework.Tests.Android/AndroidManifest.xml
index fdb95f76cf..0fbd41c388 100644
--- a/osu.Framework.Tests.Android/AndroidManifest.xml
+++ b/osu.Framework.Tests.Android/AndroidManifest.xml
@@ -1,5 +1,17 @@
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Framework.Tests.Android/osu.Framework.Tests.Android.csproj b/osu.Framework.Tests.Android/osu.Framework.Tests.Android.csproj
index c888c13927..d8837c0727 100644
--- a/osu.Framework.Tests.Android/osu.Framework.Tests.Android.csproj
+++ b/osu.Framework.Tests.Android/osu.Framework.Tests.Android.csproj
@@ -1,7 +1,7 @@
- net6.0-android
+ net8.0-android
Exe
osu.Framework.Tests.Android
osu.Framework.Tests.Android
diff --git a/osu.Framework.Tests.iOS/Application.cs b/osu.Framework.Tests.iOS/Application.cs
index 7a6c8f4634..7305fe41e6 100644
--- a/osu.Framework.Tests.iOS/Application.cs
+++ b/osu.Framework.Tests.iOS/Application.cs
@@ -1,8 +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 osu.Framework.iOS;
namespace osu.Framework.Tests
diff --git a/osu.Framework.Tests.iOS/osu.Framework.Tests.iOS.csproj b/osu.Framework.Tests.iOS/osu.Framework.Tests.iOS.csproj
index 7c4b258c80..49ad31c953 100644
--- a/osu.Framework.Tests.iOS/osu.Framework.Tests.iOS.csproj
+++ b/osu.Framework.Tests.iOS/osu.Framework.Tests.iOS.csproj
@@ -1,7 +1,7 @@
Exe
- net6.0-ios
+ net8.0-ios
13.4
osu.Framework.Tests
osu.Framework.Tests
diff --git a/osu.Framework.Tests/Audio/AudioCollectionManagerTest.cs b/osu.Framework.Tests/Audio/AudioCollectionManagerTest.cs
index 53c39d7937..8db58c235d 100644
--- a/osu.Framework.Tests/Audio/AudioCollectionManagerTest.cs
+++ b/osu.Framework.Tests/Audio/AudioCollectionManagerTest.cs
@@ -1,8 +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.Threading;
using NUnit.Framework;
using osu.Framework.Audio;
@@ -17,7 +15,6 @@ public void TestDisposalWhileItemsAreAddedDoesNotThrowInvalidOperationException(
{
var manager = new TestAudioCollectionManager();
- var threadExecutionFinished = new ManualResetEventSlim();
var updateLoopStarted = new ManualResetEventSlim();
// add a huge amount of items to the queue
@@ -25,24 +22,20 @@ public void TestDisposalWhileItemsAreAddedDoesNotThrowInvalidOperationException(
manager.AddItem(new TestingAdjustableAudioComponent());
// in a separate thread start processing the queue
- var thread = new Thread(() =>
+ var thread = AudioTestHelper.StartNewAudioThread(() =>
{
while (!manager.IsDisposed)
{
updateLoopStarted.Set();
manager.Update();
}
-
- threadExecutionFinished.Set();
});
- thread.Start();
-
Assert.IsTrue(updateLoopStarted.Wait(10000));
Assert.DoesNotThrow(() => manager.Dispose());
- Assert.IsTrue(threadExecutionFinished.Wait(10000));
+ thread.Dispose();
}
private class TestAudioCollectionManager : AudioCollectionManager
diff --git a/osu.Framework.Tests/Audio/AudioComponentTest.cs b/osu.Framework.Tests/Audio/AudioComponentTest.cs
index accb910b7e..22e8d1c3a3 100644
--- a/osu.Framework.Tests/Audio/AudioComponentTest.cs
+++ b/osu.Framework.Tests/Audio/AudioComponentTest.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// 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 NUnit.Framework;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
diff --git a/osu.Framework.Tests/Audio/AudioManagerWithDeviceLoss.cs b/osu.Framework.Tests/Audio/AudioManagerWithDeviceLoss.cs
index 924318008b..311468c0b6 100644
--- a/osu.Framework.Tests/Audio/AudioManagerWithDeviceLoss.cs
+++ b/osu.Framework.Tests/Audio/AudioManagerWithDeviceLoss.cs
@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using ManagedBass;
using osu.Framework.Audio;
@@ -42,12 +40,20 @@ protected override bool InitBass(int device)
}
}
- protected override IEnumerable EnumerateAllDevices()
+ protected override bool CheckForDeviceChanges(ImmutableArray previousDevices)
+ {
+ if (simulateLoss)
+ return true;
+
+ return base.CheckForDeviceChanges(previousDevices);
+ }
+
+ protected override ImmutableArray GetAllDevices()
{
- var devices = base.EnumerateAllDevices();
+ var devices = base.GetAllDevices();
if (simulateLoss)
- devices = devices.Take(BASS_INTERNAL_DEVICE_COUNT);
+ devices = devices.Take(BASS_INTERNAL_DEVICE_COUNT).ToImmutableArray();
return devices;
}
diff --git a/osu.Framework.Tests/Audio/AudioTestHelper.cs b/osu.Framework.Tests/Audio/AudioTestHelper.cs
new file mode 100644
index 0000000000..89e812015d
--- /dev/null
+++ b/osu.Framework.Tests/Audio/AudioTestHelper.cs
@@ -0,0 +1,65 @@
+// 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.ExceptionServices;
+using System.Threading;
+using osu.Framework.Allocation;
+using osu.Framework.Development;
+using osu.Framework.Threading;
+
+namespace osu.Framework.Tests.Audio
+{
+ internal static class AudioTestHelper
+ {
+ ///
+ /// Runs an on a newly created audio thread, and blocks until it has been run to completion.
+ ///
+ /// The action to run on the audio thread.
+ public static void RunOnAudioThread(Action action)
+ {
+ using (var _ = StartNewAudioThread(action))
+ {
+ }
+ }
+
+ ///
+ /// Runs an on a newly created audio thread.
+ ///
+ /// The action to run on the audio thread.
+ /// An that waits for the thread to stop and rethrows any unhandled exceptions thrown by the .
+ public static IDisposable StartNewAudioThread(Action action)
+ {
+ var resetEvent = new ManualResetEvent(false);
+ Exception? threadException = null;
+
+ new Thread(() =>
+ {
+ ThreadSafety.IsAudioThread = true;
+
+ try
+ {
+ action();
+ }
+ catch (Exception e)
+ {
+ threadException = e;
+ }
+
+ resetEvent.Set();
+ })
+ {
+ Name = GameThread.SuffixedThreadNameFor("Audio")
+ }.Start();
+
+ return new InvokeOnDisposal(() =>
+ {
+ if (!resetEvent.WaitOne(TimeSpan.FromSeconds(10)))
+ throw new TimeoutException();
+
+ if (threadException != null)
+ ExceptionDispatchInfo.Throw(threadException);
+ });
+ }
+ }
+}
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/Audio/BassTestComponents.cs b/osu.Framework.Tests/Audio/BassTestComponents.cs
index 2995e1cc96..416b0ccbf9 100644
--- a/osu.Framework.Tests/Audio/BassTestComponents.cs
+++ b/osu.Framework.Tests/Audio/BassTestComponents.cs
@@ -1,16 +1,12 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// 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.Threading;
using ManagedBass;
using osu.Framework.Audio;
using osu.Framework.Audio.Mixing.Bass;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
-using osu.Framework.Development;
using osu.Framework.IO.Stores;
using osu.Framework.Threading;
@@ -61,7 +57,7 @@ public void Add(params AudioComponent[] component)
internal BassAudioMixer CreateMixer()
{
- var mixer = new BassAudioMixer(Mixer, "Test mixer");
+ var mixer = new BassAudioMixer(null, Mixer, "Test mixer");
mixerComponents.AddItem(mixer);
return mixer;
}
@@ -71,32 +67,20 @@ public void Update()
RunOnAudioThread(() => allComponents.Update());
}
- public void RunOnAudioThread(Action action)
- {
- var resetEvent = new ManualResetEvent(false);
-
- new Thread(() =>
- {
- ThreadSafety.IsAudioThread = true;
- action();
- resetEvent.Set();
- })
- {
- Name = GameThread.SuffixedThreadNameFor("Audio")
- }.Start();
-
- if (!resetEvent.WaitOne(TimeSpan.FromSeconds(10)))
- throw new TimeoutException();
- }
+ ///
+ /// Runs an on a newly created audio thread, and blocks until it has been run to completion.
+ ///
+ /// The action to run on the audio thread.
+ public void RunOnAudioThread(Action action) => AudioTestHelper.RunOnAudioThread(action);
internal TrackBass GetTrack() => (TrackBass)TrackStore.Get("Resources.Tracks.sample-track.mp3");
internal SampleBass GetSample() => (SampleBass)SampleStore.Get("Resources.Tracks.sample-track.mp3");
- public void Dispose()
+ public void Dispose() => RunOnAudioThread(() =>
{
allComponents.Dispose();
allComponents.Update(); // Actually runs the disposal.
Bass.Free();
- }
+ });
}
}
diff --git a/osu.Framework.Tests/Audio/DeviceLosingAudioTest.cs b/osu.Framework.Tests/Audio/DeviceLosingAudioTest.cs
index 0a92aa35e2..cf2a00ee28 100644
--- a/osu.Framework.Tests/Audio/DeviceLosingAudioTest.cs
+++ b/osu.Framework.Tests/Audio/DeviceLosingAudioTest.cs
@@ -1,8 +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;
diff --git a/osu.Framework.Tests/Audio/DevicelessAudioTest.cs b/osu.Framework.Tests/Audio/DevicelessAudioTest.cs
index 2d38c7d82c..2ca741bd55 100644
--- a/osu.Framework.Tests/Audio/DevicelessAudioTest.cs
+++ b/osu.Framework.Tests/Audio/DevicelessAudioTest.cs
@@ -1,8 +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 NUnit.Framework;
namespace osu.Framework.Tests.Audio
diff --git a/osu.Framework.Tests/Audio/DrawableAudioWrapperTest.cs b/osu.Framework.Tests/Audio/DrawableAudioWrapperTest.cs
new file mode 100644
index 0000000000..ae87f5ef46
--- /dev/null
+++ b/osu.Framework.Tests/Audio/DrawableAudioWrapperTest.cs
@@ -0,0 +1,63 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Threading;
+using NUnit.Framework;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Audio;
+using osu.Framework.Testing;
+using osu.Framework.Tests.Visual;
+
+namespace osu.Framework.Tests.Audio
+{
+ [HeadlessTest]
+ public partial class DrawableAudioWrapperTest : FrameworkTestScene
+ {
+ [Test]
+ public void TestAdjustmentsPreservedOnOwnedComponentAfterRemoval()
+ {
+ TrackVirtual track = null!;
+ SlowDisposingDrawableAudioWrapper wrapper = null!;
+
+ AddStep("add slow disposing wrapper for owned track",
+ () => Child = wrapper = new SlowDisposingDrawableAudioWrapper(track = new TrackVirtual(1000)));
+ AddStep("mute wrapper", () => wrapper.AddAdjustment(AdjustableProperty.Volume, new BindableDouble()));
+ AddStep("expire wrapper", () => wrapper.Expire());
+ AddAssert("component still muted", () => track.AggregateVolume.Value, () => Is.EqualTo(0));
+ AddStep("allow disposal to complete", () => wrapper.AllowDisposal.Set());
+ }
+
+ [Test]
+ public void TestAdjustmentsRevertedOnOwnedComponentAfterRemoval()
+ {
+ TrackVirtual track = null!;
+ SlowDisposingDrawableAudioWrapper wrapper = null!;
+
+ AddStep("add slow disposing wrapper for non-owned track",
+ () => Child = wrapper = new SlowDisposingDrawableAudioWrapper(track = new TrackVirtual(1000), false));
+ AddStep("mute wrapper", () => wrapper.AddAdjustment(AdjustableProperty.Volume, new BindableDouble()));
+ AddStep("expire wrapper", () => wrapper.Expire());
+ AddAssert("component unmuted", () => track.AggregateVolume.Value, () => Is.EqualTo(1));
+ AddStep("allow disposal to complete", () => wrapper.AllowDisposal.Set());
+ }
+
+ private partial class SlowDisposingDrawableAudioWrapper : DrawableAudioWrapper
+ {
+ public ManualResetEvent AllowDisposal { get; private set; } = new ManualResetEvent(false);
+
+ public SlowDisposingDrawableAudioWrapper(IAdjustableAudioComponent component, bool disposeUnderlyingComponentOnDispose = true)
+ : base(component, disposeUnderlyingComponentOnDispose)
+ {
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ AllowDisposal.WaitOne(10_000);
+
+ base.Dispose(isDisposing);
+ }
+ }
+ }
+}
diff --git a/osu.Framework.Tests/Audio/SampleChannelVirtualTest.cs b/osu.Framework.Tests/Audio/SampleChannelVirtualTest.cs
index d9e657b158..9f160af34e 100644
--- a/osu.Framework.Tests/Audio/SampleChannelVirtualTest.cs
+++ b/osu.Framework.Tests/Audio/SampleChannelVirtualTest.cs
@@ -3,12 +3,8 @@
#nullable disable
-using System;
-using System.Threading;
using NUnit.Framework;
using osu.Framework.Audio.Sample;
-using osu.Framework.Development;
-using osu.Framework.Threading;
namespace osu.Framework.Tests.Audio
{
@@ -56,31 +52,6 @@ public void TestLooping()
Assert.IsTrue(channel.HasCompleted);
}
- private void updateSample() => RunOnAudioThread(() => sample.Update());
-
- ///
- /// Certain actions are invoked on the audio thread.
- /// Here we simulate this process on a correctly named thread to avoid endless blocking.
- ///
- /// The action to perform.
- public static void RunOnAudioThread(Action action)
- {
- var resetEvent = new ManualResetEvent(false);
-
- new Thread(() =>
- {
- ThreadSafety.IsAudioThread = true;
-
- action();
-
- resetEvent.Set();
- })
- {
- Name = GameThread.SuffixedThreadNameFor("Audio")
- }.Start();
-
- if (!resetEvent.WaitOne(TimeSpan.FromSeconds(10)))
- throw new TimeoutException();
- }
+ private void updateSample() => AudioTestHelper.RunOnAudioThread(() => sample.Update());
}
}
diff --git a/osu.Framework.Tests/Audio/TestAudioAdjustments.cs b/osu.Framework.Tests/Audio/TestAudioAdjustments.cs
index 3506b811ab..8897f48e87 100644
--- a/osu.Framework.Tests/Audio/TestAudioAdjustments.cs
+++ b/osu.Framework.Tests/Audio/TestAudioAdjustments.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// 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.Runtime.CompilerServices;
using NUnit.Framework;
diff --git a/osu.Framework.Tests/Audio/TrackVirtualTest.cs b/osu.Framework.Tests/Audio/TrackVirtualTest.cs
index 5dc3bb8f1e..cfec39a1bc 100644
--- a/osu.Framework.Tests/Audio/TrackVirtualTest.cs
+++ b/osu.Framework.Tests/Audio/TrackVirtualTest.cs
@@ -10,8 +10,6 @@
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
-using osu.Framework.Development;
-using osu.Framework.Threading;
namespace osu.Framework.Tests.Audio
{
@@ -284,7 +282,7 @@ public void TestCurrentTimeUpdatedAfterInlineSeek()
track.Start();
updateTrack();
- RunOnAudioThread(() => track.Seek(20000));
+ AudioTestHelper.RunOnAudioThread(() => track.Seek(20000));
Assert.That(track.CurrentTime, Is.EqualTo(20000).Within(100));
}
@@ -294,7 +292,7 @@ public void TestSeekToCurrentTime()
track.Seek(5000);
bool seekSucceeded = false;
- RunOnAudioThread(() => seekSucceeded = track.Seek(track.CurrentTime));
+ AudioTestHelper.RunOnAudioThread(() => seekSucceeded = track.Seek(track.CurrentTime));
Assert.That(seekSucceeded, Is.True);
Assert.That(track.CurrentTime, Is.EqualTo(5000));
@@ -304,7 +302,7 @@ public void TestSeekToCurrentTime()
public void TestSeekBeyondStartTime()
{
bool seekSucceeded = false;
- RunOnAudioThread(() => seekSucceeded = track.Seek(-1000));
+ AudioTestHelper.RunOnAudioThread(() => seekSucceeded = track.Seek(-1000));
Assert.That(seekSucceeded, Is.False);
Assert.That(track.CurrentTime, Is.EqualTo(0));
@@ -314,12 +312,46 @@ public void TestSeekBeyondStartTime()
public void TestSeekBeyondEndTime()
{
bool seekSucceeded = false;
- RunOnAudioThread(() => seekSucceeded = track.Seek(track.Length + 1000));
+ AudioTestHelper.RunOnAudioThread(() => seekSucceeded = track.Seek(track.Length + 1000));
Assert.That(seekSucceeded, Is.False);
Assert.That(track.CurrentTime, Is.EqualTo(track.Length));
}
+ [Test]
+ public void TestSeekBeyondStartTimeWhenRunning()
+ {
+ bool seekSucceeded = false;
+ startPlaybackAt(0);
+ AudioTestHelper.RunOnAudioThread(() => seekSucceeded = track.Seek(-1000));
+
+ Assert.That(seekSucceeded, Is.False);
+ Assert.That(track.IsRunning, Is.False);
+ Assert.That(track.CurrentTime, Is.EqualTo(0));
+ }
+
+ [Test]
+ public void TestSeekBeyondEndTimeWhenRunning()
+ {
+ bool seekSucceeded = false;
+ startPlaybackAt(0);
+ AudioTestHelper.RunOnAudioThread(() => seekSucceeded = track.Seek(track.Length + 1000));
+
+ Assert.That(seekSucceeded, Is.False);
+ Assert.That(track.IsRunning, Is.False);
+ Assert.That(track.CurrentTime, Is.EqualTo(track.Length));
+ }
+
+ [Test]
+ public void TestStartDoesNotWorkIfTrackAtEnd()
+ {
+ startPlaybackAt(track.Length);
+ track.Start();
+
+ Assert.That(track.IsRunning, Is.False);
+ Assert.That(track.CurrentTime, Is.EqualTo(track.Length));
+ }
+
private void testPlaybackRate(double expectedRate)
{
const double play_time = 1000;
@@ -349,40 +381,15 @@ private void startPlaybackAt(double time)
updateTrack();
}
- private void updateTrack() => RunOnAudioThread(() => track.Update());
+ private void updateTrack() => AudioTestHelper.RunOnAudioThread(() => track.Update());
private void restartTrack()
{
- RunOnAudioThread(() =>
+ AudioTestHelper.RunOnAudioThread(() =>
{
track.Restart();
track.Update();
});
}
-
- ///
- /// Certain actions are invoked on the audio thread.
- /// Here we simulate this process on a correctly named thread to avoid endless blocking.
- ///
- /// The action to perform.
- public static void RunOnAudioThread(Action action)
- {
- var resetEvent = new ManualResetEvent(false);
-
- new Thread(() =>
- {
- ThreadSafety.IsAudioThread = true;
-
- action();
-
- resetEvent.Set();
- })
- {
- Name = GameThread.SuffixedThreadNameFor("Audio")
- }.Start();
-
- if (!resetEvent.WaitOne(TimeSpan.FromSeconds(10)))
- throw new TimeoutException();
- }
}
}
diff --git a/osu.Framework.Tests/AutomatedVisualTestGame.cs b/osu.Framework.Tests/AutomatedVisualTestGame.cs
index 647508cfed..a2d9986a69 100644
--- a/osu.Framework.Tests/AutomatedVisualTestGame.cs
+++ b/osu.Framework.Tests/AutomatedVisualTestGame.cs
@@ -1,8 +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 osu.Framework.Testing;
namespace osu.Framework.Tests
diff --git a/osu.Framework.Tests/Bindables/BindableBoolTest.cs b/osu.Framework.Tests/Bindables/BindableBoolTest.cs
index 3114ec9610..b3cb69b10d 100644
--- a/osu.Framework.Tests/Bindables/BindableBoolTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableBoolTest.cs
@@ -1,8 +1,7 @@
// 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.Globalization;
using NUnit.Framework;
using osu.Framework.Bindables;
@@ -28,7 +27,7 @@ public void TestSet(bool value)
public void TestParsingString(string value, bool expected)
{
var bindable = new BindableBool();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
}
@@ -38,7 +37,7 @@ public void TestParsingString(string value, bool expected)
public void TestParsingBoolean(bool value)
{
var bindable = new BindableBool();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(value, bindable.Value);
}
diff --git a/osu.Framework.Tests/Bindables/BindableColour4Test.cs b/osu.Framework.Tests/Bindables/BindableColour4Test.cs
index 0c72e33919..ae0a04f8fb 100644
--- a/osu.Framework.Tests/Bindables/BindableColour4Test.cs
+++ b/osu.Framework.Tests/Bindables/BindableColour4Test.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Globalization;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -41,7 +42,7 @@ public void TestSet(byte r, byte g, byte b, byte a)
public void TestParsingString(string value, Colour4 expected)
{
var bindable = new BindableColour4();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
}
diff --git a/osu.Framework.Tests/Bindables/BindableDictionaryTest.cs b/osu.Framework.Tests/Bindables/BindableDictionaryTest.cs
index 08caac8e01..49f2646f30 100644
--- a/osu.Framework.Tests/Bindables/BindableDictionaryTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableDictionaryTest.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
@@ -166,7 +167,7 @@ public void TestBindCollectionChangedNotRunIfParsingSequenceEqualEnumerable()
NotifyDictionaryChangedEventArgs triggeredArgs = null;
dict.BindCollectionChanged((_, args) => triggeredArgs = args);
- dict.Parse(enumerable);
+ dict.Parse(enumerable, CultureInfo.InvariantCulture);
Assert.That(triggeredArgs, Is.Null);
}
@@ -862,7 +863,8 @@ public void TestDisabledNotifiesBoundDictionaries()
[Test]
public void TestGetEnumeratorDoesNotReturnNull()
{
- Assert.NotNull(bindableStringByteDictionary.GetEnumerator());
+ using var enumerator = bindableStringByteDictionary.GetEnumerator();
+ Assert.NotNull(enumerator);
}
[Test]
@@ -872,8 +874,10 @@ public void TestGetEnumeratorWhenCopyConstructorIsUsedDoesNotReturnTheEnumerator
var dict = new BindableDictionary(array);
- var enumerator = dict.GetEnumerator();
+ using var enumerator = dict.GetEnumerator();
+ // ReSharper disable once NotDisposedResource
+ // Array enumerator is not disposable
Assert.AreNotEqual(array.GetEnumerator(), enumerator);
}
@@ -900,7 +904,7 @@ public void TestParseWithNullClearsDictionary()
{
bindableStringByteDictionary.Add("a item", 0);
- bindableStringByteDictionary.Parse(null);
+ bindableStringByteDictionary.Parse(null, CultureInfo.InvariantCulture);
Assert.IsEmpty(bindableStringByteDictionary);
}
@@ -914,7 +918,7 @@ public void TestParseWithArray()
new KeyValuePair("testB", 1),
};
- bindableStringByteDictionary.Parse(array);
+ bindableStringByteDictionary.Parse(array, CultureInfo.InvariantCulture);
CollectionAssert.AreEquivalent(array, bindableStringByteDictionary);
}
@@ -926,13 +930,13 @@ public void TestParseWithDisabledDictionaryThrowsInvalidOperationException()
Assert.Multiple(() =>
{
- Assert.Throws(typeof(InvalidOperationException), () => bindableStringByteDictionary.Parse(null));
+ Assert.Throws(typeof(InvalidOperationException), () => bindableStringByteDictionary.Parse(null, CultureInfo.InvariantCulture));
Assert.Throws(typeof(InvalidOperationException), () => bindableStringByteDictionary.Parse(new[]
{
new KeyValuePair("test", 0),
new KeyValuePair("testabc", 1),
new KeyValuePair("asdasdasdasd", 1),
- }));
+ }, CultureInfo.InvariantCulture));
});
}
@@ -941,13 +945,13 @@ public void TestParseWithInvalidArgumentTypesThrowsArgumentException()
{
Assert.Multiple(() =>
{
- Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse(1));
- Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse(""));
- Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse(new object()));
- Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse(1.1));
- Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse(1.1f));
- Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse("test123"));
- Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse(29387L));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse(1, CultureInfo.InvariantCulture));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse("", CultureInfo.InvariantCulture));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse(new object(), CultureInfo.InvariantCulture));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse(1.1, CultureInfo.InvariantCulture));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse(1.1f, CultureInfo.InvariantCulture));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse("test123", CultureInfo.InvariantCulture));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringByteDictionary.Parse(29387L, CultureInfo.InvariantCulture));
});
}
@@ -967,7 +971,7 @@ public void TestParseWithNullNotifiesClearSubscribers()
var triggeredArgs = new List>();
bindableStringByteDictionary.CollectionChanged += (_, args) => triggeredArgs.Add(args);
- bindableStringByteDictionary.Parse(null);
+ bindableStringByteDictionary.Parse(null, CultureInfo.InvariantCulture);
Assert.That(triggeredArgs, Has.Count.EqualTo(1));
Assert.That(triggeredArgs.First().Action, Is.EqualTo(NotifyDictionaryChangedAction.Remove));
@@ -988,7 +992,7 @@ public void TestParseWithItemsNotifiesAddRangeAndClearSubscribers()
var triggeredArgs = new List>();
bindableStringByteDictionary.CollectionChanged += (_, args) => triggeredArgs.Add(args);
- bindableStringByteDictionary.Parse(array);
+ bindableStringByteDictionary.Parse(array, CultureInfo.InvariantCulture);
Assert.That(triggeredArgs, Has.Count.EqualTo(2));
Assert.That(triggeredArgs.First().Action, Is.EqualTo(NotifyDictionaryChangedAction.Remove));
diff --git a/osu.Framework.Tests/Bindables/BindableDoubleTest.cs b/osu.Framework.Tests/Bindables/BindableDoubleTest.cs
index 506d0ef513..d076511803 100644
--- a/osu.Framework.Tests/Bindables/BindableDoubleTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableDoubleTest.cs
@@ -1,10 +1,10 @@
// 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.Globalization;
using NUnit.Framework;
using osu.Framework.Bindables;
+using osu.Framework.Utils;
namespace osu.Framework.Tests.Bindables
{
@@ -55,7 +55,7 @@ public void TestDefaultCheck(double value, double def, double? precision = null)
public void TestParsingString(string value, double expected)
{
var bindable = new BindableDouble();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
}
@@ -68,7 +68,7 @@ public void TestParsingString(string value, double expected)
public void TestParsingStringWithRange(string value, double minValue, double maxValue, double expected)
{
var bindable = new BindableDouble { MinValue = minValue, MaxValue = maxValue };
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
}
@@ -83,7 +83,7 @@ public void TestParsingStringWithRange(string value, double minValue, double max
public void TestParsingDouble(double value)
{
var bindable = new BindableDouble();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(value, bindable.Value);
}
@@ -100,5 +100,35 @@ public void TestPropagationToPlainBindable()
number.MinValue = 0;
number.MaxValue = 10;
}
+
+ [TestCase("1.4", "en-US", 1.4)]
+ [TestCase("1,4", "de-DE", 1.4)]
+ [TestCase("1.400,01", "de-DE", 1400.01)]
+ [TestCase("1 234,57", "ru-RU", 1234.57)]
+ [TestCase("1,094", "fr-FR", 1.094)]
+ [TestCase("1,400.01", "zh-CN", 1400.01)]
+ public void TestParsingStringLocale(string value, string locale, double expected)
+ {
+ var bindable = new BindableDouble();
+ bindable.Parse(value, CultureInfo.GetCultureInfo(locale));
+ Assert.AreEqual(expected, bindable.Value);
+ }
+
+ [TestCase(1.4, "en-US", "1.4")]
+ [TestCase(1.4, "de-DE", "1,4")]
+ [TestCase(1400.01, "de-DE", "1400,01")]
+ [TestCase(1234.57, "ru-RU", "1234,57")]
+ [TestCase(1.094, "fr-FR", "1,094")]
+ [TestCase(1400.01, "zh-CN", "1400.01")]
+ public void TestParsingNumberLocale(double value, string locale, string expected)
+ {
+ CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(locale);
+
+ var bindable = new BindableDouble(value);
+ string? asString = bindable.ToString();
+ Assert.AreEqual(expected, asString);
+ Assert.DoesNotThrow(() => bindable.Parse(asString, CultureInfo.CurrentCulture));
+ Assert.AreEqual(value, bindable.Value, Precision.DOUBLE_EPSILON);
+ }
}
}
diff --git a/osu.Framework.Tests/Bindables/BindableEnumTest.cs b/osu.Framework.Tests/Bindables/BindableEnumTest.cs
index c2df413398..2ec18dfaad 100644
--- a/osu.Framework.Tests/Bindables/BindableEnumTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableEnumTest.cs
@@ -1,9 +1,8 @@
// 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.Globalization;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
@@ -34,8 +33,8 @@ public void TestParsing(TestEnum expected, params object[] values)
foreach (object value in values.Append(expected))
{
- bindable.Parse(value);
- nullable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
+ nullable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
Assert.AreEqual(expected, nullable.Value);
@@ -44,14 +43,25 @@ public void TestParsing(TestEnum expected, params object[] values)
[TestCase(1.1f)]
[TestCase("Not a value")]
- [TestCase("")]
public void TestUnparsaebles(object value)
{
var bindable = new Bindable();
var nullable = new Bindable();
- Assert.Throws(() => bindable.Parse(value));
- Assert.Throws(() => nullable.Parse(value));
+ Assert.Throws(() => bindable.Parse(value, CultureInfo.InvariantCulture));
+ Assert.Throws(() => nullable.Parse(value, CultureInfo.InvariantCulture));
+ }
+
+ [Test]
+ public void TestEmptyString()
+ {
+ var bindable = new Bindable();
+ var nullable = new Bindable();
+
+ Assert.Throws(() => bindable.Parse(string.Empty, CultureInfo.InvariantCulture));
+ nullable.Parse(string.Empty, CultureInfo.InvariantCulture);
+
+ Assert.That(nullable.Value, Is.Null);
}
public enum TestEnum
diff --git a/osu.Framework.Tests/Bindables/BindableFloatTest.cs b/osu.Framework.Tests/Bindables/BindableFloatTest.cs
index 37c5e1421c..200988ee85 100644
--- a/osu.Framework.Tests/Bindables/BindableFloatTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableFloatTest.cs
@@ -1,10 +1,10 @@
// 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.Globalization;
using NUnit.Framework;
using osu.Framework.Bindables;
+using osu.Framework.Utils;
namespace osu.Framework.Tests.Bindables
{
@@ -55,7 +55,7 @@ public void TestDefaultCheck(float value, float def, float? precision = null)
public void TestParsingString(string value, float expected)
{
var bindable = new BindableFloat();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
}
@@ -68,7 +68,7 @@ public void TestParsingString(string value, float expected)
public void TestParsingStringWithRange(string value, float minValue, float maxValue, float expected)
{
var bindable = new BindableFloat { MinValue = minValue, MaxValue = maxValue };
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
}
@@ -83,9 +83,39 @@ public void TestParsingStringWithRange(string value, float minValue, float maxVa
public void TestParsingFloat(float value)
{
var bindable = new BindableFloat();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(value, bindable.Value);
}
+
+ [TestCase("1.4", "en-US", 1.4f)]
+ [TestCase("1,4", "de-DE", 1.4f)]
+ [TestCase("1.400,01", "de-DE", 1400.01f)]
+ [TestCase("1 234,57", "ru-RU", 1234.57f)]
+ [TestCase("1,094", "fr-FR", 1.094f)]
+ [TestCase("1,400.01", "zh-CN", 1400.01f)]
+ public void TestParsingStringLocale(string value, string locale, float expected)
+ {
+ var bindable = new BindableFloat();
+ bindable.Parse(value, CultureInfo.GetCultureInfo(locale));
+ Assert.AreEqual(expected, bindable.Value);
+ }
+
+ [TestCase(1.4f, "en-US", "1.4")]
+ [TestCase(1.4f, "de-DE", "1,4")]
+ [TestCase(1400.01f, "de-DE", "1400,01")]
+ [TestCase(1234.57f, "ru-RU", "1234,57")]
+ [TestCase(1.094f, "fr-FR", "1,094")]
+ [TestCase(1400.01f, "zh-CN", "1400.01")]
+ public void TestParsingNumberLocale(float value, string locale, string expected)
+ {
+ CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(locale);
+
+ var bindable = new BindableFloat(value);
+ string? asString = bindable.ToString();
+ Assert.AreEqual(expected, asString);
+ Assert.DoesNotThrow(() => bindable.Parse(asString, CultureInfo.CurrentCulture));
+ Assert.AreEqual(value, bindable.Value, Precision.FLOAT_EPSILON);
+ }
}
}
diff --git a/osu.Framework.Tests/Bindables/BindableIntTest.cs b/osu.Framework.Tests/Bindables/BindableIntTest.cs
index 328a9c4bac..52621af94d 100644
--- a/osu.Framework.Tests/Bindables/BindableIntTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableIntTest.cs
@@ -1,8 +1,7 @@
// 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.Globalization;
using NUnit.Framework;
using osu.Framework.Bindables;
@@ -32,7 +31,7 @@ public void TestSet(int value)
public void TestParsingString(string value, int expected)
{
var bindable = new BindableInt();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
}
@@ -45,7 +44,7 @@ public void TestParsingString(string value, int expected)
public void TestParsingStringWithRange(string value, int minValue, int maxValue, int expected)
{
var bindable = new BindableInt { MinValue = minValue, MaxValue = maxValue };
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
}
@@ -60,7 +59,7 @@ public void TestParsingStringWithRange(string value, int minValue, int maxValue,
public void TestParsingInt(int value)
{
var bindable = new BindableInt();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(value, bindable.Value);
}
diff --git a/osu.Framework.Tests/Bindables/BindableListTest.cs b/osu.Framework.Tests/Bindables/BindableListTest.cs
index 9df3106dd9..10d46d4649 100644
--- a/osu.Framework.Tests/Bindables/BindableListTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableListTest.cs
@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.Globalization;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
@@ -135,7 +136,7 @@ public void TestBindCollectionChangedNotRunIfParsingSequenceEqualEnumerable()
NotifyCollectionChangedEventArgs triggeredArgs = null;
list.BindCollectionChanged((_, args) => triggeredArgs = args);
- list.Parse(enumerable);
+ list.Parse(enumerable, CultureInfo.InvariantCulture);
Assert.That(triggeredArgs, Is.Null);
}
@@ -377,6 +378,26 @@ public void TestAddWithListContainingItemsDoesNotOverrideItems(string str)
Assert.Contains(item, bindableStringList);
}
+ [Test]
+ public void TestAddBranchingBinds()
+ {
+ var b1 = new BindableList { 1, 2, 3, 4, 5 };
+
+ var b2 = b1.GetBoundCopy();
+ var b3 = b1.GetBoundCopy();
+
+ var b4 = new BindableList();
+ b4.BindTo(b2);
+ b4.BindTo(b3);
+
+ b1.Add(6);
+
+ Assert.That(b1.Count, Is.EqualTo(6));
+ Assert.That(b2.Count, Is.EqualTo(6));
+ Assert.That(b3.Count, Is.EqualTo(6));
+ Assert.That(b4.Count, Is.EqualTo(6));
+ }
+
#endregion
#region .AddRange(items)
@@ -435,6 +456,26 @@ IEnumerable valueEnumerable()
Assert.That(counter, Is.EqualTo(1));
}
+ [Test]
+ public void TestAddRangeBranchingBinds()
+ {
+ var b1 = new BindableList { 1, 2, 3, 4, 5 };
+
+ var b2 = b1.GetBoundCopy();
+ var b3 = b1.GetBoundCopy();
+
+ var b4 = new BindableList();
+ b4.BindTo(b2);
+ b4.BindTo(b3);
+
+ b1.AddRange(new[] { 6, 7 });
+
+ Assert.That(b1.Count, Is.EqualTo(7));
+ Assert.That(b2.Count, Is.EqualTo(7));
+ Assert.That(b3.Count, Is.EqualTo(7));
+ Assert.That(b4.Count, Is.EqualTo(7));
+ }
+
#endregion
#region .Move(item)
@@ -574,6 +615,28 @@ public void TestMoveNotifiesBoundListSubscriptions()
Assert.That(triggeredArgsB2, Is.Not.Null);
}
+ [Test]
+ public void TestMoveRangeBranchingBinds()
+ {
+ var b1 = new BindableList { 1, 2, 3, 4, 5 };
+
+ var b2 = b1.GetBoundCopy();
+ var b3 = b1.GetBoundCopy();
+
+ var b4 = new BindableList();
+
+ b4.BindTo(b2);
+ b4.BindTo(b3);
+
+ b1.Move(0, 1);
+
+ foreach (var list in new[] { b1, b2, b3, b4 })
+ {
+ Assert.That(list[0], Is.EqualTo(2));
+ Assert.That(list[1], Is.EqualTo(1));
+ }
+ }
+
#endregion
#region .Insert
@@ -648,6 +711,26 @@ public void TestInsertInsertsItemAtIndexInBoundList()
});
}
+ [Test]
+ public void TestInsertBranchingBinds()
+ {
+ var b1 = new BindableList { 1, 2, 3, 4, 5 };
+
+ var b2 = b1.GetBoundCopy();
+ var b3 = b1.GetBoundCopy();
+
+ var b4 = new BindableList();
+ b4.BindTo(b2);
+ b4.BindTo(b3);
+
+ b1.Insert(0, 0);
+
+ Assert.That(b1.Count, Is.EqualTo(6));
+ Assert.That(b2.Count, Is.EqualTo(6));
+ Assert.That(b3.Count, Is.EqualTo(6));
+ Assert.That(b4.Count, Is.EqualTo(6));
+ }
+
#endregion
#region .Remove(item)
@@ -995,6 +1078,21 @@ public void TestRemoveAtNotifiesBoundLists()
Assert.That(triggeredArgs.OldStartingIndex, Is.EqualTo(0));
}
+ [Test]
+ public void TestRemoveAtBranchingBinds()
+ {
+ var b1 = new BindableList { 1, 2, 3, 4, 5 };
+
+ var b2 = b1.GetBoundCopy();
+ var b3 = b1.GetBoundCopy();
+
+ var b4 = new BindableList();
+ b4.BindTo(b2);
+ b4.BindTo(b3);
+
+ b1.RemoveAt(b1.Count - 1);
+ }
+
#endregion
#region .RemoveAll(match)
@@ -1349,12 +1447,13 @@ public void TestDisabledNotifiesBoundLists()
#endregion
- #region .GetEnumberator()
+ #region .GetEnumerator()
[Test]
public void TestGetEnumeratorDoesNotReturnNull()
{
- Assert.NotNull(bindableStringList.GetEnumerator());
+ using var enumerator = bindableStringList.GetEnumerator();
+ Assert.NotNull(enumerator);
}
[Test]
@@ -1363,8 +1462,10 @@ public void TestGetEnumeratorWhenCopyConstructorIsUsedDoesNotReturnTheEnumerator
string[] array = { "" };
var list = new BindableList(array);
- var enumerator = list.GetEnumerator();
+ using var enumerator = list.GetEnumerator();
+ // ReSharper disable once NotDisposedResource
+ // Array enumerator is not disposable
Assert.AreNotEqual(array.GetEnumerator(), enumerator);
}
@@ -1391,7 +1492,7 @@ public void TestParseWithNullClearsList()
{
bindableStringList.Add("a item");
- bindableStringList.Parse(null);
+ bindableStringList.Parse(null, CultureInfo.InvariantCulture);
Assert.IsEmpty(bindableStringList);
}
@@ -1401,7 +1502,7 @@ public void TestParseWithArray()
{
IEnumerable strings = new[] { "testA", "testB" };
- bindableStringList.Parse(strings);
+ bindableStringList.Parse(strings, CultureInfo.InvariantCulture);
CollectionAssert.AreEquivalent(strings, bindableStringList);
}
@@ -1413,11 +1514,11 @@ public void TestParseWithDisabledListThrowsInvalidOperationException()
Assert.Multiple(() =>
{
- Assert.Throws(typeof(InvalidOperationException), () => bindableStringList.Parse(null));
+ Assert.Throws(typeof(InvalidOperationException), () => bindableStringList.Parse(null, CultureInfo.InvariantCulture));
Assert.Throws(typeof(InvalidOperationException), () => bindableStringList.Parse(new object[]
{
"test", "testabc", "asdasdasdasd"
- }));
+ }, CultureInfo.InvariantCulture));
});
}
@@ -1426,13 +1527,13 @@ public void TestParseWithInvalidArgumentTypesThrowsArgumentException()
{
Assert.Multiple(() =>
{
- Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse(1));
- Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse(""));
- Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse(new object()));
- Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse(1.1));
- Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse(1.1f));
- Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse("test123"));
- Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse(29387L));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse(1, CultureInfo.InvariantCulture));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse("", CultureInfo.InvariantCulture));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse(new object(), CultureInfo.InvariantCulture));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse(1.1, CultureInfo.InvariantCulture));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse(1.1f, CultureInfo.InvariantCulture));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse("test123", CultureInfo.InvariantCulture));
+ Assert.Throws(typeof(ArgumentException), () => bindableStringList.Parse(29387L, CultureInfo.InvariantCulture));
});
}
@@ -1445,7 +1546,7 @@ public void TestParseWithNullNotifiesClearSubscribers()
var triggeredArgs = new List();
bindableStringList.CollectionChanged += (_, args) => triggeredArgs.Add(args);
- bindableStringList.Parse(null);
+ bindableStringList.Parse(null, CultureInfo.InvariantCulture);
Assert.That(triggeredArgs, Has.Count.EqualTo(1));
Assert.That(triggeredArgs.First().Action, Is.EqualTo(NotifyCollectionChangedAction.Remove));
@@ -1462,7 +1563,7 @@ public void TestParseWithItemsNotifiesAddRangeAndClearSubscribers()
var triggeredArgs = new List();
bindableStringList.CollectionChanged += (_, args) => triggeredArgs.Add(args);
- bindableStringList.Parse(strings);
+ bindableStringList.Parse(strings, CultureInfo.InvariantCulture);
Assert.That(triggeredArgs, Has.Count.EqualTo(2));
Assert.That(triggeredArgs.First().Action, Is.EqualTo(NotifyCollectionChangedAction.Remove));
diff --git a/osu.Framework.Tests/Bindables/BindableLongTest.cs b/osu.Framework.Tests/Bindables/BindableLongTest.cs
index 8da72a0939..84dccc87a5 100644
--- a/osu.Framework.Tests/Bindables/BindableLongTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableLongTest.cs
@@ -1,8 +1,7 @@
// 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.Globalization;
using NUnit.Framework;
using osu.Framework.Bindables;
@@ -32,7 +31,7 @@ public void TestSet(long value)
public void TestParsingString(string value, long expected)
{
var bindable = new BindableLong();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
}
@@ -45,7 +44,7 @@ public void TestParsingString(string value, long expected)
public void TestParsingStringWithRange(string value, long minValue, long maxValue, long expected)
{
var bindable = new BindableLong { MinValue = minValue, MaxValue = maxValue };
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
}
@@ -60,7 +59,7 @@ public void TestParsingStringWithRange(string value, long minValue, long maxValu
public void TestParsingLong(long value)
{
var bindable = new BindableLong();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(value, bindable.Value);
}
diff --git a/osu.Framework.Tests/Bindables/BindableMarginPaddingTest.cs b/osu.Framework.Tests/Bindables/BindableMarginPaddingTest.cs
index 391e6a42f0..4e61d5a3b7 100644
--- a/osu.Framework.Tests/Bindables/BindableMarginPaddingTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableMarginPaddingTest.cs
@@ -1,8 +1,7 @@
// 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.Globalization;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -33,7 +32,7 @@ public void TestSet(float top, float left, float bottom, float right)
public void TestParsingString(string value, float expectedTop, float expectedLeft, float expectedBottom, float expectedRight)
{
var bindable = new BindableMarginPadding();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(new MarginPadding { Top = expectedTop, Left = expectedLeft, Bottom = expectedBottom, Right = expectedRight }, bindable.Value);
}
@@ -53,7 +52,7 @@ public void TestParsingStringWithRange(string value,
var expected = new MarginPadding { Top = expectedTop, Left = expectedLeft, Bottom = expectedBottom, Right = expectedRight };
var bindable = new BindableMarginPadding { MinValue = minValue, MaxValue = maxValue };
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
}
diff --git a/osu.Framework.Tests/Bindables/BindableNumberTest.cs b/osu.Framework.Tests/Bindables/BindableNumberTest.cs
index cf17ed00e5..e91e7f6fe6 100644
--- a/osu.Framework.Tests/Bindables/BindableNumberTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableNumberTest.cs
@@ -56,6 +56,33 @@ public void TestSetValue(Type type)
Assert.That(Convert.ChangeType(value, typeof(int)), Is.EqualTo(expectedValue));
}
+ ///
+ /// This test exercises that when sitting a decimal like 0.1 on a BindableNumber<float>,
+ /// the resulting is the closest to the actual closest float number
+ /// to the real-world decimal.
+ /// This matters because certain methods of rounding to the precision specified on the
+ /// cause rounding errors that show up elsewhere in undesirable ways.
+ ///
+ [Test]
+ public void TestDecimalPrecision()
+ {
+ var bindable = new BindableNumber
+ {
+ Precision = 0.1f,
+ Value = 5.2f
+ };
+ Assert.That(bindable.Value, Is.EqualTo(5.2f));
+
+ bindable.Value = 4.3f;
+ Assert.That(bindable.Value, Is.EqualTo(4.3f));
+
+ bindable.Precision = 0.01f;
+ Assert.That(bindable.Value, Is.EqualTo(4.3f));
+
+ bindable.Value = 9.87f;
+ Assert.That(bindable.Value, Is.EqualTo(9.87f));
+ }
+
///
/// Tests that value can be added to on all supported types.
///
diff --git a/osu.Framework.Tests/Bindables/BindableSizeTest.cs b/osu.Framework.Tests/Bindables/BindableSizeTest.cs
index 8721e4846a..23849cd759 100644
--- a/osu.Framework.Tests/Bindables/BindableSizeTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableSizeTest.cs
@@ -1,10 +1,9 @@
// 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 NUnit.Framework;
using System.Drawing;
+using System.Globalization;
using osu.Framework.Bindables;
namespace osu.Framework.Tests.Bindables
@@ -34,7 +33,7 @@ public void TestSet(int width, int height)
public void TestParsingString(string value, int expectedWidth, int expectedHeight)
{
var bindable = new BindableSize();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(new Size(expectedWidth, expectedHeight), bindable.Value);
}
@@ -55,7 +54,7 @@ public void TestParsingStringWithRange(string value,
var expected = new Size(expectedWidth, expectedHeight);
var bindable = new BindableSize { MinValue = minValue, MaxValue = maxValue };
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(expected, bindable.Value);
}
@@ -69,7 +68,7 @@ public void TestParsingSize(int width, int height)
var value = new Size(width, height);
var bindable = new BindableSize();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(value, bindable.Value);
}
diff --git a/osu.Framework.Tests/Bindables/BindableStringTest.cs b/osu.Framework.Tests/Bindables/BindableStringTest.cs
index 6419651c59..90ebc60090 100644
--- a/osu.Framework.Tests/Bindables/BindableStringTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableStringTest.cs
@@ -1,8 +1,7 @@
// 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.Globalization;
using NUnit.Framework;
using osu.Framework.Bindables;
@@ -26,7 +25,7 @@ public void TestSet(string value)
public void TestParsingString(string value)
{
var bindable = new Bindable();
- bindable.Parse(value);
+ bindable.Parse(value, CultureInfo.InvariantCulture);
Assert.AreEqual(value, bindable.Value);
}
diff --git a/osu.Framework.Tests/Bindables/BindableTest.cs b/osu.Framework.Tests/Bindables/BindableTest.cs
index 5cff3b295b..ab00765ac9 100644
--- a/osu.Framework.Tests/Bindables/BindableTest.cs
+++ b/osu.Framework.Tests/Bindables/BindableTest.cs
@@ -66,10 +66,10 @@ public void TestParseBindableOfExactSameType()
var bindable3 = new BindableBool();
var bindable4 = new Bindable();
- bindable1.Parse(new BindableInt(3));
- bindable2.Parse(new BindableDouble(2.5));
- bindable3.Parse(new BindableBool(true));
- bindable4.Parse(new Bindable("string value"));
+ bindable1.Parse(new BindableInt(3), CultureInfo.InvariantCulture);
+ bindable2.Parse(new BindableDouble(2.5), CultureInfo.InvariantCulture);
+ bindable3.Parse(new BindableBool(true), CultureInfo.InvariantCulture);
+ bindable4.Parse(new Bindable("string value"), CultureInfo.InvariantCulture);
Assert.That(bindable1.Value, Is.EqualTo(3));
Assert.That(bindable2.Value, Is.EqualTo(2.5));
@@ -77,7 +77,7 @@ public void TestParseBindableOfExactSameType()
Assert.That(bindable4.Value, Is.EqualTo("string value"));
// parsing bindable of different type should throw exception.
- Assert.Throws(() => bindable1.Parse(new BindableDouble(3.0)));
+ Assert.Throws(() => bindable1.Parse(new BindableDouble(3.0), CultureInfo.InvariantCulture));
}
[Test]
@@ -87,11 +87,11 @@ public void TestParseBindableOfMatchingInterfaceType()
var bindable1 = new BindableInt(10) { MaxValue = 15 };
var bindable2 = new Bindable(20);
- bindable1.Parse(bindable2);
+ bindable1.Parse(bindable2, CultureInfo.InvariantCulture);
// ensure MaxValue is still respected.
Assert.That(bindable1.Value, Is.EqualTo(15));
- bindable2.Parse(bindable1);
+ bindable2.Parse(bindable1, CultureInfo.InvariantCulture);
Assert.That(bindable2.Value, Is.EqualTo(15));
}
@@ -101,12 +101,152 @@ public void TestParse(Type type, object input, object output)
object bindable = Activator.CreateInstance(typeof(Bindable<>).MakeGenericType(type), type == typeof(string) ? "" : Activator.CreateInstance(type));
Debug.Assert(bindable != null);
- ((IParseable)bindable).Parse(input);
+ ((IParseable)bindable).Parse(input, CultureInfo.InvariantCulture);
object value = bindable.GetType().GetProperty(nameof(Bindable