A game framework written with osu! in mind.

Merge branch 'master' into fix-mbd-reloading

authored by

Dean Herbert and committed by
GitHub
dcf87a83 87eafac6

+776 -730
-54
.github/workflows/ci-nativelibs.yml
··· 1 - on: workflow_dispatch 2 - name: Continuous Integration - NativeLibs 3 - 4 - jobs: 5 - check-if-tag: 6 - name: Set Package Version 7 - runs-on: ubuntu-latest 8 - outputs: 9 - version: ${{steps.deployment.outputs.version}} 10 - steps: 11 - - name: Set Variables 12 - id: deployment 13 - shell: bash 14 - run: | 15 - if [ $(echo ${{github.ref}} | grep -q "refs/tags/"; echo $?) == 0 ]; then 16 - echo "::set-output name=VERSION::${GITHUB_REF#/refs\/tags\//}" 17 - else 18 - echo "::set-output name=VERSION::0.0.0+${{github.run_id}}" 19 - fi 20 - deploy: 21 - name: Deploy 22 - runs-on: ubuntu-latest 23 - needs: check-if-tag 24 - steps: 25 - - name: Checkout 26 - uses: actions/checkout@v2 27 - 28 - - name: Set Artifacts Directory 29 - id: artifactsPath 30 - run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}/artifacts" 31 - 32 - - name: Setup .NET 5.0.x 33 - uses: actions/setup-dotnet@v1 34 - with: 35 - dotnet-version: "5.0.x" 36 - 37 - - name: Build NativeLibs 38 - 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}} 39 - 40 - - name: Upload Artifacts 41 - uses: actions/upload-artifact@v2 42 - with: 43 - name: osu-framework-nativelibs 44 - path: ${{steps.artifactsPath.outputs.nuget_artifacts}}/*.nupkg 45 - 46 - - name: Deploy 47 - run: | 48 - if [ $(echo ${{needs.check-if-tag.outputs.version}} | grep -q "0.0.0+${{github.run_id}}"; echo $?) == 0 ]; then 49 - echo "Skipping publish, no tag detected." 50 - exit 0; 51 - else 52 - dotnet nuget add source https://api.nuget.org/v3/index.json -n authed-nuget -u ${{secrets.NUGET_USER_NAME}} -p ${{secrets.NUGET_AUTH_TOKEN}} 53 - dotnet nuget push ${{github.workspace}}/artifacts/*.nupkg --skip-duplicate --source authed-nuget 54 - fi
+25 -169
.github/workflows/ci.yml
··· 2 2 name: Continuous Integration 3 3 4 4 jobs: 5 - check-if-tag: 6 - name: Set Package Version 7 - runs-on: ubuntu-latest 8 - outputs: 9 - version: ${{steps.deployment.outputs.version}} 10 - steps: 11 - - name: Set Variables 12 - id: deployment 13 - shell: bash 14 - run: | 15 - if [ $(echo ${{github.ref}} | grep -q "refs/tags/"; echo $?) == 0 ]; then 16 - echo "::set-output name=VERSION::${GITHUB_REF#/refs\/tags\//}" 17 - else 18 - echo "::set-output name=VERSION::0.0.0+${{github.run_id}}" 19 - fi 20 5 test: 21 6 name: Test 22 - runs-on: windows-latest 7 + runs-on: ${{matrix.os.fullname}} 8 + env: 9 + OSU_EXECUTION_MODE: ${{matrix.threadingMode}} 10 + strategy: 11 + fail-fast: false 12 + matrix: 13 + os: 14 + - { prettyname: Windows, fullname: windows-latest } 15 + - { prettyname: macOS, fullname: macos-latest } 16 + - { prettyname: Linux, fullname: ubuntu-latest } 17 + threadingMode: ['SingleThread', 'MultiThreaded'] 23 18 steps: 24 19 - name: Checkout 25 20 uses: actions/checkout@v2 ··· 29 24 with: 30 25 dotnet-version: "5.0.x" 31 26 27 + # FIXME: libavformat is not included in Ubuntu. Let's fix that. 28 + # https://github.com/ppy/osu-framework/issues/4349 29 + # Remove this once https://github.com/actions/virtual-environments/issues/3306 has been resolved. 30 + - name: Install libavformat-dev 31 + if: ${{matrix.os.fullname == 'ubuntu-latest'}} 32 + run: | 33 + sudo apt-get update && \ 34 + sudo apt-get -y install libavformat-dev 35 + 32 36 - name: Compile 33 - run: dotnet build -c Debug build/Desktop.proj 37 + run: dotnet build -c Debug -warnaserror build/Desktop.proj 34 38 35 39 - name: Test 36 - run: dotnet test $pwd/*.Tests/bin/Debug/*/*.Tests.dll --settings $pwd/build/vstestconfig.runsettings --logger "trx;LogFileName=TestResults.trx" 37 - shell: powershell 40 + run: dotnet test $pwd/*.Tests/bin/Debug/*/*.Tests.dll --settings $pwd/build/vstestconfig.runsettings --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" 41 + shell: pwsh 38 42 39 43 # Attempt to upload results even if test fails. 40 44 # https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always ··· 42 46 uses: actions/upload-artifact@v2 43 47 if: ${{ always() }} 44 48 with: 45 - name: osu-framework-test-results 46 - path: ${{github.workspace}}\TestResults\TestResults.trx 49 + name: osu-framework-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} 50 + path: ${{github.workspace}}/TestResults/TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx 47 51 48 52 inspect-code: 49 53 name: Code Quality ··· 90 94 uses: glassechidna/resharper-action@master 91 95 with: 92 96 report: ${{github.workspace}}/inspectcodereport.xml 93 - 94 - pack-framework: 95 - name: Pack (Framework) 96 - runs-on: windows-latest 97 - needs: [test, inspect-code, check-if-tag] 98 - defaults: 99 - run: 100 - shell: powershell 101 - steps: 102 - - name: Checkout 103 - uses: actions/checkout@v2 104 - 105 - - name: Set Artifacts Directory 106 - id: artifactsPath 107 - run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}\artifacts" 108 - 109 - # FIXME: 3.1 LTS is required here because iOS builds refuse to build without it. 110 - # https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e 111 - - name: Install .NET 3.1.x LTS 112 - uses: actions/setup-dotnet@v1 113 - with: 114 - dotnet-version: "3.1.x" 115 - 116 - - name: Install .NET 5.0.x 117 - uses: actions/setup-dotnet@v1 118 - with: 119 - dotnet-version: "5.0.x" 120 - 121 - - name: Pack (Framework) 122 - run: dotnet pack -c Release osu.Framework /p:Version=${{needs.check-if-tag.outputs.version}} /p:GenerateDocumentationFile=true -o ${{steps.artifactsPath.outputs.nuget_artifacts}} 123 - 124 - - name: Upload Artifacts 125 - uses: actions/upload-artifact@v2 126 - with: 127 - name: osu-framework 128 - path: ${{steps.artifactsPath.outputs.nuget_artifacts}}\*.nupkg 129 - 130 - pack-template: 131 - name: Pack (Templates) 132 - runs-on: windows-latest 133 - needs: [test, inspect-code, check-if-tag] 134 - defaults: 135 - run: 136 - shell: powershell 137 - steps: 138 - - name: Checkout 139 - uses: actions/checkout@v2 140 - 141 - - name: Set Artifacts Directory 142 - id: artifactsPath 143 - run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}\artifacts" 144 - 145 - - name: Install .NET 5.0.x 146 - uses: actions/setup-dotnet@v1 147 - with: 148 - dotnet-version: "5.0.x" 149 - 150 - - name: Pack (Template) 151 - 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}} 152 - 153 - - name: Upload Artifacts 154 - uses: actions/upload-artifact@v2 155 - with: 156 - name: osu-framework-templates 157 - path: ${{steps.artifactsPath.outputs.nuget_artifacts}}\*.nupkg 158 - 159 - pack-android: 160 - name: Pack (Android) 161 - runs-on: windows-latest 162 - needs: [test, inspect-code, check-if-tag] 163 - defaults: 164 - run: 165 - shell: powershell 166 - steps: 167 - - name: Checkout 168 - uses: actions/checkout@v2 169 - 170 - - name: Set Artifacts Directory 171 - id: artifactsPath 172 - run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}\artifacts" 173 - 174 - - name: Add msbuild to PATH 175 - uses: microsoft/setup-msbuild@v1.0.2 176 - 177 - - name: Pack (Android Framework) 178 - run: msbuild -bl:msbuildlog.binlog -v:m -target:Pack -r osu.Framework.Android/osu.Framework.Android.csproj -p:Configuration=Release -p:Version=${{needs.check-if-tag.outputs.version}} -p:PackageOutputPath=${{steps.artifactsPath.outputs.nuget_artifacts}} 179 - 180 - - name: Upload Artifacts 181 - uses: actions/upload-artifact@v2 182 - with: 183 - name: osu-framework-android 184 - path: ${{steps.artifactsPath.outputs.nuget_artifacts}}\*.nupkg 185 - 186 - pack-ios: 187 - name: Pack (iOS) 188 - runs-on: macos-latest 189 - needs: [test, inspect-code, check-if-tag] 190 - defaults: 191 - run: 192 - shell: bash 193 - steps: 194 - - name: Checkout 195 - uses: actions/checkout@v2 196 - 197 - - name: Set Artifacts Directory 198 - id: artifactsPath 199 - run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}/artifacts" 200 - 201 - - name: Pack (iOS Framework) 202 - run: msbuild -bl:msbuildlog.binlog -v:m -target:Pack -r osu.Framework.iOS/osu.Framework.iOS.csproj -p:Configuration=Release -p:Version=${{needs.check-if-tag.outputs.version}} -p:PackageOutputPath=${{steps.artifactsPath.outputs.nuget_artifacts}} 203 - 204 - - name: Upload Artifacts 205 - uses: actions/upload-artifact@v2 206 - with: 207 - name: osu-framework-ios 208 - path: ${{steps.artifactsPath.outputs.nuget_artifacts}}/*.nupkg 209 - 210 - release: 211 - name: Release 212 - runs-on: ubuntu-latest 213 - needs: [check-if-tag, pack-android, pack-framework, pack-template, pack-ios] 214 - if: ${{ github.event != 'pull_request' }} 215 - steps: 216 - - name: Create Artifact Directory 217 - run: mkdir ${{github.workspace}}/artifacts/ 218 - 219 - - name: Download Artifacts 220 - uses: actions/download-artifact@v2 221 - with: 222 - path: ${{github.workspace}}/artifacts/ 223 - 224 - # Artifacts create their own directories. Let's fix that! 225 - # https://github.com/actions/download-artifact#download-all-artifacts 226 - - name: Move Artifacts to root of subdirectory 227 - working-directory: ${{github.workspace}}/artifacts/ 228 - run: | 229 - mv -v **/*.nupkg $(pwd) 230 - rm -rfv */ 231 - 232 - - name: Deploy 233 - run: | 234 - if [ $(echo ${{needs.check-if-tag.outputs.version}} | grep -q "0.0.0+${{github.run_id}}"; echo $?) == 0 ]; then 235 - echo "Skipping publish, no tag detected." 236 - exit 0; 237 - else 238 - dotnet nuget add source https://api.nuget.org/v3/index.json -n authed-nuget -u ${{secrets.NUGET_USER_NAME}} -p ${{secrets.NUGET_AUTH_TOKEN}} 239 - dotnet nuget push ${{github.workspace}}/artifacts/*.nupkg --skip-duplicate --source authed-nuget 240 - fi
+66
.github/workflows/deploy-nativelibs.yml
··· 1 + # There is no manual way to call this out to run this on tags via UI. 2 + # See: https://github.community/t/workflow-dispatch-from-a-tag-in-actions-tab/130561 3 + on: workflow_dispatch 4 + name: Deploy - NativeLibs 5 + 6 + jobs: 7 + check-if-tag: 8 + name: Set Package Version 9 + runs-on: ubuntu-latest 10 + outputs: 11 + version: ${{steps.deployment.outputs.version}} 12 + steps: 13 + - name: Checkout 14 + run: | 15 + REPOSITORY="https://${{ github.actor }}:${{ github.token }}@github.com/${{ github.repository }}.git" 16 + BRANCH="${GITHUB_REF/#refs\/heads\//}" 17 + 18 + git version 19 + git clone --no-checkout ${REPOSITORY} . 20 + git config --local gc.auto 0 21 + 22 + git -c protocol.version=2 fetch --no-tags --prune --progress --depth=2 origin +${GITHUB_SHA}:refs/remotes/origin/${BRANCH} 23 + git checkout --progress --force -B $BRANCH refs/remotes/origin/$BRANCH 24 + 25 + 26 + - name: Set Variables 27 + id: deployment 28 + shell: bash 29 + run: | 30 + if [ $(git describe --exact-match --tags HEAD &> /dev/null; echo $?) == 0 ]; then 31 + echo "::set-output name=VERSION::$(git describe --exact-match --tags HEAD)" 32 + else 33 + echo "fatal: no tag detected for HEAD. Workflow will now stop." 34 + exit 128; 35 + fi 36 + 37 + deploy: 38 + name: Deploy 39 + runs-on: ubuntu-latest 40 + needs: check-if-tag 41 + steps: 42 + - name: Checkout 43 + uses: actions/checkout@v2 44 + 45 + - name: Set Artifacts Directory 46 + id: artifactsPath 47 + run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}/artifacts" 48 + 49 + - name: Setup .NET 5.0.x 50 + uses: actions/setup-dotnet@v1 51 + with: 52 + dotnet-version: "5.0.x" 53 + 54 + - name: Build NativeLibs 55 + 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}} 56 + 57 + - name: Upload Artifacts 58 + uses: actions/upload-artifact@v2 59 + with: 60 + name: osu-framework-nativelibs 61 + path: ${{steps.artifactsPath.outputs.nuget_artifacts}}/*.nupkg 62 + 63 + - name: Deploy 64 + run: | 65 + dotnet nuget add source https://api.nuget.org/v3/index.json -n authed-nuget -u ${{secrets.NUGET_USER_NAME}} -p ${{secrets.NUGET_AUTH_TOKEN}} 66 + dotnet nuget push ${{github.workspace}}/artifacts/*.nupkg --skip-duplicate --source authed-nuget
+177
.github/workflows/deploy-pack.yml
··· 1 + # There is no manual way to call this out to run this on tags via UI. 2 + # See: https://github.community/t/workflow-dispatch-from-a-tag-in-actions-tab/130561 3 + on: workflow_dispatch 4 + name: Deploy - Framework 5 + 6 + jobs: 7 + check-if-tag: 8 + name: Set Package Version 9 + runs-on: ubuntu-latest 10 + outputs: 11 + version: ${{steps.deployment.outputs.version}} 12 + steps: 13 + - name: Checkout 14 + run: | 15 + REPOSITORY="https://${{ github.actor }}:${{ github.token }}@github.com/${{ github.repository }}.git" 16 + BRANCH="${GITHUB_REF/#refs\/heads\//}" 17 + 18 + git version 19 + git clone --no-checkout ${REPOSITORY} . 20 + git config --local gc.auto 0 21 + 22 + git -c protocol.version=2 fetch --no-tags --prune --progress --depth=2 origin +${GITHUB_SHA}:refs/remotes/origin/${BRANCH} 23 + git checkout --progress --force -B $BRANCH refs/remotes/origin/$BRANCH 24 + 25 + 26 + - name: Set Variables 27 + id: deployment 28 + shell: bash 29 + run: | 30 + if [ $(git describe --exact-match --tags HEAD &> /dev/null; echo $?) == 0 ]; then 31 + echo "::set-output name=VERSION::$(git describe --exact-match --tags HEAD)" 32 + else 33 + echo "fatal: no tag detected for HEAD. Workflow will now stop." 34 + exit 128; 35 + fi 36 + 37 + pack-framework: 38 + name: Pack (Framework) 39 + runs-on: windows-latest 40 + needs: [check-if-tag] 41 + defaults: 42 + run: 43 + shell: powershell 44 + steps: 45 + - name: Checkout 46 + uses: actions/checkout@v2 47 + 48 + - name: Set Artifacts Directory 49 + id: artifactsPath 50 + run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}\artifacts" 51 + 52 + # FIXME: 3.1 LTS is required here because iOS builds refuse to build without it. 53 + # https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e 54 + - name: Install .NET 3.1.x LTS 55 + uses: actions/setup-dotnet@v1 56 + with: 57 + dotnet-version: "3.1.x" 58 + 59 + - name: Install .NET 5.0.x 60 + uses: actions/setup-dotnet@v1 61 + with: 62 + dotnet-version: "5.0.x" 63 + 64 + - name: Pack (Framework) 65 + run: dotnet pack -c Release osu.Framework /p:Version=${{needs.check-if-tag.outputs.version}} /p:GenerateDocumentationFile=true -o ${{steps.artifactsPath.outputs.nuget_artifacts}} 66 + 67 + - name: Upload Artifacts 68 + uses: actions/upload-artifact@v2 69 + with: 70 + name: osu-framework 71 + path: ${{steps.artifactsPath.outputs.nuget_artifacts}}\*.nupkg 72 + 73 + pack-template: 74 + name: Pack (Templates) 75 + runs-on: windows-latest 76 + needs: [check-if-tag] 77 + defaults: 78 + run: 79 + shell: powershell 80 + steps: 81 + - name: Checkout 82 + uses: actions/checkout@v2 83 + 84 + - name: Set Artifacts Directory 85 + id: artifactsPath 86 + run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}\artifacts" 87 + 88 + - name: Install .NET 5.0.x 89 + uses: actions/setup-dotnet@v1 90 + with: 91 + dotnet-version: "5.0.x" 92 + 93 + - name: Pack (Template) 94 + 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}} 95 + 96 + - name: Upload Artifacts 97 + uses: actions/upload-artifact@v2 98 + with: 99 + name: osu-framework-templates 100 + path: ${{steps.artifactsPath.outputs.nuget_artifacts}}\*.nupkg 101 + 102 + pack-android: 103 + name: Pack (Android) 104 + runs-on: windows-latest 105 + needs: [check-if-tag] 106 + defaults: 107 + run: 108 + shell: powershell 109 + steps: 110 + - name: Checkout 111 + uses: actions/checkout@v2 112 + 113 + - name: Set Artifacts Directory 114 + id: artifactsPath 115 + run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}\artifacts" 116 + 117 + - name: Add msbuild to PATH 118 + uses: microsoft/setup-msbuild@v1.0.2 119 + 120 + - name: Pack (Android Framework) 121 + run: msbuild -bl:msbuildlog.binlog -v:m -target:Pack -r osu.Framework.Android/osu.Framework.Android.csproj -p:Configuration=Release -p:Version=${{needs.check-if-tag.outputs.version}} -p:PackageOutputPath=${{steps.artifactsPath.outputs.nuget_artifacts}} 122 + 123 + - name: Upload Artifacts 124 + uses: actions/upload-artifact@v2 125 + with: 126 + name: osu-framework-android 127 + path: ${{steps.artifactsPath.outputs.nuget_artifacts}}\*.nupkg 128 + 129 + pack-ios: 130 + name: Pack (iOS) 131 + runs-on: macos-latest 132 + needs: [check-if-tag] 133 + defaults: 134 + run: 135 + shell: bash 136 + steps: 137 + - name: Checkout 138 + uses: actions/checkout@v2 139 + 140 + - name: Set Artifacts Directory 141 + id: artifactsPath 142 + run: echo "::set-output name=NUGET_ARTIFACTS::${{github.workspace}}/artifacts" 143 + 144 + - name: Pack (iOS Framework) 145 + run: msbuild -bl:msbuildlog.binlog -v:m -target:Pack -r osu.Framework.iOS/osu.Framework.iOS.csproj -p:Configuration=Release -p:Version=${{needs.check-if-tag.outputs.version}} -p:PackageOutputPath=${{steps.artifactsPath.outputs.nuget_artifacts}} 146 + 147 + - name: Upload Artifacts 148 + uses: actions/upload-artifact@v2 149 + with: 150 + name: osu-framework-ios 151 + path: ${{steps.artifactsPath.outputs.nuget_artifacts}}/*.nupkg 152 + 153 + release: 154 + name: Release 155 + runs-on: ubuntu-latest 156 + needs: [check-if-tag, pack-android, pack-framework, pack-template, pack-ios] 157 + steps: 158 + - name: Create Artifact Directory 159 + run: mkdir ${{github.workspace}}/artifacts/ 160 + 161 + - name: Download Artifacts 162 + uses: actions/download-artifact@v2 163 + with: 164 + path: ${{github.workspace}}/artifacts/ 165 + 166 + # Artifacts create their own directories. Let's fix that! 167 + # https://github.com/actions/download-artifact#download-all-artifacts 168 + - name: Move Artifacts to root of subdirectory 169 + working-directory: ${{github.workspace}}/artifacts/ 170 + run: | 171 + mv -v **/*.nupkg $(pwd) 172 + rm -rfv */ 173 + 174 + - name: Deploy 175 + run: | 176 + dotnet nuget add source https://api.nuget.org/v3/index.json -n authed-nuget -u ${{secrets.NUGET_USER_NAME}} -p ${{secrets.NUGET_AUTH_TOKEN}} 177 + dotnet nuget push ${{github.workspace}}/artifacts/*.nupkg --skip-duplicate --source authed-nuget
+10 -2
.github/workflows/report-nunit.yml
··· 12 12 report: 13 13 runs-on: ubuntu-latest 14 14 if: ${{ github.event.workflow_run.conclusion != 'cancelled' }} 15 + strategy: 16 + fail-fast: false 17 + matrix: 18 + os: 19 + - { prettyname: Windows } 20 + - { prettyname: macOS } 21 + - { prettyname: Linux } 22 + threadingMode: ['SingleThread', 'MultiThreaded'] 15 23 steps: 16 24 - name: Continuous Integration Test Report 17 25 uses: dorny/test-reporter@v1.4.2 18 26 with: 19 - artifact: osu-framework-test-results 20 - name: Test Results 27 + artifact: osu-framework-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} 28 + name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}}) 21 29 path: "*.trx" 22 30 reporter: dotnet-trx
-110
osu.Framework.Tests/Bindables/BindableNumberTest.cs
··· 110 110 Assert.That(bindable1ValueChange, Is.EqualTo(4)); 111 111 } 112 112 113 - /// <summary> 114 - /// Tests that the value of a bindable is updated when the maximum value is changed. 115 - /// </summary> 116 - [Test] 117 - public void TestValueUpdatedOnMaxValueChange() 118 - { 119 - var bindable = new BindableInt(2) 120 - { 121 - MaxValue = 1 122 - }; 123 - 124 - Assert.That(bindable.Value, Is.EqualTo(1)); 125 - } 126 - 127 - /// <summary> 128 - /// Tests that the order of maximum value vs value changed events follows the order: 129 - /// MaxValue (bound) -> MaxValue -> Value (bound) -> Value 130 - /// </summary> 131 - [Test] 132 - public void TestValueChangeEventOccursAfterMaxValueChangeEvent() 133 - { 134 - var bindable1 = new BindableInt(2); 135 - var bindable2 = new BindableInt { BindTarget = bindable1 }; 136 - int counter = 0, bindable1ValueChange = 0, bindable1MaxChange = 0, bindable2ValueChange = 0, bindable2MaxChange = 0; 137 - bindable1.ValueChanged += _ => bindable1ValueChange = ++counter; 138 - bindable1.MaxValueChanged += _ => bindable1MaxChange = ++counter; 139 - bindable2.ValueChanged += _ => bindable2ValueChange = ++counter; 140 - bindable2.MaxValueChanged += _ => bindable2MaxChange = ++counter; 141 - 142 - bindable1.MaxValue = 1; 143 - 144 - Assert.That(bindable2MaxChange, Is.EqualTo(1)); 145 - Assert.That(bindable1MaxChange, Is.EqualTo(2)); 146 - Assert.That(bindable2ValueChange, Is.EqualTo(3)); 147 - Assert.That(bindable1ValueChange, Is.EqualTo(4)); 148 - } 149 - 150 - [Test] 151 - public void TestDefaultMaxValueAppliedInConstructor() 152 - { 153 - var bindable = new BindableNumberWithDefaultMaxValue(2); 154 - 155 - Assert.That(bindable.Value, Is.EqualTo(1)); 156 - } 157 - 158 - /// <summary> 159 - /// Tests that the value of a bindable is updated when the minimum value is changed. 160 - /// </summary> 161 - [Test] 162 - public void TestValueUpdatedOnMinValueChange() 163 - { 164 - var bindable = new BindableInt(2) 165 - { 166 - MinValue = 3 167 - }; 168 - 169 - Assert.That(bindable.Value, Is.EqualTo(3)); 170 - } 171 - 172 - /// <summary> 173 - /// Tests that the order of minimum value vs value changed events follows the order: 174 - /// MinValue (bound) -> MinValue -> Value (bound) -> Value 175 - /// </summary> 176 - [Test] 177 - public void TestValueChangeEventOccursAfterMinValueChangeEvent() 178 - { 179 - var bindable1 = new BindableInt(2); 180 - var bindable2 = new BindableInt { BindTarget = bindable1 }; 181 - int counter = 0, bindable1ValueChange = 0, bindable1MinChange = 0, bindable2ValueChange = 0, bindable2MinChange = 0; 182 - bindable1.ValueChanged += _ => bindable1ValueChange = ++counter; 183 - bindable1.MinValueChanged += _ => bindable1MinChange = ++counter; 184 - bindable2.ValueChanged += _ => bindable2ValueChange = ++counter; 185 - bindable2.MinValueChanged += _ => bindable2MinChange = ++counter; 186 - 187 - bindable1.MinValue = 3; 188 - 189 - Assert.That(bindable2MinChange, Is.EqualTo(1)); 190 - Assert.That(bindable1MinChange, Is.EqualTo(2)); 191 - Assert.That(bindable2ValueChange, Is.EqualTo(3)); 192 - Assert.That(bindable1ValueChange, Is.EqualTo(4)); 193 - } 194 - 195 - [Test] 196 - public void TestDefaultMinValueAppliedInConstructor() 197 - { 198 - var bindable = new BindableNumberWithDefaultMinValue(2); 199 - 200 - Assert.That(bindable.Value, Is.EqualTo(3)); 201 - } 202 - 203 113 private object createBindable(Type type) => Activator.CreateInstance(typeof(BindableNumber<>).MakeGenericType(type), Convert.ChangeType(0, type)); 204 - 205 - private class BindableNumberWithDefaultMaxValue : BindableInt 206 - { 207 - public BindableNumberWithDefaultMaxValue(int value = 0) 208 - : base(value) 209 - { 210 - } 211 - 212 - protected override int DefaultMaxValue => 1; 213 - } 214 - 215 - private class BindableNumberWithDefaultMinValue : BindableInt 216 - { 217 - public BindableNumberWithDefaultMinValue(int value = 0) 218 - : base(value) 219 - { 220 - } 221 - 222 - protected override int DefaultMinValue => 3; 223 - } 224 114 } 225 115 }
+122
osu.Framework.Tests/Bindables/RangeConstrainedBindableTest.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using NUnit.Framework; 5 + using osu.Framework.Bindables; 6 + 7 + namespace osu.Framework.Tests.Bindables 8 + { 9 + [TestFixture] 10 + public class RangeConstrainedBindableTest 11 + { 12 + /// <summary> 13 + /// Tests that the value of a bindable is updated when the maximum value is changed. 14 + /// </summary> 15 + [Test] 16 + public void TestValueUpdatedOnMaxValueChange() 17 + { 18 + var bindable = new BindableInt(2) 19 + { 20 + MaxValue = 1 21 + }; 22 + 23 + Assert.That(bindable.Value, Is.EqualTo(1)); 24 + } 25 + 26 + /// <summary> 27 + /// Tests that the order of maximum value vs value changed events follows the order: 28 + /// MaxValue (bound) -> MaxValue -> Value (bound) -> Value 29 + /// </summary> 30 + [Test] 31 + public void TestValueChangeEventOccursAfterMaxValueChangeEvent() 32 + { 33 + var bindable1 = new BindableInt(2); 34 + var bindable2 = new BindableInt { BindTarget = bindable1 }; 35 + int counter = 0, bindable1ValueChange = 0, bindable1MaxChange = 0, bindable2ValueChange = 0, bindable2MaxChange = 0; 36 + bindable1.ValueChanged += _ => bindable1ValueChange = ++counter; 37 + bindable1.MaxValueChanged += _ => bindable1MaxChange = ++counter; 38 + bindable2.ValueChanged += _ => bindable2ValueChange = ++counter; 39 + bindable2.MaxValueChanged += _ => bindable2MaxChange = ++counter; 40 + 41 + bindable1.MaxValue = 1; 42 + 43 + Assert.That(bindable2MaxChange, Is.EqualTo(1)); 44 + Assert.That(bindable1MaxChange, Is.EqualTo(2)); 45 + Assert.That(bindable2ValueChange, Is.EqualTo(3)); 46 + Assert.That(bindable1ValueChange, Is.EqualTo(4)); 47 + } 48 + 49 + [Test] 50 + public void TestDefaultMaxValueAppliedInConstructor() 51 + { 52 + var bindable = new BindableNumberWithDefaultMaxValue(2); 53 + 54 + Assert.That(bindable.Value, Is.EqualTo(1)); 55 + } 56 + 57 + /// <summary> 58 + /// Tests that the value of a bindable is updated when the minimum value is changed. 59 + /// </summary> 60 + [Test] 61 + public void TestValueUpdatedOnMinValueChange() 62 + { 63 + var bindable = new BindableInt(2) 64 + { 65 + MinValue = 3 66 + }; 67 + 68 + Assert.That(bindable.Value, Is.EqualTo(3)); 69 + } 70 + 71 + /// <summary> 72 + /// Tests that the order of minimum value vs value changed events follows the order: 73 + /// MinValue (bound) -> MinValue -> Value (bound) -> Value 74 + /// </summary> 75 + [Test] 76 + public void TestValueChangeEventOccursAfterMinValueChangeEvent() 77 + { 78 + var bindable1 = new BindableInt(2); 79 + var bindable2 = new BindableInt { BindTarget = bindable1 }; 80 + int counter = 0, bindable1ValueChange = 0, bindable1MinChange = 0, bindable2ValueChange = 0, bindable2MinChange = 0; 81 + bindable1.ValueChanged += _ => bindable1ValueChange = ++counter; 82 + bindable1.MinValueChanged += _ => bindable1MinChange = ++counter; 83 + bindable2.ValueChanged += _ => bindable2ValueChange = ++counter; 84 + bindable2.MinValueChanged += _ => bindable2MinChange = ++counter; 85 + 86 + bindable1.MinValue = 3; 87 + 88 + Assert.That(bindable2MinChange, Is.EqualTo(1)); 89 + Assert.That(bindable1MinChange, Is.EqualTo(2)); 90 + Assert.That(bindable2ValueChange, Is.EqualTo(3)); 91 + Assert.That(bindable1ValueChange, Is.EqualTo(4)); 92 + } 93 + 94 + [Test] 95 + public void TestDefaultMinValueAppliedInConstructor() 96 + { 97 + var bindable = new BindableNumberWithDefaultMinValue(2); 98 + 99 + Assert.That(bindable.Value, Is.EqualTo(3)); 100 + } 101 + 102 + private class BindableNumberWithDefaultMaxValue : BindableInt 103 + { 104 + public BindableNumberWithDefaultMaxValue(int value = 0) 105 + : base(value) 106 + { 107 + } 108 + 109 + protected override int DefaultMaxValue => 1; 110 + } 111 + 112 + private class BindableNumberWithDefaultMinValue : BindableInt 113 + { 114 + public BindableNumberWithDefaultMinValue(int value = 0) 115 + : base(value) 116 + { 117 + } 118 + 119 + protected override int DefaultMinValue => 3; 120 + } 121 + } 122 + }
+6 -4
osu.Framework.Tests/Platform/GameHostSuspendTest.cs
··· 41 41 42 42 host.Suspend(); 43 43 44 - completed.Reset(); 44 + // in single-threaded execution, the main thread may already be in the process of updating one last time. 45 + int gameUpdates = 0; 46 + game.Scheduler.AddDelayed(() => ++gameUpdates, 0, true); 47 + Assert.That(() => gameUpdates, Is.LessThan(2).After(timeout / 10)); 45 48 46 49 // check that scheduler doesn't process while suspended.. 50 + completed.Reset(); 47 51 game.Schedule(() => completed.Set()); 48 52 Assert.IsFalse(completed.Wait(timeout / 10)); 49 53 54 + // ..and does after resume. 50 55 host.Resume(); 51 - 52 - // ..and does after resume. 53 56 Assert.IsTrue(completed.Wait(timeout / 10)); 54 57 55 58 game.Exit(); 56 - 57 59 Assert.IsTrue(task.Wait(timeout)); 58 60 } 59 61
+17 -8
osu.Framework.Tests/Visual/Audio/TestSceneSampleChannels.cs
··· 5 5 using NUnit.Framework; 6 6 using osu.Framework.Allocation; 7 7 using osu.Framework.Audio.Sample; 8 + using osu.Framework.Platform; 9 + using osu.Framework.Threading; 8 10 9 11 namespace osu.Framework.Tests.Visual.Audio 10 12 { ··· 12 14 { 13 15 [Resolved] 14 16 private ISampleStore sampleStore { get; set; } 17 + 18 + [Resolved] 19 + private GameHost host { get; set; } 15 20 16 21 private Sample sample; 17 22 ··· 36 41 AddUntilStep("wait for channel 1 to end", () => !channel.Playing); 37 42 AddStep("play channel 1 again", () => channel.Play()); 38 43 39 - // Create another channel purely for tracking purposes in order to avoid timing issues. 40 - // When this channel is playing, the audio thread is guaranteed to have processed the above channel. 41 - SampleChannel channel2 = null; 42 - AddStep("play a silent channel 2", () => 44 + int audioFrames = 0; 45 + AddStep("begin tracking audio frames", () => 43 46 { 44 - channel2 = sample.GetChannel(); 45 - channel2.Volume.Value = 0; 46 - channel2.Play(); 47 + audioFrames = 0; 48 + 49 + ScheduledDelegate del = null; 50 + del = host.AudioThread.Scheduler.AddDelayed(() => 51 + { 52 + // ReSharper disable once AccessToModifiedClosure 53 + if (++audioFrames >= 2) 54 + del?.Cancel(); 55 + }, 0, true); 47 56 }); 48 57 49 - AddUntilStep("channel 2 playing", () => channel2.Playing); 58 + AddUntilStep("wait for two audio frames", () => audioFrames >= 2); 50 59 AddAssert("channel 1 not playing", () => !channel.Playing); 51 60 } 52 61
-71
osu.Framework.Tests/Visual/Drawables/TestSceneDelayedLoadWrapper.cs
··· 97 97 AddUntilStep("repeating schedulers removed", () => !scroll.Scheduler.HasPendingTasks); 98 98 } 99 99 100 - [Test] 101 - public void TestLifetimeOutwardsPropagation() 102 - { 103 - Container wrapped = null; 104 - DelayedLoadWrapper wrapper = null; 105 - 106 - AddStep("create child", () => 107 - { 108 - flow.Add(new Container 109 - { 110 - Size = new Vector2(128), 111 - Children = new Drawable[] 112 - { 113 - wrapper = new DelayedLoadWrapper(wrapped = new Container 114 - { 115 - RelativeSizeAxes = Axes.Both, 116 - Children = new Drawable[] 117 - { 118 - new TestBox(() => loaded++) { RelativeSizeAxes = Axes.Both } 119 - } 120 - }), 121 - } 122 - }); 123 - 124 - wrapper.DelayedLoadComplete += content => content.FadeOut(500).Expire(); 125 - }); 126 - 127 - AddAssert("wrapper lifetime equals content lifetime", () => wrapper.LifetimeEnd == wrapped.LifetimeEnd); 128 - 129 - AddUntilStep("wrapper expired", () => !wrapper.IsAlive); 130 - } 131 - 132 - [Test] 133 - public void TestLifetimeInwardsPropagation() 134 - { 135 - Container wrapped = null; 136 - DelayedLoadWrapper wrapper = null; 137 - double lifetimeEnd = 0; 138 - 139 - AddStep("create child", () => 140 - { 141 - flow.Add(new Container 142 - { 143 - Size = new Vector2(128), 144 - Children = new Drawable[] 145 - { 146 - wrapper = new DelayedLoadWrapper(wrapped = new Container 147 - { 148 - // this lifetime will be overwritten by the one on the wrapper. 149 - LifetimeEnd = Time.Current + 4000, 150 - RelativeSizeAxes = Axes.Both, 151 - Children = new Drawable[] 152 - { 153 - new TestBox(() => loaded++) { RelativeSizeAxes = Axes.Both } 154 - } 155 - }) 156 - { 157 - LifetimeEnd = lifetimeEnd = Time.Current + 2000, 158 - } 159 - } 160 - }); 161 - }); 162 - 163 - AddUntilStep("wait for load", () => wrapper.DelayedLoadCompleted); 164 - 165 - AddAssert("wrapper lifetime is correct", () => wrapper.LifetimeEnd == lifetimeEnd); 166 - AddAssert("content lifetime is correct", () => wrapped.LifetimeEnd == lifetimeEnd); 167 - 168 - AddUntilStep("wrapper expired", () => !wrapper.IsAlive); 169 - } 170 - 171 100 [TestCase(false)] 172 101 [TestCase(true)] 173 102 public void TestManyChildrenFunction(bool instant)
+52 -16
osu.Framework.Tests/Visual/UserInterface/TestSceneDropdown.cs
··· 23 23 private float calculatedHeight; 24 24 private readonly TestDropdown testDropdown, testDropdownMenu, bindableDropdown, emptyDropdown, disabledDropdown; 25 25 private readonly PlatformActionContainer platformActionContainerKeyboardSelection, platformActionContainerKeyboardPreselection, platformActionContainerEmptyDropdown; 26 - private readonly BindableList<string> bindableList = new BindableList<string>(); 26 + private readonly BindableList<TestModel> bindableList = new BindableList<TestModel>(); 27 27 28 28 private int previousIndex; 29 29 private int lastVisibleIndexOnTheCurrentPage, lastVisibleIndexOnTheNextPage; ··· 31 31 32 32 public TestSceneDropdown() 33 33 { 34 - var testItems = new string[10]; 34 + var testItems = new TestModel[10]; 35 35 int i = 0; 36 36 while (i < items_to_add) 37 37 testItems[i] = @"test " + i++; ··· 87 87 } 88 88 89 89 [Test] 90 + public void TestExternalBindableChangeKeepsSelection() 91 + { 92 + toggleDropdownViaClick(testDropdown, "dropdown1"); 93 + AddStep("click item 4", () => testDropdown.SelectItem(testDropdown.Menu.Items[4])); 94 + 95 + AddAssert("item 4 is selected", () => testDropdown.Current.Value.Identifier == "test 4"); 96 + 97 + AddStep("replace items", () => 98 + { 99 + testDropdown.Items = testDropdown.Items.Select(i => new TestModel(i.ToString())).ToArray(); 100 + }); 101 + 102 + AddAssert("item 4 is selected", () => testDropdown.Current.Value.Identifier == "test 4"); 103 + } 104 + 105 + [Test] 90 106 public void TestBasic() 91 107 { 92 108 var i = items_to_add; ··· 110 126 AddStep("click item 13", () => testDropdown.SelectItem(testDropdown.Menu.Items[13])); 111 127 112 128 AddAssert("dropdown1 is closed", () => testDropdown.Menu.State == MenuState.Closed); 113 - AddAssert("item 13 is selected", () => testDropdown.Current.Value == testDropdown.Items.ElementAt(13)); 129 + AddAssert("item 13 is selected", () => testDropdown.Current.Value.Equals(testDropdown.Items.ElementAt(13))); 114 130 115 131 AddStep("select item 15", () => testDropdown.Current.Value = testDropdown.Items.ElementAt(15)); 116 - AddAssert("item 15 is selected", () => testDropdown.Current.Value == testDropdown.Items.ElementAt(15)); 132 + AddAssert("item 15 is selected", () => testDropdown.Current.Value.Equals(testDropdown.Items.ElementAt(15))); 117 133 118 134 toggleDropdownViaClick(testDropdown, "dropdown1"); 119 135 AddAssert("dropdown1 is open", () => testDropdown.Menu.State == MenuState.Open); ··· 125 141 126 142 AddStep("select 'invalid'", () => testDropdown.Current.Value = "invalid"); 127 143 128 - AddAssert("'invalid' is selected", () => testDropdown.Current.Value == "invalid"); 144 + AddAssert("'invalid' is selected", () => testDropdown.Current.Value.Identifier == "invalid"); 129 145 AddAssert("label shows 'invalid'", () => testDropdown.Header.Label.ToString() == "invalid"); 130 146 131 147 AddStep("select item 2", () => testDropdown.Current.Value = testDropdown.Items.ElementAt(2)); 132 - AddAssert("item 2 is selected", () => testDropdown.Current.Value == testDropdown.Items.ElementAt(2)); 148 + AddAssert("item 2 is selected", () => testDropdown.Current.Value.Equals(testDropdown.Items.ElementAt(2))); 133 149 134 150 AddStep("clear bindable list", () => bindableList.Clear()); 135 151 toggleDropdownViaClick(bindableDropdown, "dropdown3"); 136 152 AddAssert("no elements in bindable dropdown", () => !bindableDropdown.Items.Any()); 137 153 138 - AddStep("add items to bindable", () => bindableList.AddRange(new[] { "one", "two", "three" })); 139 - AddAssert("three items in dropdown", () => bindableDropdown.Items.Count() == 3); 140 - 154 + AddStep("add items to bindable", () => bindableList.AddRange(new[] { "one", "two", "three" }.Select(s => new TestModel(s)))); 141 155 AddStep("select three", () => bindableDropdown.Current.Value = "three"); 142 156 AddStep("remove first item from bindable", () => bindableList.RemoveAt(0)); 143 157 AddAssert("two items in dropdown", () => bindableDropdown.Items.Count() == 2); 144 - AddAssert("current value still three", () => bindableDropdown.Current.Value == "three"); 158 + AddAssert("current value still three", () => bindableDropdown.Current.Value.Identifier == "three"); 145 159 146 160 AddStep("remove three", () => bindableList.Remove("three")); 147 - AddAssert("current value should be two", () => bindableDropdown.Current.Value == "two"); 161 + AddAssert("current value should be two", () => bindableDropdown.Current.Value.Identifier == "two"); 148 162 } 149 163 150 164 private void performKeypress(Drawable drawable, Key key) ··· 295 309 public void TestSelectNull() 296 310 { 297 311 AddStep("select item 1", () => testDropdown.Current.Value = testDropdown.Items.ElementAt(1)); 298 - AddAssert("item 1 is selected", () => testDropdown.Current.Value == testDropdown.Items.ElementAt(1)); 312 + AddAssert("item 1 is selected", () => testDropdown.Current.Value.Equals(testDropdown.Items.ElementAt(1))); 299 313 AddStep("select item null", () => testDropdown.Current.Value = null); 300 314 AddAssert("null is selected", () => testDropdown.Current.Value == null); 301 315 } ··· 303 317 [Test] 304 318 public void TestDisabledCurrent() 305 319 { 306 - string originalValue = null; 320 + TestModel originalValue = null; 307 321 308 322 AddStep("store original value", () => originalValue = disabledDropdown.Current.Value); 309 323 ··· 329 343 AddStep("disable current", () => disabledDropdown.Current.Disabled = true); 330 344 assertDropdownIsClosed(disabledDropdown); 331 345 332 - void valueIsUnchanged() => AddAssert("value is unchanged", () => disabledDropdown.Current.Value == originalValue); 346 + void valueIsUnchanged() => AddAssert("value is unchanged", () => disabledDropdown.Current.Value.Equals(originalValue)); 333 347 } 334 348 335 349 private void toggleDropdownViaClick(TestDropdown dropdown, string dropdownName = null) => AddStep($"click {dropdownName ?? "dropdown"}", () => ··· 342 356 343 357 private void assertDropdownIsClosed(TestDropdown dropdown) => AddAssert("dropdown is closed", () => dropdown.Menu.State == MenuState.Closed); 344 358 345 - private class TestDropdown : BasicDropdown<string> 359 + private class TestModel : IEquatable<TestModel> 360 + { 361 + public readonly string Identifier; 362 + 363 + public TestModel(string identifier) 364 + { 365 + Identifier = identifier; 366 + } 367 + 368 + public bool Equals(TestModel other) 369 + { 370 + if (other == null) 371 + return false; 372 + 373 + return other.Identifier == Identifier; 374 + } 375 + 376 + public override string ToString() => Identifier; 377 + 378 + public static implicit operator TestModel(string str) => new TestModel(str); 379 + } 380 + 381 + private class TestDropdown : BasicDropdown<TestModel> 346 382 { 347 383 public new DropdownMenu Menu => base.Menu; 348 384 ··· 358 394 .TriggerEvent(new ClickEvent(GetContainingInputManager().CurrentState, MouseButton.Left)); 359 395 } 360 396 361 - internal new DropdownMenuItem<string> SelectedItem => base.SelectedItem; 397 + internal new DropdownMenuItem<TestModel> SelectedItem => base.SelectedItem; 362 398 363 399 public int SelectedIndex => Menu.DrawableMenuItems.Select(d => d.Item).ToList().IndexOf(SelectedItem); 364 400 public int PreselectedIndex => Menu.DrawableMenuItems.ToList().IndexOf(Menu.PreselectedItem);
+3
osu.Framework.Tests/Visual/UserInterface/TestSceneMarkdownContainer.cs
··· 9 9 using osu.Framework.Graphics; 10 10 using osu.Framework.Graphics.Containers; 11 11 using osu.Framework.Graphics.Containers.Markdown; 12 + using osu.Framework.Graphics.Sprites; 12 13 using osu.Framework.IO.Network; 13 14 14 15 namespace osu.Framework.Tests.Visual.UserInterface ··· 296 297 { 297 298 UrlAdded = url => Links.Add(url) 298 299 }; 300 + 301 + public override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With("OpenSans", weight: "Regular")); 299 302 300 303 private class TestMarkdownTextFlowContainer : MarkdownTextFlowContainer 301 304 {
-4
osu.Framework/Audio/AudioManager.cs
··· 242 242 if (!device.IsEnabled) 243 243 return false; 244 244 245 - // same device 246 - if (device.IsInitialized && deviceIndex == Bass.CurrentDevice) 247 - return true; 248 - 249 245 // initialize new device 250 246 bool initSuccess = InitBass(deviceIndex); 251 247 if (Bass.LastError != Errors.Already && BassUtils.CheckFaulted(false))
+3 -4
osu.Framework/Audio/Sample/SampleChannelBass.cs
··· 159 159 if (Played && Bass.ChannelIsActive(channel) == PlaybackState.Stopped) 160 160 return; 161 161 162 - if (relativeFrequencyHandler.IsFrequencyZero) 163 - return; 162 + playing = true; 164 163 165 - Bass.ChannelPlay(channel); 166 - playing = true; 164 + if (!relativeFrequencyHandler.IsFrequencyZero) 165 + Bass.ChannelPlay(channel); 167 166 } 168 167 finally 169 168 {
+21 -54
osu.Framework/Bindables/BindableMarginPadding.cs
··· 7 7 8 8 namespace osu.Framework.Bindables 9 9 { 10 - public class BindableMarginPadding : Bindable<MarginPadding> 10 + public class BindableMarginPadding : RangeConstrainedBindable<MarginPadding> 11 11 { 12 - public BindableMarginPadding(MarginPadding value = default) 13 - : base(value) 14 - { 15 - MinValue = DefaultMinValue; 16 - MaxValue = DefaultMaxValue; 17 - } 12 + protected override MarginPadding DefaultMinValue => new MarginPadding(float.MinValue); 13 + protected override MarginPadding DefaultMaxValue => new MarginPadding(float.MaxValue); 18 14 19 - public MarginPadding MinValue { get; set; } 20 - public MarginPadding MaxValue { get; set; } 21 - 22 - protected MarginPadding DefaultMinValue => new MarginPadding(float.MinValue); 23 - protected MarginPadding DefaultMaxValue => new MarginPadding(float.MaxValue); 24 - 25 - public override MarginPadding Value 15 + public BindableMarginPadding(MarginPadding defaultValue = default) 16 + : base(defaultValue) 26 17 { 27 - get => base.Value; 28 - set => base.Value = clamp(value, MinValue, MaxValue); 29 - } 30 - 31 - public override void BindTo(Bindable<MarginPadding> them) 32 - { 33 - if (them is BindableMarginPadding other) 34 - { 35 - MinValue = new MarginPadding 36 - { 37 - Top = Math.Max(MinValue.Top, other.MinValue.Top), 38 - Left = Math.Max(MinValue.Left, other.MinValue.Left), 39 - Bottom = Math.Max(MinValue.Bottom, other.MinValue.Bottom), 40 - Right = Math.Max(MinValue.Right, other.MinValue.Right) 41 - }; 42 - 43 - MaxValue = new MarginPadding 44 - { 45 - Top = Math.Min(MaxValue.Top, other.MaxValue.Top), 46 - Left = Math.Min(MaxValue.Left, other.MaxValue.Left), 47 - Bottom = Math.Min(MaxValue.Bottom, other.MaxValue.Bottom), 48 - Right = Math.Min(MaxValue.Right, other.MaxValue.Right) 49 - }; 50 - 51 - if (MinValue.Top > MaxValue.Top || MinValue.Left > MaxValue.Left || MinValue.Bottom > MaxValue.Bottom || MinValue.Right > MaxValue.Right) 52 - { 53 - throw new ArgumentOutOfRangeException( 54 - nameof(them), 55 - $"Can not weld BindableMarginPaddings with non-overlapping min/max-ranges. The ranges were [{MinValue} - {MaxValue}] and [{other.MinValue} - {other.MaxValue}]." 56 - ); 57 - } 58 - } 59 - 60 - base.BindTo(them); 61 18 } 62 19 63 20 public override string ToString() => Value.ToString(); ··· 87 44 } 88 45 } 89 46 90 - private static MarginPadding clamp(MarginPadding value, MarginPadding minValue, MarginPadding maxValue) => 91 - new MarginPadding 47 + protected sealed override MarginPadding ClampValue(MarginPadding value, MarginPadding minValue, MarginPadding maxValue) 48 + { 49 + return new MarginPadding 92 50 { 93 - Top = Math.Max(minValue.Top, Math.Min(maxValue.Top, value.Top)), 94 - Left = Math.Max(minValue.Left, Math.Min(maxValue.Left, value.Left)), 95 - Bottom = Math.Max(minValue.Bottom, Math.Min(maxValue.Bottom, value.Bottom)), 96 - Right = Math.Max(minValue.Right, Math.Min(maxValue.Right, value.Right)) 51 + Top = Math.Clamp(value.Top, minValue.Top, maxValue.Top), 52 + Left = Math.Clamp(value.Left, minValue.Left, maxValue.Left), 53 + Bottom = Math.Clamp(value.Bottom, minValue.Bottom, maxValue.Bottom), 54 + Right = Math.Clamp(value.Right, minValue.Right, maxValue.Right) 97 55 }; 56 + } 57 + 58 + protected sealed override bool IsValidRange(MarginPadding min, MarginPadding max) 59 + { 60 + return min.Top <= max.Top && 61 + min.Left <= max.Left && 62 + min.Bottom <= max.Bottom && 63 + min.Right <= max.Right; 64 + } 98 65 } 99 66 }
+14 -146
osu.Framework/Bindables/BindableNumber.cs
··· 8 8 9 9 namespace osu.Framework.Bindables 10 10 { 11 - public class BindableNumber<T> : Bindable<T>, IBindableNumber<T> 11 + public class BindableNumber<T> : RangeConstrainedBindable<T>, IBindableNumber<T> 12 12 where T : struct, IComparable<T>, IConvertible, IEquatable<T> 13 13 { 14 14 public event Action<T> PrecisionChanged; 15 15 16 - public event Action<T> MinValueChanged; 17 - 18 - public event Action<T> MaxValueChanged; 19 - 20 16 public BindableNumber(T defaultValue = default) 21 17 : base(defaultValue) 22 18 { ··· 29 25 $"{nameof(BindableNumber<T>)} only accepts the primitive numeric types (except for {typeof(decimal).FullName}) as type arguments. You provided {typeof(T).FullName}."); 30 26 } 31 27 32 - minValue = DefaultMinValue; 33 - maxValue = DefaultMaxValue; 34 28 precision = DefaultPrecision; 35 29 36 - // Re-apply the current value to apply the default min/max/precision values 37 - SetValue(Value); 30 + // Re-apply the current value to apply the default precision value 31 + setValue(Value); 38 32 } 39 33 40 34 private T precision; ··· 68 62 if (updateCurrentValue) 69 63 { 70 64 // Re-apply the current value to apply the new precision 71 - SetValue(Value); 65 + setValue(Value); 72 66 } 73 67 } 74 68 75 69 public override T Value 76 70 { 77 71 get => base.Value; 78 - set => SetValue(value); 72 + set => setValue(value); 79 73 } 80 74 81 - internal void SetValue(T value) 75 + private void setValue(T value) 82 76 { 83 77 if (Precision.CompareTo(DefaultPrecision) > 0) 84 78 { 85 - double doubleValue = clamp(value, MinValue, MaxValue).ToDouble(NumberFormatInfo.InvariantInfo); 79 + double doubleValue = ClampValue(value, MinValue, MaxValue).ToDouble(NumberFormatInfo.InvariantInfo); 86 80 doubleValue = Math.Round(doubleValue / Precision.ToDouble(NumberFormatInfo.InvariantInfo)) * Precision.ToDouble(NumberFormatInfo.InvariantInfo); 87 81 88 82 base.Value = (T)Convert.ChangeType(doubleValue, typeof(T), CultureInfo.InvariantCulture); 89 83 } 90 84 else 91 - base.Value = clamp(value, MinValue, MaxValue); 92 - } 93 - 94 - private T minValue; 95 - 96 - public T MinValue 97 - { 98 - get => minValue; 99 - set 100 - { 101 - if (minValue.Equals(value)) 102 - return; 103 - 104 - SetMinValue(value, true, this); 105 - } 106 - } 107 - 108 - /// <summary> 109 - /// Sets the minimum value. This method does no equality comparisons. 110 - /// </summary> 111 - /// <param name="minValue">The new minimum value.</param> 112 - /// <param name="updateCurrentValue">Whether to update the current value after the minimum value is set.</param> 113 - /// <param name="source">The bindable that triggered this. A null value represents the current bindable instance.</param> 114 - internal void SetMinValue(T minValue, bool updateCurrentValue, BindableNumber<T> source) 115 - { 116 - this.minValue = minValue; 117 - TriggerMinValueChange(source); 118 - 119 - if (updateCurrentValue) 120 - { 121 - // Re-apply the current value to apply the new minimum value 122 - SetValue(Value); 123 - } 124 - } 125 - 126 - private T maxValue; 127 - 128 - public T MaxValue 129 - { 130 - get => maxValue; 131 - set 132 - { 133 - if (maxValue.Equals(value)) 134 - return; 135 - 136 - SetMaxValue(value, true, this); 137 - } 138 - } 139 - 140 - /// <summary> 141 - /// Sets the maximum value. This method does no equality comparisons. 142 - /// </summary> 143 - /// <param name="maxValue">The new maximum value.</param> 144 - /// <param name="updateCurrentValue">Whether to update the current value after the maximum value is set.</param> 145 - /// <param name="source">The bindable that triggered this. A null value represents the current bindable instance.</param> 146 - internal void SetMaxValue(T maxValue, bool updateCurrentValue, BindableNumber<T> source) 147 - { 148 - this.maxValue = maxValue; 149 - TriggerMaxValueChange(source); 150 - 151 - if (updateCurrentValue) 152 - { 153 - // Re-apply the current value to apply the new maximum value 154 - SetValue(Value); 155 - } 85 + base.Value = value; 156 86 } 157 87 158 - /// <summary> 159 - /// The default <see cref="MinValue"/>. This should be equal to the minimum value of type <typeparamref name="T"/>. 160 - /// </summary> 161 - protected virtual T DefaultMinValue 88 + protected override T DefaultMinValue 162 89 { 163 90 get 164 91 { ··· 187 114 } 188 115 } 189 116 190 - /// <summary> 191 - /// The default <see cref="MaxValue"/>. This should be equal to the maximum value of type <typeparamref name="T"/>. 192 - /// </summary> 193 - protected virtual T DefaultMaxValue 117 + protected override T DefaultMaxValue 194 118 { 195 119 get 196 120 { ··· 254 178 base.TriggerChange(); 255 179 256 180 TriggerPrecisionChange(this, false); 257 - TriggerMinValueChange(this, false); 258 - TriggerMaxValueChange(this, false); 259 181 } 260 182 261 183 protected void TriggerPrecisionChange(BindableNumber<T> source = null, bool propagateToBindings = true) ··· 278 200 PrecisionChanged?.Invoke(precision); 279 201 } 280 202 281 - protected void TriggerMinValueChange(BindableNumber<T> source = null, bool propagateToBindings = true) 282 - { 283 - // check a bound bindable hasn't changed the value again (it will fire its own event) 284 - T beforePropagation = minValue; 285 - 286 - if (propagateToBindings && Bindings != null) 287 - { 288 - foreach (var b in Bindings) 289 - { 290 - if (b == source) continue; 291 - 292 - if (b is BindableNumber<T> bn) 293 - bn.SetMinValue(minValue, false, this); 294 - } 295 - } 296 - 297 - if (beforePropagation.Equals(minValue)) 298 - MinValueChanged?.Invoke(minValue); 299 - } 300 - 301 - protected void TriggerMaxValueChange(BindableNumber<T> source = null, bool propagateToBindings = true) 302 - { 303 - // check a bound bindable hasn't changed the value again (it will fire its own event) 304 - T beforePropagation = maxValue; 305 - 306 - if (propagateToBindings && Bindings != null) 307 - { 308 - foreach (var b in Bindings) 309 - { 310 - if (b == source) continue; 311 - 312 - if (b is BindableNumber<T> bn) 313 - bn.SetMaxValue(maxValue, false, this); 314 - } 315 - } 316 - 317 - if (beforePropagation.Equals(maxValue)) 318 - MaxValueChanged?.Invoke(maxValue); 319 - } 320 - 321 203 public override void BindTo(Bindable<T> them) 322 204 { 323 205 if (them is BindableNumber<T> other) 324 - { 325 206 Precision = other.Precision; 326 - MinValue = other.MinValue; 327 - MaxValue = other.MaxValue; 328 - 329 - if (MinValue.CompareTo(MaxValue) > 0) 330 - { 331 - throw new ArgumentOutOfRangeException( 332 - nameof(them), $"Can not weld bindable longs with non-overlapping min/max-ranges. The ranges were [{MinValue} - {MaxValue}] and [{other.MinValue} - {other.MaxValue}]."); 333 - } 334 - } 335 207 336 208 base.BindTo(them); 337 209 } 338 - 339 - /// <summary> 340 - /// Whether this bindable has a user-defined range that is not the full range of the <typeparamref name="T"/> type. 341 - /// </summary> 342 - public bool HasDefinedRange => !MinValue.Equals(DefaultMinValue) || !MaxValue.Equals(DefaultMaxValue); 343 210 344 211 public bool IsInteger => 345 212 typeof(T) != typeof(float) && ··· 446 313 } 447 314 } 448 315 316 + protected sealed override T ClampValue(T value, T minValue, T maxValue) => max(minValue, min(maxValue, value)); 317 + 318 + protected sealed override bool IsValidRange(T min, T max) => min.CompareTo(max) <= 0; 319 + 449 320 private static T max(T value1, T value2) 450 321 { 451 322 var comparison = value1.CompareTo(value2); ··· 457 328 var comparison = value1.CompareTo(value2); 458 329 return comparison > 0 ? value2 : value1; 459 330 } 460 - 461 - private static T clamp(T value, T minValue, T maxValue) 462 - => max(minValue, min(maxValue, value)); 463 331 464 332 [MethodImpl(MethodImplOptions.AggressiveInlining)] 465 333 private static bool isSupportedType() =>
+18 -40
osu.Framework/Bindables/BindableSize.cs
··· 6 6 7 7 namespace osu.Framework.Bindables 8 8 { 9 - public class BindableSize : Bindable<Size> 9 + /// <summary> 10 + /// Represents a <see cref="Size"/> bindable with defined component-wise constraints applied to it. 11 + /// </summary> 12 + public class BindableSize : RangeConstrainedBindable<Size> 10 13 { 11 - public BindableSize(Size value = default) 12 - : base(value) 13 - { 14 - MinValue = DefaultMinValue; 15 - MaxValue = DefaultMaxValue; 16 - } 14 + protected override Size DefaultMinValue => new Size(int.MinValue, int.MinValue); 15 + protected override Size DefaultMaxValue => new Size(int.MaxValue, int.MaxValue); 17 16 18 - public Size MinValue { get; set; } 19 - public Size MaxValue { get; set; } 20 - 21 - protected Size DefaultMinValue => new Size(int.MinValue, int.MinValue); 22 - protected Size DefaultMaxValue => new Size(int.MaxValue, int.MaxValue); 23 - 24 - public override Size Value 17 + public BindableSize(Size defaultValue = default) 18 + : base(defaultValue) 25 19 { 26 - get => base.Value; 27 - set => base.Value = clamp(value, MinValue, MaxValue); 28 - } 29 - 30 - public override void BindTo(Bindable<Size> them) 31 - { 32 - if (them is BindableSize other) 33 - { 34 - MinValue = new Size(Math.Max(MinValue.Width, other.MinValue.Width), Math.Max(MinValue.Height, other.MinValue.Height)); 35 - MaxValue = new Size(Math.Min(MaxValue.Width, other.MaxValue.Width), Math.Min(MaxValue.Height, other.MaxValue.Height)); 36 - 37 - if (MinValue.Width > MaxValue.Width || MinValue.Height > MaxValue.Height) 38 - { 39 - throw new ArgumentOutOfRangeException( 40 - nameof(them), 41 - $"Can not weld BindableSizes with non-overlapping min/max-ranges. The ranges were [{MinValue} - {MaxValue}] and [{other.MinValue} - {other.MaxValue}]." 42 - ); 43 - } 44 - } 45 - 46 - base.BindTo(them); 47 20 } 48 21 49 22 public override string ToString() => $"{Value.Width}x{Value.Height}"; ··· 67 40 } 68 41 } 69 42 70 - private static Size clamp(Size value, Size minValue, Size maxValue) => 71 - new Size( 72 - Math.Max(minValue.Width, Math.Min(value.Width, maxValue.Width)), 73 - Math.Max(minValue.Height, Math.Min(value.Height, maxValue.Height)) 74 - ); 43 + protected sealed override Size ClampValue(Size value, Size minValue, Size maxValue) 44 + { 45 + return new Size 46 + { 47 + Width = Math.Clamp(value.Width, minValue.Width, maxValue.Width), 48 + Height = Math.Clamp(value.Height, minValue.Height, maxValue.Height) 49 + }; 50 + } 51 + 52 + protected sealed override bool IsValidRange(Size min, Size max) => min.Width <= max.Width && min.Height <= max.Height; 75 53 } 76 54 }
+195
osu.Framework/Bindables/RangeConstrainedBindable.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using System.Collections.Generic; 6 + 7 + namespace osu.Framework.Bindables 8 + { 9 + public abstract class RangeConstrainedBindable<T> : Bindable<T> 10 + { 11 + public event Action<T> MinValueChanged; 12 + 13 + public event Action<T> MaxValueChanged; 14 + 15 + private T minValue; 16 + 17 + public T MinValue 18 + { 19 + get => minValue; 20 + set 21 + { 22 + if (EqualityComparer<T>.Default.Equals(value, minValue)) 23 + return; 24 + 25 + SetMinValue(value, true, this); 26 + } 27 + } 28 + 29 + private T maxValue; 30 + 31 + public T MaxValue 32 + { 33 + get => maxValue; 34 + set 35 + { 36 + if (EqualityComparer<T>.Default.Equals(value, maxValue)) 37 + return; 38 + 39 + SetMaxValue(value, true, this); 40 + } 41 + } 42 + 43 + public override T Value 44 + { 45 + get => base.Value; 46 + set => setValue(value); 47 + } 48 + 49 + /// <summary> 50 + /// The default <see cref="MinValue"/>. This should be equal to the minimum value of type <typeparamref name="T"/>. 51 + /// </summary> 52 + protected abstract T DefaultMinValue { get; } 53 + 54 + /// <summary> 55 + /// The default <see cref="MaxValue"/>. This should be equal to the maximum value of type <typeparamref name="T"/>. 56 + /// </summary> 57 + protected abstract T DefaultMaxValue { get; } 58 + 59 + /// <summary> 60 + /// Whether this bindable has a user-defined range that is not the full range of the <typeparamref name="T"/> type. 61 + /// </summary> 62 + public bool HasDefinedRange => !EqualityComparer<T>.Default.Equals(MinValue, DefaultMinValue) || 63 + !EqualityComparer<T>.Default.Equals(MaxValue, DefaultMaxValue); 64 + 65 + protected RangeConstrainedBindable(T defaultValue = default) 66 + : base(defaultValue) 67 + { 68 + minValue = DefaultMinValue; 69 + maxValue = DefaultMaxValue; 70 + 71 + // Reapply the default value here for respecting the defined default min/max values. 72 + setValue(defaultValue); 73 + } 74 + 75 + /// <summary> 76 + /// Sets the minimum value. This method does no equality comparisons. 77 + /// </summary> 78 + /// <param name="minValue">The new minimum value.</param> 79 + /// <param name="updateCurrentValue">Whether to update the current value after the minimum value is set.</param> 80 + /// <param name="source">The bindable that triggered this. A null value represents the current bindable instance.</param> 81 + internal void SetMinValue(T minValue, bool updateCurrentValue, RangeConstrainedBindable<T> source) 82 + { 83 + this.minValue = minValue; 84 + TriggerMinValueChange(source); 85 + 86 + if (updateCurrentValue) 87 + { 88 + // Reapply the current value to respect the new minimum value. 89 + setValue(Value); 90 + } 91 + } 92 + 93 + /// <summary> 94 + /// Sets the maximum value. This method does no equality comparisons. 95 + /// </summary> 96 + /// <param name="maxValue">The new maximum value.</param> 97 + /// <param name="updateCurrentValue">Whether to update the current value after the maximum value is set.</param> 98 + /// <param name="source">The bindable that triggered this. A null value represents the current bindable instance.</param> 99 + internal void SetMaxValue(T maxValue, bool updateCurrentValue, RangeConstrainedBindable<T> source) 100 + { 101 + this.maxValue = maxValue; 102 + TriggerMaxValueChange(source); 103 + 104 + if (updateCurrentValue) 105 + { 106 + // Reapply the current value to respect the new maximum value. 107 + setValue(Value); 108 + } 109 + } 110 + 111 + public override void TriggerChange() 112 + { 113 + base.TriggerChange(); 114 + 115 + TriggerMinValueChange(this, false); 116 + TriggerMaxValueChange(this, false); 117 + } 118 + 119 + protected void TriggerMinValueChange(RangeConstrainedBindable<T> source = null, bool propagateToBindings = true) 120 + { 121 + // check a bound bindable hasn't changed the value again (it will fire its own event) 122 + T beforePropagation = minValue; 123 + 124 + if (propagateToBindings && Bindings != null) 125 + { 126 + foreach (var b in Bindings) 127 + { 128 + if (b == source) continue; 129 + 130 + if (b is RangeConstrainedBindable<T> cb) 131 + cb.SetMinValue(minValue, false, this); 132 + } 133 + } 134 + 135 + if (EqualityComparer<T>.Default.Equals(beforePropagation, minValue)) 136 + MinValueChanged?.Invoke(minValue); 137 + } 138 + 139 + protected void TriggerMaxValueChange(RangeConstrainedBindable<T> source = null, bool propagateToBindings = true) 140 + { 141 + // check a bound bindable hasn't changed the value again (it will fire its own event) 142 + T beforePropagation = maxValue; 143 + 144 + if (propagateToBindings && Bindings != null) 145 + { 146 + foreach (var b in Bindings) 147 + { 148 + if (b == source) continue; 149 + 150 + if (b is RangeConstrainedBindable<T> cb) 151 + cb.SetMaxValue(maxValue, false, this); 152 + } 153 + } 154 + 155 + if (EqualityComparer<T>.Default.Equals(beforePropagation, maxValue)) 156 + MaxValueChanged?.Invoke(maxValue); 157 + } 158 + 159 + public override void BindTo(Bindable<T> them) 160 + { 161 + if (them is RangeConstrainedBindable<T> other) 162 + { 163 + if (!IsValidRange(other.MinValue, other.MaxValue)) 164 + { 165 + throw new ArgumentOutOfRangeException( 166 + nameof(them), $"The target bindable has specified an invalid range of [{other.MinValue} - {other.MaxValue}]."); 167 + } 168 + 169 + MinValue = other.MinValue; 170 + MaxValue = other.MaxValue; 171 + } 172 + 173 + base.BindTo(them); 174 + } 175 + 176 + public new RangeConstrainedBindable<T> GetBoundCopy() => (RangeConstrainedBindable<T>)base.GetBoundCopy(); 177 + 178 + public new RangeConstrainedBindable<T> GetUnboundCopy() => (RangeConstrainedBindable<T>)base.GetUnboundCopy(); 179 + 180 + /// <summary> 181 + /// Clamps <paramref name="value"/> to the range defined by <paramref name="minValue"/> and <paramref name="maxValue"/>. 182 + /// </summary> 183 + protected abstract T ClampValue(T value, T minValue, T maxValue); 184 + 185 + /// <summary> 186 + /// Whether <paramref name="min"/> and <paramref name="max"/> constitute a valid range 187 + /// (usually used to check that <paramref name="min"/> is indeed lesser than or equal to <paramref name="max"/>). 188 + /// </summary> 189 + /// <param name="min">The range's minimum value.</param> 190 + /// <param name="max">The range's maximum value.</param> 191 + protected abstract bool IsValidRange(T min, T max); 192 + 193 + private void setValue(T value) => base.Value = ClampValue(value, minValue, maxValue); 194 + } 195 + }
-2
osu.Framework/Graphics/Containers/ContainerExtensions.cs
··· 39 39 40 40 drawable.Position = Vector2.Zero; 41 41 drawable.Rotation = 0; 42 - drawable.Anchor = Anchor.TopLeft; 43 - drawable.Origin = Anchor.TopLeft; 44 42 45 43 // For anchor/origin positioning to be preserved correctly, 46 44 // relatively sized axes must be lifted to the wrapping container.
-33
osu.Framework/Graphics/Containers/DelayedLoadWrapper.cs
··· 16 16 /// Has the ability to delay the loading until it has been visible on-screen for a specified duration. 17 17 /// In order to benefit from delayed load, we must be inside a <see cref="ScrollContainer{T}"/>. 18 18 /// </summary> 19 - /// <remarks> 20 - /// <see cref="LifetimeStart"/> and <see cref="LifetimeEnd"/> are propagated from the wrapper to the content on content load. 21 - /// After load, the content's lifetime is preferred, meaning any changes to content's lifetime post-load will be respected. 22 - /// </remarks> 23 19 public class DelayedLoadWrapper : CompositeDrawable 24 20 { 25 21 [Resolved] ··· 58 54 59 55 AddLayout(optimisingContainerCache); 60 56 AddLayout(isIntersectingCache); 61 - } 62 - 63 - private double lifetimeStart = double.MinValue; 64 - 65 - public override double LifetimeStart 66 - { 67 - get => Content?.LifetimeStart ?? lifetimeStart; 68 - set 69 - { 70 - if (Content != null) 71 - Content.LifetimeStart = value; 72 - lifetimeStart = value; 73 - } 74 - } 75 - 76 - private double lifetimeEnd = double.MaxValue; 77 - 78 - public override double LifetimeEnd 79 - { 80 - get => Content?.LifetimeEnd ?? lifetimeEnd; 81 - set 82 - { 83 - if (Content != null) 84 - Content.LifetimeEnd = value; 85 - lifetimeEnd = value; 86 - } 87 57 } 88 58 89 59 private Drawable content; ··· 160 130 // This code is running on the game's scheduler, while this DLW may have been async disposed, so the addition is scheduled locally to prevent adding to disposed DLWs. 161 131 scheduledAddition = Schedule(() => 162 132 { 163 - content.LifetimeStart = lifetimeStart; 164 - content.LifetimeEnd = lifetimeEnd; 165 - 166 133 AddInternal(content); 167 134 168 135 DelayedLoadCompleted = true;
+1 -1
osu.Framework/Graphics/Containers/Markdown/MarkdownTableCell.cs
··· 76 76 } 77 77 } 78 78 79 - public MarkdownTextFlowContainer CreateTextFlow() 79 + public virtual MarkdownTextFlowContainer CreateTextFlow() 80 80 { 81 81 var flow = parentFlowComponent.CreateTextFlow(); 82 82 flow.Padding = new MarginPadding(10);
+2
osu.Framework/Graphics/Containers/Markdown/MarkdownTextFlowContainer.cs
··· 160 160 AddDrawable(textDrawable); 161 161 } 162 162 163 + protected override SpriteText CreateSpriteText() => parentTextComponent.CreateSpriteText(); 164 + 163 165 /// <summary> 164 166 /// Creates an emphasised <see cref="SpriteText"/>. 165 167 /// </summary>
+1 -1
osu.Framework/Graphics/UserInterface/Dropdown.cs
··· 63 63 foreach (var entry in items) 64 64 addDropdownItem(GenerateItemText(entry), entry); 65 65 66 - if (Current.Value == null || !itemMap.Keys.Contains(Current.Value)) 66 + if (Current.Value == null || !itemMap.Keys.Contains(Current.Value, EqualityComparer<T>.Default)) 67 67 Current.Value = itemMap.Keys.FirstOrDefault(); 68 68 else 69 69 Current.TriggerChange();
+16 -10
osu.Framework/Platform/GameHost.cs
··· 583 583 584 584 ExecutionState = ExecutionState.Running; 585 585 586 - initialiseInputHandlers(); 586 + populateInputHandlers(); 587 587 588 588 SetupConfig(game.GetFrameworkConfigDefaults() ?? new Dictionary<FrameworkSetting, object>()); 589 + 590 + initialiseInputHandlers(); 589 591 590 592 if (Window != null) 591 593 { ··· 704 706 Logger.Storage = Storage.GetStorageForDirectory("logs"); 705 707 } 706 708 707 - private void initialiseInputHandlers() 709 + private void populateInputHandlers() 708 710 { 709 711 AvailableInputHandlers = CreateAvailableInputHandlers().ToImmutableArray(); 712 + } 710 713 714 + private void initialiseInputHandlers() 715 + { 711 716 foreach (var handler in AvailableInputHandlers) 712 717 { 713 - (handler as IHasCursorSensitivity)?.Sensitivity.BindTo(cursorSensitivity); 714 - 715 718 if (!handler.Initialize(this)) 716 719 handler.Enabled.Value = false; 717 720 } ··· 850 853 }; 851 854 852 855 #pragma warning disable 618 856 + // pragma region can be removed 20210911 853 857 ignoredInputHandlers = Config.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers); 854 858 ignoredInputHandlers.ValueChanged += e => 855 859 { ··· 864 868 865 869 Config.BindWith(FrameworkSetting.CursorSensitivity, cursorSensitivity); 866 870 867 - // one way binding to preserve compatibility. 871 + var cursorSensitivityHandlers = AvailableInputHandlers.OfType<IHasCursorSensitivity>(); 872 + 873 + // one way bindings to preserve compatibility. 868 874 cursorSensitivity.BindValueChanged(val => 869 875 { 870 - foreach (var h in AvailableInputHandlers.OfType<IHasCursorSensitivity>()) 876 + foreach (var h in cursorSensitivityHandlers) 871 877 h.Sensitivity.Value = val.NewValue; 872 878 }, true); 879 + 880 + foreach (var h in cursorSensitivityHandlers) 881 + h.Sensitivity.BindValueChanged(s => cursorSensitivity.Value = s.NewValue); 873 882 #pragma warning restore 618 874 883 875 884 PerformanceLogging.BindValueChanged(logging => ··· 889 898 CultureInfo.DefaultThreadCurrentCulture = culture; 890 899 CultureInfo.DefaultThreadCurrentUICulture = culture; 891 900 892 - foreach (var t in Threads) 893 - { 894 - t.Scheduler.Add(() => { t.CurrentCulture = culture; }); 895 - } 901 + threadRunner.SetCulture(culture); 896 902 }, true); 897 903 898 904 // intentionally done after everything above to ensure the new configuration location has priority over obsoleted values.
+6
osu.Framework/Platform/HeadlessGameHost.cs
··· 39 39 defaultOverrides[FrameworkSetting.AudioDevice] = "No sound"; 40 40 41 41 base.SetupConfig(defaultOverrides); 42 + 43 + if (Enum.TryParse<ExecutionMode>(Environment.GetEnvironmentVariable("OSU_EXECUTION_MODE"), out var mode)) 44 + { 45 + Config.SetValue(FrameworkSetting.ExecutionMode, mode); 46 + Logger.Log($"Startup execution mode set to {mode} from envvar"); 47 + } 42 48 } 43 49 44 50 protected override void SetupForRun()
+20
osu.Framework/Platform/ThreadRunner.cs
··· 7 7 using System; 8 8 using System.Collections.Generic; 9 9 using System.Diagnostics; 10 + using System.Globalization; 10 11 using System.Linq; 12 + using System.Threading; 11 13 using osu.Framework.Development; 12 14 using osu.Framework.Extensions.IEnumerableExtensions; 13 15 using osu.Framework.Logging; ··· 161 163 162 164 // if null, we have not yet got an execution mode, so set this early to allow usage in GameThread.Initialize overrides. 163 165 activeExecutionMode ??= ThreadSafety.ExecutionMode = ExecutionMode; 166 + Logger.Log($"Execution mode changed to {activeExecutionMode}"); 164 167 165 168 pauseAllThreads(); 166 169 ··· 217 220 { 218 221 mainThread.ActiveHz = GameThread.DEFAULT_ACTIVE_HZ; 219 222 mainThread.InactiveHz = GameThread.DEFAULT_INACTIVE_HZ; 223 + } 224 + } 225 + 226 + /// <summary> 227 + /// Sets the current culture of all threads to the supplied <paramref name="culture"/>. 228 + /// </summary> 229 + public void SetCulture(CultureInfo culture) 230 + { 231 + // for single-threaded mode, switch the current (assumed to be main) thread's culture, since it's actually the one that's running the frames. 232 + Thread.CurrentThread.CurrentCulture = culture; 233 + 234 + // for multi-threaded mode, schedule the culture change on all threads. 235 + // note that if the threads haven't been created yet (e.g. if the game started single-threaded), this will only store the culture in GameThread.CurrentCulture. 236 + // in that case, the stored value will be set on the actual threads after the next Start() call. 237 + foreach (var t in Threads) 238 + { 239 + t.Scheduler.Add(() => t.CurrentCulture = culture); 220 240 } 221 241 } 222 242 }
+1 -1
osu.Framework/osu.Framework.csproj
··· 39 39 <PackageReference Include="JetBrains.Annotations" Version="2021.1.0" /> 40 40 <PackageReference Include="ppy.osuTK.NS20" Version="1.0.173" /> 41 41 <PackageReference Include="StbiSharp" Version="1.0.13" /> 42 - <PackageReference Include="ppy.SDL2-CS" Version="1.0.225-alpha" /> 42 + <PackageReference Include="ppy.SDL2-CS" Version="1.0.238-alpha" /> 43 43 </ItemGroup> 44 44 <ItemGroup Condition="$(TargetFrameworkIdentifier) == '.NETCoreApp'"> 45 45 <!-- DO NOT use ProjectReference for native packaging project.