Normalize all the line endings

This commit is contained in:
Dean Herbert
2018-04-11 16:34:32 +09:00
parent 0526ae8d27
commit 7a598af5b9
440 changed files with 56870 additions and 56870 deletions

514
.gitignore vendored
View File

@@ -1,258 +1,258 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc

View File

@@ -1,15 +1,15 @@
language: csharp
solution: osu-framework.sln
mono:
- latest
before_install:
- git submodule update --init --recursive
install:
- nuget restore osu-framework.sln
- nuget install NUnit.Runners -Version 3.4.1 -OutputDirectory testrunner
script:
- xbuild osu-framework.sln
- |
mono \
./testrunner/NUnit.ConsoleRunner.3.4.1/tools/nunit3-console.exe \
./osu.Framework.Tests/osu.Framework.Tests.csproj
language: csharp
solution: osu-framework.sln
mono:
- latest
before_install:
- git submodule update --init --recursive
install:
- nuget restore osu-framework.sln
- nuget install NUnit.Runners -Version 3.4.1 -OutputDirectory testrunner
script:
- xbuild osu-framework.sln
- |
mono \
./testrunner/NUnit.ConsoleRunner.3.4.1/tools/nunit3-console.exe \
./osu.Framework.Tests/osu.Framework.Tests.csproj

View File

@@ -1,20 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Platform;
using osu.Framework;
namespace SampleGame
{
public static class Program
{
[STAThread]
public static void Main()
{
using (Game game = new SampleGame())
using (GameHost host = Host.GetSuitableHost(@"sample-game"))
host.Run(game);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Platform;
using osu.Framework;
namespace SampleGame
{
public static class Program
{
[STAThread]
public static void Main()
{
using (Game game = new SampleGame())
using (GameHost host = Host.GetSuitableHost(@"sample-game"))
host.Run(game);
}
}
}

View File

@@ -1,35 +1,35 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework;
using osu.Framework.Graphics;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Allocation;
namespace SampleGame
{
internal class SampleGame : Game
{
private Box box;
[BackgroundDependencyLoader]
private void load()
{
Add(box = new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(150, 150),
Colour = Color4.Tomato
});
}
protected override void Update()
{
base.Update();
box.Rotation += (float)Time.Elapsed / 10;
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework;
using osu.Framework.Graphics;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Allocation;
namespace SampleGame
{
internal class SampleGame : Game
{
private Box box;
[BackgroundDependencyLoader]
private void load()
{
Add(box = new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(150, 150),
Colour = Color4.Tomato
});
}
protected override void Update()
{
base.Update();
box.Rotation += (float)Time.Elapsed / 10;
}
}
}

View File

@@ -1,63 +1,63 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2002
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Framework", "osu.Framework\osu.Framework.csproj", "{C76BF5B3-985E-4D39-95FE-97C9C879B83A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleGame", "SampleGame\SampleGame.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Framework.Tests", "osu.Framework.Tests\osu.Framework.Tests.csproj", "{79803407-6F50-484F-93F5-641911EABD8A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|Any CPU.Build.0 = Release|Any CPU
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU
{79803407-6F50-484F-93F5-641911EABD8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{79803407-6F50-484F-93F5-641911EABD8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79803407-6F50-484F-93F5-641911EABD8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79803407-6F50-484F-93F5-641911EABD8A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0
$0.TextStylePolicy = $1
$1.EolMarker = Windows
$1.inheritsSet = VisualStudio
$1.inheritsScope = text/plain
$1.scope = text/x-csharp
$0.CSharpFormattingPolicy = $2
$2.IndentSwitchSection = True
$2.NewLinesForBracesInProperties = True
$2.NewLinesForBracesInAccessors = True
$2.NewLinesForBracesInAnonymousMethods = True
$2.NewLinesForBracesInControlBlocks = True
$2.NewLinesForBracesInAnonymousTypes = True
$2.NewLinesForBracesInObjectCollectionArrayInitializers = True
$2.NewLinesForBracesInLambdaExpressionBody = True
$2.NewLineForElse = True
$2.NewLineForCatch = True
$2.NewLineForFinally = True
$2.NewLineForMembersInObjectInit = True
$2.NewLineForMembersInAnonymousTypes = True
$2.NewLineForClausesInQuery = True
$2.SpacingAfterMethodDeclarationName = False
$2.SpaceAfterMethodCallName = False
$2.SpaceBeforeOpenSquareBracket = False
$2.inheritsSet = Mono
$2.inheritsScope = text/x-csharp
$2.scope = text/x-csharp
EndGlobalSection
EndGlobal
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2002
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Framework", "osu.Framework\osu.Framework.csproj", "{C76BF5B3-985E-4D39-95FE-97C9C879B83A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleGame", "SampleGame\SampleGame.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Framework.Tests", "osu.Framework.Tests\osu.Framework.Tests.csproj", "{79803407-6F50-484F-93F5-641911EABD8A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|Any CPU.Build.0 = Release|Any CPU
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU
{79803407-6F50-484F-93F5-641911EABD8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{79803407-6F50-484F-93F5-641911EABD8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79803407-6F50-484F-93F5-641911EABD8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79803407-6F50-484F-93F5-641911EABD8A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0
$0.TextStylePolicy = $1
$1.EolMarker = Windows
$1.inheritsSet = VisualStudio
$1.inheritsScope = text/plain
$1.scope = text/x-csharp
$0.CSharpFormattingPolicy = $2
$2.IndentSwitchSection = True
$2.NewLinesForBracesInProperties = True
$2.NewLinesForBracesInAccessors = True
$2.NewLinesForBracesInAnonymousMethods = True
$2.NewLinesForBracesInControlBlocks = True
$2.NewLinesForBracesInAnonymousTypes = True
$2.NewLinesForBracesInObjectCollectionArrayInitializers = True
$2.NewLinesForBracesInLambdaExpressionBody = True
$2.NewLineForElse = True
$2.NewLineForCatch = True
$2.NewLineForFinally = True
$2.NewLineForMembersInObjectInit = True
$2.NewLineForMembersInAnonymousTypes = True
$2.NewLineForClausesInQuery = True
$2.SpacingAfterMethodDeclarationName = False
$2.SpaceAfterMethodCallName = False
$2.SpaceBeforeOpenSquareBracket = False
$2.inheritsSet = Mono
$2.inheritsScope = text/x-csharp
$2.scope = text/x-csharp
EndGlobalSection
EndGlobal

View File

@@ -1,15 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Testing;
namespace osu.Framework.Tests
{
internal class AutomatedVisualTestGame : TestGame
{
public AutomatedVisualTestGame()
{
Add(new TestBrowserTestRunner(new TestBrowser()));
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Testing;
namespace osu.Framework.Tests
{
internal class AutomatedVisualTestGame : TestGame
{
public AutomatedVisualTestGame()
{
Add(new TestBrowserTestRunner(new TestBrowser()));
}
}
}

View File

@@ -1,44 +1,44 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableBoolTest
{
[TestCase(true)]
[TestCase(false)]
public void TestSet(bool value)
{
var bindable = new BindableBool { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("True", true)]
[TestCase("true", true)]
[TestCase("False", false)]
[TestCase("false", false)]
[TestCase("1", true)]
[TestCase("0", false)]
public void TestParsingString(string value, bool expected)
{
var bindable = new BindableBool();
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase(true)]
[TestCase(false)]
public void TestParsingBoolean(bool value)
{
var bindable = new BindableBool();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableBoolTest
{
[TestCase(true)]
[TestCase(false)]
public void TestSet(bool value)
{
var bindable = new BindableBool { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("True", true)]
[TestCase("true", true)]
[TestCase("False", false)]
[TestCase("false", false)]
[TestCase("1", true)]
[TestCase("0", false)]
public void TestParsingString(string value, bool expected)
{
var bindable = new BindableBool();
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase(true)]
[TestCase(false)]
public void TestParsingBoolean(bool value)
{
var bindable = new BindableBool();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
}
}

View File

@@ -1,66 +1,66 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableDoubleTest
{
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105.123)]
[TestCase(105.123)]
[TestCase(double.MinValue)]
[TestCase(double.MaxValue)]
public void TestSet(double value)
{
var bindable = new BindableDouble { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("0", 0f)]
[TestCase("1", 1f)]
[TestCase("-0", 0f)]
[TestCase("-105.123", -105.123)]
[TestCase("105.123", 105.123)]
public void TestParsingString(string value, double expected)
{
var bindable = new BindableDouble();
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase("0", -10, 10, 0)]
[TestCase("1", -10, 10, 1)]
[TestCase("-0", -10, 10, 0)]
[TestCase("-105.123", -10, 10, -10)]
[TestCase("105.123", -10, 10, 10)]
public void TestParsingStringWithRange(string value, double minValue, double maxValue, double expected)
{
var bindable = new BindableDouble { MinValue = minValue, MaxValue = maxValue };
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105.123)]
[TestCase(105.123)]
[TestCase(double.MinValue)]
[TestCase(double.MaxValue)]
public void TestParsingDouble(double value)
{
var bindable = new BindableDouble();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableDoubleTest
{
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105.123)]
[TestCase(105.123)]
[TestCase(double.MinValue)]
[TestCase(double.MaxValue)]
public void TestSet(double value)
{
var bindable = new BindableDouble { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("0", 0f)]
[TestCase("1", 1f)]
[TestCase("-0", 0f)]
[TestCase("-105.123", -105.123)]
[TestCase("105.123", 105.123)]
public void TestParsingString(string value, double expected)
{
var bindable = new BindableDouble();
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase("0", -10, 10, 0)]
[TestCase("1", -10, 10, 1)]
[TestCase("-0", -10, 10, 0)]
[TestCase("-105.123", -10, 10, -10)]
[TestCase("105.123", -10, 10, 10)]
public void TestParsingStringWithRange(string value, double minValue, double maxValue, double expected)
{
var bindable = new BindableDouble { MinValue = minValue, MaxValue = maxValue };
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105.123)]
[TestCase(105.123)]
[TestCase(double.MinValue)]
[TestCase(double.MaxValue)]
public void TestParsingDouble(double value)
{
var bindable = new BindableDouble();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
}
}

View File

@@ -1,52 +1,52 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableEnumTest
{
[TestCase(TestEnum.Value1)]
[TestCase(TestEnum.Value2)]
[TestCase(TestEnum.Value1 - 1)]
[TestCase(TestEnum.Value2 + 1)]
public void TestSet(TestEnum value)
{
var bindable = new Bindable<TestEnum> { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("Value1", TestEnum.Value1)]
[TestCase("Value2", TestEnum.Value2)]
[TestCase("-1", TestEnum.Value1 - 1)]
[TestCase("2", TestEnum.Value2 + 1)]
public void TestParsingString(string value, TestEnum expected)
{
var bindable = new Bindable<TestEnum>();
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase(TestEnum.Value1)]
[TestCase(TestEnum.Value2)]
[TestCase(TestEnum.Value1 - 1)]
[TestCase(TestEnum.Value2 + 1)]
public void TestParsingEnum(TestEnum value)
{
var bindable = new Bindable<TestEnum>();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
public enum TestEnum
{
Value1 = 0,
Value2 = 1
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableEnumTest
{
[TestCase(TestEnum.Value1)]
[TestCase(TestEnum.Value2)]
[TestCase(TestEnum.Value1 - 1)]
[TestCase(TestEnum.Value2 + 1)]
public void TestSet(TestEnum value)
{
var bindable = new Bindable<TestEnum> { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("Value1", TestEnum.Value1)]
[TestCase("Value2", TestEnum.Value2)]
[TestCase("-1", TestEnum.Value1 - 1)]
[TestCase("2", TestEnum.Value2 + 1)]
public void TestParsingString(string value, TestEnum expected)
{
var bindable = new Bindable<TestEnum>();
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase(TestEnum.Value1)]
[TestCase(TestEnum.Value2)]
[TestCase(TestEnum.Value1 - 1)]
[TestCase(TestEnum.Value2 + 1)]
public void TestParsingEnum(TestEnum value)
{
var bindable = new Bindable<TestEnum>();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
public enum TestEnum
{
Value1 = 0,
Value2 = 1
}
}
}

View File

@@ -1,66 +1,66 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableFloatTest
{
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105.123f)]
[TestCase(105.123f)]
[TestCase(float.MinValue)]
[TestCase(float.MaxValue)]
public void TestSet(float value)
{
var bindable = new BindableFloat { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("0", 0f)]
[TestCase("1", 1f)]
[TestCase("-0", 0f)]
[TestCase("-105.123", -105.123f)]
[TestCase("105.123", 105.123f)]
public void TestParsingString(string value, float expected)
{
var bindable = new BindableFloat();
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase("0", -10, 10, 0)]
[TestCase("1", -10, 10, 1)]
[TestCase("-0", -10, 10, 0)]
[TestCase("-105.123", -10, 10, -10)]
[TestCase("105.123", -10, 10, 10)]
public void TestParsingStringWithRange(string value, float minValue, float maxValue, float expected)
{
var bindable = new BindableFloat { MinValue = minValue, MaxValue = maxValue };
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105.123f)]
[TestCase(105.123f)]
[TestCase(float.MinValue)]
[TestCase(float.MaxValue)]
public void TestParsingFloat(float value)
{
var bindable = new BindableFloat();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableFloatTest
{
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105.123f)]
[TestCase(105.123f)]
[TestCase(float.MinValue)]
[TestCase(float.MaxValue)]
public void TestSet(float value)
{
var bindable = new BindableFloat { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("0", 0f)]
[TestCase("1", 1f)]
[TestCase("-0", 0f)]
[TestCase("-105.123", -105.123f)]
[TestCase("105.123", 105.123f)]
public void TestParsingString(string value, float expected)
{
var bindable = new BindableFloat();
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase("0", -10, 10, 0)]
[TestCase("1", -10, 10, 1)]
[TestCase("-0", -10, 10, 0)]
[TestCase("-105.123", -10, 10, -10)]
[TestCase("105.123", -10, 10, 10)]
public void TestParsingStringWithRange(string value, float minValue, float maxValue, float expected)
{
var bindable = new BindableFloat { MinValue = minValue, MaxValue = maxValue };
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105.123f)]
[TestCase(105.123f)]
[TestCase(float.MinValue)]
[TestCase(float.MaxValue)]
public void TestParsingFloat(float value)
{
var bindable = new BindableFloat();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
}
}

View File

@@ -1,66 +1,66 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableIntTest
{
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105)]
[TestCase(105)]
[TestCase(int.MinValue)]
[TestCase(int.MaxValue)]
public void TestSet(int value)
{
var bindable = new BindableInt { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("0", 0)]
[TestCase("1", 1)]
[TestCase("-0", 0)]
[TestCase("-105", -105)]
[TestCase("105", 105)]
public void TestParsingString(string value, int expected)
{
var bindable = new BindableInt();
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase("0", -10, 10, 0)]
[TestCase("1", -10, 10, 1)]
[TestCase("-0", -10, 10, 0)]
[TestCase("-105", -10, 10, -10)]
[TestCase("105", -10, 10, 10)]
public void TestParsingStringWithRange(string value, int minValue, int maxValue, int expected)
{
var bindable = new BindableInt { MinValue = minValue, MaxValue = maxValue };
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105)]
[TestCase(105)]
[TestCase(int.MinValue)]
[TestCase(int.MaxValue)]
public void TestParsingInt(int value)
{
var bindable = new BindableInt();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableIntTest
{
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105)]
[TestCase(105)]
[TestCase(int.MinValue)]
[TestCase(int.MaxValue)]
public void TestSet(int value)
{
var bindable = new BindableInt { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("0", 0)]
[TestCase("1", 1)]
[TestCase("-0", 0)]
[TestCase("-105", -105)]
[TestCase("105", 105)]
public void TestParsingString(string value, int expected)
{
var bindable = new BindableInt();
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase("0", -10, 10, 0)]
[TestCase("1", -10, 10, 1)]
[TestCase("-0", -10, 10, 0)]
[TestCase("-105", -10, 10, -10)]
[TestCase("105", -10, 10, 10)]
public void TestParsingStringWithRange(string value, int minValue, int maxValue, int expected)
{
var bindable = new BindableInt { MinValue = minValue, MaxValue = maxValue };
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105)]
[TestCase(105)]
[TestCase(int.MinValue)]
[TestCase(int.MaxValue)]
public void TestParsingInt(int value)
{
var bindable = new BindableInt();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
}
}

View File

@@ -1,66 +1,66 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableLongTest
{
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105)]
[TestCase(105)]
[TestCase(long.MinValue)]
[TestCase(long.MaxValue)]
public void TestSet(long value)
{
var bindable = new BindableLong { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("0", 0)]
[TestCase("1", 1)]
[TestCase("-0", 0)]
[TestCase("-105", -105)]
[TestCase("105", 105)]
public void TestParsingString(string value, long expected)
{
var bindable = new BindableLong();
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase("0", -10, 10, 0)]
[TestCase("1", -10, 10, 1)]
[TestCase("-0", -10, 10, 0)]
[TestCase("-105", -10, 10, -10)]
[TestCase("105", -10, 10, 10)]
public void TestParsingStringWithRange(string value, long minValue, long maxValue, long expected)
{
var bindable = new BindableLong { MinValue = minValue, MaxValue = maxValue };
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105)]
[TestCase(105)]
[TestCase(long.MinValue)]
[TestCase(long.MaxValue)]
public void TestParsingLong(long value)
{
var bindable = new BindableLong();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableLongTest
{
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105)]
[TestCase(105)]
[TestCase(long.MinValue)]
[TestCase(long.MaxValue)]
public void TestSet(long value)
{
var bindable = new BindableLong { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("0", 0)]
[TestCase("1", 1)]
[TestCase("-0", 0)]
[TestCase("-105", -105)]
[TestCase("105", 105)]
public void TestParsingString(string value, long expected)
{
var bindable = new BindableLong();
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase("0", -10, 10, 0)]
[TestCase("1", -10, 10, 1)]
[TestCase("-0", -10, 10, 0)]
[TestCase("-105", -10, 10, -10)]
[TestCase("105", -10, 10, 10)]
public void TestParsingStringWithRange(string value, long minValue, long maxValue, long expected)
{
var bindable = new BindableLong { MinValue = minValue, MaxValue = maxValue };
bindable.Parse(value);
Assert.AreEqual(expected, bindable.Value);
}
[TestCase(0)]
[TestCase(-0)]
[TestCase(1)]
[TestCase(-105)]
[TestCase(105)]
[TestCase(long.MinValue)]
[TestCase(long.MaxValue)]
public void TestParsingLong(long value)
{
var bindable = new BindableLong();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
}
}

View File

@@ -1,32 +1,32 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableStringTest
{
[TestCase("")]
[TestCase(null)]
[TestCase("this is a string")]
public void TestSet(string value)
{
var bindable = new Bindable<string> { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("")]
[TestCase("null")]
[TestCase("this is a string")]
public void TestParsingString(string value)
{
var bindable = new Bindable<string>();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Configuration;
namespace osu.Framework.Tests.Bindables
{
[TestFixture]
public class BindableStringTest
{
[TestCase("")]
[TestCase(null)]
[TestCase("this is a string")]
public void TestSet(string value)
{
var bindable = new Bindable<string> { Value = value };
Assert.AreEqual(value, bindable.Value);
}
[TestCase("")]
[TestCase("null")]
[TestCase("this is a string")]
public void TestParsingString(string value)
{
var bindable = new Bindable<string>();
bindable.Parse(value);
Assert.AreEqual(value, bindable.Value);
}
}
}

View File

@@ -1,64 +1,64 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.Lists;
namespace osu.Framework.Tests.IO
{
[TestFixture]
public class TestSortedListSerialization
{
[Test]
public void TestUnsortedSerialization()
{
var original = new SortedList<int>();
original.AddRange(new[] { 1, 2, 3, 4, 5, 6 });
var deserialized = JsonConvert.DeserializeObject<SortedList<int>>(JsonConvert.SerializeObject(original));
Assert.AreEqual(original.Count, deserialized.Count, "Counts are not equal");
for (int i = 0; i < original.Count; i++)
Assert.AreEqual(original[i], deserialized[i], $"Item at index {i} differs");
}
[Test]
public void TestSortedSerialization()
{
var original = new SortedList<int>();
original.AddRange(new[] { 6, 5, 4, 3, 2, 1 });
var deserialized = JsonConvert.DeserializeObject<SortedList<int>>(JsonConvert.SerializeObject(original));
Assert.AreEqual(original.Count, deserialized.Count, "Counts are not equal");
for (int i = 0; i < original.Count; i++)
Assert.AreEqual(original[i], deserialized[i], $"Item at index {i} differs");
}
[Test]
public void TestEmptySerialization()
{
var original = new SortedList<int>();
var deserialized = JsonConvert.DeserializeObject<SortedList<int>>(JsonConvert.SerializeObject(original));
Assert.AreEqual(original.Count, deserialized.Count, "Counts are not equal");
}
[Test]
public void TestCustomComparer()
{
int compare(int i1, int i2) => i2.CompareTo(i1);
var original = new SortedList<int>(compare);
original.AddRange(new[] { 1, 2, 3, 4, 5, 6 });
var deserialized = new SortedList<int>(compare);
JsonConvert.PopulateObject(JsonConvert.SerializeObject(original), deserialized);
Assert.AreEqual(original.Count, deserialized.Count, "Counts are not equal");
for (int i = 0; i < original.Count; i++)
Assert.AreEqual(original[i], deserialized[i], $"Item at index {i} differs");
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.Lists;
namespace osu.Framework.Tests.IO
{
[TestFixture]
public class TestSortedListSerialization
{
[Test]
public void TestUnsortedSerialization()
{
var original = new SortedList<int>();
original.AddRange(new[] { 1, 2, 3, 4, 5, 6 });
var deserialized = JsonConvert.DeserializeObject<SortedList<int>>(JsonConvert.SerializeObject(original));
Assert.AreEqual(original.Count, deserialized.Count, "Counts are not equal");
for (int i = 0; i < original.Count; i++)
Assert.AreEqual(original[i], deserialized[i], $"Item at index {i} differs");
}
[Test]
public void TestSortedSerialization()
{
var original = new SortedList<int>();
original.AddRange(new[] { 6, 5, 4, 3, 2, 1 });
var deserialized = JsonConvert.DeserializeObject<SortedList<int>>(JsonConvert.SerializeObject(original));
Assert.AreEqual(original.Count, deserialized.Count, "Counts are not equal");
for (int i = 0; i < original.Count; i++)
Assert.AreEqual(original[i], deserialized[i], $"Item at index {i} differs");
}
[Test]
public void TestEmptySerialization()
{
var original = new SortedList<int>();
var deserialized = JsonConvert.DeserializeObject<SortedList<int>>(JsonConvert.SerializeObject(original));
Assert.AreEqual(original.Count, deserialized.Count, "Counts are not equal");
}
[Test]
public void TestCustomComparer()
{
int compare(int i1, int i2) => i2.CompareTo(i1);
var original = new SortedList<int>(compare);
original.AddRange(new[] { 1, 2, 3, 4, 5, 6 });
var deserialized = new SortedList<int>(compare);
JsonConvert.PopulateObject(JsonConvert.SerializeObject(original), deserialized);
Assert.AreEqual(original.Count, deserialized.Count, "Counts are not equal");
for (int i = 0; i < original.Count; i++)
Assert.AreEqual(original[i], deserialized[i], $"Item at index {i} differs");
}
}
}

View File

@@ -1,438 +1,438 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Reflection;
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.IO.Network;
using HttpMethod = osu.Framework.IO.Network.HttpMethod;
using WebRequest = osu.Framework.IO.Network.WebRequest;
namespace osu.Framework.Tests.IO
{
[TestFixture]
public class TestWebRequest
{
private const string valid_get_url = "httpbin.org/get";
private const string invalid_get_url = "a.ppy.shhhhh";
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestValidGet([Values("http", "https")] string protocol, [Values(true, false)] bool async)
{
var url = $"{protocol}://httpbin.org/get";
var request = new JsonWebRequest<HttpBinGetResponse>(url) { Method = HttpMethod.GET };
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
if (async)
Assert.DoesNotThrowAsync(request.PerformAsync);
else
Assert.DoesNotThrow(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsFalse(request.Aborted);
var responseObject = request.ResponseObject;
Assert.IsTrue(responseObject != null);
Assert.IsTrue(responseObject.Headers.UserAgent == "osu!");
Assert.IsTrue(responseObject.Url == url);
Assert.IsFalse(hasThrown);
}
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestInvalidGetExceptions([Values("http", "https")] string protocol, [Values(true, false)] bool async)
{
var request = new WebRequest($"{protocol}://{invalid_get_url}") { Method = HttpMethod.GET };
Exception finishedException = null;
request.Failed += exception => finishedException = exception;
if (async)
Assert.ThrowsAsync<HttpRequestException>(request.PerformAsync);
else
Assert.Throws<HttpRequestException>(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsTrue(request.Aborted);
Assert.IsTrue(request.ResponseString == null);
Assert.IsNotNull(finishedException);
}
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestBadStatusCode([Values(true, false)] bool async)
{
var request = new WebRequest("https://httpbin.org/hidden-basic-auth/user/passwd");
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
if (async)
Assert.ThrowsAsync<WebException>(request.PerformAsync);
else
Assert.Throws<WebException>(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsTrue(request.Aborted);
Assert.IsEmpty(request.ResponseString);
Assert.IsTrue(hasThrown);
}
/// <summary>
/// Tests aborting the <see cref="WebRequest"/> after response has been received from the server
/// but before data has been read.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestAbortReceive([Values(true, false)] bool async)
{
var request = new JsonWebRequest<HttpBinGetResponse>("https://httpbin.org/get") { Method = HttpMethod.GET };
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
request.Started += () => request.Abort();
if (async)
Assert.DoesNotThrowAsync(request.PerformAsync);
else
Assert.DoesNotThrow(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsTrue(request.Aborted);
Assert.IsTrue(request.ResponseObject == null);
Assert.IsFalse(hasThrown);
}
/// <summary>
/// Tests aborting the <see cref="WebRequest"/> before the request is sent to the server.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestAbortRequest()
{
var request = new JsonWebRequest<HttpBinGetResponse>("https://httpbin.org/get") { Method = HttpMethod.GET };
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
#pragma warning disable 4014
request.PerformAsync();
#pragma warning restore 4014
Assert.DoesNotThrow(request.Abort);
Assert.IsTrue(request.Completed);
Assert.IsTrue(request.Aborted);
Assert.IsTrue(request.ResponseObject == null);
Assert.IsFalse(hasThrown);
}
/// <summary>
/// Tests being able to abort + restart a request.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestRestartAfterAbort([Values(true, false)] bool async)
{
var request = new JsonWebRequest<HttpBinGetResponse>("https://httpbin.org/get") { Method = HttpMethod.GET };
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
#pragma warning disable 4014
request.PerformAsync();
#pragma warning restore 4014
Assert.DoesNotThrow(request.Abort);
if (async)
Assert.ThrowsAsync<InvalidOperationException>(request.PerformAsync);
else
Assert.Throws<InvalidOperationException>(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsTrue(request.Aborted);
var responseObject = request.ResponseObject;
Assert.IsTrue(responseObject == null);
Assert.IsFalse(hasThrown);
}
/// <summary>
/// Tests that specifically-crafted <see cref="WebRequest"/> is completed after one timeout.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestOneTimeout()
{
var request = new DelayedWebRequest
{
Method = HttpMethod.GET,
Timeout = 1000,
Delay = 2
};
Exception thrownException = null;
request.Failed += e => thrownException = e;
request.CompleteInvoked = () => request.Delay = 0;
Assert.DoesNotThrow(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsFalse(request.Aborted);
Assert.IsTrue(thrownException == null);
Assert.AreEqual(WebRequest.MAX_RETRIES, request.RetryCount);
}
/// <summary>
/// Tests that a <see cref="WebRequest"/> will only timeout a maximum of <see cref="WebRequest.MAX_RETRIES"/> times before being aborted.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestFailTimeout()
{
var request = new WebRequest("https://httpbin.org/delay/4")
{
Method = HttpMethod.GET,
Timeout = 1000
};
Exception thrownException = null;
request.Failed += e => thrownException = e;
Assert.Throws<WebException>(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsTrue(request.Aborted);
Assert.IsTrue(thrownException != null);
Assert.AreEqual(WebRequest.MAX_RETRIES, request.RetryCount);
Assert.AreEqual(typeof(WebException), thrownException.GetType());
}
/// <summary>
/// Tests being able to abort + restart a request.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestEventUnbindOnCompletion([Values(true, false)] bool async)
{
var request = new JsonWebRequest<HttpBinGetResponse>("https://httpbin.org/get") { Method = HttpMethod.GET };
request.Started += () => { };
request.Failed += e => { };
request.DownloadProgress += (l1, l2) => { };
request.UploadProgress += (l1, l2) => { };
Assert.DoesNotThrow(request.Perform);
var events = request.GetType().GetEvents(BindingFlags.Instance | BindingFlags.Public);
foreach (var e in events)
{
var field = request.GetType().GetField(e.Name, BindingFlags.Instance | BindingFlags.Public);
Assert.IsFalse(((Delegate)field?.GetValue(request))?.GetInvocationList().Length > 0);
}
}
/// <summary>
/// Tests being able to abort + restart a request.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestUnbindOnDispose([Values(true, false)] bool async)
{
WebRequest request;
using (request = new JsonWebRequest<HttpBinGetResponse>("https://httpbin.org/get") { Method = HttpMethod.GET })
{
request.Started += () => { };
request.Failed += e => { };
request.DownloadProgress += (l1, l2) => { };
request.UploadProgress += (l1, l2) => { };
Assert.DoesNotThrow(request.Perform);
}
var events = request.GetType().GetEvents(BindingFlags.Instance | BindingFlags.Public);
foreach (var e in events)
{
var field = request.GetType().GetField(e.Name, BindingFlags.Instance | BindingFlags.Public);
Assert.IsFalse(((Delegate)field?.GetValue(request))?.GetInvocationList().Length > 0);
}
}
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestPostWithJsonResponse([Values(true, false)] bool async)
{
var request = new JsonWebRequest<HttpBinPostResponse>("https://httpbin.org/post") { Method = HttpMethod.POST };
request.AddParameter("testkey1", "testval1");
request.AddParameter("testkey2", "testval2");
if (async)
Assert.DoesNotThrowAsync(request.PerformAsync);
else
Assert.DoesNotThrow(request.Perform);
var responseObject = request.ResponseObject;
Assert.IsTrue(request.Completed);
Assert.IsFalse(request.Aborted);
Assert.IsTrue(responseObject.Form != null);
Assert.IsTrue(responseObject.Form.Count == 2);
Assert.IsTrue(responseObject.Headers.ContentLength > 0);
Assert.IsTrue(responseObject.Form.ContainsKey("testkey1"));
Assert.IsTrue(responseObject.Form["testkey1"] == "testval1");
Assert.IsTrue(responseObject.Form.ContainsKey("testkey2"));
Assert.IsTrue(responseObject.Form["testkey2"] == "testval2");
Assert.IsTrue(responseObject.Headers.ContentType.StartsWith("multipart/form-data; boundary="));
}
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestPostWithJsonRequest([Values(true, false)] bool async)
{
var request = new JsonWebRequest<HttpBinPostResponse>("https://httpbin.org/post") { Method = HttpMethod.POST };
var testObject = new TestObject();
request.AddRaw(JsonConvert.SerializeObject(testObject));
if (async)
Assert.DoesNotThrowAsync(request.PerformAsync);
else
Assert.DoesNotThrow(request.Perform);
var responseObject = request.ResponseObject;
Assert.IsTrue(request.Completed);
Assert.IsFalse(request.Aborted);
Assert.IsTrue(responseObject.Headers.ContentLength > 0);
Assert.IsTrue(responseObject.Json != null);
Assert.AreEqual(testObject.TestString, responseObject.Json.TestString);
Assert.IsTrue(responseObject.Headers.ContentType == null);
}
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestGetBinaryData([Values(true, false)] bool async, [Values(true, false)] bool chunked)
{
const int bytes_count = 65536;
const int chunk_size = 1024;
string endpoint = chunked ? "stream-bytes" : "bytes";
WebRequest request = new WebRequest($"http://httpbin.org/{endpoint}/{bytes_count}") { Method = HttpMethod.GET };
if (chunked)
request.AddParameter("chunk_size", chunk_size.ToString());
if (async)
Assert.DoesNotThrowAsync(request.PerformAsync);
else
Assert.DoesNotThrow(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsFalse(request.Aborted);
Assert.AreEqual(bytes_count, request.ResponseStream.Length);
}
[Serializable]
private class HttpBinGetResponse
{
[JsonProperty("headers")]
public HttpBinHeaders Headers { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
}
[Serializable]
private class HttpBinPostResponse
{
[JsonProperty("data")]
public string Data { get; set; }
[JsonProperty("form")]
public IDictionary<string, string> Form { get; set; }
[JsonProperty("headers")]
public HttpBinHeaders Headers { get; set; }
[JsonProperty("json")]
public TestObject Json { get; set; }
}
[Serializable]
public class HttpBinHeaders
{
[JsonProperty("Content-Length")]
public int ContentLength { get; set; }
[JsonProperty("Content-Type")]
public string ContentType { get; set; }
[JsonProperty("User-Agent")]
public string UserAgent { get; set; }
}
[Serializable]
public class TestObject
{
public string TestString = "readable";
}
private class DelayedWebRequest : WebRequest
{
public Action CompleteInvoked;
private int delay;
public int Delay
{
get { return delay; }
set
{
delay = value;
Url = $"http://httpbin.org/delay/{delay}";
}
}
public DelayedWebRequest()
: base("http://httpbin.org/delay/0")
{
}
protected override void Complete(Exception e = null)
{
CompleteInvoked?.Invoke();
base.Complete(e);
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Reflection;
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.IO.Network;
using HttpMethod = osu.Framework.IO.Network.HttpMethod;
using WebRequest = osu.Framework.IO.Network.WebRequest;
namespace osu.Framework.Tests.IO
{
[TestFixture]
public class TestWebRequest
{
private const string valid_get_url = "httpbin.org/get";
private const string invalid_get_url = "a.ppy.shhhhh";
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestValidGet([Values("http", "https")] string protocol, [Values(true, false)] bool async)
{
var url = $"{protocol}://httpbin.org/get";
var request = new JsonWebRequest<HttpBinGetResponse>(url) { Method = HttpMethod.GET };
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
if (async)
Assert.DoesNotThrowAsync(request.PerformAsync);
else
Assert.DoesNotThrow(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsFalse(request.Aborted);
var responseObject = request.ResponseObject;
Assert.IsTrue(responseObject != null);
Assert.IsTrue(responseObject.Headers.UserAgent == "osu!");
Assert.IsTrue(responseObject.Url == url);
Assert.IsFalse(hasThrown);
}
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestInvalidGetExceptions([Values("http", "https")] string protocol, [Values(true, false)] bool async)
{
var request = new WebRequest($"{protocol}://{invalid_get_url}") { Method = HttpMethod.GET };
Exception finishedException = null;
request.Failed += exception => finishedException = exception;
if (async)
Assert.ThrowsAsync<HttpRequestException>(request.PerformAsync);
else
Assert.Throws<HttpRequestException>(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsTrue(request.Aborted);
Assert.IsTrue(request.ResponseString == null);
Assert.IsNotNull(finishedException);
}
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestBadStatusCode([Values(true, false)] bool async)
{
var request = new WebRequest("https://httpbin.org/hidden-basic-auth/user/passwd");
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
if (async)
Assert.ThrowsAsync<WebException>(request.PerformAsync);
else
Assert.Throws<WebException>(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsTrue(request.Aborted);
Assert.IsEmpty(request.ResponseString);
Assert.IsTrue(hasThrown);
}
/// <summary>
/// Tests aborting the <see cref="WebRequest"/> after response has been received from the server
/// but before data has been read.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestAbortReceive([Values(true, false)] bool async)
{
var request = new JsonWebRequest<HttpBinGetResponse>("https://httpbin.org/get") { Method = HttpMethod.GET };
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
request.Started += () => request.Abort();
if (async)
Assert.DoesNotThrowAsync(request.PerformAsync);
else
Assert.DoesNotThrow(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsTrue(request.Aborted);
Assert.IsTrue(request.ResponseObject == null);
Assert.IsFalse(hasThrown);
}
/// <summary>
/// Tests aborting the <see cref="WebRequest"/> before the request is sent to the server.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestAbortRequest()
{
var request = new JsonWebRequest<HttpBinGetResponse>("https://httpbin.org/get") { Method = HttpMethod.GET };
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
#pragma warning disable 4014
request.PerformAsync();
#pragma warning restore 4014
Assert.DoesNotThrow(request.Abort);
Assert.IsTrue(request.Completed);
Assert.IsTrue(request.Aborted);
Assert.IsTrue(request.ResponseObject == null);
Assert.IsFalse(hasThrown);
}
/// <summary>
/// Tests being able to abort + restart a request.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestRestartAfterAbort([Values(true, false)] bool async)
{
var request = new JsonWebRequest<HttpBinGetResponse>("https://httpbin.org/get") { Method = HttpMethod.GET };
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
#pragma warning disable 4014
request.PerformAsync();
#pragma warning restore 4014
Assert.DoesNotThrow(request.Abort);
if (async)
Assert.ThrowsAsync<InvalidOperationException>(request.PerformAsync);
else
Assert.Throws<InvalidOperationException>(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsTrue(request.Aborted);
var responseObject = request.ResponseObject;
Assert.IsTrue(responseObject == null);
Assert.IsFalse(hasThrown);
}
/// <summary>
/// Tests that specifically-crafted <see cref="WebRequest"/> is completed after one timeout.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestOneTimeout()
{
var request = new DelayedWebRequest
{
Method = HttpMethod.GET,
Timeout = 1000,
Delay = 2
};
Exception thrownException = null;
request.Failed += e => thrownException = e;
request.CompleteInvoked = () => request.Delay = 0;
Assert.DoesNotThrow(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsFalse(request.Aborted);
Assert.IsTrue(thrownException == null);
Assert.AreEqual(WebRequest.MAX_RETRIES, request.RetryCount);
}
/// <summary>
/// Tests that a <see cref="WebRequest"/> will only timeout a maximum of <see cref="WebRequest.MAX_RETRIES"/> times before being aborted.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestFailTimeout()
{
var request = new WebRequest("https://httpbin.org/delay/4")
{
Method = HttpMethod.GET,
Timeout = 1000
};
Exception thrownException = null;
request.Failed += e => thrownException = e;
Assert.Throws<WebException>(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsTrue(request.Aborted);
Assert.IsTrue(thrownException != null);
Assert.AreEqual(WebRequest.MAX_RETRIES, request.RetryCount);
Assert.AreEqual(typeof(WebException), thrownException.GetType());
}
/// <summary>
/// Tests being able to abort + restart a request.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestEventUnbindOnCompletion([Values(true, false)] bool async)
{
var request = new JsonWebRequest<HttpBinGetResponse>("https://httpbin.org/get") { Method = HttpMethod.GET };
request.Started += () => { };
request.Failed += e => { };
request.DownloadProgress += (l1, l2) => { };
request.UploadProgress += (l1, l2) => { };
Assert.DoesNotThrow(request.Perform);
var events = request.GetType().GetEvents(BindingFlags.Instance | BindingFlags.Public);
foreach (var e in events)
{
var field = request.GetType().GetField(e.Name, BindingFlags.Instance | BindingFlags.Public);
Assert.IsFalse(((Delegate)field?.GetValue(request))?.GetInvocationList().Length > 0);
}
}
/// <summary>
/// Tests being able to abort + restart a request.
/// </summary>
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestUnbindOnDispose([Values(true, false)] bool async)
{
WebRequest request;
using (request = new JsonWebRequest<HttpBinGetResponse>("https://httpbin.org/get") { Method = HttpMethod.GET })
{
request.Started += () => { };
request.Failed += e => { };
request.DownloadProgress += (l1, l2) => { };
request.UploadProgress += (l1, l2) => { };
Assert.DoesNotThrow(request.Perform);
}
var events = request.GetType().GetEvents(BindingFlags.Instance | BindingFlags.Public);
foreach (var e in events)
{
var field = request.GetType().GetField(e.Name, BindingFlags.Instance | BindingFlags.Public);
Assert.IsFalse(((Delegate)field?.GetValue(request))?.GetInvocationList().Length > 0);
}
}
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestPostWithJsonResponse([Values(true, false)] bool async)
{
var request = new JsonWebRequest<HttpBinPostResponse>("https://httpbin.org/post") { Method = HttpMethod.POST };
request.AddParameter("testkey1", "testval1");
request.AddParameter("testkey2", "testval2");
if (async)
Assert.DoesNotThrowAsync(request.PerformAsync);
else
Assert.DoesNotThrow(request.Perform);
var responseObject = request.ResponseObject;
Assert.IsTrue(request.Completed);
Assert.IsFalse(request.Aborted);
Assert.IsTrue(responseObject.Form != null);
Assert.IsTrue(responseObject.Form.Count == 2);
Assert.IsTrue(responseObject.Headers.ContentLength > 0);
Assert.IsTrue(responseObject.Form.ContainsKey("testkey1"));
Assert.IsTrue(responseObject.Form["testkey1"] == "testval1");
Assert.IsTrue(responseObject.Form.ContainsKey("testkey2"));
Assert.IsTrue(responseObject.Form["testkey2"] == "testval2");
Assert.IsTrue(responseObject.Headers.ContentType.StartsWith("multipart/form-data; boundary="));
}
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestPostWithJsonRequest([Values(true, false)] bool async)
{
var request = new JsonWebRequest<HttpBinPostResponse>("https://httpbin.org/post") { Method = HttpMethod.POST };
var testObject = new TestObject();
request.AddRaw(JsonConvert.SerializeObject(testObject));
if (async)
Assert.DoesNotThrowAsync(request.PerformAsync);
else
Assert.DoesNotThrow(request.Perform);
var responseObject = request.ResponseObject;
Assert.IsTrue(request.Completed);
Assert.IsFalse(request.Aborted);
Assert.IsTrue(responseObject.Headers.ContentLength > 0);
Assert.IsTrue(responseObject.Json != null);
Assert.AreEqual(testObject.TestString, responseObject.Json.TestString);
Assert.IsTrue(responseObject.Headers.ContentType == null);
}
[Test, Retry(5)]
[Ignore("Broken (appveyor or httpbin.org, see https://ci.appveyor.com/project/peppy/osu-framework/build/5155)")]
public void TestGetBinaryData([Values(true, false)] bool async, [Values(true, false)] bool chunked)
{
const int bytes_count = 65536;
const int chunk_size = 1024;
string endpoint = chunked ? "stream-bytes" : "bytes";
WebRequest request = new WebRequest($"http://httpbin.org/{endpoint}/{bytes_count}") { Method = HttpMethod.GET };
if (chunked)
request.AddParameter("chunk_size", chunk_size.ToString());
if (async)
Assert.DoesNotThrowAsync(request.PerformAsync);
else
Assert.DoesNotThrow(request.Perform);
Assert.IsTrue(request.Completed);
Assert.IsFalse(request.Aborted);
Assert.AreEqual(bytes_count, request.ResponseStream.Length);
}
[Serializable]
private class HttpBinGetResponse
{
[JsonProperty("headers")]
public HttpBinHeaders Headers { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
}
[Serializable]
private class HttpBinPostResponse
{
[JsonProperty("data")]
public string Data { get; set; }
[JsonProperty("form")]
public IDictionary<string, string> Form { get; set; }
[JsonProperty("headers")]
public HttpBinHeaders Headers { get; set; }
[JsonProperty("json")]
public TestObject Json { get; set; }
}
[Serializable]
public class HttpBinHeaders
{
[JsonProperty("Content-Length")]
public int ContentLength { get; set; }
[JsonProperty("Content-Type")]
public string ContentType { get; set; }
[JsonProperty("User-Agent")]
public string UserAgent { get; set; }
}
[Serializable]
public class TestObject
{
public string TestString = "readable";
}
private class DelayedWebRequest : WebRequest
{
public Action CompleteInvoked;
private int delay;
public int Delay
{
get { return delay; }
set
{
delay = value;
Url = $"http://httpbin.org/delay/{delay}";
}
}
public DelayedWebRequest()
: base("http://httpbin.org/delay/0")
{
}
protected override void Complete(Exception e = null)
{
CompleteInvoked?.Invoke();
base.Complete(e);
}
}
}
}

View File

@@ -1,140 +1,140 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Extensions;
namespace osu.Framework.Tests.Lists
{
[TestFixture]
public class TestArrayExtensions
{
[Test]
public void TestNullToJagged()
{
int[][] result = null;
Assert.DoesNotThrow(() => result = ((int[,])null).ToJagged());
Assert.AreEqual(null, result);
}
[Test]
public void TestNullToRectangular()
{
int[,] result = null;
Assert.DoesNotThrow(() => result = ((int[][])null).ToRectangular());
Assert.AreEqual(null, result);
}
[Test]
public void TestEmptyRectangularToJagged()
{
int[][] result = null;
Assert.DoesNotThrow(() => result = new int[0, 0].ToJagged());
Assert.AreEqual(0, result.Length);
}
[Test]
public void TestEmptyJaggedToRectangular()
{
int[,] result = null;
Assert.DoesNotThrow(() => result = new int[0][].ToRectangular());
Assert.AreEqual(0, result.Length);
}
[Test]
public void TestRectangularColumnToJagged()
{
int[][] result = null;
Assert.DoesNotThrow(() => result = new int[1, 10].ToJagged());
Assert.AreEqual(1, result.Length);
Assert.AreEqual(10, result[0].Length);
}
[Test]
public void TestJaggedColumnToRectangular()
{
var jagged = new int[10][];
int[,] result = null;
Assert.DoesNotThrow(() => result = jagged.ToRectangular());
Assert.AreEqual(10, result.GetLength(0));
}
[Test]
public void TestRectangularRowToJagged()
{
int[][] result = null;
Assert.DoesNotThrow(() => result = new int[10, 0].ToJagged());
Assert.AreEqual(10, result.Length);
for (int i = 0; i < 10; i++)
Assert.AreEqual(0, result[i].Length);
}
[Test]
public void TestJaggedRowToRectangular()
{
var jagged = new int[1][];
jagged[0] = new int[10];
int[,] result = null;
Assert.DoesNotThrow(() => result = jagged.ToRectangular());
Assert.AreEqual(10, result.GetLength(1));
Assert.AreEqual(1, result.GetLength(0));
}
[Test]
public void TestSquareRectangularToJagged()
{
int[][] result = null;
Assert.DoesNotThrow(() => result = new int[10, 10].ToJagged());
Assert.AreEqual(10, result.Length);
for (int i = 0; i < 10; i++)
Assert.AreEqual(10, result[i].Length);
}
[Test]
public void TestSquareJaggedToRectangular()
{
var jagged = new int[10][];
for (int i = 0; i < 10; i++)
jagged[i] = new int[10];
int[,] result = null;
Assert.DoesNotThrow(() => result = jagged.ToRectangular());
Assert.AreEqual(10, result.GetLength(0));
Assert.AreEqual(10, result.GetLength(1));
}
[Test]
public void TestNonSquareJaggedToRectangular()
{
var jagged = new int[10][];
for (int i = 0; i < 10; i++)
jagged[i] = new int[i];
int[,] result = null;
Assert.DoesNotThrow(() => result = jagged.ToRectangular());
Assert.AreEqual(10, result.GetLength(0));
Assert.AreEqual(9, result.GetLength(1));
}
[Test]
public void TestNonSquareJaggedWithNullRowsToRectangular()
{
var jagged = new int[10][];
for (int i = 1; i < 10; i += 2)
{
if (i % 2 == 1)
jagged[i] = new int[i];
}
int[,] result = null;
Assert.DoesNotThrow(() => result = jagged.ToRectangular());
Assert.AreEqual(10, result.GetLength(0));
Assert.AreEqual(9, result.GetLength(1));
for (int i = 0; i < 10; i++)
Assert.AreEqual(0, result[i, 0]);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Extensions;
namespace osu.Framework.Tests.Lists
{
[TestFixture]
public class TestArrayExtensions
{
[Test]
public void TestNullToJagged()
{
int[][] result = null;
Assert.DoesNotThrow(() => result = ((int[,])null).ToJagged());
Assert.AreEqual(null, result);
}
[Test]
public void TestNullToRectangular()
{
int[,] result = null;
Assert.DoesNotThrow(() => result = ((int[][])null).ToRectangular());
Assert.AreEqual(null, result);
}
[Test]
public void TestEmptyRectangularToJagged()
{
int[][] result = null;
Assert.DoesNotThrow(() => result = new int[0, 0].ToJagged());
Assert.AreEqual(0, result.Length);
}
[Test]
public void TestEmptyJaggedToRectangular()
{
int[,] result = null;
Assert.DoesNotThrow(() => result = new int[0][].ToRectangular());
Assert.AreEqual(0, result.Length);
}
[Test]
public void TestRectangularColumnToJagged()
{
int[][] result = null;
Assert.DoesNotThrow(() => result = new int[1, 10].ToJagged());
Assert.AreEqual(1, result.Length);
Assert.AreEqual(10, result[0].Length);
}
[Test]
public void TestJaggedColumnToRectangular()
{
var jagged = new int[10][];
int[,] result = null;
Assert.DoesNotThrow(() => result = jagged.ToRectangular());
Assert.AreEqual(10, result.GetLength(0));
}
[Test]
public void TestRectangularRowToJagged()
{
int[][] result = null;
Assert.DoesNotThrow(() => result = new int[10, 0].ToJagged());
Assert.AreEqual(10, result.Length);
for (int i = 0; i < 10; i++)
Assert.AreEqual(0, result[i].Length);
}
[Test]
public void TestJaggedRowToRectangular()
{
var jagged = new int[1][];
jagged[0] = new int[10];
int[,] result = null;
Assert.DoesNotThrow(() => result = jagged.ToRectangular());
Assert.AreEqual(10, result.GetLength(1));
Assert.AreEqual(1, result.GetLength(0));
}
[Test]
public void TestSquareRectangularToJagged()
{
int[][] result = null;
Assert.DoesNotThrow(() => result = new int[10, 10].ToJagged());
Assert.AreEqual(10, result.Length);
for (int i = 0; i < 10; i++)
Assert.AreEqual(10, result[i].Length);
}
[Test]
public void TestSquareJaggedToRectangular()
{
var jagged = new int[10][];
for (int i = 0; i < 10; i++)
jagged[i] = new int[10];
int[,] result = null;
Assert.DoesNotThrow(() => result = jagged.ToRectangular());
Assert.AreEqual(10, result.GetLength(0));
Assert.AreEqual(10, result.GetLength(1));
}
[Test]
public void TestNonSquareJaggedToRectangular()
{
var jagged = new int[10][];
for (int i = 0; i < 10; i++)
jagged[i] = new int[i];
int[,] result = null;
Assert.DoesNotThrow(() => result = jagged.ToRectangular());
Assert.AreEqual(10, result.GetLength(0));
Assert.AreEqual(9, result.GetLength(1));
}
[Test]
public void TestNonSquareJaggedWithNullRowsToRectangular()
{
var jagged = new int[10][];
for (int i = 1; i < 10; i += 2)
{
if (i % 2 == 1)
jagged[i] = new int[i];
}
int[,] result = null;
Assert.DoesNotThrow(() => result = jagged.ToRectangular());
Assert.AreEqual(10, result.GetLength(0));
Assert.AreEqual(9, result.GetLength(1));
for (int i = 0; i < 10; i++)
Assert.AreEqual(0, result[i, 0]);
}
}
}

View File

@@ -1,88 +1,88 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Lists;
using NUnit.Framework;
namespace osu.Framework.Tests.Lists
{
[TestFixture]
public class TestSortedList
{
[Test]
public void TestAdd()
{
var list = new SortedList<int>(Comparer<int>.Create((a, b) => a - b))
{
10,
8,
13,
-10
};
Assert.AreEqual(-10, list[0]);
Assert.AreEqual(8, list[1]);
Assert.AreEqual(10, list[2]);
Assert.AreEqual(13, list[3]);
}
[Test]
public void TestRemove()
{
var list = new SortedList<int>(Comparer<int>.Create((a, b) => a - b))
{
10,
8,
13,
-10
};
list.Remove(8);
Assert.IsFalse(list.Any(i => i == 8));
Assert.AreEqual(3, list.Count);
}
[Test]
public void TestRemoveAt()
{
var list = new SortedList<int>(Comparer<int>.Create((a, b) => a - b))
{
10,
8,
13,
-10
};
list.RemoveAt(0);
Assert.IsFalse(list.Any(i => i == -10));
Assert.AreEqual(3, list.Count);
}
[Test]
public void TestClear()
{
var list = new SortedList<int>(Comparer<int>.Create((a, b) => a - b))
{
10,
8,
13,
-10
};
list.Clear();
Assert.IsFalse(list.Any());
Assert.AreEqual(0, list.Count);
}
[Test]
public void TestIndexOf()
{
var list = new SortedList<int>(Comparer<int>.Default)
{
10,
10,
10,
};
Assert.IsTrue(list.IndexOf(10) >= 0);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Lists;
using NUnit.Framework;
namespace osu.Framework.Tests.Lists
{
[TestFixture]
public class TestSortedList
{
[Test]
public void TestAdd()
{
var list = new SortedList<int>(Comparer<int>.Create((a, b) => a - b))
{
10,
8,
13,
-10
};
Assert.AreEqual(-10, list[0]);
Assert.AreEqual(8, list[1]);
Assert.AreEqual(10, list[2]);
Assert.AreEqual(13, list[3]);
}
[Test]
public void TestRemove()
{
var list = new SortedList<int>(Comparer<int>.Create((a, b) => a - b))
{
10,
8,
13,
-10
};
list.Remove(8);
Assert.IsFalse(list.Any(i => i == 8));
Assert.AreEqual(3, list.Count);
}
[Test]
public void TestRemoveAt()
{
var list = new SortedList<int>(Comparer<int>.Create((a, b) => a - b))
{
10,
8,
13,
-10
};
list.RemoveAt(0);
Assert.IsFalse(list.Any(i => i == -10));
Assert.AreEqual(3, list.Count);
}
[Test]
public void TestClear()
{
var list = new SortedList<int>(Comparer<int>.Create((a, b) => a - b))
{
10,
8,
13,
-10
};
list.Clear();
Assert.IsFalse(list.Any());
Assert.AreEqual(0, list.Count);
}
[Test]
public void TestIndexOf()
{
var list = new SortedList<int>(Comparer<int>.Default)
{
10,
10,
10,
};
Assert.IsTrue(list.IndexOf(10) >= 0);
}
}
}

View File

@@ -1,20 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.MathUtils;
namespace osu.Framework.Tests.MathUtils
{
[TestFixture]
public class TestInterpolation
{
[Test]
public void TestLerp()
{
Assert.AreEqual(5, Interpolation.Lerp(0, 10, 0.5f));
Assert.AreEqual(0, Interpolation.Lerp(0, 10, 0));
Assert.AreEqual(10, Interpolation.Lerp(0, 10, 1));
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.MathUtils;
namespace osu.Framework.Tests.MathUtils
{
[TestFixture]
public class TestInterpolation
{
[Test]
public void TestLerp()
{
Assert.AreEqual(5, Interpolation.Lerp(0, 10, 0.5f));
Assert.AreEqual(0, Interpolation.Lerp(0, 10, 0));
Assert.AreEqual(10, Interpolation.Lerp(0, 10, 1));
}
}
}

View File

@@ -1,51 +1,51 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Threading;
using NUnit.Framework;
using osu.Framework.Platform;
namespace osu.Framework.Tests.Platform
{
[TestFixture]
public class HeadlessGameHostTest
{
private class Foobar
{
public string Bar;
}
[Test]
public void TestIpc()
{
using (var server = new HeadlessGameHost(@"server", true))
using (var client = new HeadlessGameHost(@"client", true))
{
Assert.IsTrue(server.IsPrimaryInstance, @"Server wasn't able to bind");
Assert.IsFalse(client.IsPrimaryInstance, @"Client was able to bind when it shouldn't have been able to");
var serverChannel = new IpcChannel<Foobar>(server);
var clientChannel = new IpcChannel<Foobar>(client);
Action waitAction = () =>
{
bool received = false;
serverChannel.MessageReceived += message =>
{
Assert.AreEqual("example", message.Bar);
received = true;
};
clientChannel.SendMessageAsync(new Foobar { Bar = "example" }).Wait();
while (!received)
Thread.Sleep(1);
};
Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(10000),
@"Message was not received in a timely fashion");
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Threading;
using NUnit.Framework;
using osu.Framework.Platform;
namespace osu.Framework.Tests.Platform
{
[TestFixture]
public class HeadlessGameHostTest
{
private class Foobar
{
public string Bar;
}
[Test]
public void TestIpc()
{
using (var server = new HeadlessGameHost(@"server", true))
using (var client = new HeadlessGameHost(@"client", true))
{
Assert.IsTrue(server.IsPrimaryInstance, @"Server wasn't able to bind");
Assert.IsFalse(client.IsPrimaryInstance, @"Client was able to bind when it shouldn't have been able to");
var serverChannel = new IpcChannel<Foobar>(server);
var clientChannel = new IpcChannel<Foobar>(client);
Action waitAction = () =>
{
bool received = false;
serverChannel.MessageReceived += message =>
{
Assert.AreEqual("example", message.Bar);
received = true;
};
clientChannel.SendMessageAsync(new Foobar { Bar = "example" }).Wait();
while (!received)
Thread.Sleep(1);
};
Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(10000),
@"Message was not received in a timely fashion");
}
}
}
}

View File

@@ -1,25 +1,25 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Platform;
namespace osu.Framework.Tests
{
public static class Program
{
[STAThread]
public static void Main(string[] args)
{
bool benchmark = args.Length > 0 && args[0] == @"-benchmark";
using (GameHost host = Host.GetSuitableHost(@"visual-tests"))
{
if (benchmark)
host.Run(new AutomatedVisualTestGame());
else
host.Run(new VisualTestGame());
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Platform;
namespace osu.Framework.Tests
{
public static class Program
{
[STAThread]
public static void Main(string[] args)
{
bool benchmark = args.Length > 0 && args[0] == @"-benchmark";
using (GameHost host = Host.GetSuitableHost(@"visual-tests"))
{
if (benchmark)
host.Run(new AutomatedVisualTestGame());
else
host.Run(new VisualTestGame());
}
}
}
}

View File

@@ -1,18 +1,18 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Reflection;
using osu.Framework.Allocation;
using osu.Framework.IO.Stores;
namespace osu.Framework.Tests
{
internal class TestGame : Game
{
[BackgroundDependencyLoader]
private void load()
{
Resources.AddStore(new NamespacedResourceStore<byte[]>(new DllResourceStore(Assembly.GetExecutingAssembly().Location), "Resources"));
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Reflection;
using osu.Framework.Allocation;
using osu.Framework.IO.Stores;
namespace osu.Framework.Tests
{
internal class TestGame : Game
{
[BackgroundDependencyLoader]
private void load()
{
Resources.AddStore(new NamespacedResourceStore<byte[]>(new DllResourceStore(Assembly.GetExecutingAssembly().Location), "Resources"));
}
}
}

View File

@@ -1,33 +1,33 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
public abstract class FrameworkTestCase : TestCase
{
public override void RunTest()
{
using (var host = new HeadlessGameHost())
host.Run(new FrameworkTestCaseTestRunner(this));
}
private class FrameworkTestCaseTestRunner : TestCaseTestRunner
{
public FrameworkTestCaseTestRunner(TestCase testCase)
: base(testCase)
{
}
[BackgroundDependencyLoader]
private void load()
{
Resources.AddStore(new NamespacedResourceStore<byte[]>(new DllResourceStore(@"osu.Framework.Tests.exe"), "Resources"));
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
public abstract class FrameworkTestCase : TestCase
{
public override void RunTest()
{
using (var host = new HeadlessGameHost())
host.Run(new FrameworkTestCaseTestRunner(this));
}
private class FrameworkTestCaseTestRunner : TestCaseTestRunner
{
public FrameworkTestCaseTestRunner(TestCase testCase)
: base(testCase)
{
}
[BackgroundDependencyLoader]
private void load()
{
Resources.AddStore(new NamespacedResourceStore<byte[]>(new DllResourceStore(@"osu.Framework.Tests.exe"), "Resources"));
}
}
}
}

View File

@@ -1,65 +1,65 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("frame-based animations")]
public class TestCaseAnimation : TestCase
{
public TestCaseAnimation()
{
DrawableAnimation drawableAnimation;
Add(new Container
{
Position = new Vector2(10f, 10f),
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new DelayedLoadWrapper(new AvatarAnimation
{
AutoSizeAxes = Axes.None,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.25f)
}),
drawableAnimation = new DrawableAnimation
{
RelativePositionAxes = Axes.Both,
Position = new Vector2(0f, 0.3f),
AutoSizeAxes = Axes.None,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.25f)
}
}
});
drawableAnimation.AddFrames(new[]
{
new FrameData<Drawable>(new Box { Size = new Vector2(50f), Colour = Color4.Red }, 500),
new FrameData<Drawable>(new Box { Size = new Vector2(50f), Colour = Color4.Green }, 500),
new FrameData<Drawable>(new Box { Size = new Vector2(50f), Colour = Color4.Blue }, 500),
});
}
private class AvatarAnimation : TextureAnimation
{
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
AddFrame(textures.Get("https://a.ppy.sh/2"), 500);
AddFrame(textures.Get("https://a.ppy.sh/3"), 500);
AddFrame(textures.Get("https://a.ppy.sh/1876669"), 500);
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("frame-based animations")]
public class TestCaseAnimation : TestCase
{
public TestCaseAnimation()
{
DrawableAnimation drawableAnimation;
Add(new Container
{
Position = new Vector2(10f, 10f),
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new DelayedLoadWrapper(new AvatarAnimation
{
AutoSizeAxes = Axes.None,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.25f)
}),
drawableAnimation = new DrawableAnimation
{
RelativePositionAxes = Axes.Both,
Position = new Vector2(0f, 0.3f),
AutoSizeAxes = Axes.None,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.25f)
}
}
});
drawableAnimation.AddFrames(new[]
{
new FrameData<Drawable>(new Box { Size = new Vector2(50f), Colour = Color4.Red }, 500),
new FrameData<Drawable>(new Box { Size = new Vector2(50f), Colour = Color4.Green }, 500),
new FrameData<Drawable>(new Box { Size = new Vector2(50f), Colour = Color4.Blue }, 500),
});
}
private class AvatarAnimation : TextureAnimation
{
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
AddFrame(textures.Get("https://a.ppy.sh/2"), 500);
AddFrame(textures.Get("https://a.ppy.sh/3"), 500);
AddFrame(textures.Get("https://a.ppy.sh/1876669"), 500);
}
}
}
}

View File

@@ -1,232 +1,232 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Globalization;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
public class TestCaseBindableNumbers : TestCase
{
private readonly BindableInt bindableInt = new BindableInt();
private readonly BindableLong bindableLong = new BindableLong();
private readonly BindableDouble bindableDouble = new BindableDouble();
private readonly BindableFloat bindableFloat = new BindableFloat();
public TestCaseBindableNumbers()
{
AddStep("Reset", () =>
{
setValue(0);
setPrecision(1);
});
testBasic();
testPrecision3();
testPrecision10();
testMinMaxWithoutPrecision();
testMinMaxWithPrecision();
testInvalidPrecision();
testFractionalPrecision();
AddSliderStep("Min value", -100, 100, -100, setMin);
AddSliderStep("Max value", -100, 100, 100, setMax);
AddSliderStep("Value", -100, 100, 0, setValue);
AddSliderStep("Precision", 1, 10, 1, setPrecision);
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new BindableDisplayContainer<int>(bindableInt),
new BindableDisplayContainer<long>(bindableLong),
},
new Drawable[]
{
new BindableDisplayContainer<float>(bindableFloat),
new BindableDisplayContainer<double>(bindableDouble),
}
}
};
}
/// <summary>
/// Tests basic setting of values.
/// </summary>
private void testBasic()
{
AddStep("Value = 10", () => setValue(10));
AddAssert("Check = 10", () => checkExact(10));
}
/// <summary>
/// Tests that midpoint values are correctly rounded with a precision of 3.
/// </summary>
private void testPrecision3()
{
AddStep("Precision = 3", () => setPrecision(3));
AddStep("Value = 4", () => setValue(3));
AddAssert("Check = 3", () => checkExact(3));
AddStep("Value = 5", () => setValue(5));
AddAssert("Check = 6", () => checkExact(6));
AddStep("Value = 59", () => setValue(59));
AddAssert("Check = 60", () => checkExact(60));
}
/// <summary>
/// Tests that midpoint values are correctly rounded with a precision of 10.
/// </summary>
private void testPrecision10()
{
AddStep("Precision = 10", () => setPrecision(10));
AddStep("Value = 6", () => setValue(6));
AddAssert("Check = 10", () => checkExact(10));
}
/// <summary>
/// Tests that values are correctly clamped to min/max values.
/// </summary>
private void testMinMaxWithoutPrecision()
{
AddStep("Precision = 1", () => setPrecision(1));
AddStep("Min = -30", () => setMin(-30));
AddStep("Max = 30", () => setMax(30));
AddStep("Value = -50", () => setValue(-50));
AddAssert("Check = -30", () => checkExact(-30));
AddStep("Value = 50", () => setValue(50));
AddAssert("Check = 30", () => checkExact(30));
}
/// <summary>
/// Tests that values are correctly clamped to min/max values when precision is involved.
/// In this case, precision is preferred over min/max values.
/// </summary>
private void testMinMaxWithPrecision()
{
AddStep("Precision = 5", () => setPrecision(5));
AddStep("Min = -27", () => setMin(-27));
AddStep("Max = 27", () => setMax(27));
AddStep("Value = -30", () => setValue(-30));
AddAssert("Check = -25", () => checkExact(-25));
AddStep("Value = 30", () => setValue(30));
AddAssert("Check = 25", () => checkExact(25));
}
/// <summary>
/// Tests that invalid precisions cause exceptions.
/// </summary>
private void testInvalidPrecision()
{
AddAssert("Precision = 0 throws", () =>
{
try
{
setPrecision(0);
return false;
}
catch (Exception)
{
return true;
}
});
AddAssert("Precision = -1 throws", () =>
{
try
{
setPrecision(-1);
return false;
}
catch (Exception)
{
return true;
}
});
}
/// <summary>
/// Tests that fractional precisions are obeyed.
/// Note that int bindables are assigned int precisions/values, so their results will differ.
/// </summary>
private void testFractionalPrecision()
{
AddStep("Precision = 2.25/2", () => setPrecision(2.25));
AddStep("Value = 3.3/3", () => setValue(3.3));
AddAssert("Check = 2.25/4", () => checkExact(2.25m, 4));
AddStep("Value = 4.17/4", () => setValue(4.17));
AddAssert("Check = 4.5/4", () => checkExact(4.5m, 4));
}
private bool checkExact(decimal value) => checkExact(value, value);
private bool checkExact(decimal floatValue, decimal intValue)
=> bindableInt.Value == Convert.ToInt32(intValue)
&& bindableLong.Value == Convert.ToInt64(intValue)
&& bindableFloat.Value == Convert.ToSingle(floatValue)
&& bindableDouble.Value == Convert.ToDouble(floatValue);
private void setMin<T>(T value)
{
bindableInt.MinValue = Convert.ToInt32(value);
bindableLong.MinValue = Convert.ToInt64(value);
bindableFloat.MinValue = Convert.ToSingle(value);
bindableDouble.MinValue = Convert.ToDouble(value);
}
private void setMax<T>(T value)
{
bindableInt.MaxValue = Convert.ToInt32(value);
bindableLong.MaxValue = Convert.ToInt64(value);
bindableFloat.MaxValue = Convert.ToSingle(value);
bindableDouble.MaxValue = Convert.ToDouble(value);
}
private void setValue<T>(T value)
{
bindableInt.Value = Convert.ToInt32(value);
bindableLong.Value = Convert.ToInt64(value);
bindableFloat.Value = Convert.ToSingle(value);
bindableDouble.Value = Convert.ToDouble(value);
}
private void setPrecision<T>(T precision)
{
bindableInt.Precision = Convert.ToInt32(precision);
bindableLong.Precision = Convert.ToInt64(precision);
bindableFloat.Precision = Convert.ToSingle(precision);
bindableDouble.Precision = Convert.ToDouble(precision);
}
private class BindableDisplayContainer<T> : CompositeDrawable
where T : struct, IComparable, IConvertible
{
public BindableDisplayContainer(BindableNumber<T> bindable)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
SpriteText valueText;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new SpriteText { Text = $"{typeof(T).Name} value:" },
valueText = new SpriteText { Text = bindable.Value.ToString(CultureInfo.InvariantCulture) }
}
};
bindable.ValueChanged += v => valueText.Text = v.ToString(CultureInfo.InvariantCulture);
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Globalization;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
public class TestCaseBindableNumbers : TestCase
{
private readonly BindableInt bindableInt = new BindableInt();
private readonly BindableLong bindableLong = new BindableLong();
private readonly BindableDouble bindableDouble = new BindableDouble();
private readonly BindableFloat bindableFloat = new BindableFloat();
public TestCaseBindableNumbers()
{
AddStep("Reset", () =>
{
setValue(0);
setPrecision(1);
});
testBasic();
testPrecision3();
testPrecision10();
testMinMaxWithoutPrecision();
testMinMaxWithPrecision();
testInvalidPrecision();
testFractionalPrecision();
AddSliderStep("Min value", -100, 100, -100, setMin);
AddSliderStep("Max value", -100, 100, 100, setMax);
AddSliderStep("Value", -100, 100, 0, setValue);
AddSliderStep("Precision", 1, 10, 1, setPrecision);
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new BindableDisplayContainer<int>(bindableInt),
new BindableDisplayContainer<long>(bindableLong),
},
new Drawable[]
{
new BindableDisplayContainer<float>(bindableFloat),
new BindableDisplayContainer<double>(bindableDouble),
}
}
};
}
/// <summary>
/// Tests basic setting of values.
/// </summary>
private void testBasic()
{
AddStep("Value = 10", () => setValue(10));
AddAssert("Check = 10", () => checkExact(10));
}
/// <summary>
/// Tests that midpoint values are correctly rounded with a precision of 3.
/// </summary>
private void testPrecision3()
{
AddStep("Precision = 3", () => setPrecision(3));
AddStep("Value = 4", () => setValue(3));
AddAssert("Check = 3", () => checkExact(3));
AddStep("Value = 5", () => setValue(5));
AddAssert("Check = 6", () => checkExact(6));
AddStep("Value = 59", () => setValue(59));
AddAssert("Check = 60", () => checkExact(60));
}
/// <summary>
/// Tests that midpoint values are correctly rounded with a precision of 10.
/// </summary>
private void testPrecision10()
{
AddStep("Precision = 10", () => setPrecision(10));
AddStep("Value = 6", () => setValue(6));
AddAssert("Check = 10", () => checkExact(10));
}
/// <summary>
/// Tests that values are correctly clamped to min/max values.
/// </summary>
private void testMinMaxWithoutPrecision()
{
AddStep("Precision = 1", () => setPrecision(1));
AddStep("Min = -30", () => setMin(-30));
AddStep("Max = 30", () => setMax(30));
AddStep("Value = -50", () => setValue(-50));
AddAssert("Check = -30", () => checkExact(-30));
AddStep("Value = 50", () => setValue(50));
AddAssert("Check = 30", () => checkExact(30));
}
/// <summary>
/// Tests that values are correctly clamped to min/max values when precision is involved.
/// In this case, precision is preferred over min/max values.
/// </summary>
private void testMinMaxWithPrecision()
{
AddStep("Precision = 5", () => setPrecision(5));
AddStep("Min = -27", () => setMin(-27));
AddStep("Max = 27", () => setMax(27));
AddStep("Value = -30", () => setValue(-30));
AddAssert("Check = -25", () => checkExact(-25));
AddStep("Value = 30", () => setValue(30));
AddAssert("Check = 25", () => checkExact(25));
}
/// <summary>
/// Tests that invalid precisions cause exceptions.
/// </summary>
private void testInvalidPrecision()
{
AddAssert("Precision = 0 throws", () =>
{
try
{
setPrecision(0);
return false;
}
catch (Exception)
{
return true;
}
});
AddAssert("Precision = -1 throws", () =>
{
try
{
setPrecision(-1);
return false;
}
catch (Exception)
{
return true;
}
});
}
/// <summary>
/// Tests that fractional precisions are obeyed.
/// Note that int bindables are assigned int precisions/values, so their results will differ.
/// </summary>
private void testFractionalPrecision()
{
AddStep("Precision = 2.25/2", () => setPrecision(2.25));
AddStep("Value = 3.3/3", () => setValue(3.3));
AddAssert("Check = 2.25/4", () => checkExact(2.25m, 4));
AddStep("Value = 4.17/4", () => setValue(4.17));
AddAssert("Check = 4.5/4", () => checkExact(4.5m, 4));
}
private bool checkExact(decimal value) => checkExact(value, value);
private bool checkExact(decimal floatValue, decimal intValue)
=> bindableInt.Value == Convert.ToInt32(intValue)
&& bindableLong.Value == Convert.ToInt64(intValue)
&& bindableFloat.Value == Convert.ToSingle(floatValue)
&& bindableDouble.Value == Convert.ToDouble(floatValue);
private void setMin<T>(T value)
{
bindableInt.MinValue = Convert.ToInt32(value);
bindableLong.MinValue = Convert.ToInt64(value);
bindableFloat.MinValue = Convert.ToSingle(value);
bindableDouble.MinValue = Convert.ToDouble(value);
}
private void setMax<T>(T value)
{
bindableInt.MaxValue = Convert.ToInt32(value);
bindableLong.MaxValue = Convert.ToInt64(value);
bindableFloat.MaxValue = Convert.ToSingle(value);
bindableDouble.MaxValue = Convert.ToDouble(value);
}
private void setValue<T>(T value)
{
bindableInt.Value = Convert.ToInt32(value);
bindableLong.Value = Convert.ToInt64(value);
bindableFloat.Value = Convert.ToSingle(value);
bindableDouble.Value = Convert.ToDouble(value);
}
private void setPrecision<T>(T precision)
{
bindableInt.Precision = Convert.ToInt32(precision);
bindableLong.Precision = Convert.ToInt64(precision);
bindableFloat.Precision = Convert.ToSingle(precision);
bindableDouble.Precision = Convert.ToDouble(precision);
}
private class BindableDisplayContainer<T> : CompositeDrawable
where T : struct, IComparable, IConvertible
{
public BindableDisplayContainer(BindableNumber<T> bindable)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
SpriteText valueText;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new SpriteText { Text = $"{typeof(T).Name} value:" },
valueText = new SpriteText { Text = bindable.Value.ToString(CultureInfo.InvariantCulture) }
}
};
bindable.ValueChanged += v => valueText.Text = v.ToString(CultureInfo.InvariantCulture);
}
}
}
}

View File

@@ -1,177 +1,177 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseBlending : TestCase
{
private readonly Dropdown<BlendingMode> colourModeDropdown;
private readonly Dropdown<BlendingEquation> colourEquation;
private readonly Dropdown<BlendingEquation> alphaEquation;
private readonly BufferedContainer foregroundContainer;
public TestCaseBlending()
{
Children = new Drawable[]
{
new FillFlowContainer
{
Name = "Settings",
AutoSizeAxes = Axes.Both,
Y = 50,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 20),
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
new SpriteText { Text = "Blending mode" },
colourModeDropdown = new BasicDropdown<BlendingMode> { Width = 200 }
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
new SpriteText { Text = "Blending equation (colour)" },
colourEquation = new BasicDropdown<BlendingEquation> { Width = 200 }
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
new SpriteText { Text = "Blending equation (alpha)" },
alphaEquation = new BasicDropdown<BlendingEquation> { Width = 200 }
}
}
}
},
new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Behind background"
},
new BufferedContainer
{
Name = "Background",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Size = new Vector2(0.85f),
Masking = true,
Children = new Drawable[]
{
new GradientPart(0, Color4.Orange, Color4.Yellow),
new GradientPart(1, Color4.Yellow, Color4.Green),
new GradientPart(2, Color4.Green, Color4.Cyan),
new GradientPart(3, Color4.Cyan, Color4.Blue),
new GradientPart(4, Color4.Blue, Color4.Violet),
foregroundContainer = new BufferedContainer
{
Name = "Foreground",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Alpha = 0.8f,
Children = new[]
{
new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.45f),
Y = -0.15f,
Colour = Color4.Cyan
},
new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.45f),
X = -0.15f,
Colour = Color4.Magenta
},
new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.45f),
X = 0.15f,
Colour = Color4.Yellow
},
}
},
}
},
};
colourModeDropdown.Items = Enum.GetNames(typeof(BlendingMode)).Select(n => new KeyValuePair<string, BlendingMode>(n, (BlendingMode)Enum.Parse(typeof(BlendingMode), n)));
colourEquation.Items = Enum.GetNames(typeof(BlendingEquation)).Select(n => new KeyValuePair<string, BlendingEquation>(n, (BlendingEquation)Enum.Parse(typeof(BlendingEquation), n)));
alphaEquation.Items = Enum.GetNames(typeof(BlendingEquation)).Select(n => new KeyValuePair<string, BlendingEquation>(n, (BlendingEquation)Enum.Parse(typeof(BlendingEquation), n)));
colourModeDropdown.Current.Value = foregroundContainer.Blending.Mode;
colourEquation.Current.Value = foregroundContainer.Blending.RGBEquation;
alphaEquation.Current.Value = foregroundContainer.Blending.AlphaEquation;
colourModeDropdown.Current.ValueChanged += v => updateBlending();
colourEquation.Current.ValueChanged += v => updateBlending();
alphaEquation.Current.ValueChanged += v => updateBlending();
}
private void updateBlending()
{
foregroundContainer.Blending = new BlendingParameters
{
Mode = colourModeDropdown.Current,
RGBEquation = colourEquation.Current,
AlphaEquation = alphaEquation.Current
};
}
private class GradientPart : Box
{
public GradientPart(int index, Color4 start, Color4 end)
{
RelativeSizeAxes = Axes.Both;
RelativePositionAxes = Axes.Both;
Width = 1 / 5f; // Assume 5 gradients
X = 1 / 5f * index;
Colour = ColourInfo.GradientHorizontal(start, end);
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseBlending : TestCase
{
private readonly Dropdown<BlendingMode> colourModeDropdown;
private readonly Dropdown<BlendingEquation> colourEquation;
private readonly Dropdown<BlendingEquation> alphaEquation;
private readonly BufferedContainer foregroundContainer;
public TestCaseBlending()
{
Children = new Drawable[]
{
new FillFlowContainer
{
Name = "Settings",
AutoSizeAxes = Axes.Both,
Y = 50,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 20),
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
new SpriteText { Text = "Blending mode" },
colourModeDropdown = new BasicDropdown<BlendingMode> { Width = 200 }
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
new SpriteText { Text = "Blending equation (colour)" },
colourEquation = new BasicDropdown<BlendingEquation> { Width = 200 }
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
new SpriteText { Text = "Blending equation (alpha)" },
alphaEquation = new BasicDropdown<BlendingEquation> { Width = 200 }
}
}
}
},
new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Behind background"
},
new BufferedContainer
{
Name = "Background",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Size = new Vector2(0.85f),
Masking = true,
Children = new Drawable[]
{
new GradientPart(0, Color4.Orange, Color4.Yellow),
new GradientPart(1, Color4.Yellow, Color4.Green),
new GradientPart(2, Color4.Green, Color4.Cyan),
new GradientPart(3, Color4.Cyan, Color4.Blue),
new GradientPart(4, Color4.Blue, Color4.Violet),
foregroundContainer = new BufferedContainer
{
Name = "Foreground",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Alpha = 0.8f,
Children = new[]
{
new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.45f),
Y = -0.15f,
Colour = Color4.Cyan
},
new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.45f),
X = -0.15f,
Colour = Color4.Magenta
},
new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.45f),
X = 0.15f,
Colour = Color4.Yellow
},
}
},
}
},
};
colourModeDropdown.Items = Enum.GetNames(typeof(BlendingMode)).Select(n => new KeyValuePair<string, BlendingMode>(n, (BlendingMode)Enum.Parse(typeof(BlendingMode), n)));
colourEquation.Items = Enum.GetNames(typeof(BlendingEquation)).Select(n => new KeyValuePair<string, BlendingEquation>(n, (BlendingEquation)Enum.Parse(typeof(BlendingEquation), n)));
alphaEquation.Items = Enum.GetNames(typeof(BlendingEquation)).Select(n => new KeyValuePair<string, BlendingEquation>(n, (BlendingEquation)Enum.Parse(typeof(BlendingEquation), n)));
colourModeDropdown.Current.Value = foregroundContainer.Blending.Mode;
colourEquation.Current.Value = foregroundContainer.Blending.RGBEquation;
alphaEquation.Current.Value = foregroundContainer.Blending.AlphaEquation;
colourModeDropdown.Current.ValueChanged += v => updateBlending();
colourEquation.Current.ValueChanged += v => updateBlending();
alphaEquation.Current.ValueChanged += v => updateBlending();
}
private void updateBlending()
{
foregroundContainer.Blending = new BlendingParameters
{
Mode = colourModeDropdown.Current,
RGBEquation = colourEquation.Current,
AlphaEquation = alphaEquation.Current
};
}
private class GradientPart : Box
{
public GradientPart(int index, Color4 start, Color4 end)
{
RelativeSizeAxes = Axes.Both;
RelativePositionAxes = Axes.Both;
Width = 1 / 5f; // Assume 5 gradients
X = 1 / 5f * index;
Colour = ColourInfo.GradientHorizontal(start, end);
}
}
}
}

View File

@@ -1,32 +1,32 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
public class TestCaseBufferedContainer : TestCaseMasking
{
private readonly BufferedContainer buffer;
public TestCaseBufferedContainer()
{
Remove(TestContainer);
Add(buffer = new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[] { TestContainer }
});
}
protected override void LoadComplete()
{
base.LoadComplete();
buffer.BlurTo(new Vector2(20), 1000).Then().BlurTo(Vector2.Zero, 1000).Loop();
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
public class TestCaseBufferedContainer : TestCaseMasking
{
private readonly BufferedContainer buffer;
public TestCaseBufferedContainer()
{
Remove(TestContainer);
Add(buffer = new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[] { TestContainer }
});
}
protected override void LoadComplete()
{
base.LoadComplete();
buffer.BlurTo(new Vector2(20), 1000).Then().BlurTo(Vector2.Zero, 1000).Loop();
}
}
}

View File

@@ -1,168 +1,168 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseCachedBufferedContainer : GridTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(BufferedContainer),
typeof(BufferedContainerDrawNode),
};
public TestCaseCachedBufferedContainer()
: base(5, 2)
{
string[] labels =
{
"uncached",
"cached",
"uncached with rotation",
"cached with rotation",
"uncached with movement",
"cached with movement",
"uncached with parent scale",
"cached with parent scale",
"uncached with parent scale&fade",
"cached with parent scale&fade",
};
var boxes = new List<ContainingBox>();
for (int i = 0; i < Rows * Cols; ++i)
{
ContainingBox box;
Cell(i).AddRange(new Drawable[]
{
new SpriteText
{
Text = labels[i],
TextSize = 20,
},
box = new ContainingBox(i >= 6, i >= 8)
{
Child = new CountingBox(i == 2 || i == 3, i == 4 || i == 5)
{
CacheDrawnFrameBuffer = i % 2 == 1,
},
}
});
boxes.Add(box);
}
AddWaitStep(5);
// ensure uncached is always updating children.
AddAssert("box 0 count > 0", () => boxes[0].Count > 0);
AddAssert("even box counts equal", () =>
boxes[0].Count == boxes[2].Count &&
boxes[2].Count == boxes[4].Count &&
boxes[4].Count == boxes[6].Count);
// ensure cached is never updating children.
AddAssert("box 1 count is 1", () => boxes[1].Count == 1);
// ensure rotation changes are invalidating cache (for now).
AddAssert("box 2 count > 0", () => boxes[2].Count > 0);
AddAssert("box 2 count equals box 3 count", () => boxes[2].Count == boxes[3].Count);
// ensure cached with only translation is never updating children.
AddAssert("box 5 count is 1", () => boxes[1].Count == 1);
// ensure a parent scaling is invalidating cache.
AddAssert("box 5 count equals box 6 count", () => boxes[5].Count == boxes[6].Count);
// ensure we don't break on colour invalidations (due to blanket invalidation logic in Drawable.Invalidate).
AddAssert("box 7 count equals box 8 count", () => boxes[7].Count == boxes[8].Count);
}
private class ContainingBox : Container
{
private readonly bool scaling;
private readonly bool fading;
public ContainingBox(bool scaling, bool fading)
{
this.scaling = scaling;
this.fading = fading;
RelativeSizeAxes = Axes.Both;
}
protected override void LoadComplete()
{
base.LoadComplete();
if (scaling) this.ScaleTo(1.2f, 1000).Then().ScaleTo(1, 1000).Loop();
if (fading) this.FadeTo(0.5f, 1000).Then().FadeTo(1, 1000).Loop();
}
}
private class CountingBox : BufferedContainer
{
private readonly bool rotating;
private readonly bool moving;
private readonly SpriteText count;
public new int Count;
public CountingBox(bool rotating = false, bool moving = false)
{
this.rotating = rotating;
this.moving = moving;
RelativeSizeAxes = Axes.Both;
Origin = Anchor.Centre;
Anchor = Anchor.Centre;
Scale = new Vector2(0.5f);
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.NavajoWhite,
},
count = new SpriteText
{
Colour = Color4.Black,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
TextSize = 80
}
};
}
protected override void Update()
{
base.Update();
if (RequiresChildrenUpdate)
{
Count++;
count.Text = Count.ToString();
}
}
protected override void LoadComplete()
{
base.LoadComplete();
if (rotating) this.RotateTo(360, 1000).Loop();
if (moving) this.MoveTo(new Vector2(100, 0), 2000, Easing.InOutSine).Then().MoveTo(new Vector2(0, 0), 2000, Easing.InOutSine).Loop();
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseCachedBufferedContainer : GridTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(BufferedContainer),
typeof(BufferedContainerDrawNode),
};
public TestCaseCachedBufferedContainer()
: base(5, 2)
{
string[] labels =
{
"uncached",
"cached",
"uncached with rotation",
"cached with rotation",
"uncached with movement",
"cached with movement",
"uncached with parent scale",
"cached with parent scale",
"uncached with parent scale&fade",
"cached with parent scale&fade",
};
var boxes = new List<ContainingBox>();
for (int i = 0; i < Rows * Cols; ++i)
{
ContainingBox box;
Cell(i).AddRange(new Drawable[]
{
new SpriteText
{
Text = labels[i],
TextSize = 20,
},
box = new ContainingBox(i >= 6, i >= 8)
{
Child = new CountingBox(i == 2 || i == 3, i == 4 || i == 5)
{
CacheDrawnFrameBuffer = i % 2 == 1,
},
}
});
boxes.Add(box);
}
AddWaitStep(5);
// ensure uncached is always updating children.
AddAssert("box 0 count > 0", () => boxes[0].Count > 0);
AddAssert("even box counts equal", () =>
boxes[0].Count == boxes[2].Count &&
boxes[2].Count == boxes[4].Count &&
boxes[4].Count == boxes[6].Count);
// ensure cached is never updating children.
AddAssert("box 1 count is 1", () => boxes[1].Count == 1);
// ensure rotation changes are invalidating cache (for now).
AddAssert("box 2 count > 0", () => boxes[2].Count > 0);
AddAssert("box 2 count equals box 3 count", () => boxes[2].Count == boxes[3].Count);
// ensure cached with only translation is never updating children.
AddAssert("box 5 count is 1", () => boxes[1].Count == 1);
// ensure a parent scaling is invalidating cache.
AddAssert("box 5 count equals box 6 count", () => boxes[5].Count == boxes[6].Count);
// ensure we don't break on colour invalidations (due to blanket invalidation logic in Drawable.Invalidate).
AddAssert("box 7 count equals box 8 count", () => boxes[7].Count == boxes[8].Count);
}
private class ContainingBox : Container
{
private readonly bool scaling;
private readonly bool fading;
public ContainingBox(bool scaling, bool fading)
{
this.scaling = scaling;
this.fading = fading;
RelativeSizeAxes = Axes.Both;
}
protected override void LoadComplete()
{
base.LoadComplete();
if (scaling) this.ScaleTo(1.2f, 1000).Then().ScaleTo(1, 1000).Loop();
if (fading) this.FadeTo(0.5f, 1000).Then().FadeTo(1, 1000).Loop();
}
}
private class CountingBox : BufferedContainer
{
private readonly bool rotating;
private readonly bool moving;
private readonly SpriteText count;
public new int Count;
public CountingBox(bool rotating = false, bool moving = false)
{
this.rotating = rotating;
this.moving = moving;
RelativeSizeAxes = Axes.Both;
Origin = Anchor.Centre;
Anchor = Anchor.Centre;
Scale = new Vector2(0.5f);
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.NavajoWhite,
},
count = new SpriteText
{
Colour = Color4.Black,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
TextSize = 80
}
};
}
protected override void Update()
{
base.Update();
if (RequiresChildrenUpdate)
{
Count++;
count.Text = Count.ToString();
}
}
protected override void LoadComplete()
{
base.LoadComplete();
if (rotating) this.RotateTo(360, 1000).Loop();
if (moving) this.MoveTo(new Vector2(100, 0), 2000, Easing.InOutSine).Then().MoveTo(new Vector2(0, 0), 2000, Easing.InOutSine).Loop();
}
}
}
}

View File

@@ -1,54 +1,54 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
public class TestCaseCheckboxes : TestCase
{
public TestCaseCheckboxes()
{
Children = new Drawable[]
{
new FillFlowContainer
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Padding = new MarginPadding(10),
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new BasicCheckbox
{
LabelText = @"Basic Test"
},
new BasicCheckbox
{
LabelText = @"FadeDuration Test",
FadeDuration = 300
},
new ActionsTestCheckbox
{
LabelText = @"Enabled/Disabled Actions Test",
},
}
}
};
}
}
public class ActionsTestCheckbox : BasicCheckbox
{
public ActionsTestCheckbox()
{
Current.ValueChanged += v => this.RotateTo(v ? 45 : 0, 100);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
public class TestCaseCheckboxes : TestCase
{
public TestCaseCheckboxes()
{
Children = new Drawable[]
{
new FillFlowContainer
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Padding = new MarginPadding(10),
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new BasicCheckbox
{
LabelText = @"Basic Test"
},
new BasicCheckbox
{
LabelText = @"FadeDuration Test",
FadeDuration = 300
},
new ActionsTestCheckbox
{
LabelText = @"Enabled/Disabled Actions Test",
},
}
}
};
}
}
public class ActionsTestCheckbox : BasicCheckbox
{
public ActionsTestCheckbox()
{
Current.ValueChanged += v => this.RotateTo(v ? 45 : 0, 100);
}
}
}

View File

@@ -1,53 +1,53 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using OpenTK;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description(@"Checking for bugged corner radius")]
public class TestCaseCircularContainer : TestCase
{
private SingleUpdateCircularContainer container;
public TestCaseCircularContainer()
{
AddStep("128x128 box", () => addContainer(new Vector2(128)));
AddAssert("Expect CornerRadius = 64", () => Precision.AlmostEquals(container.CornerRadius, 64));
AddStep("128x64 box", () => addContainer(new Vector2(128, 64)));
AddAssert("Expect CornerRadius = 32", () => Precision.AlmostEquals(container.CornerRadius, 32));
AddStep("64x128 box", () => addContainer(new Vector2(64, 128)));
AddAssert("Expect CornerRadius = 32", () => Precision.AlmostEquals(container.CornerRadius, 32));
}
private void addContainer(Vector2 size)
{
Clear();
Add(container = new SingleUpdateCircularContainer
{
Masking = true,
AutoSizeAxes = Axes.Both,
Child = new Box { Size = size }
});
}
private class SingleUpdateCircularContainer : CircularContainer
{
private bool firstUpdate = true;
public override bool UpdateSubTree()
{
if (!firstUpdate)
return true;
firstUpdate = false;
return base.UpdateSubTree();
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using OpenTK;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description(@"Checking for bugged corner radius")]
public class TestCaseCircularContainer : TestCase
{
private SingleUpdateCircularContainer container;
public TestCaseCircularContainer()
{
AddStep("128x128 box", () => addContainer(new Vector2(128)));
AddAssert("Expect CornerRadius = 64", () => Precision.AlmostEquals(container.CornerRadius, 64));
AddStep("128x64 box", () => addContainer(new Vector2(128, 64)));
AddAssert("Expect CornerRadius = 32", () => Precision.AlmostEquals(container.CornerRadius, 32));
AddStep("64x128 box", () => addContainer(new Vector2(64, 128)));
AddAssert("Expect CornerRadius = 32", () => Precision.AlmostEquals(container.CornerRadius, 32));
}
private void addContainer(Vector2 size)
{
Clear();
Add(container = new SingleUpdateCircularContainer
{
Masking = true,
AutoSizeAxes = Axes.Both,
Child = new Box { Size = size }
});
}
private class SingleUpdateCircularContainer : CircularContainer
{
private bool firstUpdate = true;
public override bool UpdateSubTree()
{
if (!firstUpdate)
return true;
firstUpdate = false;
return base.UpdateSubTree();
}
}
}
}

View File

@@ -1,191 +1,191 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseCircularProgress : TestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(CircularProgress), typeof(CircularProgressDrawNode), typeof(CircularProgressDrawNodeSharedData) };
private readonly CircularProgress clock;
private int rotateMode;
private const double period = 4000;
private const double transition_period = 2000;
private readonly Texture gradientTextureHorizontal;
private readonly Texture gradientTextureVertical;
private readonly Texture gradientTextureBoth;
public TestCaseCircularProgress()
{
const int width = 128;
byte[] data = new byte[width * 4];
gradientTextureHorizontal = new Texture(width, 1, true);
for (int i = 0; i < width; ++i)
{
float brightness = (float)i / (width - 1);
int index = i * 4;
data[index + 0] = (byte)(128 + (1 - brightness) * 127);
data[index + 1] = (byte)(128 + brightness * 127);
data[index + 2] = 128;
data[index + 3] = 255;
}
gradientTextureHorizontal.SetData(new TextureUpload(data));
gradientTextureVertical = new Texture(1, width, true);
for (int i = 0; i < width; ++i)
{
float brightness = (float)i / (width - 1);
int index = i * 4;
data[index + 0] = (byte)(128 + (1 - brightness) * 127);
data[index + 1] = (byte)(128 + brightness * 127);
data[index + 2] = 128;
data[index + 3] = 255;
}
gradientTextureVertical.SetData(new TextureUpload(data));
byte[] data2 = new byte[width * width * 4];
gradientTextureBoth = new Texture(width, width, true);
for (int i = 0; i < width; ++i)
{
for (int j = 0; j < width; ++j)
{
float brightness = (float)i / (width - 1);
float brightness2 = (float)j / (width - 1);
int index = i * 4 * width + j * 4;
data2[index + 0] = (byte)(128 + (1 + brightness - brightness2) / 2 * 127);
data2[index + 1] = (byte)(128 + (1 + brightness2 - brightness) / 2 * 127);
data2[index + 2] = (byte)(128 + (brightness + brightness2) / 2 * 127);
data2[index + 3] = 255;
}
}
gradientTextureBoth.SetData(new TextureUpload(data2));
Children = new Drawable[]
{
clock = new CircularProgress
{
Width = 0.8f,
Height = 0.8f,
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
AddStep("Forward", delegate { rotateMode = 1; });
AddStep("Backward", delegate { rotateMode = 2; });
AddStep("Transition Focus", delegate { rotateMode = 3; });
AddStep("Transition Focus 2", delegate { rotateMode = 4; });
AddStep("Forward/Backward", delegate { rotateMode = 0; });
AddStep("Horizontal Gradient Texture", delegate { setTexture(1); });
AddStep("Vertical Gradient Texture", delegate { setTexture(2); });
AddStep("2D Graident Texture", delegate { setTexture(3); });
AddStep("White Texture", delegate { setTexture(0); });
AddStep("Red Colour", delegate { setColour(1); });
AddStep("Horzontal Gradient Colour", delegate { setColour(2); });
AddStep("Vertical Gradient Colour", delegate { setColour(3); });
AddStep("2D Gradient Colour", delegate { setColour(4); });
AddStep("White Colour", delegate { setColour(0); });
AddSliderStep("Fill", 0, 10, 10, fill => clock.InnerRadius = fill / 10f);
}
protected override void Update()
{
base.Update();
switch (rotateMode)
{
case 0:
clock.Current.Value = Time.Current % (period * 2) / period - 1;
break;
case 1:
clock.Current.Value = Time.Current % period / period;
break;
case 2:
clock.Current.Value = Time.Current % period / period - 1;
break;
case 3:
clock.Current.Value = Time.Current % transition_period / transition_period / 5 - 0.1f;
break;
case 4:
clock.Current.Value = (Time.Current % transition_period / transition_period / 5 - 0.1f + 2) % 2 - 1;
break;
}
}
private void setTexture(int textureMode)
{
switch (textureMode)
{
case 0:
clock.Texture = Texture.WhitePixel;
break;
case 1:
clock.Texture = gradientTextureHorizontal;
break;
case 2:
clock.Texture = gradientTextureVertical;
break;
case 3:
clock.Texture = gradientTextureBoth;
break;
}
}
private void setColour(int colourMode)
{
switch (colourMode)
{
case 0:
clock.Colour = new Color4(255, 255, 255, 255);
break;
case 1:
clock.Colour = new Color4(255, 128, 128, 255);
break;
case 2:
clock.Colour = new ColourInfo
{
TopLeft = new Color4(255, 128, 128, 255),
TopRight = new Color4(128, 255, 128, 255),
BottomLeft = new Color4(255, 128, 128, 255),
BottomRight = new Color4(128, 255, 128, 255),
};
break;
case 3:
clock.Colour = new ColourInfo
{
TopLeft = new Color4(255, 128, 128, 255),
TopRight = new Color4(255, 128, 128, 255),
BottomLeft = new Color4(128, 255, 128, 255),
BottomRight = new Color4(128, 255, 128, 255),
};
break;
case 4:
clock.Colour = new ColourInfo
{
TopLeft = new Color4(255, 128, 128, 255),
TopRight = new Color4(128, 255, 128, 255),
BottomLeft = new Color4(128, 128, 255, 255),
BottomRight = new Color4(255, 255, 255, 255),
};
break;
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseCircularProgress : TestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(CircularProgress), typeof(CircularProgressDrawNode), typeof(CircularProgressDrawNodeSharedData) };
private readonly CircularProgress clock;
private int rotateMode;
private const double period = 4000;
private const double transition_period = 2000;
private readonly Texture gradientTextureHorizontal;
private readonly Texture gradientTextureVertical;
private readonly Texture gradientTextureBoth;
public TestCaseCircularProgress()
{
const int width = 128;
byte[] data = new byte[width * 4];
gradientTextureHorizontal = new Texture(width, 1, true);
for (int i = 0; i < width; ++i)
{
float brightness = (float)i / (width - 1);
int index = i * 4;
data[index + 0] = (byte)(128 + (1 - brightness) * 127);
data[index + 1] = (byte)(128 + brightness * 127);
data[index + 2] = 128;
data[index + 3] = 255;
}
gradientTextureHorizontal.SetData(new TextureUpload(data));
gradientTextureVertical = new Texture(1, width, true);
for (int i = 0; i < width; ++i)
{
float brightness = (float)i / (width - 1);
int index = i * 4;
data[index + 0] = (byte)(128 + (1 - brightness) * 127);
data[index + 1] = (byte)(128 + brightness * 127);
data[index + 2] = 128;
data[index + 3] = 255;
}
gradientTextureVertical.SetData(new TextureUpload(data));
byte[] data2 = new byte[width * width * 4];
gradientTextureBoth = new Texture(width, width, true);
for (int i = 0; i < width; ++i)
{
for (int j = 0; j < width; ++j)
{
float brightness = (float)i / (width - 1);
float brightness2 = (float)j / (width - 1);
int index = i * 4 * width + j * 4;
data2[index + 0] = (byte)(128 + (1 + brightness - brightness2) / 2 * 127);
data2[index + 1] = (byte)(128 + (1 + brightness2 - brightness) / 2 * 127);
data2[index + 2] = (byte)(128 + (brightness + brightness2) / 2 * 127);
data2[index + 3] = 255;
}
}
gradientTextureBoth.SetData(new TextureUpload(data2));
Children = new Drawable[]
{
clock = new CircularProgress
{
Width = 0.8f,
Height = 0.8f,
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
AddStep("Forward", delegate { rotateMode = 1; });
AddStep("Backward", delegate { rotateMode = 2; });
AddStep("Transition Focus", delegate { rotateMode = 3; });
AddStep("Transition Focus 2", delegate { rotateMode = 4; });
AddStep("Forward/Backward", delegate { rotateMode = 0; });
AddStep("Horizontal Gradient Texture", delegate { setTexture(1); });
AddStep("Vertical Gradient Texture", delegate { setTexture(2); });
AddStep("2D Graident Texture", delegate { setTexture(3); });
AddStep("White Texture", delegate { setTexture(0); });
AddStep("Red Colour", delegate { setColour(1); });
AddStep("Horzontal Gradient Colour", delegate { setColour(2); });
AddStep("Vertical Gradient Colour", delegate { setColour(3); });
AddStep("2D Gradient Colour", delegate { setColour(4); });
AddStep("White Colour", delegate { setColour(0); });
AddSliderStep("Fill", 0, 10, 10, fill => clock.InnerRadius = fill / 10f);
}
protected override void Update()
{
base.Update();
switch (rotateMode)
{
case 0:
clock.Current.Value = Time.Current % (period * 2) / period - 1;
break;
case 1:
clock.Current.Value = Time.Current % period / period;
break;
case 2:
clock.Current.Value = Time.Current % period / period - 1;
break;
case 3:
clock.Current.Value = Time.Current % transition_period / transition_period / 5 - 0.1f;
break;
case 4:
clock.Current.Value = (Time.Current % transition_period / transition_period / 5 - 0.1f + 2) % 2 - 1;
break;
}
}
private void setTexture(int textureMode)
{
switch (textureMode)
{
case 0:
clock.Texture = Texture.WhitePixel;
break;
case 1:
clock.Texture = gradientTextureHorizontal;
break;
case 2:
clock.Texture = gradientTextureVertical;
break;
case 3:
clock.Texture = gradientTextureBoth;
break;
}
}
private void setColour(int colourMode)
{
switch (colourMode)
{
case 0:
clock.Colour = new Color4(255, 255, 255, 255);
break;
case 1:
clock.Colour = new Color4(255, 128, 128, 255);
break;
case 2:
clock.Colour = new ColourInfo
{
TopLeft = new Color4(255, 128, 128, 255),
TopRight = new Color4(128, 255, 128, 255),
BottomLeft = new Color4(255, 128, 128, 255),
BottomRight = new Color4(128, 255, 128, 255),
};
break;
case 3:
clock.Colour = new ColourInfo
{
TopLeft = new Color4(255, 128, 128, 255),
TopRight = new Color4(255, 128, 128, 255),
BottomLeft = new Color4(128, 255, 128, 255),
BottomRight = new Color4(128, 255, 128, 255),
};
break;
case 4:
clock.Colour = new ColourInfo
{
TopLeft = new Color4(255, 128, 128, 255),
TopRight = new Color4(128, 255, 128, 255),
BottomLeft = new Color4(128, 128, 255, 255),
BottomRight = new Color4(255, 255, 255, 255),
};
break;
}
}
}
}

View File

@@ -1,92 +1,92 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseColourGradient : GridTestCase
{
public TestCaseColourGradient() : base(2, 2)
{
Color4 transparentBlack = new Color4(0, 0, 0, 0);
ColourInfo[] colours =
{
new ColourInfo
{
TopLeft = Color4.White,
BottomLeft = Color4.Blue,
TopRight = Color4.Red,
BottomRight = Color4.Green,
},
new ColourInfo
{
TopLeft = Color4.White,
BottomLeft = Color4.White,
TopRight = Color4.Black,
BottomRight = Color4.Black,
},
new ColourInfo
{
TopLeft = Color4.White,
BottomLeft = Color4.White,
TopRight = Color4.Transparent,
BottomRight = Color4.Transparent,
},
new ColourInfo
{
TopLeft = Color4.White,
BottomLeft = Color4.White,
TopRight = transparentBlack,
BottomRight = transparentBlack,
},
};
string[] labels =
{
"Colours",
"White to black (linear brightness gradient)",
"White to transparent white (sRGB brightness gradient)",
"White to transparent black (mixed brightness gradient)",
};
for (int i = 0; i < Rows * Cols; ++i)
{
Cell(i).AddRange(new Drawable[]
{
new SpriteText
{
Text = labels[i],
TextSize = 20,
Colour = colours[0],
},
boxes[i] = new Box
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(0.5f),
Colour = colours[i],
},
});
}
}
private readonly Box[] boxes = new Box[4];
protected override void Update()
{
base.Update();
foreach (Box box in boxes)
box.Rotation += 0.01f;
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseColourGradient : GridTestCase
{
public TestCaseColourGradient() : base(2, 2)
{
Color4 transparentBlack = new Color4(0, 0, 0, 0);
ColourInfo[] colours =
{
new ColourInfo
{
TopLeft = Color4.White,
BottomLeft = Color4.Blue,
TopRight = Color4.Red,
BottomRight = Color4.Green,
},
new ColourInfo
{
TopLeft = Color4.White,
BottomLeft = Color4.White,
TopRight = Color4.Black,
BottomRight = Color4.Black,
},
new ColourInfo
{
TopLeft = Color4.White,
BottomLeft = Color4.White,
TopRight = Color4.Transparent,
BottomRight = Color4.Transparent,
},
new ColourInfo
{
TopLeft = Color4.White,
BottomLeft = Color4.White,
TopRight = transparentBlack,
BottomRight = transparentBlack,
},
};
string[] labels =
{
"Colours",
"White to black (linear brightness gradient)",
"White to transparent white (sRGB brightness gradient)",
"White to transparent black (mixed brightness gradient)",
};
for (int i = 0; i < Rows * Cols; ++i)
{
Cell(i).AddRange(new Drawable[]
{
new SpriteText
{
Text = labels[i],
TextSize = 20,
Colour = colours[0],
},
boxes[i] = new Box
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(0.5f),
Colour = colours[i],
},
});
}
}
private readonly Box[] boxes = new Box[4];
protected override void Update()
{
base.Update();
foreach (Box box in boxes)
box.Rotation += 0.01f;
}
}
}

View File

@@ -1,123 +1,123 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("ensure valid container state in various scenarios")]
public class TestCaseContainerState : TestCase
{
private readonly Container container;
public TestCaseContainerState()
{
Add(container = new Container());
}
[BackgroundDependencyLoader]
private void load()
{
testLoadedMultipleAdds();
}
/// <summary>
/// Tests if a drawable can be added to a container, removed, and then re-added to the same container.
/// </summary>
[Test]
public void TestPreLoadReAdding()
{
var sprite = new Sprite();
// Add
Assert.DoesNotThrow(() => container.Add(sprite));
Assert.IsTrue(container.Contains(sprite));
// Remove
Assert.DoesNotThrow(() => container.Remove(sprite));
Assert.IsFalse(container.Contains(sprite));
// Re-add
Assert.DoesNotThrow(() => container.Add(sprite));
Assert.IsTrue(container.Contains(sprite));
}
/// <summary>
/// Tests whether adding a child to multiple containers by abusing <see cref="Container{T}.Children"/>
/// results in a <see cref="InvalidOperationException"/>.
/// </summary>
[Test]
public void TestPreLoadMultipleAdds()
{
// Non-async
Assert.Throws<InvalidOperationException>(() =>
{
container.Add(new Container
{
// Container is an IReadOnlyList<T>, so Children can accept a Container.
// This further means that CompositeDrawable.AddInternal will try to add all of
// the children of the Container that was set to Children, which should throw an exception
Children = new Container { Child = new Container() }
});
});
}
/// <summary>
/// The same as <see cref="TestPreLoadMultipleAdds"/> however instead runs after the container is loaded.
/// </summary>
private void testLoadedMultipleAdds()
{
AddAssert("Test loaded multiple adds", () =>
{
try
{
container.Add(new Container
{
// Container is an IReadOnlyList<T>, so Children can accept a Container.
// This further means that CompositeDrawable.AddInternal will try to add all of
// the children of the Container that was set to Children, which should throw an exception
Children = new Container { Child = new Container() }
});
return false;
}
catch (InvalidOperationException)
{
return true;
}
});
}
/// <summary>
/// Tests whether the result of a <see cref="Container{T}.Contains(T)"/> operation is valid between multiple containers.
/// This tests whether the comparator + equality operation in <see cref="CompositeDrawable.IndexOfInternal(Graphics.Drawable)"/> is valid.
/// </summary>
[Test]
public void TestContainerContains()
{
var drawableA = new Sprite();
var drawableB = new Sprite();
var containerA = new Container { Child = drawableA };
var containerB = new Container { Child = drawableB };
var newContainer = new Container<Container> { Children = new[] { containerA, containerB } };
// Because drawableA and drawableB have been added to separate containers,
// they will both have Depth = 0 and ChildID = 1, which leads to edge cases if a
// sorting comparer that doesn't compare references is used for Contains().
// If this is not handled properly, it may have devastating effects in, e.g. Remove().
Assert.IsTrue(newContainer.First(c => c.Contains(drawableA)) == containerA);
Assert.IsTrue(newContainer.First(c => c.Contains(drawableB)) == containerB);
Assert.DoesNotThrow(() => newContainer.First(c => c.Contains(drawableA)).Remove(drawableA));
Assert.DoesNotThrow(() => newContainer.First(c => c.Contains(drawableB)).Remove(drawableB));
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("ensure valid container state in various scenarios")]
public class TestCaseContainerState : TestCase
{
private readonly Container container;
public TestCaseContainerState()
{
Add(container = new Container());
}
[BackgroundDependencyLoader]
private void load()
{
testLoadedMultipleAdds();
}
/// <summary>
/// Tests if a drawable can be added to a container, removed, and then re-added to the same container.
/// </summary>
[Test]
public void TestPreLoadReAdding()
{
var sprite = new Sprite();
// Add
Assert.DoesNotThrow(() => container.Add(sprite));
Assert.IsTrue(container.Contains(sprite));
// Remove
Assert.DoesNotThrow(() => container.Remove(sprite));
Assert.IsFalse(container.Contains(sprite));
// Re-add
Assert.DoesNotThrow(() => container.Add(sprite));
Assert.IsTrue(container.Contains(sprite));
}
/// <summary>
/// Tests whether adding a child to multiple containers by abusing <see cref="Container{T}.Children"/>
/// results in a <see cref="InvalidOperationException"/>.
/// </summary>
[Test]
public void TestPreLoadMultipleAdds()
{
// Non-async
Assert.Throws<InvalidOperationException>(() =>
{
container.Add(new Container
{
// Container is an IReadOnlyList<T>, so Children can accept a Container.
// This further means that CompositeDrawable.AddInternal will try to add all of
// the children of the Container that was set to Children, which should throw an exception
Children = new Container { Child = new Container() }
});
});
}
/// <summary>
/// The same as <see cref="TestPreLoadMultipleAdds"/> however instead runs after the container is loaded.
/// </summary>
private void testLoadedMultipleAdds()
{
AddAssert("Test loaded multiple adds", () =>
{
try
{
container.Add(new Container
{
// Container is an IReadOnlyList<T>, so Children can accept a Container.
// This further means that CompositeDrawable.AddInternal will try to add all of
// the children of the Container that was set to Children, which should throw an exception
Children = new Container { Child = new Container() }
});
return false;
}
catch (InvalidOperationException)
{
return true;
}
});
}
/// <summary>
/// Tests whether the result of a <see cref="Container{T}.Contains(T)"/> operation is valid between multiple containers.
/// This tests whether the comparator + equality operation in <see cref="CompositeDrawable.IndexOfInternal(Graphics.Drawable)"/> is valid.
/// </summary>
[Test]
public void TestContainerContains()
{
var drawableA = new Sprite();
var drawableB = new Sprite();
var containerA = new Container { Child = drawableA };
var containerB = new Container { Child = drawableB };
var newContainer = new Container<Container> { Children = new[] { containerA, containerB } };
// Because drawableA and drawableB have been added to separate containers,
// they will both have Depth = 0 and ChildID = 1, which leads to edge cases if a
// sorting comparer that doesn't compare references is used for Contains().
// If this is not handled properly, it may have devastating effects in, e.g. Remove().
Assert.IsTrue(newContainer.First(c => c.Contains(drawableA)) == containerA);
Assert.IsTrue(newContainer.First(c => c.Contains(drawableB)) == containerB);
Assert.DoesNotThrow(() => newContainer.First(c => c.Contains(drawableA)).Remove(drawableA));
Assert.DoesNotThrow(() => newContainer.First(c => c.Contains(drawableB)).Remove(drawableB));
}
}
}

View File

@@ -1,79 +1,79 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseContextMenu : TestCase
{
private const int start_time = 0;
private const int duration = 1000;
private readonly ContextMenuBox movingBox;
private ContextMenuBox makeBox(Anchor anchor)
{
return new ContextMenuBox
{
Size = new Vector2(200),
Anchor = anchor,
Origin = anchor,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Blue,
}
}
};
}
public TestCaseContextMenu()
{
Add(new ContextMenuContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
makeBox(Anchor.TopLeft),
makeBox(Anchor.TopRight),
makeBox(Anchor.BottomLeft),
makeBox(Anchor.BottomRight),
movingBox = makeBox(Anchor.Centre),
}
});
}
protected override void LoadComplete()
{
base.LoadComplete();
// Move box along a square trajectory
movingBox.MoveTo(new Vector2(0, 100), duration)
.Then().MoveTo(new Vector2(100, 100), duration)
.Then().MoveTo(new Vector2(100, 0), duration)
.Then().MoveTo(Vector2.Zero, duration)
.Loop();
}
private class ContextMenuBox : Container, IHasContextMenu
{
public MenuItem[] ContextMenuItems => new[]
{
new MenuItem(@"Change width", () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint)),
new MenuItem(@"Change height", () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint)),
new MenuItem(@"Change width back", () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint)),
new MenuItem(@"Change height back", () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint)),
};
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseContextMenu : TestCase
{
private const int start_time = 0;
private const int duration = 1000;
private readonly ContextMenuBox movingBox;
private ContextMenuBox makeBox(Anchor anchor)
{
return new ContextMenuBox
{
Size = new Vector2(200),
Anchor = anchor,
Origin = anchor,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Blue,
}
}
};
}
public TestCaseContextMenu()
{
Add(new ContextMenuContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
makeBox(Anchor.TopLeft),
makeBox(Anchor.TopRight),
makeBox(Anchor.BottomLeft),
makeBox(Anchor.BottomRight),
movingBox = makeBox(Anchor.Centre),
}
});
}
protected override void LoadComplete()
{
base.LoadComplete();
// Move box along a square trajectory
movingBox.MoveTo(new Vector2(0, 100), duration)
.Then().MoveTo(new Vector2(100, 100), duration)
.Then().MoveTo(new Vector2(100, 0), duration)
.Then().MoveTo(Vector2.Zero, duration)
.Loop();
}
private class ContextMenuBox : Container, IHasContextMenu
{
public MenuItem[] ContextMenuItems => new[]
{
new MenuItem(@"Change width", () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint)),
new MenuItem(@"Change height", () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint)),
new MenuItem(@"Change width back", () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint)),
new MenuItem(@"Change height back", () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint)),
};
}
}
}

View File

@@ -1,219 +1,219 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Globalization;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseCoordinateSpaces : TestCase
{
public TestCaseCoordinateSpaces()
{
AddStep("0-1 space", () => loadCase(0));
AddStep("0-150 space", () => loadCase(1));
AddStep("50-200 space", () => loadCase(2));
AddStep("150-(-50) space", () => loadCase(3));
AddStep("0-300 space", () => loadCase(4));
AddStep("-250-250 space", () => loadCase(5));
}
private void loadCase(int i)
{
Clear();
HorizontalVisualiser h;
Add(h = new HorizontalVisualiser
{
Size = new Vector2(200, 50),
X = 150
});
switch (i)
{
case 0:
h.CreateMarkerAt(-0.1f);
h.CreateMarkerAt(0);
h.CreateMarkerAt(0.1f);
h.CreateMarkerAt(0.3f);
h.CreateMarkerAt(0.7f);
h.CreateMarkerAt(0.9f);
h.CreateMarkerAt(1f);
h.CreateMarkerAt(1.1f);
break;
case 1:
h.RelativeChildSize = new Vector2(150, 1);
h.CreateMarkerAt(0);
h.CreateMarkerAt(50);
h.CreateMarkerAt(100);
h.CreateMarkerAt(150);
h.CreateMarkerAt(200);
h.CreateMarkerAt(250);
break;
case 2:
h.RelativeChildOffset = new Vector2(50, 0);
h.RelativeChildSize = new Vector2(150, 1);
h.CreateMarkerAt(0);
h.CreateMarkerAt(50);
h.CreateMarkerAt(100);
h.CreateMarkerAt(150);
h.CreateMarkerAt(200);
h.CreateMarkerAt(250);
break;
case 3:
h.RelativeChildOffset = new Vector2(150, 0);
h.RelativeChildSize = new Vector2(-200, 1);
h.CreateMarkerAt(0);
h.CreateMarkerAt(50);
h.CreateMarkerAt(100);
h.CreateMarkerAt(150);
h.CreateMarkerAt(200);
h.CreateMarkerAt(250);
break;
case 4:
h.RelativeChildOffset = new Vector2(0, 0);
h.RelativeChildSize = new Vector2(300, 1);
h.CreateMarkerAt(0);
h.CreateMarkerAt(50);
h.CreateMarkerAt(100);
h.CreateMarkerAt(150);
h.CreateMarkerAt(200);
h.CreateMarkerAt(250);
break;
case 5:
h.RelativeChildOffset = new Vector2(-250, 0);
h.RelativeChildSize = new Vector2(500, 1);
h.CreateMarkerAt(-300);
h.CreateMarkerAt(-200);
h.CreateMarkerAt(-100);
h.CreateMarkerAt(0);
h.CreateMarkerAt(100);
h.CreateMarkerAt(200);
h.CreateMarkerAt(300);
break;
}
}
private class HorizontalVisualiser : Visualiser
{
protected override void Update()
{
base.Update();
Left.Text = $"X = {RelativeChildOffset.X.ToString(CultureInfo.InvariantCulture)}";
Right.Text = $"X = {(RelativeChildOffset.X + RelativeChildSize.X).ToString(CultureInfo.InvariantCulture)}";
}
}
private abstract class Visualiser : Container
{
public new Vector2 RelativeChildSize
{
protected get { return innerContainer.RelativeChildSize; }
set { innerContainer.RelativeChildSize = value; }
}
public new Vector2 RelativeChildOffset
{
protected get { return innerContainer.RelativeChildOffset; }
set { innerContainer.RelativeChildOffset = value; }
}
private readonly Container innerContainer;
protected readonly SpriteText Left;
protected readonly SpriteText Right;
protected Visualiser()
{
Height = 50;
InternalChildren = new Drawable[]
{
new Box
{
Name = "Left marker",
Colour = Color4.Gray,
RelativeSizeAxes = Axes.Y,
},
Left = new SpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopCentre,
Y = 6
},
new Box
{
Name = "Centre line",
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Colour = Color4.Gray,
RelativeSizeAxes = Axes.X
},
innerContainer = new Container
{
RelativeSizeAxes = Axes.Both
},
new Box
{
Name = "Right marker",
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Colour = Color4.Gray,
RelativeSizeAxes = Axes.Y
},
Right = new SpriteText
{
Anchor = Anchor.BottomRight,
Origin = Anchor.TopCentre,
Y = 6
},
};
}
public void CreateMarkerAt(float x)
{
innerContainer.Add(new Container
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,
AutoSizeAxes = Axes.Both,
X = x,
Colour = Color4.Yellow,
Children = new Drawable[]
{
new Box
{
Name = "Centre marker horizontal",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(8, 1)
},
new Box
{
Name = "Centre marker vertical",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(1, 8)
},
new SpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre,
Y = 6,
BypassAutoSizeAxes = Axes.Both,
Text = x.ToString(CultureInfo.InvariantCulture)
}
}
});
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Globalization;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseCoordinateSpaces : TestCase
{
public TestCaseCoordinateSpaces()
{
AddStep("0-1 space", () => loadCase(0));
AddStep("0-150 space", () => loadCase(1));
AddStep("50-200 space", () => loadCase(2));
AddStep("150-(-50) space", () => loadCase(3));
AddStep("0-300 space", () => loadCase(4));
AddStep("-250-250 space", () => loadCase(5));
}
private void loadCase(int i)
{
Clear();
HorizontalVisualiser h;
Add(h = new HorizontalVisualiser
{
Size = new Vector2(200, 50),
X = 150
});
switch (i)
{
case 0:
h.CreateMarkerAt(-0.1f);
h.CreateMarkerAt(0);
h.CreateMarkerAt(0.1f);
h.CreateMarkerAt(0.3f);
h.CreateMarkerAt(0.7f);
h.CreateMarkerAt(0.9f);
h.CreateMarkerAt(1f);
h.CreateMarkerAt(1.1f);
break;
case 1:
h.RelativeChildSize = new Vector2(150, 1);
h.CreateMarkerAt(0);
h.CreateMarkerAt(50);
h.CreateMarkerAt(100);
h.CreateMarkerAt(150);
h.CreateMarkerAt(200);
h.CreateMarkerAt(250);
break;
case 2:
h.RelativeChildOffset = new Vector2(50, 0);
h.RelativeChildSize = new Vector2(150, 1);
h.CreateMarkerAt(0);
h.CreateMarkerAt(50);
h.CreateMarkerAt(100);
h.CreateMarkerAt(150);
h.CreateMarkerAt(200);
h.CreateMarkerAt(250);
break;
case 3:
h.RelativeChildOffset = new Vector2(150, 0);
h.RelativeChildSize = new Vector2(-200, 1);
h.CreateMarkerAt(0);
h.CreateMarkerAt(50);
h.CreateMarkerAt(100);
h.CreateMarkerAt(150);
h.CreateMarkerAt(200);
h.CreateMarkerAt(250);
break;
case 4:
h.RelativeChildOffset = new Vector2(0, 0);
h.RelativeChildSize = new Vector2(300, 1);
h.CreateMarkerAt(0);
h.CreateMarkerAt(50);
h.CreateMarkerAt(100);
h.CreateMarkerAt(150);
h.CreateMarkerAt(200);
h.CreateMarkerAt(250);
break;
case 5:
h.RelativeChildOffset = new Vector2(-250, 0);
h.RelativeChildSize = new Vector2(500, 1);
h.CreateMarkerAt(-300);
h.CreateMarkerAt(-200);
h.CreateMarkerAt(-100);
h.CreateMarkerAt(0);
h.CreateMarkerAt(100);
h.CreateMarkerAt(200);
h.CreateMarkerAt(300);
break;
}
}
private class HorizontalVisualiser : Visualiser
{
protected override void Update()
{
base.Update();
Left.Text = $"X = {RelativeChildOffset.X.ToString(CultureInfo.InvariantCulture)}";
Right.Text = $"X = {(RelativeChildOffset.X + RelativeChildSize.X).ToString(CultureInfo.InvariantCulture)}";
}
}
private abstract class Visualiser : Container
{
public new Vector2 RelativeChildSize
{
protected get { return innerContainer.RelativeChildSize; }
set { innerContainer.RelativeChildSize = value; }
}
public new Vector2 RelativeChildOffset
{
protected get { return innerContainer.RelativeChildOffset; }
set { innerContainer.RelativeChildOffset = value; }
}
private readonly Container innerContainer;
protected readonly SpriteText Left;
protected readonly SpriteText Right;
protected Visualiser()
{
Height = 50;
InternalChildren = new Drawable[]
{
new Box
{
Name = "Left marker",
Colour = Color4.Gray,
RelativeSizeAxes = Axes.Y,
},
Left = new SpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopCentre,
Y = 6
},
new Box
{
Name = "Centre line",
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Colour = Color4.Gray,
RelativeSizeAxes = Axes.X
},
innerContainer = new Container
{
RelativeSizeAxes = Axes.Both
},
new Box
{
Name = "Right marker",
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Colour = Color4.Gray,
RelativeSizeAxes = Axes.Y
},
Right = new SpriteText
{
Anchor = Anchor.BottomRight,
Origin = Anchor.TopCentre,
Y = 6
},
};
}
public void CreateMarkerAt(float x)
{
innerContainer.Add(new Container
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,
AutoSizeAxes = Axes.Both,
X = x,
Colour = Color4.Yellow,
Children = new Drawable[]
{
new Box
{
Name = "Centre marker horizontal",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(8, 1)
},
new Box
{
Name = "Centre marker vertical",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(1, 8)
},
new SpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre,
Y = 6,
BypassAutoSizeAxes = Axes.Both,
Text = x.ToString(CultureInfo.InvariantCulture)
}
}
});
}
}
}
}

View File

@@ -1,100 +1,100 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using OpenTK;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
public class TestCaseCountingText : TestCase
{
private readonly Bindable<CountType> countType = new Bindable<CountType>();
public TestCaseCountingText()
{
Counter counter;
BasicDropdown<CountType> typeDropdown;
Children = new Drawable[]
{
typeDropdown = new BasicDropdown<CountType>
{
Position = new Vector2(10),
Width = 150,
},
counter = new TestTextCounter(createResult)
{
Position = new Vector2(180)
}
};
typeDropdown.Items = Enum.GetNames(typeof(CountType)).Select(n => new KeyValuePair<string, CountType>(n, (CountType)Enum.Parse(typeof(CountType), n)));
countType.BindTo(typeDropdown.Current);
countType.ValueChanged += v => beginStep(lastStep)();
AddStep("1 -> 4 | 1 sec", beginStep(() => counter.CountTo(1).CountTo(4, 1000)));
AddStep("1 -> 4 | 3 sec", beginStep(() => counter.CountTo(1).CountTo(4, 3000)));
AddStep("4 -> 1 | 1 sec", beginStep(() => counter.CountTo(4).CountTo(1, 1000)));
AddStep("4 -> 1 | 3 sec", beginStep(() => counter.CountTo(4).CountTo(1, 3000)));
AddStep("1 -> 4 -> 1 | 6 sec", beginStep(() => counter.CountTo(1).CountTo(4, 3000).Then().CountTo(1, 3000)));
AddStep("1 -> 4 -> 1 | 2 sec", beginStep(() => counter.CountTo(1).CountTo(4, 1000).Then().CountTo(1, 1000)));
AddStep("1 -> 100 | 5 sec | OutQuint", beginStep(() => counter.CountTo(1).CountTo(100, 5000, Easing.OutQuint)));
}
private Action lastStep;
private Action beginStep(Action stepAction) => () =>
{
lastStep = stepAction;
stepAction?.Invoke();
};
private string createResult(double value)
{
switch (countType.Value)
{
default:
case CountType.AsDouble:
return value.ToString(CultureInfo.InvariantCulture);
case CountType.AsInteger:
return ((int)value).ToString();
case CountType.AsIntegerCeiling:
return ((int)Math.Ceiling(value)).ToString();
case CountType.AsDouble2:
return Math.Round(value, 2).ToString(CultureInfo.InvariantCulture);
case CountType.AsDouble4:
return Math.Round(value, 4).ToString(CultureInfo.InvariantCulture);
}
}
private enum CountType
{
AsInteger,
AsIntegerCeiling,
AsDouble,
AsDouble2,
AsDouble4,
}
}
public class TestTextCounter : Counter
{
private readonly Func<double, string> resultFunction;
private readonly SpriteText text;
public TestTextCounter(Func<double, string> resultFunction)
{
this.resultFunction = resultFunction;
AddInternal(text = new SpriteText { TextSize = 24 });
}
protected override void OnCountChanged(double count) => text.Text = resultFunction(count);
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using OpenTK;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
public class TestCaseCountingText : TestCase
{
private readonly Bindable<CountType> countType = new Bindable<CountType>();
public TestCaseCountingText()
{
Counter counter;
BasicDropdown<CountType> typeDropdown;
Children = new Drawable[]
{
typeDropdown = new BasicDropdown<CountType>
{
Position = new Vector2(10),
Width = 150,
},
counter = new TestTextCounter(createResult)
{
Position = new Vector2(180)
}
};
typeDropdown.Items = Enum.GetNames(typeof(CountType)).Select(n => new KeyValuePair<string, CountType>(n, (CountType)Enum.Parse(typeof(CountType), n)));
countType.BindTo(typeDropdown.Current);
countType.ValueChanged += v => beginStep(lastStep)();
AddStep("1 -> 4 | 1 sec", beginStep(() => counter.CountTo(1).CountTo(4, 1000)));
AddStep("1 -> 4 | 3 sec", beginStep(() => counter.CountTo(1).CountTo(4, 3000)));
AddStep("4 -> 1 | 1 sec", beginStep(() => counter.CountTo(4).CountTo(1, 1000)));
AddStep("4 -> 1 | 3 sec", beginStep(() => counter.CountTo(4).CountTo(1, 3000)));
AddStep("1 -> 4 -> 1 | 6 sec", beginStep(() => counter.CountTo(1).CountTo(4, 3000).Then().CountTo(1, 3000)));
AddStep("1 -> 4 -> 1 | 2 sec", beginStep(() => counter.CountTo(1).CountTo(4, 1000).Then().CountTo(1, 1000)));
AddStep("1 -> 100 | 5 sec | OutQuint", beginStep(() => counter.CountTo(1).CountTo(100, 5000, Easing.OutQuint)));
}
private Action lastStep;
private Action beginStep(Action stepAction) => () =>
{
lastStep = stepAction;
stepAction?.Invoke();
};
private string createResult(double value)
{
switch (countType.Value)
{
default:
case CountType.AsDouble:
return value.ToString(CultureInfo.InvariantCulture);
case CountType.AsInteger:
return ((int)value).ToString();
case CountType.AsIntegerCeiling:
return ((int)Math.Ceiling(value)).ToString();
case CountType.AsDouble2:
return Math.Round(value, 2).ToString(CultureInfo.InvariantCulture);
case CountType.AsDouble4:
return Math.Round(value, 4).ToString(CultureInfo.InvariantCulture);
}
}
private enum CountType
{
AsInteger,
AsIntegerCeiling,
AsDouble,
AsDouble2,
AsDouble4,
}
}
public class TestTextCounter : Counter
{
private readonly Func<double, string> resultFunction;
private readonly SpriteText text;
public TestTextCounter(Func<double, string> resultFunction)
{
this.resultFunction = resultFunction;
AddInternal(text = new SpriteText { TextSize = 24 });
}
protected override void OnCountChanged(double count) => text.Text = resultFunction(count);
}
}

View File

@@ -1,93 +1,93 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseDelayedLoad : TestCase
{
private const int panel_count = 2048;
public TestCaseDelayedLoad()
{
FillFlowContainerNoInput flow;
ScrollContainer scroll;
Children = new Drawable[]
{
scroll = new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
flow = new FillFlowContainerNoInput
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
}
}
}
};
for (int i = 1; i < panel_count; i++)
flow.Add(new Container
{
Size = new Vector2(128),
Children = new Drawable[]
{
new DelayedLoadWrapper(new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new TestBox{ RelativeSizeAxes = Axes.Both }
}
}),
new SpriteText { Text = i.ToString() },
}
});
var childrenWithAvatarsLoaded = flow.Children.Where(c => c.Children.OfType<DelayedLoadWrapper>().First().Content?.IsLoaded ?? false);
AddWaitStep(10);
AddStep("scroll down", () => scroll.ScrollToEnd());
AddWaitStep(10);
AddAssert("some loaded", () => childrenWithAvatarsLoaded.Count() > 5);
AddAssert("not too many loaded", () => childrenWithAvatarsLoaded.Count() < panel_count / 4);
}
private class FillFlowContainerNoInput : FillFlowContainer<Container>
{
public override bool HandleKeyboardInput => false;
public override bool HandleMouseInput => false;
}
}
public class TestBox : Container
{
public TestBox()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
Child = new SpriteText
{
Colour = Color4.Yellow,
Text = @"loaded",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseDelayedLoad : TestCase
{
private const int panel_count = 2048;
public TestCaseDelayedLoad()
{
FillFlowContainerNoInput flow;
ScrollContainer scroll;
Children = new Drawable[]
{
scroll = new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
flow = new FillFlowContainerNoInput
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
}
}
}
};
for (int i = 1; i < panel_count; i++)
flow.Add(new Container
{
Size = new Vector2(128),
Children = new Drawable[]
{
new DelayedLoadWrapper(new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new TestBox{ RelativeSizeAxes = Axes.Both }
}
}),
new SpriteText { Text = i.ToString() },
}
});
var childrenWithAvatarsLoaded = flow.Children.Where(c => c.Children.OfType<DelayedLoadWrapper>().First().Content?.IsLoaded ?? false);
AddWaitStep(10);
AddStep("scroll down", () => scroll.ScrollToEnd());
AddWaitStep(10);
AddAssert("some loaded", () => childrenWithAvatarsLoaded.Count() > 5);
AddAssert("not too many loaded", () => childrenWithAvatarsLoaded.Count() < panel_count / 4);
}
private class FillFlowContainerNoInput : FillFlowContainer<Container>
{
public override bool HandleKeyboardInput => false;
public override bool HandleMouseInput => false;
}
}
public class TestBox : Container
{
public TestBox()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
Child = new SpriteText
{
Colour = Color4.Yellow,
Text = @"loaded",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
}
}
}

View File

@@ -1,61 +1,61 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
public class TestCaseDrawSizePreservingFillContainer : TestCase
{
public TestCaseDrawSizePreservingFillContainer()
{
DrawSizePreservingFillContainer fillContainer;
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Red,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(10),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
fillContainer = new DrawSizePreservingFillContainer
{
Child = new TestCaseSizing(),
},
}
},
}
};
AddStep("Strategy: Minimum", () => fillContainer.Strategy = DrawSizePreservationStrategy.Minimum);
AddStep("Strategy: Maximum", () => fillContainer.Strategy = DrawSizePreservationStrategy.Maximum);
AddStep("Strategy: Average", () => fillContainer.Strategy = DrawSizePreservationStrategy.Average);
AddStep("Strategy: Separate", () => fillContainer.Strategy = DrawSizePreservationStrategy.Separate);
AddSliderStep("Width", 50, 650, 500, v => Child.Width = v);
AddSliderStep("Height", 50, 650, 500, v => Child.Height = v);
AddStep("Override Size to 1x1", () => Child.Size = Vector2.One);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
public class TestCaseDrawSizePreservingFillContainer : TestCase
{
public TestCaseDrawSizePreservingFillContainer()
{
DrawSizePreservingFillContainer fillContainer;
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Red,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(10),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
fillContainer = new DrawSizePreservingFillContainer
{
Child = new TestCaseSizing(),
},
}
},
}
};
AddStep("Strategy: Minimum", () => fillContainer.Strategy = DrawSizePreservationStrategy.Minimum);
AddStep("Strategy: Maximum", () => fillContainer.Strategy = DrawSizePreservationStrategy.Maximum);
AddStep("Strategy: Average", () => fillContainer.Strategy = DrawSizePreservationStrategy.Average);
AddStep("Strategy: Separate", () => fillContainer.Strategy = DrawSizePreservationStrategy.Separate);
AddSliderStep("Width", 50, 650, 500, v => Child.Width = v);
AddSliderStep("Height", 50, 650, 500, v => Child.Height = v);
AddStep("Override Size to 1x1", () => Child.Size = Vector2.One);
}
}
}

View File

@@ -1,128 +1,128 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseDrawablePath : GridTestCase
{
public TestCaseDrawablePath() : base(2, 2)
{
const int width = 20;
Texture gradientTexture = new Texture(width, 1, true);
byte[] data = new byte[width * 4];
for (int i = 0; i < width; ++i)
{
float brightness = (float)i / (width - 1);
int index = i * 4;
data[index + 0] = (byte)(brightness * 255);
data[index + 1] = (byte)(brightness * 255);
data[index + 2] = (byte)(brightness * 255);
data[index + 3] = 255;
}
gradientTexture.SetData(new TextureUpload(data));
Cell(0).AddRange(new[]
{
createLabel("Simple path"),
new Path
{
RelativeSizeAxes = Axes.Both,
Positions = new List<Vector2> { Vector2.One * 50, Vector2.One * 100 },
Texture = gradientTexture,
Colour = Color4.Green,
},
});
Cell(1).AddRange(new[]
{
createLabel("Curved path"),
new Path
{
RelativeSizeAxes = Axes.Both,
Positions = new List<Vector2>
{
new Vector2(50, 50),
new Vector2(50, 250),
new Vector2(250, 250),
new Vector2(250, 50),
new Vector2(50, 50),
},
Texture = gradientTexture,
Colour = Color4.Blue,
},
});
Cell(2).AddRange(new[]
{
createLabel("Self-overlapping path"),
new Path
{
RelativeSizeAxes = Axes.Both,
Positions = new List<Vector2>
{
new Vector2(50, 50),
new Vector2(50, 250),
new Vector2(250, 250),
new Vector2(250, 150),
new Vector2(20, 150),
},
Texture = gradientTexture,
Colour = Color4.Red,
},
});
Cell(3).AddRange(new[]
{
createLabel("Draw something ;)"),
new UserDrawnPath
{
RelativeSizeAxes = Axes.Both,
Texture = gradientTexture,
Colour = Color4.White,
},
});
}
private Drawable createLabel(string text) => new SpriteText
{
Text = text,
TextSize = 20,
Colour = Color4.White,
};
private class UserDrawnPath : Path
{
private Vector2 oldPos;
protected override bool OnDragStart(InputState state)
{
AddVertex(state.Mouse.Position);
oldPos = state.Mouse.Position;
return true;
}
protected override bool OnDrag(InputState state)
{
Vector2 pos = state.Mouse.Position;
if ((pos - oldPos).Length > 10)
{
AddVertex(pos);
oldPos = pos;
}
return base.OnDrag(state);
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseDrawablePath : GridTestCase
{
public TestCaseDrawablePath() : base(2, 2)
{
const int width = 20;
Texture gradientTexture = new Texture(width, 1, true);
byte[] data = new byte[width * 4];
for (int i = 0; i < width; ++i)
{
float brightness = (float)i / (width - 1);
int index = i * 4;
data[index + 0] = (byte)(brightness * 255);
data[index + 1] = (byte)(brightness * 255);
data[index + 2] = (byte)(brightness * 255);
data[index + 3] = 255;
}
gradientTexture.SetData(new TextureUpload(data));
Cell(0).AddRange(new[]
{
createLabel("Simple path"),
new Path
{
RelativeSizeAxes = Axes.Both,
Positions = new List<Vector2> { Vector2.One * 50, Vector2.One * 100 },
Texture = gradientTexture,
Colour = Color4.Green,
},
});
Cell(1).AddRange(new[]
{
createLabel("Curved path"),
new Path
{
RelativeSizeAxes = Axes.Both,
Positions = new List<Vector2>
{
new Vector2(50, 50),
new Vector2(50, 250),
new Vector2(250, 250),
new Vector2(250, 50),
new Vector2(50, 50),
},
Texture = gradientTexture,
Colour = Color4.Blue,
},
});
Cell(2).AddRange(new[]
{
createLabel("Self-overlapping path"),
new Path
{
RelativeSizeAxes = Axes.Both,
Positions = new List<Vector2>
{
new Vector2(50, 50),
new Vector2(50, 250),
new Vector2(250, 250),
new Vector2(250, 150),
new Vector2(20, 150),
},
Texture = gradientTexture,
Colour = Color4.Red,
},
});
Cell(3).AddRange(new[]
{
createLabel("Draw something ;)"),
new UserDrawnPath
{
RelativeSizeAxes = Axes.Both,
Texture = gradientTexture,
Colour = Color4.White,
},
});
}
private Drawable createLabel(string text) => new SpriteText
{
Text = text,
TextSize = 20,
Colour = Color4.White,
};
private class UserDrawnPath : Path
{
private Vector2 oldPos;
protected override bool OnDragStart(InputState state)
{
AddVertex(state.Mouse.Position);
oldPos = state.Mouse.Position;
return true;
}
protected override bool OnDrag(InputState state)
{
Vector2 pos = state.Mouse.Position;
if ((pos - oldPos).Length > 10)
{
AddVertex(pos);
oldPos = pos;
}
return base.OnDrag(state);
}
}
}
}

View File

@@ -1,105 +1,105 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseDropdownBox : TestCase
{
private const int items_to_add = 10;
public TestCaseDropdownBox()
{
StyledDropdown styledDropdown, styledDropdownMenu2;
var testItems = new string[10];
int i = 0;
while (i < items_to_add)
testItems[i] = @"test " + i++;
Add(styledDropdown = new StyledDropdown
{
Width = 150,
Position = new Vector2(200, 70),
Items = testItems.Select(item => new KeyValuePair<string, string>(item, item)),
});
Add(styledDropdownMenu2 = new StyledDropdown
{
Width = 150,
Position = new Vector2(400, 70),
Items = testItems.Select(item => new KeyValuePair<string, string>(item, item)),
});
AddStep("click dropdown1", () => toggleDropdownViaClick(styledDropdown));
AddAssert("dropdown is open", () => styledDropdown.Menu.State == MenuState.Open);
AddRepeatStep("add item", () => styledDropdown.AddDropdownItem(@"test " + i, @"test " + i++), items_to_add);
AddAssert("item count is correct", () => styledDropdown.Items.Count() == items_to_add * 2);
AddStep("click item 13", () => styledDropdown.SelectItem(styledDropdown.Menu.Items[13]));
AddAssert("dropdown1 is closed", () => styledDropdown.Menu.State == MenuState.Closed);
AddAssert("item 13 is selected", () => styledDropdown.Current == styledDropdown.Items.ElementAt(13).Value);
AddStep("select item 15", () => styledDropdown.Current.Value = styledDropdown.Items.ElementAt(15).Value);
AddAssert("item 15 is selected", () => styledDropdown.Current == styledDropdown.Items.ElementAt(15).Value);
AddStep("click dropdown1", () => toggleDropdownViaClick(styledDropdown));
AddAssert("dropdown1 is open", () => styledDropdown.Menu.State == MenuState.Open);
AddStep("click dropdown2", () => toggleDropdownViaClick(styledDropdownMenu2));
AddAssert("dropdown1 is closed", () => styledDropdown.Menu.State == MenuState.Closed);
AddAssert("dropdown2 is open", () => styledDropdownMenu2.Menu.State == MenuState.Open);
}
private void toggleDropdownViaClick(StyledDropdown dropdown) => dropdown.Children.First().TriggerOnClick();
private class StyledDropdown : BasicDropdown<string>
{
public new DropdownMenu Menu => base.Menu;
protected override DropdownMenu CreateMenu() => new StyledDropdownMenu();
protected override DropdownHeader CreateHeader() => new StyledDropdownHeader();
public void SelectItem(MenuItem item) => ((StyledDropdownMenu)Menu).SelectItem(item);
private class StyledDropdownMenu : DropdownMenu
{
public void SelectItem(MenuItem item) => Children.FirstOrDefault(c => c.Item == item)?.TriggerOnClick();
}
}
private class StyledDropdownHeader : DropdownHeader
{
private readonly SpriteText label;
protected internal override string Label
{
get { return label.Text; }
set { label.Text = value; }
}
public StyledDropdownHeader()
{
Foreground.Padding = new MarginPadding(4);
BackgroundColour = new Color4(255, 255, 255, 100);
BackgroundColourHover = Color4.HotPink;
Children = new[]
{
label = new SpriteText(),
};
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseDropdownBox : TestCase
{
private const int items_to_add = 10;
public TestCaseDropdownBox()
{
StyledDropdown styledDropdown, styledDropdownMenu2;
var testItems = new string[10];
int i = 0;
while (i < items_to_add)
testItems[i] = @"test " + i++;
Add(styledDropdown = new StyledDropdown
{
Width = 150,
Position = new Vector2(200, 70),
Items = testItems.Select(item => new KeyValuePair<string, string>(item, item)),
});
Add(styledDropdownMenu2 = new StyledDropdown
{
Width = 150,
Position = new Vector2(400, 70),
Items = testItems.Select(item => new KeyValuePair<string, string>(item, item)),
});
AddStep("click dropdown1", () => toggleDropdownViaClick(styledDropdown));
AddAssert("dropdown is open", () => styledDropdown.Menu.State == MenuState.Open);
AddRepeatStep("add item", () => styledDropdown.AddDropdownItem(@"test " + i, @"test " + i++), items_to_add);
AddAssert("item count is correct", () => styledDropdown.Items.Count() == items_to_add * 2);
AddStep("click item 13", () => styledDropdown.SelectItem(styledDropdown.Menu.Items[13]));
AddAssert("dropdown1 is closed", () => styledDropdown.Menu.State == MenuState.Closed);
AddAssert("item 13 is selected", () => styledDropdown.Current == styledDropdown.Items.ElementAt(13).Value);
AddStep("select item 15", () => styledDropdown.Current.Value = styledDropdown.Items.ElementAt(15).Value);
AddAssert("item 15 is selected", () => styledDropdown.Current == styledDropdown.Items.ElementAt(15).Value);
AddStep("click dropdown1", () => toggleDropdownViaClick(styledDropdown));
AddAssert("dropdown1 is open", () => styledDropdown.Menu.State == MenuState.Open);
AddStep("click dropdown2", () => toggleDropdownViaClick(styledDropdownMenu2));
AddAssert("dropdown1 is closed", () => styledDropdown.Menu.State == MenuState.Closed);
AddAssert("dropdown2 is open", () => styledDropdownMenu2.Menu.State == MenuState.Open);
}
private void toggleDropdownViaClick(StyledDropdown dropdown) => dropdown.Children.First().TriggerOnClick();
private class StyledDropdown : BasicDropdown<string>
{
public new DropdownMenu Menu => base.Menu;
protected override DropdownMenu CreateMenu() => new StyledDropdownMenu();
protected override DropdownHeader CreateHeader() => new StyledDropdownHeader();
public void SelectItem(MenuItem item) => ((StyledDropdownMenu)Menu).SelectItem(item);
private class StyledDropdownMenu : DropdownMenu
{
public void SelectItem(MenuItem item) => Children.FirstOrDefault(c => c.Item == item)?.TriggerOnClick();
}
}
private class StyledDropdownHeader : DropdownHeader
{
private readonly SpriteText label;
protected internal override string Label
{
get { return label.Text; }
set { label.Text = value; }
}
public StyledDropdownHeader()
{
Foreground.Padding = new MarginPadding(4);
BackgroundColour = new Color4(255, 255, 255, 100);
BackgroundColourHover = Color4.HotPink;
Children = new[]
{
label = new SpriteText(),
};
}
}
}
}

View File

@@ -1,83 +1,83 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("changing depth of child dynamically")]
public class TestCaseDynamicDepth : TestCase
{
private void addDepthSteps(DepthBox box, Container container)
{
AddStep($@"bring forward {box.Name}", () => container.ChangeChildDepth(box, box.Depth - 1));
AddStep($@"send backward {box.Name}", () => container.ChangeChildDepth(box, box.Depth + 1));
}
public TestCaseDynamicDepth()
{
DepthBox red, blue, green, purple;
Container container;
AddRange(new[]
{
container = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(340),
Children = new[]
{
red = new DepthBox(Color4.Red, Anchor.TopLeft) { Name = "red" },
blue = new DepthBox(Color4.Blue, Anchor.TopRight) { Name = "blue" },
green = new DepthBox(Color4.Green, Anchor.BottomRight) { Name = "green" },
purple = new DepthBox(Color4.Purple, Anchor.BottomLeft) { Name = "purple" },
}
}
});
addDepthSteps(red, container);
addDepthSteps(blue, container);
addDepthSteps(green, container);
addDepthSteps(purple, container);
}
private class DepthBox : Container
{
private readonly SpriteText depthText;
public DepthBox(Color4 colour, Anchor anchor)
{
Size = new Vector2(240);
Anchor = Origin = anchor;
AddRange(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colour,
},
depthText = new SpriteText
{
Anchor = anchor,
Origin = anchor,
}
});
}
protected override void Update()
{
base.Update();
depthText.Text = $@"Depth: {Depth}";
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("changing depth of child dynamically")]
public class TestCaseDynamicDepth : TestCase
{
private void addDepthSteps(DepthBox box, Container container)
{
AddStep($@"bring forward {box.Name}", () => container.ChangeChildDepth(box, box.Depth - 1));
AddStep($@"send backward {box.Name}", () => container.ChangeChildDepth(box, box.Depth + 1));
}
public TestCaseDynamicDepth()
{
DepthBox red, blue, green, purple;
Container container;
AddRange(new[]
{
container = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(340),
Children = new[]
{
red = new DepthBox(Color4.Red, Anchor.TopLeft) { Name = "red" },
blue = new DepthBox(Color4.Blue, Anchor.TopRight) { Name = "blue" },
green = new DepthBox(Color4.Green, Anchor.BottomRight) { Name = "green" },
purple = new DepthBox(Color4.Purple, Anchor.BottomLeft) { Name = "purple" },
}
}
});
addDepthSteps(red, container);
addDepthSteps(blue, container);
addDepthSteps(green, container);
addDepthSteps(purple, container);
}
private class DepthBox : Container
{
private readonly SpriteText depthText;
public DepthBox(Color4 colour, Anchor anchor)
{
Size = new Vector2(240);
Anchor = Origin = anchor;
AddRange(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colour,
},
depthText = new SpriteText
{
Anchor = anchor,
Origin = anchor,
}
});
}
protected override void Update()
{
base.Update();
depthText.Text = $@"Depth: {Depth}";
}
}
}
}

View File

@@ -1,238 +1,238 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("implementing the IEffect interface")]
public class TestCaseEffects : TestCase
{
public TestCaseEffects()
{
var effect = new EdgeEffect
{
CornerRadius = 3f,
Parameters = new EdgeEffectParameters
{
Colour = Color4.LightBlue,
Hollow = true,
Radius = 5f,
Type = EdgeEffectType.Glow
}
};
Add(new FillFlowContainer
{
Position = new Vector2(10f, 10f),
Spacing = new Vector2(25f, 25f),
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new SpriteText
{
Text = "Blur Test",
TextSize = 32f
}.WithEffect(new BlurEffect
{
Sigma = new Vector2(2f, 0f),
Strength = 2f,
Rotation = 45f,
}),
new SpriteText
{
Text = "EdgeEffect Test",
TextSize = 32f
}.WithEffect(new EdgeEffect
{
CornerRadius = 3f,
Parameters = new EdgeEffectParameters
{
Colour = Color4.Yellow,
Hollow = true,
Radius = 5f,
Type = EdgeEffectType.Shadow
}
}),
new SpriteText
{
Text = "Repeated usage of same effect test",
TextSize = 32f
}.WithEffect(effect),
new SpriteText
{
Text = "Repeated usage of same effect test",
TextSize = 32f
}.WithEffect(effect),
new SpriteText
{
Text = "Repeated usage of same effect test",
TextSize = 32f
}.WithEffect(effect),
new SpriteText
{
Text = "Multiple effects Test",
TextSize = 32f
}.WithEffect(new BlurEffect
{
Sigma = new Vector2(2f, 2f),
Strength = 2f
}).WithEffect(new EdgeEffect
{
CornerRadius = 3f,
Parameters = new EdgeEffectParameters
{
Colour = Color4.Yellow,
Hollow = true,
Radius = 5f,
Type = EdgeEffectType.Shadow
}
}),
new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.CornflowerBlue,
RelativeSizeAxes = Axes.Both,
},
new SpriteText
{
Text = "Outlined Text",
TextSize = 32f
}.WithEffect(new OutlineEffect
{
BlurSigma = new Vector2(3f),
Strength = 3f,
Colour = Color4.Red,
PadExtent = true,
})
}
},
new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.CornflowerBlue,
RelativeSizeAxes = Axes.Both,
},
new SpriteText
{
Text = "Glowing Text",
TextSize = 32f,
}.WithEffect(new GlowEffect
{
BlurSigma = new Vector2(3f),
Strength = 3f,
Colour = ColourInfo.GradientHorizontal(new Color4(1.2f, 0, 0, 1f), new Color4(0, 1f, 0, 1f)),
PadExtent = true,
}),
}
},
new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = Color4.White,
Size = new Vector2(150, 40),
}.WithEffect(new GlowEffect
{
BlurSigma = new Vector2(3f),
Strength = 3f,
Colour = ColourInfo.GradientHorizontal(new Color4(1.2f, 0, 0, 1f), new Color4(0, 1f, 0, 1f)),
PadExtent = true,
}),
new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Absolute Size",
TextSize = 32f,
Colour = Color4.Red,
Shadow = true,
}
}
},
new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1.1f, 1.1f),
}.WithEffect(new GlowEffect
{
BlurSigma = new Vector2(3f),
Strength = 3f,
Colour = ColourInfo.GradientHorizontal(new Color4(1.2f, 0, 0, 1f), new Color4(0, 1f, 0, 1f)),
PadExtent = true,
}),
new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Relative Size",
TextSize = 32f,
Colour = Color4.Red,
Shadow = true,
},
}
},
new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1.1f, 1.1f),
Rotation = 10,
}.WithEffect(new GlowEffect
{
BlurSigma = new Vector2(3f),
Strength = 3f,
Colour = ColourInfo.GradientHorizontal(new Color4(1.2f, 0, 0, 1f), new Color4(0, 1f, 0, 1f)),
PadExtent = true,
}),
new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Rotation",
TextSize = 32f,
Colour = Color4.Red,
Shadow = true,
},
}
},
}
});
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("implementing the IEffect interface")]
public class TestCaseEffects : TestCase
{
public TestCaseEffects()
{
var effect = new EdgeEffect
{
CornerRadius = 3f,
Parameters = new EdgeEffectParameters
{
Colour = Color4.LightBlue,
Hollow = true,
Radius = 5f,
Type = EdgeEffectType.Glow
}
};
Add(new FillFlowContainer
{
Position = new Vector2(10f, 10f),
Spacing = new Vector2(25f, 25f),
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new SpriteText
{
Text = "Blur Test",
TextSize = 32f
}.WithEffect(new BlurEffect
{
Sigma = new Vector2(2f, 0f),
Strength = 2f,
Rotation = 45f,
}),
new SpriteText
{
Text = "EdgeEffect Test",
TextSize = 32f
}.WithEffect(new EdgeEffect
{
CornerRadius = 3f,
Parameters = new EdgeEffectParameters
{
Colour = Color4.Yellow,
Hollow = true,
Radius = 5f,
Type = EdgeEffectType.Shadow
}
}),
new SpriteText
{
Text = "Repeated usage of same effect test",
TextSize = 32f
}.WithEffect(effect),
new SpriteText
{
Text = "Repeated usage of same effect test",
TextSize = 32f
}.WithEffect(effect),
new SpriteText
{
Text = "Repeated usage of same effect test",
TextSize = 32f
}.WithEffect(effect),
new SpriteText
{
Text = "Multiple effects Test",
TextSize = 32f
}.WithEffect(new BlurEffect
{
Sigma = new Vector2(2f, 2f),
Strength = 2f
}).WithEffect(new EdgeEffect
{
CornerRadius = 3f,
Parameters = new EdgeEffectParameters
{
Colour = Color4.Yellow,
Hollow = true,
Radius = 5f,
Type = EdgeEffectType.Shadow
}
}),
new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.CornflowerBlue,
RelativeSizeAxes = Axes.Both,
},
new SpriteText
{
Text = "Outlined Text",
TextSize = 32f
}.WithEffect(new OutlineEffect
{
BlurSigma = new Vector2(3f),
Strength = 3f,
Colour = Color4.Red,
PadExtent = true,
})
}
},
new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.CornflowerBlue,
RelativeSizeAxes = Axes.Both,
},
new SpriteText
{
Text = "Glowing Text",
TextSize = 32f,
}.WithEffect(new GlowEffect
{
BlurSigma = new Vector2(3f),
Strength = 3f,
Colour = ColourInfo.GradientHorizontal(new Color4(1.2f, 0, 0, 1f), new Color4(0, 1f, 0, 1f)),
PadExtent = true,
}),
}
},
new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = Color4.White,
Size = new Vector2(150, 40),
}.WithEffect(new GlowEffect
{
BlurSigma = new Vector2(3f),
Strength = 3f,
Colour = ColourInfo.GradientHorizontal(new Color4(1.2f, 0, 0, 1f), new Color4(0, 1f, 0, 1f)),
PadExtent = true,
}),
new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Absolute Size",
TextSize = 32f,
Colour = Color4.Red,
Shadow = true,
}
}
},
new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1.1f, 1.1f),
}.WithEffect(new GlowEffect
{
BlurSigma = new Vector2(3f),
Strength = 3f,
Colour = ColourInfo.GradientHorizontal(new Color4(1.2f, 0, 0, 1f), new Color4(0, 1f, 0, 1f)),
PadExtent = true,
}),
new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Relative Size",
TextSize = 32f,
Colour = Color4.Red,
Shadow = true,
},
}
},
new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1.1f, 1.1f),
Rotation = 10,
}.WithEffect(new GlowEffect
{
BlurSigma = new Vector2(3f),
Strength = 3f,
Colour = ColourInfo.GradientHorizontal(new Color4(1.2f, 0, 0, 1f), new Color4(0, 1f, 0, 1f)),
PadExtent = true,
}),
new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Rotation",
TextSize = 32f,
Colour = Color4.Red,
Shadow = true,
},
}
},
}
});
}
}
}

View File

@@ -1,420 +1,420 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
using osu.Framework.Threading;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseFillFlowContainer : TestCase
{
private FillDirectionDropdown selectionDropdown;
private Anchor childAnchor = Anchor.TopLeft;
private AnchorDropdown anchorDropdown;
private Anchor childOrigin = Anchor.TopLeft;
private AnchorDropdown originDropdown;
private FillFlowContainer fillContainer;
private ScheduledDelegate scheduledAdder;
private bool doNotAddChildren;
public TestCaseFillFlowContainer()
{
reset();
}
private void reset()
{
doNotAddChildren = false;
scheduledAdder?.Cancel();
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Width = 0.2f,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Depth = float.MinValue,
Children = new[]
{
new FillFlowContainer
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new SpriteText { Text = @"Fill mode" },
selectionDropdown = new FillDirectionDropdown
{
RelativeSizeAxes = Axes.X,
Items = Enum.GetValues(typeof(FlowTestCase)).Cast<FlowTestCase>()
.Select(value => new KeyValuePair<string, FlowTestCase>(value.ToString(), value)),
},
new SpriteText { Text = @"Child anchor" },
anchorDropdown = new AnchorDropdown
{
RelativeSizeAxes = Axes.X,
Items = new[]
{
Anchor.TopLeft,
Anchor.TopCentre,
Anchor.TopRight,
Anchor.CentreLeft,
Anchor.Centre,
Anchor.CentreRight,
Anchor.BottomLeft,
Anchor.BottomCentre,
Anchor.BottomRight,
}.Select(anchor => new KeyValuePair<string, Anchor>(anchor.ToString(), anchor)),
},
new SpriteText { Text = @"Child origin" },
originDropdown = new AnchorDropdown
{
RelativeSizeAxes = Axes.X,
Items = new[]
{
Anchor.TopLeft,
Anchor.TopCentre,
Anchor.TopRight,
Anchor.CentreLeft,
Anchor.Centre,
Anchor.CentreRight,
Anchor.BottomLeft,
Anchor.BottomCentre,
Anchor.BottomRight,
}.Select(anchor => new KeyValuePair<string, Anchor>(anchor.ToString(), anchor)),
},
}
}
}
};
selectionDropdown.Current.ValueChanged += changeTest;
buildTest();
selectionDropdown.Current.Value = FlowTestCase.Full;
changeTest(FlowTestCase.Full);
}
protected override void Update()
{
base.Update();
if (childAnchor != anchorDropdown.Current)
{
childAnchor = anchorDropdown.Current;
foreach (var child in fillContainer.Children)
child.Anchor = childAnchor;
}
if (childOrigin != originDropdown.Current)
{
childOrigin = originDropdown.Current;
foreach (var child in fillContainer.Children)
child.Origin = childOrigin;
}
}
private void changeTest(FlowTestCase testCase)
{
var method =
GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).SingleOrDefault(m => m.GetCustomAttribute<FlowTestCaseAttribute>()?.TestCase == testCase);
if (method != null)
method.Invoke(this, new object[0]);
}
private void buildTest()
{
Add(new Container
{
Padding = new MarginPadding(25f),
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
fillContainer = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
AutoSizeAxes = Axes.None,
},
new Box
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(3, 1),
Colour = Color4.HotPink,
},
new Box
{
Anchor = Anchor.CentreRight,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(3, 1),
Colour = Color4.HotPink,
},
new Box
{
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Size = new Vector2(1, 3),
Colour = Color4.HotPink,
},
new Box
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Size = new Vector2(1, 3),
Colour = Color4.HotPink,
}
}
});
AddToggleStep("Rotate Container", state => { fillContainer.RotateTo(state ? 45f : 0, 1000); });
AddToggleStep("Scale Container", state => { fillContainer.ScaleTo(state ? 1.2f : 1f, 1000); });
AddToggleStep("Shear Container", state => { fillContainer.Shear = state ? new Vector2(0.5f, 0f) : new Vector2(0f, 0f); });
AddToggleStep("Center Container Anchor", state => { fillContainer.Anchor = state ? Anchor.Centre : Anchor.TopLeft; });
AddToggleStep("Center Container Origin", state => { fillContainer.Origin = state ? Anchor.Centre : Anchor.TopLeft; });
AddToggleStep("Autosize Container", state =>
{
if (state)
{
fillContainer.RelativeSizeAxes = Axes.None;
fillContainer.AutoSizeAxes = Axes.Both;
}
else
{
fillContainer.AutoSizeAxes = Axes.None;
fillContainer.RelativeSizeAxes = Axes.Both;
fillContainer.Width = 1;
fillContainer.Height = 1;
}
});
AddToggleStep("Rotate children", state =>
{
if (state)
{
foreach (var child in fillContainer.Children)
child.RotateTo(45f, 1000);
}
else
{
foreach (var child in fillContainer.Children)
child.RotateTo(0f, 1000);
}
});
AddToggleStep("Shear children", state =>
{
if (state)
{
foreach (var child in fillContainer.Children)
child.Shear = new Vector2(0.2f, 0.2f);
}
else
{
foreach (var child in fillContainer.Children)
child.Shear = Vector2.Zero;
}
});
AddToggleStep("Scale children", state =>
{
if (state)
{
foreach (var child in fillContainer.Children)
child.ScaleTo(1.25f, 1000);
}
else
{
foreach (var child in fillContainer.Children)
child.ScaleTo(1f, 1000);
}
});
AddToggleStep("Randomly scale children", state =>
{
if (state)
{
foreach (var child in fillContainer.Children)
child.ScaleTo(RNG.NextSingle(1, 2), 1000);
}
else
{
foreach (var child in fillContainer.Children)
child.ScaleTo(1f, 1000);
}
});
AddToggleStep("Randomly set child origins", state =>
{
if (state)
{
foreach (var child in fillContainer.Children)
{
switch (RNG.Next(9))
{
case 0: child.Origin = Anchor.TopLeft; break;
case 1: child.Origin = Anchor.TopCentre; break;
case 2: child.Origin = Anchor.TopRight; break;
case 3: child.Origin = Anchor.CentreLeft; break;
case 4: child.Origin = Anchor.Centre; break;
case 5: child.Origin = Anchor.CentreRight; break;
case 6: child.Origin = Anchor.BottomLeft; break;
case 7: child.Origin = Anchor.BottomCentre; break;
case 8: child.Origin = Anchor.BottomRight; break;
}
}
}
else
{
foreach (var child in fillContainer.Children)
child.Origin = originDropdown.Current;
}
});
AddToggleStep("Stop adding children", state => { doNotAddChildren = state; });
scheduledAdder?.Cancel();
scheduledAdder = Scheduler.AddDelayed(
() =>
{
if (fillContainer.Parent == null)
scheduledAdder.Cancel();
if (doNotAddChildren)
{
fillContainer.Invalidate();
}
if (fillContainer.Children.Count < 1000 && !doNotAddChildren)
{
fillContainer.Add(new Container
{
Anchor = childAnchor,
Origin = childOrigin,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Width = 50,
Height = 50,
Colour = Color4.White
},
new SpriteText
{
Colour = Color4.Black,
RelativePositionAxes = Axes.Both,
Position = new Vector2(0.5f, 0.5f),
Origin = Anchor.Centre,
Text = fillContainer.Children.Count.ToString()
}
}
});
}
},
100,
true
);
}
[FlowTestCase(FlowTestCase.Full)]
private void test1()
{
fillContainer.Direction = FillDirection.Full;
fillContainer.Spacing = new Vector2(5, 5);
}
[FlowTestCase(FlowTestCase.Horizontal)]
private void test2()
{
fillContainer.Direction = FillDirection.Horizontal;
fillContainer.Spacing = new Vector2(5, 5);
}
[FlowTestCase(FlowTestCase.Vertical)]
private void test3()
{
fillContainer.Direction = FillDirection.Vertical;
fillContainer.Spacing = new Vector2(5, 5);
}
private class TestCaseDropdownHeader : DropdownHeader
{
private readonly SpriteText label;
protected internal override string Label
{
get { return label.Text; }
set { label.Text = value; }
}
public TestCaseDropdownHeader()
{
Foreground.Padding = new MarginPadding(4);
BackgroundColour = new Color4(100, 100, 100, 255);
BackgroundColourHover = Color4.HotPink;
Children = new[]
{
label = new SpriteText(),
};
}
}
private class AnchorDropdown : BasicDropdown<Anchor>
{
protected override DropdownHeader CreateHeader() => new TestCaseDropdownHeader();
}
private class AnchorDropdownMenuItem : DropdownMenuItem<Anchor>
{
public AnchorDropdownMenuItem(Anchor anchor)
: base(anchor.ToString(), anchor)
{
}
}
private class FillDirectionDropdown : BasicDropdown<FlowTestCase>
{
protected override DropdownHeader CreateHeader() => new TestCaseDropdownHeader();
}
private class FillDirectionDropdownMenuItem : DropdownMenuItem<FlowTestCase>
{
public FillDirectionDropdownMenuItem(FlowTestCase testCase)
: base(testCase.ToString(), testCase)
{
}
}
[AttributeUsage(AttributeTargets.Method)]
private class FlowTestCaseAttribute : Attribute
{
public FlowTestCase TestCase { get; }
public FlowTestCaseAttribute(FlowTestCase testCase)
{
TestCase = testCase;
}
}
private enum FlowTestCase
{
Full,
Horizontal,
Vertical,
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
using osu.Framework.Threading;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseFillFlowContainer : TestCase
{
private FillDirectionDropdown selectionDropdown;
private Anchor childAnchor = Anchor.TopLeft;
private AnchorDropdown anchorDropdown;
private Anchor childOrigin = Anchor.TopLeft;
private AnchorDropdown originDropdown;
private FillFlowContainer fillContainer;
private ScheduledDelegate scheduledAdder;
private bool doNotAddChildren;
public TestCaseFillFlowContainer()
{
reset();
}
private void reset()
{
doNotAddChildren = false;
scheduledAdder?.Cancel();
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Width = 0.2f,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Depth = float.MinValue,
Children = new[]
{
new FillFlowContainer
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new SpriteText { Text = @"Fill mode" },
selectionDropdown = new FillDirectionDropdown
{
RelativeSizeAxes = Axes.X,
Items = Enum.GetValues(typeof(FlowTestCase)).Cast<FlowTestCase>()
.Select(value => new KeyValuePair<string, FlowTestCase>(value.ToString(), value)),
},
new SpriteText { Text = @"Child anchor" },
anchorDropdown = new AnchorDropdown
{
RelativeSizeAxes = Axes.X,
Items = new[]
{
Anchor.TopLeft,
Anchor.TopCentre,
Anchor.TopRight,
Anchor.CentreLeft,
Anchor.Centre,
Anchor.CentreRight,
Anchor.BottomLeft,
Anchor.BottomCentre,
Anchor.BottomRight,
}.Select(anchor => new KeyValuePair<string, Anchor>(anchor.ToString(), anchor)),
},
new SpriteText { Text = @"Child origin" },
originDropdown = new AnchorDropdown
{
RelativeSizeAxes = Axes.X,
Items = new[]
{
Anchor.TopLeft,
Anchor.TopCentre,
Anchor.TopRight,
Anchor.CentreLeft,
Anchor.Centre,
Anchor.CentreRight,
Anchor.BottomLeft,
Anchor.BottomCentre,
Anchor.BottomRight,
}.Select(anchor => new KeyValuePair<string, Anchor>(anchor.ToString(), anchor)),
},
}
}
}
};
selectionDropdown.Current.ValueChanged += changeTest;
buildTest();
selectionDropdown.Current.Value = FlowTestCase.Full;
changeTest(FlowTestCase.Full);
}
protected override void Update()
{
base.Update();
if (childAnchor != anchorDropdown.Current)
{
childAnchor = anchorDropdown.Current;
foreach (var child in fillContainer.Children)
child.Anchor = childAnchor;
}
if (childOrigin != originDropdown.Current)
{
childOrigin = originDropdown.Current;
foreach (var child in fillContainer.Children)
child.Origin = childOrigin;
}
}
private void changeTest(FlowTestCase testCase)
{
var method =
GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).SingleOrDefault(m => m.GetCustomAttribute<FlowTestCaseAttribute>()?.TestCase == testCase);
if (method != null)
method.Invoke(this, new object[0]);
}
private void buildTest()
{
Add(new Container
{
Padding = new MarginPadding(25f),
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
fillContainer = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
AutoSizeAxes = Axes.None,
},
new Box
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(3, 1),
Colour = Color4.HotPink,
},
new Box
{
Anchor = Anchor.CentreRight,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(3, 1),
Colour = Color4.HotPink,
},
new Box
{
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Size = new Vector2(1, 3),
Colour = Color4.HotPink,
},
new Box
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Size = new Vector2(1, 3),
Colour = Color4.HotPink,
}
}
});
AddToggleStep("Rotate Container", state => { fillContainer.RotateTo(state ? 45f : 0, 1000); });
AddToggleStep("Scale Container", state => { fillContainer.ScaleTo(state ? 1.2f : 1f, 1000); });
AddToggleStep("Shear Container", state => { fillContainer.Shear = state ? new Vector2(0.5f, 0f) : new Vector2(0f, 0f); });
AddToggleStep("Center Container Anchor", state => { fillContainer.Anchor = state ? Anchor.Centre : Anchor.TopLeft; });
AddToggleStep("Center Container Origin", state => { fillContainer.Origin = state ? Anchor.Centre : Anchor.TopLeft; });
AddToggleStep("Autosize Container", state =>
{
if (state)
{
fillContainer.RelativeSizeAxes = Axes.None;
fillContainer.AutoSizeAxes = Axes.Both;
}
else
{
fillContainer.AutoSizeAxes = Axes.None;
fillContainer.RelativeSizeAxes = Axes.Both;
fillContainer.Width = 1;
fillContainer.Height = 1;
}
});
AddToggleStep("Rotate children", state =>
{
if (state)
{
foreach (var child in fillContainer.Children)
child.RotateTo(45f, 1000);
}
else
{
foreach (var child in fillContainer.Children)
child.RotateTo(0f, 1000);
}
});
AddToggleStep("Shear children", state =>
{
if (state)
{
foreach (var child in fillContainer.Children)
child.Shear = new Vector2(0.2f, 0.2f);
}
else
{
foreach (var child in fillContainer.Children)
child.Shear = Vector2.Zero;
}
});
AddToggleStep("Scale children", state =>
{
if (state)
{
foreach (var child in fillContainer.Children)
child.ScaleTo(1.25f, 1000);
}
else
{
foreach (var child in fillContainer.Children)
child.ScaleTo(1f, 1000);
}
});
AddToggleStep("Randomly scale children", state =>
{
if (state)
{
foreach (var child in fillContainer.Children)
child.ScaleTo(RNG.NextSingle(1, 2), 1000);
}
else
{
foreach (var child in fillContainer.Children)
child.ScaleTo(1f, 1000);
}
});
AddToggleStep("Randomly set child origins", state =>
{
if (state)
{
foreach (var child in fillContainer.Children)
{
switch (RNG.Next(9))
{
case 0: child.Origin = Anchor.TopLeft; break;
case 1: child.Origin = Anchor.TopCentre; break;
case 2: child.Origin = Anchor.TopRight; break;
case 3: child.Origin = Anchor.CentreLeft; break;
case 4: child.Origin = Anchor.Centre; break;
case 5: child.Origin = Anchor.CentreRight; break;
case 6: child.Origin = Anchor.BottomLeft; break;
case 7: child.Origin = Anchor.BottomCentre; break;
case 8: child.Origin = Anchor.BottomRight; break;
}
}
}
else
{
foreach (var child in fillContainer.Children)
child.Origin = originDropdown.Current;
}
});
AddToggleStep("Stop adding children", state => { doNotAddChildren = state; });
scheduledAdder?.Cancel();
scheduledAdder = Scheduler.AddDelayed(
() =>
{
if (fillContainer.Parent == null)
scheduledAdder.Cancel();
if (doNotAddChildren)
{
fillContainer.Invalidate();
}
if (fillContainer.Children.Count < 1000 && !doNotAddChildren)
{
fillContainer.Add(new Container
{
Anchor = childAnchor,
Origin = childOrigin,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Width = 50,
Height = 50,
Colour = Color4.White
},
new SpriteText
{
Colour = Color4.Black,
RelativePositionAxes = Axes.Both,
Position = new Vector2(0.5f, 0.5f),
Origin = Anchor.Centre,
Text = fillContainer.Children.Count.ToString()
}
}
});
}
},
100,
true
);
}
[FlowTestCase(FlowTestCase.Full)]
private void test1()
{
fillContainer.Direction = FillDirection.Full;
fillContainer.Spacing = new Vector2(5, 5);
}
[FlowTestCase(FlowTestCase.Horizontal)]
private void test2()
{
fillContainer.Direction = FillDirection.Horizontal;
fillContainer.Spacing = new Vector2(5, 5);
}
[FlowTestCase(FlowTestCase.Vertical)]
private void test3()
{
fillContainer.Direction = FillDirection.Vertical;
fillContainer.Spacing = new Vector2(5, 5);
}
private class TestCaseDropdownHeader : DropdownHeader
{
private readonly SpriteText label;
protected internal override string Label
{
get { return label.Text; }
set { label.Text = value; }
}
public TestCaseDropdownHeader()
{
Foreground.Padding = new MarginPadding(4);
BackgroundColour = new Color4(100, 100, 100, 255);
BackgroundColourHover = Color4.HotPink;
Children = new[]
{
label = new SpriteText(),
};
}
}
private class AnchorDropdown : BasicDropdown<Anchor>
{
protected override DropdownHeader CreateHeader() => new TestCaseDropdownHeader();
}
private class AnchorDropdownMenuItem : DropdownMenuItem<Anchor>
{
public AnchorDropdownMenuItem(Anchor anchor)
: base(anchor.ToString(), anchor)
{
}
}
private class FillDirectionDropdown : BasicDropdown<FlowTestCase>
{
protected override DropdownHeader CreateHeader() => new TestCaseDropdownHeader();
}
private class FillDirectionDropdownMenuItem : DropdownMenuItem<FlowTestCase>
{
public FillDirectionDropdownMenuItem(FlowTestCase testCase)
: base(testCase.ToString(), testCase)
{
}
}
[AttributeUsage(AttributeTargets.Method)]
private class FlowTestCaseAttribute : Attribute
{
public FlowTestCase TestCase { get; }
public FlowTestCaseAttribute(FlowTestCase testCase)
{
TestCase = testCase;
}
}
private enum FlowTestCase
{
Full,
Horizontal,
Vertical,
}
}
}

View File

@@ -1,207 +1,207 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("sprite stretching")]
public class TestCaseFillModes : GridTestCase
{
public TestCaseFillModes() : base(3, 3)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
FillMode[] fillModes =
{
FillMode.Stretch,
FillMode.Fit,
FillMode.Fill,
};
float[] aspects = { 1, 2, 0.5f };
for (int i = 0; i < Rows; ++i)
{
for (int j = 0; j < Cols; ++j)
{
Cell(i, j).AddRange(new Drawable[]
{
new SpriteText
{
Text = $"{nameof(FillMode)}=FillMode.{fillModes[i]}, {nameof(FillAspectRatio)}={aspects[j]}",
TextSize = 20,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Blue,
},
new Sprite
{
RelativeSizeAxes = Axes.Both,
Texture = texture,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = fillModes[i],
FillAspectRatio = aspects[j],
}
}
}
});
}
}
}
private Texture texture;
[BackgroundDependencyLoader]
private void load(TextureStore store)
{
texture = store.Get(@"sample-texture");
}
private class PaddedBox : Container
{
private readonly SpriteText t1;
private readonly SpriteText t2;
private readonly SpriteText t3;
private readonly SpriteText t4;
private readonly Container content;
protected override Container<Drawable> Content => content;
public PaddedBox(Color4 colour)
{
AddRangeInternal(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colour,
},
content = new Container
{
RelativeSizeAxes = Axes.Both,
},
t1 = new SpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
t2 = new SpriteText
{
Rotation = 90,
Anchor = Anchor.CentreRight,
Origin = Anchor.TopCentre
},
t3 = new SpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre
},
t4 = new SpriteText
{
Rotation = -90,
Anchor = Anchor.CentreLeft,
Origin = Anchor.TopCentre
}
});
Masking = true;
}
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
{
t1.Text = (Padding.Top > 0 ? $"p{Padding.Top}" : string.Empty) + (Margin.Top > 0 ? $"m{Margin.Top}" : string.Empty);
t2.Text = (Padding.Right > 0 ? $"p{Padding.Right}" : string.Empty) + (Margin.Right > 0 ? $"m{Margin.Right}" : string.Empty);
t3.Text = (Padding.Bottom > 0 ? $"p{Padding.Bottom}" : string.Empty) + (Margin.Bottom > 0 ? $"m{Margin.Bottom}" : string.Empty);
t4.Text = (Padding.Left > 0 ? $"p{Padding.Left}" : string.Empty) + (Margin.Left > 0 ? $"m{Margin.Left}" : string.Empty);
return base.Invalidate(invalidation, source, shallPropagate);
}
protected override bool OnDrag(InputState state)
{
Position += state.Mouse.Delta;
return true;
}
protected override bool OnDragEnd(InputState state) => true;
protected override bool OnDragStart(InputState state) => true;
}
#region Test Cases
private const float container_width = 60;
private Box fitBox;
/// <summary>
/// Tests that using <see cref="FillMode.Fit"/> inside a <see cref="FlowContainer{T}"/> that is autosizing in one axis doesn't result in autosize feedback loops.
/// Various sizes of the box are tested to ensure that non-one sizes also don't lead to erroneous sizes.
/// </summary>
/// <param name="value">The relative size of the box that is fitting.</param>
[TestCase(0f)]
[TestCase(0.5f)]
[TestCase(1f)]
public void TestFitInsideFlow(float value)
{
ClearInternal();
AddInternal(new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Y,
Width = container_width,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
fitBox = new Box
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit
},
// A box which forces the minimum dimension of the autosize flow container to be the horizontal dimension
new Box { Size = new Vector2(container_width, container_width * 2) }
}
});
AddStep("Set size", () => fitBox.Size = new Vector2(value));
var expectedSize = new Vector2(container_width * value, container_width * value);
AddAssert("Check size before invalidate (1/2)", () => fitBox.DrawSize == expectedSize);
AddAssert("Check size before invalidate (2/2)", () => fitBox.DrawSize == expectedSize);
AddStep("Invalidate", () => fitBox.Invalidate());
AddAssert("Check size after invalidate (1/2)", () => fitBox.DrawSize == expectedSize);
AddAssert("Check size after invalidate (2/2)", () => fitBox.DrawSize == expectedSize);
}
#endregion
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("sprite stretching")]
public class TestCaseFillModes : GridTestCase
{
public TestCaseFillModes() : base(3, 3)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
FillMode[] fillModes =
{
FillMode.Stretch,
FillMode.Fit,
FillMode.Fill,
};
float[] aspects = { 1, 2, 0.5f };
for (int i = 0; i < Rows; ++i)
{
for (int j = 0; j < Cols; ++j)
{
Cell(i, j).AddRange(new Drawable[]
{
new SpriteText
{
Text = $"{nameof(FillMode)}=FillMode.{fillModes[i]}, {nameof(FillAspectRatio)}={aspects[j]}",
TextSize = 20,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Blue,
},
new Sprite
{
RelativeSizeAxes = Axes.Both,
Texture = texture,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = fillModes[i],
FillAspectRatio = aspects[j],
}
}
}
});
}
}
}
private Texture texture;
[BackgroundDependencyLoader]
private void load(TextureStore store)
{
texture = store.Get(@"sample-texture");
}
private class PaddedBox : Container
{
private readonly SpriteText t1;
private readonly SpriteText t2;
private readonly SpriteText t3;
private readonly SpriteText t4;
private readonly Container content;
protected override Container<Drawable> Content => content;
public PaddedBox(Color4 colour)
{
AddRangeInternal(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colour,
},
content = new Container
{
RelativeSizeAxes = Axes.Both,
},
t1 = new SpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
t2 = new SpriteText
{
Rotation = 90,
Anchor = Anchor.CentreRight,
Origin = Anchor.TopCentre
},
t3 = new SpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre
},
t4 = new SpriteText
{
Rotation = -90,
Anchor = Anchor.CentreLeft,
Origin = Anchor.TopCentre
}
});
Masking = true;
}
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
{
t1.Text = (Padding.Top > 0 ? $"p{Padding.Top}" : string.Empty) + (Margin.Top > 0 ? $"m{Margin.Top}" : string.Empty);
t2.Text = (Padding.Right > 0 ? $"p{Padding.Right}" : string.Empty) + (Margin.Right > 0 ? $"m{Margin.Right}" : string.Empty);
t3.Text = (Padding.Bottom > 0 ? $"p{Padding.Bottom}" : string.Empty) + (Margin.Bottom > 0 ? $"m{Margin.Bottom}" : string.Empty);
t4.Text = (Padding.Left > 0 ? $"p{Padding.Left}" : string.Empty) + (Margin.Left > 0 ? $"m{Margin.Left}" : string.Empty);
return base.Invalidate(invalidation, source, shallPropagate);
}
protected override bool OnDrag(InputState state)
{
Position += state.Mouse.Delta;
return true;
}
protected override bool OnDragEnd(InputState state) => true;
protected override bool OnDragStart(InputState state) => true;
}
#region Test Cases
private const float container_width = 60;
private Box fitBox;
/// <summary>
/// Tests that using <see cref="FillMode.Fit"/> inside a <see cref="FlowContainer{T}"/> that is autosizing in one axis doesn't result in autosize feedback loops.
/// Various sizes of the box are tested to ensure that non-one sizes also don't lead to erroneous sizes.
/// </summary>
/// <param name="value">The relative size of the box that is fitting.</param>
[TestCase(0f)]
[TestCase(0.5f)]
[TestCase(1f)]
public void TestFitInsideFlow(float value)
{
ClearInternal();
AddInternal(new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Y,
Width = container_width,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
fitBox = new Box
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit
},
// A box which forces the minimum dimension of the autosize flow container to be the horizontal dimension
new Box { Size = new Vector2(container_width, container_width * 2) }
}
});
AddStep("Set size", () => fitBox.Size = new Vector2(value));
var expectedSize = new Vector2(container_width * value, container_width * value);
AddAssert("Check size before invalidate (1/2)", () => fitBox.DrawSize == expectedSize);
AddAssert("Check size before invalidate (2/2)", () => fitBox.DrawSize == expectedSize);
AddStep("Invalidate", () => fitBox.Invalidate());
AddAssert("Check size after invalidate (1/2)", () => fitBox.DrawSize == expectedSize);
AddAssert("Check size after invalidate (2/2)", () => fitBox.DrawSize == expectedSize);
}
#endregion
}
}

View File

@@ -1,370 +1,370 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
public class TestCaseGridContainer : TestCase
{
private readonly GridContainer grid;
public TestCaseGridContainer()
{
Add(new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Masking = true,
BorderColour = Color4.White,
BorderThickness = 2,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
},
grid = new GridContainer { RelativeSizeAxes = Axes.Both }
}
});
AddStep("Blank grid", reset);
AddStep("1-cell (auto)", () =>
{
reset();
grid.Content = new[] { new Drawable[] { new FillBox() } };
});
AddStep("1-cell (absolute)", () =>
{
reset();
grid.Content = new[] { new Drawable[] { new FillBox() } };
grid.RowDimensions = grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.Absolute, 100) };
});
AddStep("1-cell (relative)", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox() } };
grid.RowDimensions = grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.Relative, 0.5f) };
});
AddStep("1-cell (mixed)", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox() } };
grid.RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 100) };
grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.Relative, 0.5f) };
});
AddStep("1-cell (mixed) 2", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox() } };
grid.RowDimensions = new [] { new Dimension(GridSizeMode.Relative, 0.5f) };
});
AddStep("3-cell row (auto)", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox(), new FillBox(), new FillBox() } };
});
AddStep("3-cell row (absolute)", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox(), new FillBox(), new FillBox() } };
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 50),
new Dimension(GridSizeMode.Absolute, 100),
new Dimension(GridSizeMode.Absolute, 150)
};
});
AddStep("3-cell row (relative)", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox(), new FillBox(), new FillBox() } };
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Relative, 0.1f),
new Dimension(GridSizeMode.Relative, 0.2f),
new Dimension(GridSizeMode.Relative, 0.3f)
};
});
AddStep("3-cell row (mixed)", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox(), new FillBox(), new FillBox() } };
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 50),
new Dimension(GridSizeMode.Relative, 0.2f)
};
});
AddStep("3-cell column (auto)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() }
};
});
AddStep("3-cell column (absolute)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() }
};
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 50),
new Dimension(GridSizeMode.Absolute, 100),
new Dimension(GridSizeMode.Absolute, 150)
};
});
AddStep("3-cell column (relative)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() }
};
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Relative, 0.1f),
new Dimension(GridSizeMode.Relative, 0.2f),
new Dimension(GridSizeMode.Relative, 0.3f)
};
});
AddStep("3-cell column (mixed)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() }
};
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 50),
new Dimension(GridSizeMode.Relative, 0.2f)
};
});
AddStep("3x3-cell (auto)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() }
};
});
AddStep("3x3-cell (absolute)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() }
};
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 50),
new Dimension(GridSizeMode.Absolute, 100),
new Dimension(GridSizeMode.Absolute, 150)
};
});
AddStep("3x3-cell (relative)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() }
};
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Relative, 0.1f),
new Dimension(GridSizeMode.Relative, 0.2f),
new Dimension(GridSizeMode.Relative, 0.3f)
};
});
AddStep("3x3-cell (mixed)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() }
};
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 50),
new Dimension(GridSizeMode.Relative, 0.2f)
};
});
AddStep("Larger sides", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() }
};
grid.ColumnDimensions = grid.RowDimensions = new[]
{
new Dimension(GridSizeMode.Relative, 0.4f),
new Dimension(),
new Dimension(GridSizeMode.Relative, 0.4f)
};
});
AddStep("Separated", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), null, new FillBox() },
null,
new Drawable[] { new FillBox(), null, new FillBox() }
};
});
AddStep("Separated 2", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), null, new FillBox(), null },
null,
new Drawable[] { new FillBox(), null, new FillBox(), null },
null
};
});
AddStep("Nested grids", () =>
{
reset();
grid.Content = new[]
{
new Drawable[]
{
new FillBox(),
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[] { new FillBox(), new FillBox() },
new Drawable[]
{
new FillBox(),
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[] { new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox() }
}
}
}
}
},
new FillBox()
}
};
});
AddStep("Auto size", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new Box { Size = new Vector2(30) }, new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() }
};
grid.RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.Relative, 0.5f) };
grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.Relative, 0.5f) };
});
AddStep("Autosizing child", () =>
{
reset();
grid.Content = new[]
{
new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Child = new Box { Size = new Vector2(100, 50) }
},
new FillBox()
}
};
grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) };
});
}
private void reset()
{
grid.ClearInternal();
grid.RowDimensions = grid.ColumnDimensions = new Dimension[] { };
}
private class FillBox : Box
{
public FillBox()
{
RelativeSizeAxes = Axes.Both;
Colour = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1);
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
public class TestCaseGridContainer : TestCase
{
private readonly GridContainer grid;
public TestCaseGridContainer()
{
Add(new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Masking = true,
BorderColour = Color4.White,
BorderThickness = 2,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
},
grid = new GridContainer { RelativeSizeAxes = Axes.Both }
}
});
AddStep("Blank grid", reset);
AddStep("1-cell (auto)", () =>
{
reset();
grid.Content = new[] { new Drawable[] { new FillBox() } };
});
AddStep("1-cell (absolute)", () =>
{
reset();
grid.Content = new[] { new Drawable[] { new FillBox() } };
grid.RowDimensions = grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.Absolute, 100) };
});
AddStep("1-cell (relative)", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox() } };
grid.RowDimensions = grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.Relative, 0.5f) };
});
AddStep("1-cell (mixed)", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox() } };
grid.RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 100) };
grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.Relative, 0.5f) };
});
AddStep("1-cell (mixed) 2", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox() } };
grid.RowDimensions = new [] { new Dimension(GridSizeMode.Relative, 0.5f) };
});
AddStep("3-cell row (auto)", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox(), new FillBox(), new FillBox() } };
});
AddStep("3-cell row (absolute)", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox(), new FillBox(), new FillBox() } };
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 50),
new Dimension(GridSizeMode.Absolute, 100),
new Dimension(GridSizeMode.Absolute, 150)
};
});
AddStep("3-cell row (relative)", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox(), new FillBox(), new FillBox() } };
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Relative, 0.1f),
new Dimension(GridSizeMode.Relative, 0.2f),
new Dimension(GridSizeMode.Relative, 0.3f)
};
});
AddStep("3-cell row (mixed)", () =>
{
reset();
grid.Content = new [] { new Drawable[] { new FillBox(), new FillBox(), new FillBox() } };
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 50),
new Dimension(GridSizeMode.Relative, 0.2f)
};
});
AddStep("3-cell column (auto)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() }
};
});
AddStep("3-cell column (absolute)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() }
};
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 50),
new Dimension(GridSizeMode.Absolute, 100),
new Dimension(GridSizeMode.Absolute, 150)
};
});
AddStep("3-cell column (relative)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() }
};
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Relative, 0.1f),
new Dimension(GridSizeMode.Relative, 0.2f),
new Dimension(GridSizeMode.Relative, 0.3f)
};
});
AddStep("3-cell column (mixed)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() },
new Drawable[] { new FillBox() }
};
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 50),
new Dimension(GridSizeMode.Relative, 0.2f)
};
});
AddStep("3x3-cell (auto)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() }
};
});
AddStep("3x3-cell (absolute)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() }
};
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 50),
new Dimension(GridSizeMode.Absolute, 100),
new Dimension(GridSizeMode.Absolute, 150)
};
});
AddStep("3x3-cell (relative)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() }
};
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Relative, 0.1f),
new Dimension(GridSizeMode.Relative, 0.2f),
new Dimension(GridSizeMode.Relative, 0.3f)
};
});
AddStep("3x3-cell (mixed)", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() }
};
grid.RowDimensions = grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 50),
new Dimension(GridSizeMode.Relative, 0.2f)
};
});
AddStep("Larger sides", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() }
};
grid.ColumnDimensions = grid.RowDimensions = new[]
{
new Dimension(GridSizeMode.Relative, 0.4f),
new Dimension(),
new Dimension(GridSizeMode.Relative, 0.4f)
};
});
AddStep("Separated", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), null, new FillBox() },
null,
new Drawable[] { new FillBox(), null, new FillBox() }
};
});
AddStep("Separated 2", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new FillBox(), null, new FillBox(), null },
null,
new Drawable[] { new FillBox(), null, new FillBox(), null },
null
};
});
AddStep("Nested grids", () =>
{
reset();
grid.Content = new[]
{
new Drawable[]
{
new FillBox(),
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[] { new FillBox(), new FillBox() },
new Drawable[]
{
new FillBox(),
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[] { new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox() }
}
}
}
}
},
new FillBox()
}
};
});
AddStep("Auto size", () =>
{
reset();
grid.Content = new[]
{
new Drawable[] { new Box { Size = new Vector2(30) }, new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() },
new Drawable[] { new FillBox(), new FillBox(), new FillBox() }
};
grid.RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.Relative, 0.5f) };
grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.Relative, 0.5f) };
});
AddStep("Autosizing child", () =>
{
reset();
grid.Content = new[]
{
new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Child = new Box { Size = new Vector2(100, 50) }
},
new FillBox()
}
};
grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) };
});
}
private void reset()
{
grid.ClearInternal();
grid.RowDimensions = grid.ColumnDimensions = new Dimension[] { };
}
private class FillBox : Box
{
public FillBox()
{
RelativeSizeAxes = Axes.Both;
Colour = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1);
}
}
}
}

View File

@@ -1,87 +1,87 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseHollowEdgeEffect : GridTestCase
{
public TestCaseHollowEdgeEffect() : base(2, 2)
{
const float size = 60;
float[] cornerRadii = { 0, 0.5f, 0, 0.5f };
float[] alphas = { 0.5f, 0.5f, 0, 0 };
EdgeEffectParameters[] edgeEffects =
{
new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Color4.Khaki,
Radius = size,
Hollow = true,
},
new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Color4.Khaki,
Radius = size,
Hollow = true,
},
new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Color4.Khaki,
Radius = size,
Hollow = true,
},
new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Color4.Khaki,
Radius = size,
Hollow = true,
},
};
for (int i = 0; i < Rows * Cols; ++i)
{
Cell(i).AddRange(new Drawable[]
{
new SpriteText
{
Text = $"{nameof(CornerRadius)}={cornerRadii[i]} {nameof(Alpha)}={alphas[i]}",
TextSize = 20,
},
new Container
{
Size = new Vector2(size),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
EdgeEffect = edgeEffects[i],
CornerRadius = cornerRadii[i] * size,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Aqua,
Alpha = alphas[i],
},
},
},
});
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseHollowEdgeEffect : GridTestCase
{
public TestCaseHollowEdgeEffect() : base(2, 2)
{
const float size = 60;
float[] cornerRadii = { 0, 0.5f, 0, 0.5f };
float[] alphas = { 0.5f, 0.5f, 0, 0 };
EdgeEffectParameters[] edgeEffects =
{
new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Color4.Khaki,
Radius = size,
Hollow = true,
},
new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Color4.Khaki,
Radius = size,
Hollow = true,
},
new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Color4.Khaki,
Radius = size,
Hollow = true,
},
new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Color4.Khaki,
Radius = size,
Hollow = true,
},
};
for (int i = 0; i < Rows * Cols; ++i)
{
Cell(i).AddRange(new Drawable[]
{
new SpriteText
{
Text = $"{nameof(CornerRadius)}={cornerRadii[i]} {nameof(Alpha)}={alphas[i]}",
TextSize = 20,
},
new Container
{
Size = new Vector2(size),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
EdgeEffect = edgeEffects[i],
CornerRadius = cornerRadii[i] * size,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Aqua,
Alpha = alphas[i],
},
},
},
});
}
}
}
}

View File

@@ -1,208 +1,208 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("live path optimiastion")]
public class TestCaseInputResampler : GridTestCase
{
public TestCaseInputResampler() : base(3, 3)
{
const int width = 2;
Texture gradientTexture = new Texture(width, 1, true);
byte[] data = new byte[width * 4];
for (int i = 0; i < width; ++i)
{
float brightness = (float)i / (width - 1);
int index = i * 4;
data[index + 0] = (byte)(brightness * 255);
data[index + 1] = (byte)(brightness * 255);
data[index + 2] = (byte)(brightness * 255);
data[index + 3] = 255;
}
gradientTexture.SetData(new TextureUpload(data));
SpriteText[] text = new SpriteText[6];
Cell(0, 0).AddRange(new Drawable[]
{
text[0] = createLabel("Raw"),
new ArcPath(true, true, new InputResampler(), gradientTexture, Color4.Green, text[0]),
});
Cell(0, 1).AddRange(new Drawable[]
{
text[1] = createLabel("Rounded (resembles mouse input)"),
new ArcPath(true, false, new InputResampler(), gradientTexture, Color4.Blue, text[1]),
});
Cell(0, 2).AddRange(new Drawable[]
{
text[2] = createLabel("Custom: Smoothed=0, Raw=0"),
new UserDrawnPath
{
DrawText = text[2],
RelativeSizeAxes = Axes.Both,
Texture = gradientTexture,
Colour = Color4.White,
},
});
Cell(1, 0).AddRange(new Drawable[]
{
text[3] = createLabel("Smoothed raw"),
new ArcPath(false, true, new InputResampler(), gradientTexture, Color4.Green, text[3]),
});
Cell(1, 1).AddRange(new Drawable[]
{
text[4] = createLabel("Smoothed rounded"),
new ArcPath(false, false, new InputResampler(), gradientTexture, Color4.Blue, text[4]),
});
Cell(1, 2).AddRange(new Drawable[]
{
text[5] = createLabel("Smoothed custom: Smoothed=0, Raw=0"),
new SmoothedUserDrawnPath
{
DrawText = text[5],
RelativeSizeAxes = Axes.Both,
Texture = gradientTexture,
Colour = Color4.White,
InputResampler = new InputResampler(),
},
});
Cell(2, 0).AddRange(new Drawable[]
{
text[3] = createLabel("Force-smoothed raw"),
new ArcPath(false, true, new InputResampler { ResampleRawInput = true }, gradientTexture, Color4.Green, text[3]),
});
Cell(2, 1).AddRange(new Drawable[]
{
text[4] = createLabel("Force-smoothed rounded"),
new ArcPath(false, false, new InputResampler { ResampleRawInput = true }, gradientTexture, Color4.Blue, text[4]),
});
Cell(2, 2).AddRange(new Drawable[]
{
text[5] = createLabel("Force-smoothed custom: Smoothed=0, Raw=0"),
new SmoothedUserDrawnPath
{
DrawText = text[5],
RelativeSizeAxes = Axes.Both,
Texture = gradientTexture,
Colour = Color4.White,
InputResampler = new InputResampler
{
ResampleRawInput = true
},
},
});
}
private SpriteText createLabel(string text) => new SpriteText
{
Text = text,
TextSize = 14,
Colour = Color4.White,
};
private class SmoothedPath : Path
{
protected SmoothedPath()
{
PathWidth = 2;
}
public InputResampler InputResampler { get; set; } = new InputResampler();
protected int NumVertices { get; set; }
protected int NumRaw { get; set; }
protected void AddRawVertex(Vector2 pos)
{
NumRaw++;
AddVertex(pos);
NumVertices++;
}
protected bool AddSmoothedVertex(Vector2 pos)
{
NumRaw++;
bool foundOne = false;
foreach (Vector2 relevant in InputResampler.AddPosition(pos))
{
AddVertex(relevant);
NumVertices++;
foundOne = true;
}
return foundOne;
}
}
private class ArcPath : SmoothedPath
{
public ArcPath(bool raw, bool keepFraction, InputResampler inputResampler, Texture texture, Color4 colour, SpriteText output)
{
InputResampler = inputResampler;
const int target_raw = 1024;
RelativeSizeAxes = Axes.Both;
Texture = texture;
Colour = colour;
for (int i = 0; i < target_raw; i++)
{
float x = (float)(Math.Sin(i / (double)target_raw * (Math.PI * 0.5)) * 200) + 50.5f;
float y = (float)(Math.Cos(i / (double)target_raw * (Math.PI * 0.5)) * 200) + 50.5f;
Vector2 v = keepFraction ? new Vector2(x, y) : new Vector2((int)x, (int)y);
if (raw)
AddRawVertex(v);
else
AddSmoothedVertex(v);
}
output.Text += ": Smoothed=" + NumVertices + ", Raw=" + NumRaw;
}
}
private class UserDrawnPath : SmoothedPath
{
public SpriteText DrawText;
protected virtual void AddUserVertex(Vector2 v) => AddRawVertex(v);
protected override bool OnDragStart(InputState state)
{
AddUserVertex(state.Mouse.Position);
DrawText.Text = "Custom Smoothed Drawn: Smoothed=" + NumVertices + ", Raw=" + NumRaw;
return true;
}
protected override bool OnDrag(InputState state)
{
AddUserVertex(state.Mouse.Position);
DrawText.Text = "Custom Smoothed Drawn: Smoothed=" + NumVertices + ", Raw=" + NumRaw;
return base.OnDrag(state);
}
}
private class SmoothedUserDrawnPath : UserDrawnPath
{
protected override void AddUserVertex(Vector2 v) => AddSmoothedVertex(v);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("live path optimiastion")]
public class TestCaseInputResampler : GridTestCase
{
public TestCaseInputResampler() : base(3, 3)
{
const int width = 2;
Texture gradientTexture = new Texture(width, 1, true);
byte[] data = new byte[width * 4];
for (int i = 0; i < width; ++i)
{
float brightness = (float)i / (width - 1);
int index = i * 4;
data[index + 0] = (byte)(brightness * 255);
data[index + 1] = (byte)(brightness * 255);
data[index + 2] = (byte)(brightness * 255);
data[index + 3] = 255;
}
gradientTexture.SetData(new TextureUpload(data));
SpriteText[] text = new SpriteText[6];
Cell(0, 0).AddRange(new Drawable[]
{
text[0] = createLabel("Raw"),
new ArcPath(true, true, new InputResampler(), gradientTexture, Color4.Green, text[0]),
});
Cell(0, 1).AddRange(new Drawable[]
{
text[1] = createLabel("Rounded (resembles mouse input)"),
new ArcPath(true, false, new InputResampler(), gradientTexture, Color4.Blue, text[1]),
});
Cell(0, 2).AddRange(new Drawable[]
{
text[2] = createLabel("Custom: Smoothed=0, Raw=0"),
new UserDrawnPath
{
DrawText = text[2],
RelativeSizeAxes = Axes.Both,
Texture = gradientTexture,
Colour = Color4.White,
},
});
Cell(1, 0).AddRange(new Drawable[]
{
text[3] = createLabel("Smoothed raw"),
new ArcPath(false, true, new InputResampler(), gradientTexture, Color4.Green, text[3]),
});
Cell(1, 1).AddRange(new Drawable[]
{
text[4] = createLabel("Smoothed rounded"),
new ArcPath(false, false, new InputResampler(), gradientTexture, Color4.Blue, text[4]),
});
Cell(1, 2).AddRange(new Drawable[]
{
text[5] = createLabel("Smoothed custom: Smoothed=0, Raw=0"),
new SmoothedUserDrawnPath
{
DrawText = text[5],
RelativeSizeAxes = Axes.Both,
Texture = gradientTexture,
Colour = Color4.White,
InputResampler = new InputResampler(),
},
});
Cell(2, 0).AddRange(new Drawable[]
{
text[3] = createLabel("Force-smoothed raw"),
new ArcPath(false, true, new InputResampler { ResampleRawInput = true }, gradientTexture, Color4.Green, text[3]),
});
Cell(2, 1).AddRange(new Drawable[]
{
text[4] = createLabel("Force-smoothed rounded"),
new ArcPath(false, false, new InputResampler { ResampleRawInput = true }, gradientTexture, Color4.Blue, text[4]),
});
Cell(2, 2).AddRange(new Drawable[]
{
text[5] = createLabel("Force-smoothed custom: Smoothed=0, Raw=0"),
new SmoothedUserDrawnPath
{
DrawText = text[5],
RelativeSizeAxes = Axes.Both,
Texture = gradientTexture,
Colour = Color4.White,
InputResampler = new InputResampler
{
ResampleRawInput = true
},
},
});
}
private SpriteText createLabel(string text) => new SpriteText
{
Text = text,
TextSize = 14,
Colour = Color4.White,
};
private class SmoothedPath : Path
{
protected SmoothedPath()
{
PathWidth = 2;
}
public InputResampler InputResampler { get; set; } = new InputResampler();
protected int NumVertices { get; set; }
protected int NumRaw { get; set; }
protected void AddRawVertex(Vector2 pos)
{
NumRaw++;
AddVertex(pos);
NumVertices++;
}
protected bool AddSmoothedVertex(Vector2 pos)
{
NumRaw++;
bool foundOne = false;
foreach (Vector2 relevant in InputResampler.AddPosition(pos))
{
AddVertex(relevant);
NumVertices++;
foundOne = true;
}
return foundOne;
}
}
private class ArcPath : SmoothedPath
{
public ArcPath(bool raw, bool keepFraction, InputResampler inputResampler, Texture texture, Color4 colour, SpriteText output)
{
InputResampler = inputResampler;
const int target_raw = 1024;
RelativeSizeAxes = Axes.Both;
Texture = texture;
Colour = colour;
for (int i = 0; i < target_raw; i++)
{
float x = (float)(Math.Sin(i / (double)target_raw * (Math.PI * 0.5)) * 200) + 50.5f;
float y = (float)(Math.Cos(i / (double)target_raw * (Math.PI * 0.5)) * 200) + 50.5f;
Vector2 v = keepFraction ? new Vector2(x, y) : new Vector2((int)x, (int)y);
if (raw)
AddRawVertex(v);
else
AddSmoothedVertex(v);
}
output.Text += ": Smoothed=" + NumVertices + ", Raw=" + NumRaw;
}
}
private class UserDrawnPath : SmoothedPath
{
public SpriteText DrawText;
protected virtual void AddUserVertex(Vector2 v) => AddRawVertex(v);
protected override bool OnDragStart(InputState state)
{
AddUserVertex(state.Mouse.Position);
DrawText.Text = "Custom Smoothed Drawn: Smoothed=" + NumVertices + ", Raw=" + NumRaw;
return true;
}
protected override bool OnDrag(InputState state)
{
AddUserVertex(state.Mouse.Position);
DrawText.Text = "Custom Smoothed Drawn: Smoothed=" + NumVertices + ", Raw=" + NumRaw;
return base.OnDrag(state);
}
}
private class SmoothedUserDrawnPath : UserDrawnPath
{
protected override void AddUserVertex(Vector2 v) => AddSmoothedVertex(v);
}
}
}

View File

@@ -1,341 +1,341 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
public class TestCaseIsMaskedAway : TestCase
{
/// <summary>
/// Tests that a box which is within the bounds of a parent is never masked away, regardless of whether the parent is masking or not.
/// </summary>
/// <param name="masking">Whether the box's parent is masking.</param>
[TestCase(false)]
[TestCase(true)]
public void TestBoxInBounds(bool masking)
{
Box box;
Child = new Container
{
Size = new Vector2(200),
Masking = masking,
Child = box = new Box()
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => !box.IsMaskedAway);
}
/// <summary>
/// Tests that a box which is outside the bounds of a parent is never masked away if the parent is not masking.
/// </summary>
[Test]
public void TestBoxOutOfBoundsNoMasking()
{
Box box;
Child = new Container
{
Size = new Vector2(200),
Child = box = new Box { Position = new Vector2(-1) }
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => !box.IsMaskedAway);
}
/// <summary>
/// Tests that a box which is slightly outside the bounds of a masking parent is never masked away, regardless of its anchor/origin.
/// Ensures that all screen-space calculations are current by the time <see cref="Drawable.IsMaskedAway"/> is calculated.
/// </summary>
/// <param name="anchor">The box's anchor in the masking parent.</param>
[TestCase(Anchor.TopLeft)]
[TestCase(Anchor.TopRight)]
[TestCase(Anchor.BottomLeft)]
[TestCase(Anchor.BottomRight)]
public void TestBoxSlightlyOutOfBoundsMasking(Anchor anchor)
{
Box box;
Child = new Container
{
Size = new Vector2(200),
Masking = true,
Child = box = new Box
{
Anchor = anchor,
Origin = anchor,
Size = new Vector2(10),
Position = new Vector2((anchor & Anchor.x0) > 0 ? -5 : 5, (anchor & Anchor.y0) > 0 ? -5 : 5),
}
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => !box.IsMaskedAway);
}
/// <summary>
/// Tests that a box which is fully outside the bounds of a masking parent is always masked away, regardless of its anchor/origin.
/// Ensures that all screen-space calculations are current by the time <see cref="Drawable.IsMaskedAway"/> is calculated.
/// </summary>
/// <param name="anchor">The box's anchor in the masking parent.</param>
[TestCase(Anchor.TopLeft)]
[TestCase(Anchor.TopRight)]
[TestCase(Anchor.BottomLeft)]
[TestCase(Anchor.BottomRight)]
public void TestBoxFullyOutOfBoundsMasking(Anchor anchor)
{
Box box;
Child = new Container
{
Size = new Vector2(200),
Masking = true,
Child = box = new Box
{
Anchor = anchor,
Origin = anchor,
Size = new Vector2(10),
Position = new Vector2((anchor & Anchor.x0) > 0 ? -20 : 20, (anchor & Anchor.y0) > 0 ? -20 : 20),
}
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => box.IsMaskedAway);
}
/// <summary>
/// Tests that a box is never masked away when it and its proxy are within the bounds of their parents, regardless of whether their parents
/// are masking or not.
/// </summary>
/// <param name="boxMasking">Whether the box's parent is masking.</param>
/// <param name="proxyMasking">Whether the proxy's parent is masking.</param>
[TestCase(false, false)]
[TestCase(true, true)]
public void TestBoxInBoundsWithProxyInBounds(bool boxMasking, bool proxyMasking)
{
var box = new Box();
ProxyDrawable proxy;
Children = new Drawable[]
{
new Container
{
Size = new Vector2(200),
Masking = boxMasking,
Child = box
},
new Container
{
Size = new Vector2(200),
Masking = proxyMasking,
Child = proxy = box.CreateProxy()
},
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => !box.IsMaskedAway);
AddAssert("Check proxy IsMaskedAway", () => !proxy.IsMaskedAway);
}
/// <summary>
/// Tests that a box is never masked away when its proxy is within the bounds of its parent, even if the box is outside the bounds of its parent.
/// </summary>
/// <param name="masking">Whether the box's parent is masking. This does not affect the proxy's parent.</param>
[TestCase(false)]
[TestCase(true)]
public void TestBoxOutOfBoundsWithProxyInBounds(bool masking)
{
var box = new Box { Position = new Vector2(-1) };
ProxyDrawable proxy;
Children = new Drawable[]
{
new Container
{
Size = new Vector2(200),
Masking = masking,
Child = box
},
proxy = box.CreateProxy()
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => !box.IsMaskedAway);
AddAssert("Check proxy IsMaskedAway", () => !proxy.IsMaskedAway);
}
/// <summary>
/// Tests that a box is only masked away when its proxy is masked away.
/// </summary>
/// <param name="boxMasking">Whether the box's parent is masking.</param>
/// <param name="proxyMasking">Whether the proxy's parent is masking.</param>
/// <param name="shouldBeMaskedAway">Whether the box should be masked away.</param>
[TestCase(false, false, false)]
[TestCase(false, true, true)]
[TestCase(true, false, false)]
[TestCase(true, true, true)]
public void TestBoxInBoundsWithProxyOutOfBounds(bool boxMasking, bool proxyMasking, bool shouldBeMaskedAway)
{
var box = new Box();
ProxyDrawable proxy;
Children = new Drawable[]
{
new Container
{
Size = new Vector2(200),
Masking = boxMasking,
Child = box
},
new Container
{
Position = new Vector2(10),
Size = new Vector2(200),
Masking = proxyMasking,
Child = proxy = box.CreateProxy()
},
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => box.IsMaskedAway == shouldBeMaskedAway);
AddAssert("Check proxy IsMaskedAway", () => !proxy.IsMaskedAway);
}
/// <summary>
/// Tests that whether the box is out of bounds of its parent is not a consideration for masking, only whether its proxy is out of bounds of its parent.
/// </summary>
/// <param name="boxMasking">Whether the box's parent is masking.</param>
/// <param name="proxyMasking">Whether the proxy's parent is masking.</param>
/// <param name="shouldBeMaskedAway">Whether the box should be masked away</param>
[TestCase(false, false, false)]
[TestCase(false, true, true)]
[TestCase(true, false, false)]
[TestCase(true, true, true)]
public void TestBoxOutOfBoundsWithProxyOutOfBounds(bool boxMasking, bool proxyMasking, bool shouldBeMaskedAway)
{
var box = new Box { Position = new Vector2(-1) };
ProxyDrawable proxy;
Children = new Drawable[]
{
new Container
{
Size = new Vector2(200),
Masking = boxMasking,
Child = box
},
new Container
{
Position = new Vector2(10),
Size = new Vector2(200),
Masking = proxyMasking,
Child = proxy = box.CreateProxy()
},
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => box.IsMaskedAway == shouldBeMaskedAway);
AddAssert("Check proxy IsMaskedAway", () => !proxy.IsMaskedAway);
}
/// <summary>
/// Tests that the box doesn't get masked away unless its most-proxied-proxy is masked away.
/// In this case, the most-proxied-proxy is never going to be masked away, because it is within the bounds of its parent.
/// </summary>
/// <param name="boxMasking">Whether the box's parent is masking.</param>
/// <param name="proxy1Masking">Whether the parent of box's proxy is masking.</param>
/// <param name="proxy2Masking">Whether the parent of the proxy's proxy is masking.</param>
[TestCase(false, false, false)]
[TestCase(true, false, false)]
[TestCase(true, true, false)]
[TestCase(true, true, true)]
[TestCase(false, true, true)]
public void TestBoxInBoundsWithProxy1OutOfBoundsWithProxy2InBounds(bool boxMasking, bool proxy1Masking, bool proxy2Masking)
{
var box = new Box();
var proxy1 = box.CreateProxy();
var proxy2 = proxy1.CreateProxy();
Children = new Drawable[]
{
new Container
{
Size = new Vector2(200),
Masking = boxMasking,
Child = box
},
new Container
{
Size = new Vector2(200),
Position = new Vector2(10),
Masking = proxy1Masking,
Child = proxy1
},
new Container
{
Size = new Vector2(200),
Masking = proxy2Masking,
Child = proxy2
}
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => !box.IsMaskedAway);
AddAssert("Check proxy1 IsMaskedAway", () => !proxy1.IsMaskedAway);
AddAssert("Check proxy2 IsMaskedAway", () => !proxy2.IsMaskedAway);
}
/// <summary>
/// Tests that whether the box is out of bounds of its parent is not a consideration for masking, only whether its most-proxied-proxy is out of bounds of its parent,
/// and the most-proxied-proxy's parent is masking.
/// </summary>
/// <param name="boxMasking">Whether the box's parent is masking.</param>
/// <param name="proxy1Masking">Whether the parent of box's proxy is masking.</param>
/// <param name="proxy2Masking">Whether the parent of the proxy's proxy is masking.</param>
/// <param name="shouldBeMaskedAway">Whether the box should be masked away.</param>
[TestCase(false, false, false, false)]
[TestCase(true, false, false, false)]
[TestCase(true, true, false, false)]
[TestCase(true, true, true, true)]
[TestCase(false, true, true, true)]
[TestCase(false, false, true, true)]
public void TestBoxInBoundsWithProxy1OutOfBoundsWithProxy2OutOfBounds(bool boxMasking, bool proxy1Masking, bool proxy2Masking, bool shouldBeMaskedAway)
{
var box = new Box();
var proxy1 = box.CreateProxy();
var proxy2 = proxy1.CreateProxy();
Children = new Drawable[]
{
new Container
{
Size = new Vector2(200),
Masking = boxMasking,
Child = box
},
new Container
{
Size = new Vector2(200),
Masking = proxy1Masking,
Child = proxy1
},
new Container
{
Size = new Vector2(200),
Position = new Vector2(10),
Masking = proxy2Masking,
Child = proxy2
}
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => box.IsMaskedAway == shouldBeMaskedAway);
AddAssert("Check proxy1 IsMaskedAway", () => !proxy1.IsMaskedAway);
AddAssert("Check proxy2 IsMaskedAway", () => !proxy2.IsMaskedAway);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
public class TestCaseIsMaskedAway : TestCase
{
/// <summary>
/// Tests that a box which is within the bounds of a parent is never masked away, regardless of whether the parent is masking or not.
/// </summary>
/// <param name="masking">Whether the box's parent is masking.</param>
[TestCase(false)]
[TestCase(true)]
public void TestBoxInBounds(bool masking)
{
Box box;
Child = new Container
{
Size = new Vector2(200),
Masking = masking,
Child = box = new Box()
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => !box.IsMaskedAway);
}
/// <summary>
/// Tests that a box which is outside the bounds of a parent is never masked away if the parent is not masking.
/// </summary>
[Test]
public void TestBoxOutOfBoundsNoMasking()
{
Box box;
Child = new Container
{
Size = new Vector2(200),
Child = box = new Box { Position = new Vector2(-1) }
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => !box.IsMaskedAway);
}
/// <summary>
/// Tests that a box which is slightly outside the bounds of a masking parent is never masked away, regardless of its anchor/origin.
/// Ensures that all screen-space calculations are current by the time <see cref="Drawable.IsMaskedAway"/> is calculated.
/// </summary>
/// <param name="anchor">The box's anchor in the masking parent.</param>
[TestCase(Anchor.TopLeft)]
[TestCase(Anchor.TopRight)]
[TestCase(Anchor.BottomLeft)]
[TestCase(Anchor.BottomRight)]
public void TestBoxSlightlyOutOfBoundsMasking(Anchor anchor)
{
Box box;
Child = new Container
{
Size = new Vector2(200),
Masking = true,
Child = box = new Box
{
Anchor = anchor,
Origin = anchor,
Size = new Vector2(10),
Position = new Vector2((anchor & Anchor.x0) > 0 ? -5 : 5, (anchor & Anchor.y0) > 0 ? -5 : 5),
}
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => !box.IsMaskedAway);
}
/// <summary>
/// Tests that a box which is fully outside the bounds of a masking parent is always masked away, regardless of its anchor/origin.
/// Ensures that all screen-space calculations are current by the time <see cref="Drawable.IsMaskedAway"/> is calculated.
/// </summary>
/// <param name="anchor">The box's anchor in the masking parent.</param>
[TestCase(Anchor.TopLeft)]
[TestCase(Anchor.TopRight)]
[TestCase(Anchor.BottomLeft)]
[TestCase(Anchor.BottomRight)]
public void TestBoxFullyOutOfBoundsMasking(Anchor anchor)
{
Box box;
Child = new Container
{
Size = new Vector2(200),
Masking = true,
Child = box = new Box
{
Anchor = anchor,
Origin = anchor,
Size = new Vector2(10),
Position = new Vector2((anchor & Anchor.x0) > 0 ? -20 : 20, (anchor & Anchor.y0) > 0 ? -20 : 20),
}
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => box.IsMaskedAway);
}
/// <summary>
/// Tests that a box is never masked away when it and its proxy are within the bounds of their parents, regardless of whether their parents
/// are masking or not.
/// </summary>
/// <param name="boxMasking">Whether the box's parent is masking.</param>
/// <param name="proxyMasking">Whether the proxy's parent is masking.</param>
[TestCase(false, false)]
[TestCase(true, true)]
public void TestBoxInBoundsWithProxyInBounds(bool boxMasking, bool proxyMasking)
{
var box = new Box();
ProxyDrawable proxy;
Children = new Drawable[]
{
new Container
{
Size = new Vector2(200),
Masking = boxMasking,
Child = box
},
new Container
{
Size = new Vector2(200),
Masking = proxyMasking,
Child = proxy = box.CreateProxy()
},
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => !box.IsMaskedAway);
AddAssert("Check proxy IsMaskedAway", () => !proxy.IsMaskedAway);
}
/// <summary>
/// Tests that a box is never masked away when its proxy is within the bounds of its parent, even if the box is outside the bounds of its parent.
/// </summary>
/// <param name="masking">Whether the box's parent is masking. This does not affect the proxy's parent.</param>
[TestCase(false)]
[TestCase(true)]
public void TestBoxOutOfBoundsWithProxyInBounds(bool masking)
{
var box = new Box { Position = new Vector2(-1) };
ProxyDrawable proxy;
Children = new Drawable[]
{
new Container
{
Size = new Vector2(200),
Masking = masking,
Child = box
},
proxy = box.CreateProxy()
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => !box.IsMaskedAway);
AddAssert("Check proxy IsMaskedAway", () => !proxy.IsMaskedAway);
}
/// <summary>
/// Tests that a box is only masked away when its proxy is masked away.
/// </summary>
/// <param name="boxMasking">Whether the box's parent is masking.</param>
/// <param name="proxyMasking">Whether the proxy's parent is masking.</param>
/// <param name="shouldBeMaskedAway">Whether the box should be masked away.</param>
[TestCase(false, false, false)]
[TestCase(false, true, true)]
[TestCase(true, false, false)]
[TestCase(true, true, true)]
public void TestBoxInBoundsWithProxyOutOfBounds(bool boxMasking, bool proxyMasking, bool shouldBeMaskedAway)
{
var box = new Box();
ProxyDrawable proxy;
Children = new Drawable[]
{
new Container
{
Size = new Vector2(200),
Masking = boxMasking,
Child = box
},
new Container
{
Position = new Vector2(10),
Size = new Vector2(200),
Masking = proxyMasking,
Child = proxy = box.CreateProxy()
},
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => box.IsMaskedAway == shouldBeMaskedAway);
AddAssert("Check proxy IsMaskedAway", () => !proxy.IsMaskedAway);
}
/// <summary>
/// Tests that whether the box is out of bounds of its parent is not a consideration for masking, only whether its proxy is out of bounds of its parent.
/// </summary>
/// <param name="boxMasking">Whether the box's parent is masking.</param>
/// <param name="proxyMasking">Whether the proxy's parent is masking.</param>
/// <param name="shouldBeMaskedAway">Whether the box should be masked away</param>
[TestCase(false, false, false)]
[TestCase(false, true, true)]
[TestCase(true, false, false)]
[TestCase(true, true, true)]
public void TestBoxOutOfBoundsWithProxyOutOfBounds(bool boxMasking, bool proxyMasking, bool shouldBeMaskedAway)
{
var box = new Box { Position = new Vector2(-1) };
ProxyDrawable proxy;
Children = new Drawable[]
{
new Container
{
Size = new Vector2(200),
Masking = boxMasking,
Child = box
},
new Container
{
Position = new Vector2(10),
Size = new Vector2(200),
Masking = proxyMasking,
Child = proxy = box.CreateProxy()
},
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => box.IsMaskedAway == shouldBeMaskedAway);
AddAssert("Check proxy IsMaskedAway", () => !proxy.IsMaskedAway);
}
/// <summary>
/// Tests that the box doesn't get masked away unless its most-proxied-proxy is masked away.
/// In this case, the most-proxied-proxy is never going to be masked away, because it is within the bounds of its parent.
/// </summary>
/// <param name="boxMasking">Whether the box's parent is masking.</param>
/// <param name="proxy1Masking">Whether the parent of box's proxy is masking.</param>
/// <param name="proxy2Masking">Whether the parent of the proxy's proxy is masking.</param>
[TestCase(false, false, false)]
[TestCase(true, false, false)]
[TestCase(true, true, false)]
[TestCase(true, true, true)]
[TestCase(false, true, true)]
public void TestBoxInBoundsWithProxy1OutOfBoundsWithProxy2InBounds(bool boxMasking, bool proxy1Masking, bool proxy2Masking)
{
var box = new Box();
var proxy1 = box.CreateProxy();
var proxy2 = proxy1.CreateProxy();
Children = new Drawable[]
{
new Container
{
Size = new Vector2(200),
Masking = boxMasking,
Child = box
},
new Container
{
Size = new Vector2(200),
Position = new Vector2(10),
Masking = proxy1Masking,
Child = proxy1
},
new Container
{
Size = new Vector2(200),
Masking = proxy2Masking,
Child = proxy2
}
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => !box.IsMaskedAway);
AddAssert("Check proxy1 IsMaskedAway", () => !proxy1.IsMaskedAway);
AddAssert("Check proxy2 IsMaskedAway", () => !proxy2.IsMaskedAway);
}
/// <summary>
/// Tests that whether the box is out of bounds of its parent is not a consideration for masking, only whether its most-proxied-proxy is out of bounds of its parent,
/// and the most-proxied-proxy's parent is masking.
/// </summary>
/// <param name="boxMasking">Whether the box's parent is masking.</param>
/// <param name="proxy1Masking">Whether the parent of box's proxy is masking.</param>
/// <param name="proxy2Masking">Whether the parent of the proxy's proxy is masking.</param>
/// <param name="shouldBeMaskedAway">Whether the box should be masked away.</param>
[TestCase(false, false, false, false)]
[TestCase(true, false, false, false)]
[TestCase(true, true, false, false)]
[TestCase(true, true, true, true)]
[TestCase(false, true, true, true)]
[TestCase(false, false, true, true)]
public void TestBoxInBoundsWithProxy1OutOfBoundsWithProxy2OutOfBounds(bool boxMasking, bool proxy1Masking, bool proxy2Masking, bool shouldBeMaskedAway)
{
var box = new Box();
var proxy1 = box.CreateProxy();
var proxy2 = proxy1.CreateProxy();
Children = new Drawable[]
{
new Container
{
Size = new Vector2(200),
Masking = boxMasking,
Child = box
},
new Container
{
Size = new Vector2(200),
Masking = proxy1Masking,
Child = proxy1
},
new Container
{
Size = new Vector2(200),
Position = new Vector2(10),
Masking = proxy2Masking,
Child = proxy2
}
};
AddWaitStep(1, "Wait for UpdateSubTree");
AddAssert("Check box IsMaskedAway", () => box.IsMaskedAway == shouldBeMaskedAway);
AddAssert("Check proxy1 IsMaskedAway", () => !proxy1.IsMaskedAway);
AddAssert("Check proxy2 IsMaskedAway", () => !proxy2.IsMaskedAway);
}
}
}

View File

@@ -1,165 +1,165 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Bindings;
using osu.Framework.Testing;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseKeyBindings : GridTestCase
{
public TestCaseKeyBindings()
: base(2, 2)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
Cell(0).Add(new KeyBindingTester(SimultaneousBindingMode.None));
Cell(1).Add(new KeyBindingTester(SimultaneousBindingMode.Unique));
Cell(2).Add(new KeyBindingTester(SimultaneousBindingMode.All));
}
private enum TestAction
{
A,
S,
D_or_F,
Ctrl_A,
Ctrl_S,
Ctrl_D_or_F,
Shift_A,
Shift_S,
Shift_D_or_F,
Ctrl_Shift_A,
Ctrl_Shift_S,
Ctrl_Shift_D_or_F,
Ctrl,
Shift,
Ctrl_And_Shift,
Ctrl_Or_Shift,
LeftMouse,
RightMouse
}
private class TestInputManager : KeyBindingContainer<TestAction>
{
public TestInputManager(SimultaneousBindingMode concurrencyMode = SimultaneousBindingMode.None) : base(concurrencyMode)
{
}
public override IEnumerable<KeyBinding> DefaultKeyBindings => new[]
{
new KeyBinding(InputKey.A, TestAction.A ),
new KeyBinding(InputKey.S, TestAction.S ),
new KeyBinding(InputKey.D, TestAction.D_or_F ),
new KeyBinding(InputKey.F, TestAction.D_or_F ),
new KeyBinding(new[] { InputKey.Control, InputKey.A }, TestAction.Ctrl_A ),
new KeyBinding(new[] { InputKey.Control, InputKey.S }, TestAction.Ctrl_S ),
new KeyBinding(new[] { InputKey.Control, InputKey.D }, TestAction.Ctrl_D_or_F ),
new KeyBinding(new[] { InputKey.Control, InputKey.F }, TestAction.Ctrl_D_or_F ),
new KeyBinding(new[] { InputKey.Shift, InputKey.A }, TestAction.Shift_A ),
new KeyBinding(new[] { InputKey.Shift, InputKey.S }, TestAction.Shift_S ),
new KeyBinding(new[] { InputKey.Shift, InputKey.D }, TestAction.Shift_D_or_F ),
new KeyBinding(new[] { InputKey.Shift, InputKey.F }, TestAction.Shift_D_or_F ),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, TestAction.Ctrl_Shift_A ),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, TestAction.Ctrl_Shift_S),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.D }, TestAction.Ctrl_Shift_D_or_F),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, TestAction.Ctrl_Shift_D_or_F),
new KeyBinding(new[] { InputKey.Control }, TestAction.Ctrl),
new KeyBinding(new[] { InputKey.Shift }, TestAction.Shift),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift }, TestAction.Ctrl_And_Shift),
new KeyBinding(new[] { InputKey.Control }, TestAction.Ctrl_Or_Shift),
new KeyBinding(new[] { InputKey.Shift }, TestAction.Ctrl_Or_Shift),
new KeyBinding(new[] { InputKey.MouseLeft }, TestAction.LeftMouse),
new KeyBinding(new[] { InputKey.MouseRight }, TestAction.RightMouse),
};
}
private class TestButton : Button, IKeyBindingHandler<TestAction>
{
private readonly TestAction action;
public TestButton(TestAction action)
{
this.action = action;
BackgroundColour = Color4.SkyBlue;
Text = action.ToString().Replace('_', ' ');
RelativeSizeAxes = Axes.X;
Height = 40;
Width = 0.3f;
Padding = new MarginPadding(2);
Background.Alpha = alphaTarget;
}
private float alphaTarget = 0.5f;
public bool OnPressed(TestAction action)
{
if (this.action == action)
{
alphaTarget += 0.2f;
Background.FadeTo(alphaTarget, 100, Easing.OutQuint);
}
return false;
}
public bool OnReleased(TestAction action)
{
if (this.action == action)
{
alphaTarget -= 0.2f;
Background.FadeTo(alphaTarget, 100, Easing.OutQuint);
}
return false;
}
}
private class KeyBindingTester : Container
{
public KeyBindingTester(SimultaneousBindingMode concurrency)
{
RelativeSizeAxes = Axes.Both;
Children = new Drawable[]
{
new SpriteText
{
Text = concurrency.ToString(),
},
new TestInputManager(concurrency)
{
Y = 30,
RelativeSizeAxes = Axes.Both,
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
ChildrenEnumerable = Enum.GetValues(typeof(TestAction)).Cast<TestAction>().Select(t => new TestButton(t))
}
},
};
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Bindings;
using osu.Framework.Testing;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseKeyBindings : GridTestCase
{
public TestCaseKeyBindings()
: base(2, 2)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
Cell(0).Add(new KeyBindingTester(SimultaneousBindingMode.None));
Cell(1).Add(new KeyBindingTester(SimultaneousBindingMode.Unique));
Cell(2).Add(new KeyBindingTester(SimultaneousBindingMode.All));
}
private enum TestAction
{
A,
S,
D_or_F,
Ctrl_A,
Ctrl_S,
Ctrl_D_or_F,
Shift_A,
Shift_S,
Shift_D_or_F,
Ctrl_Shift_A,
Ctrl_Shift_S,
Ctrl_Shift_D_or_F,
Ctrl,
Shift,
Ctrl_And_Shift,
Ctrl_Or_Shift,
LeftMouse,
RightMouse
}
private class TestInputManager : KeyBindingContainer<TestAction>
{
public TestInputManager(SimultaneousBindingMode concurrencyMode = SimultaneousBindingMode.None) : base(concurrencyMode)
{
}
public override IEnumerable<KeyBinding> DefaultKeyBindings => new[]
{
new KeyBinding(InputKey.A, TestAction.A ),
new KeyBinding(InputKey.S, TestAction.S ),
new KeyBinding(InputKey.D, TestAction.D_or_F ),
new KeyBinding(InputKey.F, TestAction.D_or_F ),
new KeyBinding(new[] { InputKey.Control, InputKey.A }, TestAction.Ctrl_A ),
new KeyBinding(new[] { InputKey.Control, InputKey.S }, TestAction.Ctrl_S ),
new KeyBinding(new[] { InputKey.Control, InputKey.D }, TestAction.Ctrl_D_or_F ),
new KeyBinding(new[] { InputKey.Control, InputKey.F }, TestAction.Ctrl_D_or_F ),
new KeyBinding(new[] { InputKey.Shift, InputKey.A }, TestAction.Shift_A ),
new KeyBinding(new[] { InputKey.Shift, InputKey.S }, TestAction.Shift_S ),
new KeyBinding(new[] { InputKey.Shift, InputKey.D }, TestAction.Shift_D_or_F ),
new KeyBinding(new[] { InputKey.Shift, InputKey.F }, TestAction.Shift_D_or_F ),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, TestAction.Ctrl_Shift_A ),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, TestAction.Ctrl_Shift_S),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.D }, TestAction.Ctrl_Shift_D_or_F),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, TestAction.Ctrl_Shift_D_or_F),
new KeyBinding(new[] { InputKey.Control }, TestAction.Ctrl),
new KeyBinding(new[] { InputKey.Shift }, TestAction.Shift),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift }, TestAction.Ctrl_And_Shift),
new KeyBinding(new[] { InputKey.Control }, TestAction.Ctrl_Or_Shift),
new KeyBinding(new[] { InputKey.Shift }, TestAction.Ctrl_Or_Shift),
new KeyBinding(new[] { InputKey.MouseLeft }, TestAction.LeftMouse),
new KeyBinding(new[] { InputKey.MouseRight }, TestAction.RightMouse),
};
}
private class TestButton : Button, IKeyBindingHandler<TestAction>
{
private readonly TestAction action;
public TestButton(TestAction action)
{
this.action = action;
BackgroundColour = Color4.SkyBlue;
Text = action.ToString().Replace('_', ' ');
RelativeSizeAxes = Axes.X;
Height = 40;
Width = 0.3f;
Padding = new MarginPadding(2);
Background.Alpha = alphaTarget;
}
private float alphaTarget = 0.5f;
public bool OnPressed(TestAction action)
{
if (this.action == action)
{
alphaTarget += 0.2f;
Background.FadeTo(alphaTarget, 100, Easing.OutQuint);
}
return false;
}
public bool OnReleased(TestAction action)
{
if (this.action == action)
{
alphaTarget -= 0.2f;
Background.FadeTo(alphaTarget, 100, Easing.OutQuint);
}
return false;
}
}
private class KeyBindingTester : Container
{
public KeyBindingTester(SimultaneousBindingMode concurrency)
{
RelativeSizeAxes = Axes.Both;
Children = new Drawable[]
{
new SpriteText
{
Text = concurrency.ToString(),
},
new TestInputManager(concurrency)
{
Y = 30,
RelativeSizeAxes = Axes.Both,
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
ChildrenEnumerable = Enum.GetValues(typeof(TestAction)).Cast<TestAction>().Select(t => new TestButton(t))
}
},
};
}
}
}
}

View File

@@ -1,97 +1,97 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("Rewinding of transforms that are important to layout.")]
public class TestCaseLayoutTransformRewinding : TestCase
{
private readonly ManualUpdateSubTreeContainer manualContainer;
public TestCaseLayoutTransformRewinding()
{
Child = manualContainer = new ManualUpdateSubTreeContainer();
testAutoSizeInstant();
testFlowInstant();
}
private void testAutoSizeInstant()
{
AddStep("Initialize autosize test", () =>
{
manualContainer.Child = new Container
{
AutoSizeAxes = Axes.Both,
Masking = true,
Child = new Box { Size = new Vector2(150) }
};
});
AddStep("Run to end", () => manualContainer.PerformUpdate(null));
AddAssert("Size = 150", () => Precision.AlmostEquals(new Vector2(150), manualContainer.Child.Size));
AddStep("Rewind", () => manualContainer.PerformUpdate(() => manualContainer.ApplyTransformsAt(-1, true)));
AddAssert("Size = 150", () => Precision.AlmostEquals(new Vector2(150), manualContainer.Child.Size));
}
private void testFlowInstant()
{
Box box2 = null;
AddStep("Initialize flow test", () =>
{
manualContainer.Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Children = new[]
{
new Box { Size = new Vector2(150) },
box2 = new Box { Size = new Vector2(150) }
}
};
});
AddStep("Run to end", () => manualContainer.PerformUpdate(null));
AddAssert("Box2 @ (150, 0)", () => Precision.AlmostEquals(new Vector2(150, 0), box2.Position));
AddStep("Rewind", () => manualContainer.PerformUpdate(() => manualContainer.ApplyTransformsAt(-1, true)));
AddAssert("Box2 @ (150, 0)", () => Precision.AlmostEquals(new Vector2(150, 0), box2.Position));
}
private class ManualUpdateSubTreeContainer : Container
{
public override bool RemoveCompletedTransforms => false;
private Action onUpdateAfterChildren;
public ManualUpdateSubTreeContainer()
{
RelativeSizeAxes = Axes.Both;
}
public void PerformUpdate(Action afterChildren)
{
onUpdateAfterChildren = afterChildren;
base.UpdateSubTree();
onUpdateAfterChildren = null;
}
public override bool UpdateSubTree() => false;
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
onUpdateAfterChildren?.Invoke();
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("Rewinding of transforms that are important to layout.")]
public class TestCaseLayoutTransformRewinding : TestCase
{
private readonly ManualUpdateSubTreeContainer manualContainer;
public TestCaseLayoutTransformRewinding()
{
Child = manualContainer = new ManualUpdateSubTreeContainer();
testAutoSizeInstant();
testFlowInstant();
}
private void testAutoSizeInstant()
{
AddStep("Initialize autosize test", () =>
{
manualContainer.Child = new Container
{
AutoSizeAxes = Axes.Both,
Masking = true,
Child = new Box { Size = new Vector2(150) }
};
});
AddStep("Run to end", () => manualContainer.PerformUpdate(null));
AddAssert("Size = 150", () => Precision.AlmostEquals(new Vector2(150), manualContainer.Child.Size));
AddStep("Rewind", () => manualContainer.PerformUpdate(() => manualContainer.ApplyTransformsAt(-1, true)));
AddAssert("Size = 150", () => Precision.AlmostEquals(new Vector2(150), manualContainer.Child.Size));
}
private void testFlowInstant()
{
Box box2 = null;
AddStep("Initialize flow test", () =>
{
manualContainer.Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Children = new[]
{
new Box { Size = new Vector2(150) },
box2 = new Box { Size = new Vector2(150) }
}
};
});
AddStep("Run to end", () => manualContainer.PerformUpdate(null));
AddAssert("Box2 @ (150, 0)", () => Precision.AlmostEquals(new Vector2(150, 0), box2.Position));
AddStep("Rewind", () => manualContainer.PerformUpdate(() => manualContainer.ApplyTransformsAt(-1, true)));
AddAssert("Box2 @ (150, 0)", () => Precision.AlmostEquals(new Vector2(150, 0), box2.Position));
}
private class ManualUpdateSubTreeContainer : Container
{
public override bool RemoveCompletedTransforms => false;
private Action onUpdateAfterChildren;
public ManualUpdateSubTreeContainer()
{
RelativeSizeAxes = Axes.Both;
}
public void PerformUpdate(Action afterChildren)
{
onUpdateAfterChildren = afterChildren;
base.UpdateSubTree();
onUpdateAfterChildren = null;
}
public override bool UpdateSubTree() => false;
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
onUpdateAfterChildren?.Invoke();
}
}
}
}

View File

@@ -1,104 +1,104 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Globalization;
using System.IO;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.IO.Stores;
using osu.Framework.Localisation;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseLocalisation : TestCase
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly LocalisationEngine engine; //keep a reference to avoid GC of the engine
public TestCaseLocalisation()
{
var config = new FakeFrameworkConfigManager();
engine = new LocalisationEngine(config);
engine.AddLanguage("en", new FakeStorage());
engine.AddLanguage("zh-CHS", new FakeStorage());
engine.AddLanguage("ja", new FakeStorage());
Add(new FillFlowContainer<SpriteText>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Padding = new MarginPadding(10),
AutoSizeAxes = Axes.Both,
Children = new[]
{
new SpriteText
{
Text = "Not localisable",
TextSize = 48,
Colour = Color4.White
},
new SpriteText
{
Current = engine.GetLocalisedString("localisable"),
TextSize = 48,
Colour = Color4.White
},
new SpriteText
{
Current = engine.GetUnicodePreference("Unicode on", "Unicode off"),
TextSize = 48,
Colour = Color4.White
},
new SpriteText
{
Current = engine.GetUnicodePreference(null, "I miss unicode"),
TextSize = 48,
Colour = Color4.White
},
new SpriteText
{
Current = engine.Format($"{DateTime.Now}"),
TextSize = 48,
Colour = Color4.White
},
}
});
AddStep("English", () => config.Set(FrameworkSetting.Locale, "en"));
AddStep("Japanese", () => config.Set(FrameworkSetting.Locale, "ja"));
AddStep("Simplified Chinese", () => config.Set(FrameworkSetting.Locale, "zh-CHS"));
AddToggleStep("ShowUnicode", b => config.Set(FrameworkSetting.ShowUnicode, b));
}
private class FakeFrameworkConfigManager : FrameworkConfigManager
{
protected override string Filename => null;
public FakeFrameworkConfigManager() : base(null) { }
protected override void InitialiseDefaults()
{
Set(FrameworkSetting.Locale, "");
Set(FrameworkSetting.ShowUnicode, false);
}
}
private class FakeStorage : IResourceStore<string>
{
public string Get(string name) => $"{name} in {CultureInfo.CurrentCulture.EnglishName}";
public Stream GetStream(string name)
{
throw new NotSupportedException();
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Globalization;
using System.IO;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.IO.Stores;
using osu.Framework.Localisation;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseLocalisation : TestCase
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly LocalisationEngine engine; //keep a reference to avoid GC of the engine
public TestCaseLocalisation()
{
var config = new FakeFrameworkConfigManager();
engine = new LocalisationEngine(config);
engine.AddLanguage("en", new FakeStorage());
engine.AddLanguage("zh-CHS", new FakeStorage());
engine.AddLanguage("ja", new FakeStorage());
Add(new FillFlowContainer<SpriteText>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Padding = new MarginPadding(10),
AutoSizeAxes = Axes.Both,
Children = new[]
{
new SpriteText
{
Text = "Not localisable",
TextSize = 48,
Colour = Color4.White
},
new SpriteText
{
Current = engine.GetLocalisedString("localisable"),
TextSize = 48,
Colour = Color4.White
},
new SpriteText
{
Current = engine.GetUnicodePreference("Unicode on", "Unicode off"),
TextSize = 48,
Colour = Color4.White
},
new SpriteText
{
Current = engine.GetUnicodePreference(null, "I miss unicode"),
TextSize = 48,
Colour = Color4.White
},
new SpriteText
{
Current = engine.Format($"{DateTime.Now}"),
TextSize = 48,
Colour = Color4.White
},
}
});
AddStep("English", () => config.Set(FrameworkSetting.Locale, "en"));
AddStep("Japanese", () => config.Set(FrameworkSetting.Locale, "ja"));
AddStep("Simplified Chinese", () => config.Set(FrameworkSetting.Locale, "zh-CHS"));
AddToggleStep("ShowUnicode", b => config.Set(FrameworkSetting.ShowUnicode, b));
}
private class FakeFrameworkConfigManager : FrameworkConfigManager
{
protected override string Filename => null;
public FakeFrameworkConfigManager() : base(null) { }
protected override void InitialiseDefaults()
{
Set(FrameworkSetting.Locale, "");
Set(FrameworkSetting.ShowUnicode, false);
}
}
private class FakeStorage : IResourceStore<string>
{
public string Get(string name) => $"{name} in {CultureInfo.CurrentCulture.EnglishName}";
public Stream GetStream(string name)
{
throw new NotSupportedException();
}
}
}
}

View File

@@ -1,458 +1,458 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseMasking : TestCase
{
protected Container TestContainer;
public TestCaseMasking()
{
Add(TestContainer = new Container
{
RelativeSizeAxes = Axes.Both,
});
string[] testNames =
{
@"Round corner masking",
@"Round corner AABB 1",
@"Round corner AABB 2",
@"Round corner AABB 3",
@"Edge/border blurriness",
@"Nested masking",
@"Rounded corner input"
};
for (int i = 0; i < testNames.Length; i++)
{
int test = i;
AddStep(testNames[i], delegate { loadTest(test); });
}
loadTest(0);
addCrosshair();
}
private void addCrosshair()
{
Add(new Box
{
Colour = Color4.Black,
Size = new Vector2(22, 4),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
Add(new Box
{
Colour = Color4.Black,
Size = new Vector2(4, 22),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
Add(new Box
{
Colour = Color4.WhiteSmoke,
Size = new Vector2(20, 2),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
Add(new Box
{
Colour = Color4.WhiteSmoke,
Size = new Vector2(2, 20),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
}
private void loadTest(int testType)
{
TestContainer.Clear();
switch (testType)
{
default:
{
Container box;
TestContainer.Add(box = new InfofulBoxAutoSize
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
CornerRadius = 100,
BorderColour = Color4.Aquamarine,
BorderThickness = 3,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Radius = 100,
Colour = new Color4(0, 50, 100, 200),
},
});
box.Add(box = new InfofulBox
{
Size = new Vector2(250, 250),
Alpha = 0.5f,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.DarkSeaGreen,
});
box.OnUpdate += delegate { box.Rotation += 0.05f; };
break;
}
case 1:
{
Container box;
TestContainer.Add(new InfofulBoxAutoSize
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
box = new InfofulBox
{
Masking = true,
CornerRadius = 100,
Size = new Vector2(400, 400),
Alpha = 0.5f,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.DarkSeaGreen,
}
}
});
box.OnUpdate += delegate
{
box.Rotation += 0.05f;
box.CornerRadius = 100 + 100 * (float)Math.Sin(box.Rotation * 0.01);
};
break;
}
case 2:
{
Container box;
TestContainer.Add(new InfofulBoxAutoSize
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
box = new InfofulBox
{
Masking = true,
CornerRadius = 25,
Shear = new Vector2(0.5f, 0),
Size = new Vector2(150, 150),
Scale = new Vector2(2.5f, 1.5f),
Alpha = 0.5f,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.DarkSeaGreen,
}
}
});
box.OnUpdate += delegate { box.Rotation += 0.05f; };
break;
}
case 3:
{
Color4 glowColour = Color4.Aquamarine;
glowColour.A = 0.5f;
Container box1;
Container box2;
TestContainer.Add(new InfofulBoxAutoSize
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 100,
Roundness = 50,
Colour = glowColour,
},
BorderColour = Color4.Aquamarine,
BorderThickness = 3,
Children = new[]
{
box1 = new InfofulBoxAutoSize
{
Masking = true,
CornerRadius = 25,
Shear = new Vector2(0.5f, 0),
Alpha = 0.5f,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.DarkSeaGreen,
Children = new[]
{
box2 = new InfofulBox
{
Masking = true,
CornerRadius = 25,
Shear = new Vector2(0.25f, 0.25f),
Size = new Vector2(100, 200),
Alpha = 0.5f,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.Blue,
}
}
}
}
});
box1.OnUpdate += delegate { box1.Rotation += 0.07f; };
box2.OnUpdate += delegate { box2.Rotation -= 0.15f; };
break;
}
case 4:
{
Func<float, Drawable> createMaskingBox = delegate (float scale)
{
float size = 200 / scale;
return new Container
{
Masking = true,
CornerRadius = 25 / scale,
BorderThickness = 12.5f / scale,
BorderColour = Color4.Red,
Size = new Vector2(size),
Scale = new Vector2(scale),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new SpriteText
{
Text = @"Size: " + size + ", Scale: " + scale,
TextSize = 20 / scale,
Colour = Color4.Blue,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
}
};
};
TestContainer.Add(new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Masking = true,
Children = new[] { createMaskingBox(100) }
},
new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Masking = true,
Children = new[] { createMaskingBox(10) }
},
new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Masking = true,
Children = new[] { createMaskingBox(1) }
},
new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Masking = true,
Children = new[] { createMaskingBox(0.1f) }
},
}
});
break;
}
case 5:
{
TestContainer.Add(new Container
{
Masking = true,
Size = new Vector2(0.5f),
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new Container
{
Masking = true,
CornerRadius = 100f,
BorderThickness = 50f,
BorderColour = Color4.Red,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1.5f),
Anchor = Anchor.BottomRight,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
}
}
}
});
break;
}
case 6:
{
TestContainer.Add(new FillFlowContainer
{
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
new SpriteText
{
Text = $"None of the folowing {nameof(CircularContainer)}s should trigger until the white part is hovered"
},
new FillFlowContainer
{
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(0, 2),
Children = new Drawable[]
{
new SpriteText
{
Text = "No masking"
},
new CircularContainerWithInput
{
Size = new Vector2(200),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Red
},
new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
Masking = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both
}
}
}
}
}
}
},
new FillFlowContainer
{
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(0, 2),
Children = new Drawable[]
{
new SpriteText
{
Text = "With masking"
},
new CircularContainerWithInput
{
Size = new Vector2(200),
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Red
},
new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
Masking = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both
}
}
}
}
}
}
}
}
});
break;
}
}
#if DEBUG
//if (toggleDebugAutosize.State)
// testContainer.Children.FindAll(c => c.HasAutosizeChildren).ForEach(c => c.AutoSizeDebug = true);
#endif
}
private class CircularContainerWithInput : CircularContainer
{
protected override bool OnHover(InputState state)
{
this.ScaleTo(1.2f, 100);
return true;
}
protected override void OnHoverLost(InputState state)
{
this.ScaleTo(1f, 100);
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseMasking : TestCase
{
protected Container TestContainer;
public TestCaseMasking()
{
Add(TestContainer = new Container
{
RelativeSizeAxes = Axes.Both,
});
string[] testNames =
{
@"Round corner masking",
@"Round corner AABB 1",
@"Round corner AABB 2",
@"Round corner AABB 3",
@"Edge/border blurriness",
@"Nested masking",
@"Rounded corner input"
};
for (int i = 0; i < testNames.Length; i++)
{
int test = i;
AddStep(testNames[i], delegate { loadTest(test); });
}
loadTest(0);
addCrosshair();
}
private void addCrosshair()
{
Add(new Box
{
Colour = Color4.Black,
Size = new Vector2(22, 4),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
Add(new Box
{
Colour = Color4.Black,
Size = new Vector2(4, 22),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
Add(new Box
{
Colour = Color4.WhiteSmoke,
Size = new Vector2(20, 2),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
Add(new Box
{
Colour = Color4.WhiteSmoke,
Size = new Vector2(2, 20),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
}
private void loadTest(int testType)
{
TestContainer.Clear();
switch (testType)
{
default:
{
Container box;
TestContainer.Add(box = new InfofulBoxAutoSize
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
CornerRadius = 100,
BorderColour = Color4.Aquamarine,
BorderThickness = 3,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Radius = 100,
Colour = new Color4(0, 50, 100, 200),
},
});
box.Add(box = new InfofulBox
{
Size = new Vector2(250, 250),
Alpha = 0.5f,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.DarkSeaGreen,
});
box.OnUpdate += delegate { box.Rotation += 0.05f; };
break;
}
case 1:
{
Container box;
TestContainer.Add(new InfofulBoxAutoSize
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
box = new InfofulBox
{
Masking = true,
CornerRadius = 100,
Size = new Vector2(400, 400),
Alpha = 0.5f,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.DarkSeaGreen,
}
}
});
box.OnUpdate += delegate
{
box.Rotation += 0.05f;
box.CornerRadius = 100 + 100 * (float)Math.Sin(box.Rotation * 0.01);
};
break;
}
case 2:
{
Container box;
TestContainer.Add(new InfofulBoxAutoSize
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
box = new InfofulBox
{
Masking = true,
CornerRadius = 25,
Shear = new Vector2(0.5f, 0),
Size = new Vector2(150, 150),
Scale = new Vector2(2.5f, 1.5f),
Alpha = 0.5f,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.DarkSeaGreen,
}
}
});
box.OnUpdate += delegate { box.Rotation += 0.05f; };
break;
}
case 3:
{
Color4 glowColour = Color4.Aquamarine;
glowColour.A = 0.5f;
Container box1;
Container box2;
TestContainer.Add(new InfofulBoxAutoSize
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 100,
Roundness = 50,
Colour = glowColour,
},
BorderColour = Color4.Aquamarine,
BorderThickness = 3,
Children = new[]
{
box1 = new InfofulBoxAutoSize
{
Masking = true,
CornerRadius = 25,
Shear = new Vector2(0.5f, 0),
Alpha = 0.5f,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.DarkSeaGreen,
Children = new[]
{
box2 = new InfofulBox
{
Masking = true,
CornerRadius = 25,
Shear = new Vector2(0.25f, 0.25f),
Size = new Vector2(100, 200),
Alpha = 0.5f,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.Blue,
}
}
}
}
});
box1.OnUpdate += delegate { box1.Rotation += 0.07f; };
box2.OnUpdate += delegate { box2.Rotation -= 0.15f; };
break;
}
case 4:
{
Func<float, Drawable> createMaskingBox = delegate (float scale)
{
float size = 200 / scale;
return new Container
{
Masking = true,
CornerRadius = 25 / scale,
BorderThickness = 12.5f / scale,
BorderColour = Color4.Red,
Size = new Vector2(size),
Scale = new Vector2(scale),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new SpriteText
{
Text = @"Size: " + size + ", Scale: " + scale,
TextSize = 20 / scale,
Colour = Color4.Blue,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
}
};
};
TestContainer.Add(new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Masking = true,
Children = new[] { createMaskingBox(100) }
},
new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Masking = true,
Children = new[] { createMaskingBox(10) }
},
new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Masking = true,
Children = new[] { createMaskingBox(1) }
},
new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Masking = true,
Children = new[] { createMaskingBox(0.1f) }
},
}
});
break;
}
case 5:
{
TestContainer.Add(new Container
{
Masking = true,
Size = new Vector2(0.5f),
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new Container
{
Masking = true,
CornerRadius = 100f,
BorderThickness = 50f,
BorderColour = Color4.Red,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1.5f),
Anchor = Anchor.BottomRight,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
}
}
}
});
break;
}
case 6:
{
TestContainer.Add(new FillFlowContainer
{
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
new SpriteText
{
Text = $"None of the folowing {nameof(CircularContainer)}s should trigger until the white part is hovered"
},
new FillFlowContainer
{
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(0, 2),
Children = new Drawable[]
{
new SpriteText
{
Text = "No masking"
},
new CircularContainerWithInput
{
Size = new Vector2(200),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Red
},
new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
Masking = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both
}
}
}
}
}
}
},
new FillFlowContainer
{
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(0, 2),
Children = new Drawable[]
{
new SpriteText
{
Text = "With masking"
},
new CircularContainerWithInput
{
Size = new Vector2(200),
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Red
},
new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
Masking = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both
}
}
}
}
}
}
}
}
});
break;
}
}
#if DEBUG
//if (toggleDebugAutosize.State)
// testContainer.Children.FindAll(c => c.HasAutosizeChildren).ForEach(c => c.AutoSizeDebug = true);
#endif
}
private class CircularContainerWithInput : CircularContainer
{
protected override bool OnHover(InputState state)
{
this.ScaleTo(1.2f, 100);
return true;
}
protected override void OnHoverLost(InputState state)
{
this.ScaleTo(1f, 100);
}
}
}
}

View File

@@ -1,80 +1,80 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseNestedHover : TestCase
{
public TestCaseNestedHover()
{
HoverBox box1;
Add(box1 = new HoverBox(Color4.Gray, Color4.White)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(300, 300)
});
HoverBox box2;
box1.Add(box2 = new HoverBox(Color4.Pink, Color4.Red)
{
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
Position = new Vector2(0.2f, 0.2f),
Size = new Vector2(0.6f, 0.6f)
});
box2.Add(new HoverBox(Color4.LightBlue, Color4.Blue, false)
{
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
Position = new Vector2(0.2f, 0.2f),
Size = new Vector2(0.6f, 0.6f)
});
}
private class HoverBox : Container
{
private readonly Color4 normalColour;
private readonly Color4 hoveredColour;
private readonly Box box;
private readonly bool propagateHover;
public HoverBox(Color4 normalColour, Color4 hoveredColour, bool propagateHover = true)
{
this.normalColour = normalColour;
this.hoveredColour = hoveredColour;
this.propagateHover = propagateHover;
Children = new Drawable[]
{
box = new Box
{
Colour = normalColour,
RelativeSizeAxes = Axes.Both
}
};
}
protected override bool OnHover(InputState state)
{
box.Colour = hoveredColour;
return !propagateHover;
}
protected override void OnHoverLost(InputState state)
{
box.Colour = normalColour;
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseNestedHover : TestCase
{
public TestCaseNestedHover()
{
HoverBox box1;
Add(box1 = new HoverBox(Color4.Gray, Color4.White)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(300, 300)
});
HoverBox box2;
box1.Add(box2 = new HoverBox(Color4.Pink, Color4.Red)
{
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
Position = new Vector2(0.2f, 0.2f),
Size = new Vector2(0.6f, 0.6f)
});
box2.Add(new HoverBox(Color4.LightBlue, Color4.Blue, false)
{
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
Position = new Vector2(0.2f, 0.2f),
Size = new Vector2(0.6f, 0.6f)
});
}
private class HoverBox : Container
{
private readonly Color4 normalColour;
private readonly Color4 hoveredColour;
private readonly Box box;
private readonly bool propagateHover;
public HoverBox(Color4 normalColour, Color4 hoveredColour, bool propagateHover = true)
{
this.normalColour = normalColour;
this.hoveredColour = hoveredColour;
this.propagateHover = propagateHover;
Children = new Drawable[]
{
box = new Box
{
Colour = normalColour,
RelativeSizeAxes = Axes.Both
}
};
}
protected override bool OnHover(InputState state)
{
box.Colour = hoveredColour;
return !propagateHover;
}
protected override void OnHoverLost(InputState state)
{
box.Colour = normalColour;
}
}
}
}

View File

@@ -1,493 +1,493 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Framework.Testing.Input;
using OpenTK;
using OpenTK.Input;
namespace osu.Framework.Tests.Visual
{
public class TestCaseNestedMenus : TestCase
{
private const int max_depth = 5;
private const int max_count = 5;
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(Menu) };
private Random rng;
private ManualInputManager inputManager;
private MenuStructure menus;
[SetUp]
public void SetUp()
{
Clear();
rng = new Random(1337);
Menu menu;
Add(inputManager = new ManualInputManager
{
Children = new Drawable[]
{
new CursorContainer(),
new Container
{
RelativeSizeAxes = Axes.Both,
Child = menu = createMenu()
}
}
});
menus = new MenuStructure(menu);
}
private Menu createMenu() => new ClickOpenMenu(TimePerAction)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Items = new[]
{
generateRandomMenuItem("First"),
generateRandomMenuItem("Second"),
generateRandomMenuItem("Third"),
}
};
private class ClickOpenMenu : Menu
{
protected override Menu CreateSubMenu() => new ClickOpenMenu(HoverOpenDelay, false);
public ClickOpenMenu(double timePerAction, bool topLevel = true) : base(Direction.Vertical, topLevel)
{
HoverOpenDelay = timePerAction;
}
}
#region Test Cases
/// <summary>
/// Tests if the <see cref="Menu"/> respects <see cref="Menu.TopLevelMenu"/> = true, by not alowing it to be closed
/// when a click happens outside the <see cref="Menu"/>.
/// </summary>
[Test]
public void TestAlwaysOpen()
{
AddStep("Click outside", () => inputManager.Click(MouseButton.Left));
AddAssert("Check AlwaysOpen = true", () => menus.GetSubMenu(0).State == MenuState.Open);
}
/// <summary>
/// Tests if the hover state on <see cref="Menu.DrawableMenuItem"/>s is valid.
/// </summary>
[Test]
public void TestHoverState()
{
AddAssert("Check submenu closed", () => menus.GetSubMenu(1)?.State != MenuState.Open);
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetMenuItems()[0]));
AddAssert("Check item hovered", () => menus.GetMenuItems()[0].IsHovered);
}
/// <summary>
/// Tests if the <see cref="Menu"/> respects <see cref="Menu.TopLevelMenu"/> = true.
/// </summary>
[Test]
public void TestTopLevelMenu()
{
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(0).GetMenuItems()[0]));
AddAssert("Check closed", () => menus.GetSubMenu(1)?.State != MenuState.Open);
AddAssert("Check closed", () => menus.GetSubMenu(1)?.State != MenuState.Open);
AddStep("Click item", () => inputManager.Click(MouseButton.Left));
AddAssert("Check open", () => menus.GetSubMenu(1).State == MenuState.Open);
}
/// <summary>
/// Tests if clicking once on a menu that has <see cref="Menu.TopLevelMenu"/> opens it, and clicking a second time
/// closes it.
/// </summary>
[Test]
public void TestDoubleClick()
{
AddStep("Click item", () => clickItem(0, 0));
AddAssert("Check open", () => menus.GetSubMenu(1).State == MenuState.Open);
AddStep("Click item", () => clickItem(0, 0));
AddAssert("Check closed", () => menus.GetSubMenu(1)?.State != MenuState.Open);
}
/// <summary>
/// Tests whether click on <see cref="Menu.DrawableMenuItem"/>s causes sub-menus to instantly appear.
/// </summary>
[Test]
public void TestInstantOpen()
{
AddStep("Click item", () => clickItem(0, 1));
AddAssert("Check open", () => menus.GetSubMenu(1).State == MenuState.Open);
AddStep("Click item", () => clickItem(1, 0));
AddAssert("Check open", () => menus.GetSubMenu(2).State == MenuState.Open);
}
/// <summary>
/// Tests if clicking on an item that has no sub-menu causes the menu to close.
/// </summary>
[Test]
public void TestActionClick()
{
AddStep("Click item", () => clickItem(0, 0));
AddStep("Click item", () => clickItem(1, 0));
AddAssert("Check closed", () => menus.GetSubMenu(1)?.State != MenuState.Open);
}
/// <summary>
/// Tests if hovering over menu items respects the <see cref="Menu.HoverOpenDelay"/>.
/// </summary>
[Test]
public void TestHoverOpen()
{
AddStep("Click item", () => clickItem(0, 1));
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(1).GetMenuItems()[0]));
AddAssert("Check closed", () => menus.GetSubMenu(2)?.State != MenuState.Open);
AddAssert("Check open", () => menus.GetSubMenu(2).State == MenuState.Open);
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(2).GetMenuItems()[0]));
AddAssert("Check closed", () => menus.GetSubMenu(3)?.State != MenuState.Open);
AddAssert("Check open", () => menus.GetSubMenu(3).State == MenuState.Open);
}
/// <summary>
/// Tests if hovering over a different item on the main <see cref="Menu"/> will instantly open another menu
/// and correctly changes the sub-menu items to the new items from the hovered item.
/// </summary>
[Test]
public void TestHoverChange()
{
IReadOnlyList<MenuItem> currentItems = null;
AddStep("Click item", () =>
{
clickItem(0, 0);
});
AddStep("Get items", () =>
{
currentItems = menus.GetSubMenu(1).Items;
});
AddAssert("Check open", () => menus.GetSubMenu(1).State == MenuState.Open);
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(0).GetMenuItems()[1]));
AddAssert("Check open", () => menus.GetSubMenu(1).State == MenuState.Open);
AddAssert("Check new items", () => !menus.GetSubMenu(1).Items.SequenceEqual(currentItems));
AddAssert("Check closed", () =>
{
int currentSubMenu = 3;
while (true)
{
var subMenu = menus.GetSubMenu(currentSubMenu);
if (subMenu == null)
break;
if (subMenu.State == MenuState.Open)
return false;
currentSubMenu++;
}
return true;
});
}
/// <summary>
/// Tests whether hovering over a different item on a sub-menu opens a new sub-menu in a delayed fashion
/// and correctly changes the sub-menu items to the new items from the hovered item.
/// </summary>
[Test]
public void TestDelayedHoverChange()
{
AddStep("Click item", () => clickItem(0, 2));
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(1).GetMenuItems()[0]));
AddAssert("Check closed", () => menus.GetSubMenu(2)?.State != MenuState.Open);
AddAssert("Check closed", () => menus.GetSubMenu(2)?.State != MenuState.Open);
AddStep("Hover item", () =>
{
inputManager.MoveMouseTo(menus.GetSubStructure(1).GetMenuItems()[1]);
});
AddAssert("Check closed", () => menus.GetSubMenu(2)?.State != MenuState.Open);
AddAssert("Check open", () => menus.GetSubMenu(2).State == MenuState.Open);
AddAssert("Check closed", () =>
{
int currentSubMenu = 3;
while (true)
{
var subMenu = menus.GetSubMenu(currentSubMenu);
if (subMenu == null)
break;
if (subMenu.State == MenuState.Open)
return false;
currentSubMenu++;
}
return true;
});
}
/// <summary>
/// Tests whether clicking on <see cref="Menu"/>s that have opened sub-menus don't close the sub-menus.
/// Then tests hovering in reverse order to make sure only the lower level menus close.
/// </summary>
[Test]
public void TestMenuClicksDontClose()
{
AddStep("Click item", () => clickItem(0, 1));
AddStep("Click item", () => clickItem(1, 0));
AddStep("Click item", () => clickItem(2, 0));
AddStep("Click item", () => clickItem(3, 0));
for (int i = 3; i >= 1; i--)
{
int menuIndex = i;
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(menuIndex).GetMenuItems()[0]));
AddAssert("Check submenu open", () => menus.GetSubMenu(menuIndex + 1).State == MenuState.Open);
AddStep("Click item", () => inputManager.Click(MouseButton.Left));
AddAssert("Check all open", () =>
{
for (int j = 0; j <= menuIndex; j++)
{
int menuIndex2 = j;
if (menus.GetSubMenu(menuIndex2)?.State != MenuState.Open)
return false;
}
return true;
});
}
}
/// <summary>
/// Tests whether clicking on the <see cref="Menu"/> that has <see cref="Menu.TopLevelMenu"/> closes all sub menus.
/// </summary>
[Test]
public void TestMenuClickClosesSubMenus()
{
AddStep("Click item", () => clickItem(0, 1));
AddStep("Click item", () => clickItem(1, 0));
AddStep("Click item", () => clickItem(2, 0));
AddStep("Click item", () => clickItem(3, 0));
AddStep("Click item", () => clickItem(0, 1));
AddAssert("Check submenus closed", () =>
{
for (int j = 1; j <= 3; j++)
{
int menuIndex2 = j;
if (menus.GetSubMenu(menuIndex2).State == MenuState.Open)
return false;
}
return true;
});
}
/// <summary>
/// Tests whether clicking on an action in a sub-menu closes all <see cref="Menu"/>s.
/// </summary>
[Test]
public void TestActionClickClosesMenus()
{
AddStep("Click item", () => clickItem(0, 1));
AddStep("Click item", () => clickItem(1, 0));
AddStep("Click item", () => clickItem(2, 0));
AddStep("Click item", () => clickItem(3, 0));
AddStep("Click item", () => clickItem(4, 0));
AddAssert("Check submenus closed", () =>
{
for (int j = 1; j <= 3; j++)
{
int menuIndex2 = j;
if (menus.GetSubMenu(menuIndex2).State == MenuState.Open)
return false;
}
return true;
});
}
/// <summary>
/// Tests whether clicking outside the <see cref="Menu"/> structure closes all sub-menus.
/// </summary>
/// <param name="hoverPrevious">Whether the previous menu should first be hovered before clicking outside.</param>
[TestCase(false)]
[TestCase(true)]
public void TestClickingOutsideClosesMenus(bool hoverPrevious)
{
for (int i = 0; i <= 3; i++)
{
int i2 = i;
for (int j = 0; j <= i; j++)
{
int menuToOpen = j;
int itemToOpen = menuToOpen == 0 ? 1 : 0;
AddStep("Click item", () => clickItem(menuToOpen, itemToOpen));
}
if (hoverPrevious && i > 0)
AddStep("Hover previous", () => inputManager.MoveMouseTo(menus.GetSubStructure(i2 - 1).GetMenuItems()[i2 > 1 ? 0 : 1]));
AddStep("Remove hover", () => inputManager.MoveMouseTo(Vector2.Zero));
AddStep("Click outside", () => inputManager.Click(MouseButton.Left));
AddAssert("Check submenus closed", () =>
{
for (int j = 1; j <= i2 + 1; j++)
{
int menuIndex2 = j;
if (menus.GetSubMenu(menuIndex2).State == MenuState.Open)
return false;
}
return true;
});
}
}
/// <summary>
/// Opens some menus and then changes the selected item.
/// </summary>
[Test]
public void TestSelectedState()
{
AddStep("Click item", () => clickItem(0, 2));
AddAssert("Check open", () => menus.GetSubMenu(1).State == MenuState.Open);
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(1).GetMenuItems()[1]));
AddAssert("Check closed 1", () => menus.GetSubMenu(2)?.State != MenuState.Open);
AddAssert("Check open", () => menus.GetSubMenu(2).State == MenuState.Open);
AddAssert("Check selected index 1", () => menus.GetSubStructure(1).GetSelectedIndex() == 1);
AddStep("Change selection", () => menus.GetSubStructure(1).SetSelectedState(0, MenuItemState.Selected));
AddAssert("Check selected index", () => menus.GetSubStructure(1).GetSelectedIndex() == 0);
AddStep("Change selection", () => menus.GetSubStructure(1).SetSelectedState(2, MenuItemState.Selected));
AddAssert("Check selected index 2", () => menus.GetSubStructure(1).GetSelectedIndex() == 2);
AddStep("Close menus", () => menus.GetSubMenu(0).Close());
AddAssert("Check selected index 4", () => menus.GetSubStructure(1).GetSelectedIndex() == -1);
}
#endregion
/// <summary>
/// Click an item in a menu.
/// </summary>
/// <param name="menuIndex">The level of menu our click targets.</param>
/// <param name="itemIndex">The item to click in the menu.</param>
private void clickItem(int menuIndex, int itemIndex)
{
inputManager.MoveMouseTo(menus.GetSubStructure(menuIndex).GetMenuItems()[itemIndex]);
inputManager.Click(MouseButton.Left);
}
private MenuItem generateRandomMenuItem(string name = "Menu Item", int currDepth = 1)
{
var item = new MenuItem(name);
if (currDepth == max_depth)
return item;
int subCount = rng.Next(0, max_count);
var subItems = new List<MenuItem>();
for (int i = 0; i < subCount; i++)
subItems.Add(generateRandomMenuItem(item.Text + $" #{i + 1}", currDepth + 1));
item.Items = subItems;
return item;
}
/// <summary>
/// Helper class used to retrieve various internal properties/items from a <see cref="Menu"/>.
/// </summary>
private class MenuStructure
{
private readonly Menu menu;
public MenuStructure(Menu menu)
{
this.menu = menu;
}
/// <summary>
/// Retrieves the <see cref="Menu.DrawableMenuItem"/>s of the <see cref="Menu"/> represented by this <see cref="MenuStructure"/>.
/// </summary>
public IReadOnlyList<Drawable> GetMenuItems()
{
var contents = (CompositeDrawable)menu.InternalChildren[0];
var contentContainer = (CompositeDrawable)contents.InternalChildren[1];
return ((CompositeDrawable)((CompositeDrawable)contentContainer.InternalChildren[0]).InternalChildren[0]).InternalChildren;
}
/// <summary>
/// Finds the <see cref="Menu.DrawableMenuItem"/> index in the <see cref="Menu"/> represented by this <see cref="MenuStructure"/> that
/// has <see cref="Menu.DrawableMenuItem.State"/> set to <see cref="MenuItemState.Selected"/>.
/// </summary>
public int GetSelectedIndex()
{
var items = GetMenuItems();
for (int i = 0; i < items.Count; i++)
{
var state = (MenuItemState)(items[i]?.GetType().GetProperty("State")?.GetValue(items[i]) ?? MenuItemState.NotSelected);
if (state == MenuItemState.Selected)
return i;
}
return -1;
}
/// <summary>
/// Sets the <see cref="Menu.DrawableMenuItem"/> <see cref="Menu.DrawableMenuItem.State"/> at the specified index to a specified state.
/// </summary>
/// <param name="index">The index of the <see cref="Menu.DrawableMenuItem"/> to set the state of.</param>
/// <param name="state">The state to be set.</param>
public void SetSelectedState(int index, MenuItemState state)
{
var item = GetMenuItems()[index];
item.GetType().GetProperty("State")?.SetValue(item, state);
}
/// <summary>
/// Retrieves the sub-<see cref="Menu"/> at an index-offset from the current <see cref="Menu"/>.
/// </summary>
/// <param name="index">The sub-<see cref="Menu"/> index. An index of 0 is the <see cref="Menu"/> represented by this <see cref="MenuStructure"/>.</param>
public Menu GetSubMenu(int index)
{
var currentMenu = menu;
for (int i = 0; i < index; i++)
{
if (currentMenu == null)
break;
var container = (CompositeDrawable)currentMenu.InternalChildren[1];
currentMenu = (container.InternalChildren.Count > 0 ? container.InternalChildren[0] : null) as Menu;
}
return currentMenu;
}
/// <summary>
/// Generates a new <see cref="MenuStructure"/> for the a sub-<see cref="Menu"/>.
/// </summary>
/// <param name="index">The sub-<see cref="Menu"/> index to generate the <see cref="MenuStructure"/> for. An index of 0 is the <see cref="Menu"/> represented by this <see cref="MenuStructure"/>.</param>
public MenuStructure GetSubStructure(int index) => new MenuStructure(GetSubMenu(index));
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Framework.Testing.Input;
using OpenTK;
using OpenTK.Input;
namespace osu.Framework.Tests.Visual
{
public class TestCaseNestedMenus : TestCase
{
private const int max_depth = 5;
private const int max_count = 5;
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(Menu) };
private Random rng;
private ManualInputManager inputManager;
private MenuStructure menus;
[SetUp]
public void SetUp()
{
Clear();
rng = new Random(1337);
Menu menu;
Add(inputManager = new ManualInputManager
{
Children = new Drawable[]
{
new CursorContainer(),
new Container
{
RelativeSizeAxes = Axes.Both,
Child = menu = createMenu()
}
}
});
menus = new MenuStructure(menu);
}
private Menu createMenu() => new ClickOpenMenu(TimePerAction)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Items = new[]
{
generateRandomMenuItem("First"),
generateRandomMenuItem("Second"),
generateRandomMenuItem("Third"),
}
};
private class ClickOpenMenu : Menu
{
protected override Menu CreateSubMenu() => new ClickOpenMenu(HoverOpenDelay, false);
public ClickOpenMenu(double timePerAction, bool topLevel = true) : base(Direction.Vertical, topLevel)
{
HoverOpenDelay = timePerAction;
}
}
#region Test Cases
/// <summary>
/// Tests if the <see cref="Menu"/> respects <see cref="Menu.TopLevelMenu"/> = true, by not alowing it to be closed
/// when a click happens outside the <see cref="Menu"/>.
/// </summary>
[Test]
public void TestAlwaysOpen()
{
AddStep("Click outside", () => inputManager.Click(MouseButton.Left));
AddAssert("Check AlwaysOpen = true", () => menus.GetSubMenu(0).State == MenuState.Open);
}
/// <summary>
/// Tests if the hover state on <see cref="Menu.DrawableMenuItem"/>s is valid.
/// </summary>
[Test]
public void TestHoverState()
{
AddAssert("Check submenu closed", () => menus.GetSubMenu(1)?.State != MenuState.Open);
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetMenuItems()[0]));
AddAssert("Check item hovered", () => menus.GetMenuItems()[0].IsHovered);
}
/// <summary>
/// Tests if the <see cref="Menu"/> respects <see cref="Menu.TopLevelMenu"/> = true.
/// </summary>
[Test]
public void TestTopLevelMenu()
{
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(0).GetMenuItems()[0]));
AddAssert("Check closed", () => menus.GetSubMenu(1)?.State != MenuState.Open);
AddAssert("Check closed", () => menus.GetSubMenu(1)?.State != MenuState.Open);
AddStep("Click item", () => inputManager.Click(MouseButton.Left));
AddAssert("Check open", () => menus.GetSubMenu(1).State == MenuState.Open);
}
/// <summary>
/// Tests if clicking once on a menu that has <see cref="Menu.TopLevelMenu"/> opens it, and clicking a second time
/// closes it.
/// </summary>
[Test]
public void TestDoubleClick()
{
AddStep("Click item", () => clickItem(0, 0));
AddAssert("Check open", () => menus.GetSubMenu(1).State == MenuState.Open);
AddStep("Click item", () => clickItem(0, 0));
AddAssert("Check closed", () => menus.GetSubMenu(1)?.State != MenuState.Open);
}
/// <summary>
/// Tests whether click on <see cref="Menu.DrawableMenuItem"/>s causes sub-menus to instantly appear.
/// </summary>
[Test]
public void TestInstantOpen()
{
AddStep("Click item", () => clickItem(0, 1));
AddAssert("Check open", () => menus.GetSubMenu(1).State == MenuState.Open);
AddStep("Click item", () => clickItem(1, 0));
AddAssert("Check open", () => menus.GetSubMenu(2).State == MenuState.Open);
}
/// <summary>
/// Tests if clicking on an item that has no sub-menu causes the menu to close.
/// </summary>
[Test]
public void TestActionClick()
{
AddStep("Click item", () => clickItem(0, 0));
AddStep("Click item", () => clickItem(1, 0));
AddAssert("Check closed", () => menus.GetSubMenu(1)?.State != MenuState.Open);
}
/// <summary>
/// Tests if hovering over menu items respects the <see cref="Menu.HoverOpenDelay"/>.
/// </summary>
[Test]
public void TestHoverOpen()
{
AddStep("Click item", () => clickItem(0, 1));
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(1).GetMenuItems()[0]));
AddAssert("Check closed", () => menus.GetSubMenu(2)?.State != MenuState.Open);
AddAssert("Check open", () => menus.GetSubMenu(2).State == MenuState.Open);
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(2).GetMenuItems()[0]));
AddAssert("Check closed", () => menus.GetSubMenu(3)?.State != MenuState.Open);
AddAssert("Check open", () => menus.GetSubMenu(3).State == MenuState.Open);
}
/// <summary>
/// Tests if hovering over a different item on the main <see cref="Menu"/> will instantly open another menu
/// and correctly changes the sub-menu items to the new items from the hovered item.
/// </summary>
[Test]
public void TestHoverChange()
{
IReadOnlyList<MenuItem> currentItems = null;
AddStep("Click item", () =>
{
clickItem(0, 0);
});
AddStep("Get items", () =>
{
currentItems = menus.GetSubMenu(1).Items;
});
AddAssert("Check open", () => menus.GetSubMenu(1).State == MenuState.Open);
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(0).GetMenuItems()[1]));
AddAssert("Check open", () => menus.GetSubMenu(1).State == MenuState.Open);
AddAssert("Check new items", () => !menus.GetSubMenu(1).Items.SequenceEqual(currentItems));
AddAssert("Check closed", () =>
{
int currentSubMenu = 3;
while (true)
{
var subMenu = menus.GetSubMenu(currentSubMenu);
if (subMenu == null)
break;
if (subMenu.State == MenuState.Open)
return false;
currentSubMenu++;
}
return true;
});
}
/// <summary>
/// Tests whether hovering over a different item on a sub-menu opens a new sub-menu in a delayed fashion
/// and correctly changes the sub-menu items to the new items from the hovered item.
/// </summary>
[Test]
public void TestDelayedHoverChange()
{
AddStep("Click item", () => clickItem(0, 2));
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(1).GetMenuItems()[0]));
AddAssert("Check closed", () => menus.GetSubMenu(2)?.State != MenuState.Open);
AddAssert("Check closed", () => menus.GetSubMenu(2)?.State != MenuState.Open);
AddStep("Hover item", () =>
{
inputManager.MoveMouseTo(menus.GetSubStructure(1).GetMenuItems()[1]);
});
AddAssert("Check closed", () => menus.GetSubMenu(2)?.State != MenuState.Open);
AddAssert("Check open", () => menus.GetSubMenu(2).State == MenuState.Open);
AddAssert("Check closed", () =>
{
int currentSubMenu = 3;
while (true)
{
var subMenu = menus.GetSubMenu(currentSubMenu);
if (subMenu == null)
break;
if (subMenu.State == MenuState.Open)
return false;
currentSubMenu++;
}
return true;
});
}
/// <summary>
/// Tests whether clicking on <see cref="Menu"/>s that have opened sub-menus don't close the sub-menus.
/// Then tests hovering in reverse order to make sure only the lower level menus close.
/// </summary>
[Test]
public void TestMenuClicksDontClose()
{
AddStep("Click item", () => clickItem(0, 1));
AddStep("Click item", () => clickItem(1, 0));
AddStep("Click item", () => clickItem(2, 0));
AddStep("Click item", () => clickItem(3, 0));
for (int i = 3; i >= 1; i--)
{
int menuIndex = i;
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(menuIndex).GetMenuItems()[0]));
AddAssert("Check submenu open", () => menus.GetSubMenu(menuIndex + 1).State == MenuState.Open);
AddStep("Click item", () => inputManager.Click(MouseButton.Left));
AddAssert("Check all open", () =>
{
for (int j = 0; j <= menuIndex; j++)
{
int menuIndex2 = j;
if (menus.GetSubMenu(menuIndex2)?.State != MenuState.Open)
return false;
}
return true;
});
}
}
/// <summary>
/// Tests whether clicking on the <see cref="Menu"/> that has <see cref="Menu.TopLevelMenu"/> closes all sub menus.
/// </summary>
[Test]
public void TestMenuClickClosesSubMenus()
{
AddStep("Click item", () => clickItem(0, 1));
AddStep("Click item", () => clickItem(1, 0));
AddStep("Click item", () => clickItem(2, 0));
AddStep("Click item", () => clickItem(3, 0));
AddStep("Click item", () => clickItem(0, 1));
AddAssert("Check submenus closed", () =>
{
for (int j = 1; j <= 3; j++)
{
int menuIndex2 = j;
if (menus.GetSubMenu(menuIndex2).State == MenuState.Open)
return false;
}
return true;
});
}
/// <summary>
/// Tests whether clicking on an action in a sub-menu closes all <see cref="Menu"/>s.
/// </summary>
[Test]
public void TestActionClickClosesMenus()
{
AddStep("Click item", () => clickItem(0, 1));
AddStep("Click item", () => clickItem(1, 0));
AddStep("Click item", () => clickItem(2, 0));
AddStep("Click item", () => clickItem(3, 0));
AddStep("Click item", () => clickItem(4, 0));
AddAssert("Check submenus closed", () =>
{
for (int j = 1; j <= 3; j++)
{
int menuIndex2 = j;
if (menus.GetSubMenu(menuIndex2).State == MenuState.Open)
return false;
}
return true;
});
}
/// <summary>
/// Tests whether clicking outside the <see cref="Menu"/> structure closes all sub-menus.
/// </summary>
/// <param name="hoverPrevious">Whether the previous menu should first be hovered before clicking outside.</param>
[TestCase(false)]
[TestCase(true)]
public void TestClickingOutsideClosesMenus(bool hoverPrevious)
{
for (int i = 0; i <= 3; i++)
{
int i2 = i;
for (int j = 0; j <= i; j++)
{
int menuToOpen = j;
int itemToOpen = menuToOpen == 0 ? 1 : 0;
AddStep("Click item", () => clickItem(menuToOpen, itemToOpen));
}
if (hoverPrevious && i > 0)
AddStep("Hover previous", () => inputManager.MoveMouseTo(menus.GetSubStructure(i2 - 1).GetMenuItems()[i2 > 1 ? 0 : 1]));
AddStep("Remove hover", () => inputManager.MoveMouseTo(Vector2.Zero));
AddStep("Click outside", () => inputManager.Click(MouseButton.Left));
AddAssert("Check submenus closed", () =>
{
for (int j = 1; j <= i2 + 1; j++)
{
int menuIndex2 = j;
if (menus.GetSubMenu(menuIndex2).State == MenuState.Open)
return false;
}
return true;
});
}
}
/// <summary>
/// Opens some menus and then changes the selected item.
/// </summary>
[Test]
public void TestSelectedState()
{
AddStep("Click item", () => clickItem(0, 2));
AddAssert("Check open", () => menus.GetSubMenu(1).State == MenuState.Open);
AddStep("Hover item", () => inputManager.MoveMouseTo(menus.GetSubStructure(1).GetMenuItems()[1]));
AddAssert("Check closed 1", () => menus.GetSubMenu(2)?.State != MenuState.Open);
AddAssert("Check open", () => menus.GetSubMenu(2).State == MenuState.Open);
AddAssert("Check selected index 1", () => menus.GetSubStructure(1).GetSelectedIndex() == 1);
AddStep("Change selection", () => menus.GetSubStructure(1).SetSelectedState(0, MenuItemState.Selected));
AddAssert("Check selected index", () => menus.GetSubStructure(1).GetSelectedIndex() == 0);
AddStep("Change selection", () => menus.GetSubStructure(1).SetSelectedState(2, MenuItemState.Selected));
AddAssert("Check selected index 2", () => menus.GetSubStructure(1).GetSelectedIndex() == 2);
AddStep("Close menus", () => menus.GetSubMenu(0).Close());
AddAssert("Check selected index 4", () => menus.GetSubStructure(1).GetSelectedIndex() == -1);
}
#endregion
/// <summary>
/// Click an item in a menu.
/// </summary>
/// <param name="menuIndex">The level of menu our click targets.</param>
/// <param name="itemIndex">The item to click in the menu.</param>
private void clickItem(int menuIndex, int itemIndex)
{
inputManager.MoveMouseTo(menus.GetSubStructure(menuIndex).GetMenuItems()[itemIndex]);
inputManager.Click(MouseButton.Left);
}
private MenuItem generateRandomMenuItem(string name = "Menu Item", int currDepth = 1)
{
var item = new MenuItem(name);
if (currDepth == max_depth)
return item;
int subCount = rng.Next(0, max_count);
var subItems = new List<MenuItem>();
for (int i = 0; i < subCount; i++)
subItems.Add(generateRandomMenuItem(item.Text + $" #{i + 1}", currDepth + 1));
item.Items = subItems;
return item;
}
/// <summary>
/// Helper class used to retrieve various internal properties/items from a <see cref="Menu"/>.
/// </summary>
private class MenuStructure
{
private readonly Menu menu;
public MenuStructure(Menu menu)
{
this.menu = menu;
}
/// <summary>
/// Retrieves the <see cref="Menu.DrawableMenuItem"/>s of the <see cref="Menu"/> represented by this <see cref="MenuStructure"/>.
/// </summary>
public IReadOnlyList<Drawable> GetMenuItems()
{
var contents = (CompositeDrawable)menu.InternalChildren[0];
var contentContainer = (CompositeDrawable)contents.InternalChildren[1];
return ((CompositeDrawable)((CompositeDrawable)contentContainer.InternalChildren[0]).InternalChildren[0]).InternalChildren;
}
/// <summary>
/// Finds the <see cref="Menu.DrawableMenuItem"/> index in the <see cref="Menu"/> represented by this <see cref="MenuStructure"/> that
/// has <see cref="Menu.DrawableMenuItem.State"/> set to <see cref="MenuItemState.Selected"/>.
/// </summary>
public int GetSelectedIndex()
{
var items = GetMenuItems();
for (int i = 0; i < items.Count; i++)
{
var state = (MenuItemState)(items[i]?.GetType().GetProperty("State")?.GetValue(items[i]) ?? MenuItemState.NotSelected);
if (state == MenuItemState.Selected)
return i;
}
return -1;
}
/// <summary>
/// Sets the <see cref="Menu.DrawableMenuItem"/> <see cref="Menu.DrawableMenuItem.State"/> at the specified index to a specified state.
/// </summary>
/// <param name="index">The index of the <see cref="Menu.DrawableMenuItem"/> to set the state of.</param>
/// <param name="state">The state to be set.</param>
public void SetSelectedState(int index, MenuItemState state)
{
var item = GetMenuItems()[index];
item.GetType().GetProperty("State")?.SetValue(item, state);
}
/// <summary>
/// Retrieves the sub-<see cref="Menu"/> at an index-offset from the current <see cref="Menu"/>.
/// </summary>
/// <param name="index">The sub-<see cref="Menu"/> index. An index of 0 is the <see cref="Menu"/> represented by this <see cref="MenuStructure"/>.</param>
public Menu GetSubMenu(int index)
{
var currentMenu = menu;
for (int i = 0; i < index; i++)
{
if (currentMenu == null)
break;
var container = (CompositeDrawable)currentMenu.InternalChildren[1];
currentMenu = (container.InternalChildren.Count > 0 ? container.InternalChildren[0] : null) as Menu;
}
return currentMenu;
}
/// <summary>
/// Generates a new <see cref="MenuStructure"/> for the a sub-<see cref="Menu"/>.
/// </summary>
/// <param name="index">The sub-<see cref="Menu"/> index to generate the <see cref="MenuStructure"/> for. An index of 0 is the <see cref="Menu"/> represented by this <see cref="MenuStructure"/>.</param>
public MenuStructure GetSubStructure(int index) => new MenuStructure(GetSubMenu(index));
}
}
}

View File

@@ -1,248 +1,248 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCasePadding : GridTestCase
{
public TestCasePadding() : base(2, 2)
{
Cell(0).AddRange(new Drawable[]
{
new SpriteText { Text = @"Padding - 20 All Sides" },
new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
new PaddedBox(Color4.Blue)
{
Padding = new MarginPadding(20),
Size = new Vector2(200),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new PaddedBox(Color4.DarkSeaGreen)
{
Padding = new MarginPadding(40),
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre
}
}
}
}
}
});
Cell(1).AddRange(new Drawable[]
{
new SpriteText { Text = @"Padding - 20 Top, Left" },
new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
new PaddedBox(Color4.Blue)
{
Padding = new MarginPadding
{
Top = 20,
Left = 20,
},
Size = new Vector2(200),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new PaddedBox(Color4.DarkSeaGreen)
{
Padding = new MarginPadding(40),
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre
}
}
}
}
}
});
Cell(2).AddRange(new Drawable[]
{
new SpriteText { Text = @"Margin - 20 All Sides" },
new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
new PaddedBox(Color4.Blue)
{
Margin = new MarginPadding(20),
Size = new Vector2(200),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new PaddedBox(Color4.DarkSeaGreen)
{
Padding = new MarginPadding(20),
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre
}
}
}
}
}
});
Cell(3).AddRange(new Drawable[]
{
new SpriteText { Text = @"Margin - 20 Top, Left" },
new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
new PaddedBox(Color4.Blue)
{
Margin = new MarginPadding
{
Top = 20,
Left = 20,
},
Size = new Vector2(200),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new PaddedBox(Color4.DarkSeaGreen)
{
Padding = new MarginPadding(40),
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre
}
}
}
}
}
});
}
private class PaddedBox : Container
{
private readonly SpriteText t1;
private readonly SpriteText t2;
private readonly SpriteText t3;
private readonly SpriteText t4;
private readonly Container content;
protected override Container<Drawable> Content => content;
public PaddedBox(Color4 colour)
{
AddRangeInternal(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colour,
},
content = new Container
{
RelativeSizeAxes = Axes.Both,
},
t1 = new SpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
t2 = new SpriteText
{
Rotation = 90,
Anchor = Anchor.CentreRight,
Origin = Anchor.TopCentre
},
t3 = new SpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre
},
t4 = new SpriteText
{
Rotation = -90,
Anchor = Anchor.CentreLeft,
Origin = Anchor.TopCentre
}
});
Masking = true;
}
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
{
t1.Text = (Padding.Top > 0 ? $"p{Padding.Top}" : string.Empty) + (Margin.Top > 0 ? $"m{Margin.Top}" : string.Empty);
t2.Text = (Padding.Right > 0 ? $"p{Padding.Right}" : string.Empty) + (Margin.Right > 0 ? $"m{Margin.Right}" : string.Empty);
t3.Text = (Padding.Bottom > 0 ? $"p{Padding.Bottom}" : string.Empty) + (Margin.Bottom > 0 ? $"m{Margin.Bottom}" : string.Empty);
t4.Text = (Padding.Left > 0 ? $"p{Padding.Left}" : string.Empty) + (Margin.Left > 0 ? $"m{Margin.Left}" : string.Empty);
return base.Invalidate(invalidation, source, shallPropagate);
}
protected override bool OnDrag(InputState state)
{
Position += state.Mouse.Delta;
return true;
}
protected override bool OnDragEnd(InputState state) => true;
protected override bool OnDragStart(InputState state) => true;
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCasePadding : GridTestCase
{
public TestCasePadding() : base(2, 2)
{
Cell(0).AddRange(new Drawable[]
{
new SpriteText { Text = @"Padding - 20 All Sides" },
new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
new PaddedBox(Color4.Blue)
{
Padding = new MarginPadding(20),
Size = new Vector2(200),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new PaddedBox(Color4.DarkSeaGreen)
{
Padding = new MarginPadding(40),
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre
}
}
}
}
}
});
Cell(1).AddRange(new Drawable[]
{
new SpriteText { Text = @"Padding - 20 Top, Left" },
new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
new PaddedBox(Color4.Blue)
{
Padding = new MarginPadding
{
Top = 20,
Left = 20,
},
Size = new Vector2(200),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new PaddedBox(Color4.DarkSeaGreen)
{
Padding = new MarginPadding(40),
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre
}
}
}
}
}
});
Cell(2).AddRange(new Drawable[]
{
new SpriteText { Text = @"Margin - 20 All Sides" },
new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
new PaddedBox(Color4.Blue)
{
Margin = new MarginPadding(20),
Size = new Vector2(200),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new PaddedBox(Color4.DarkSeaGreen)
{
Padding = new MarginPadding(20),
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre
}
}
}
}
}
});
Cell(3).AddRange(new Drawable[]
{
new SpriteText { Text = @"Margin - 20 Top, Left" },
new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
new PaddedBox(Color4.Blue)
{
Margin = new MarginPadding
{
Top = 20,
Left = 20,
},
Size = new Vector2(200),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new PaddedBox(Color4.DarkSeaGreen)
{
Padding = new MarginPadding(40),
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre
}
}
}
}
}
});
}
private class PaddedBox : Container
{
private readonly SpriteText t1;
private readonly SpriteText t2;
private readonly SpriteText t3;
private readonly SpriteText t4;
private readonly Container content;
protected override Container<Drawable> Content => content;
public PaddedBox(Color4 colour)
{
AddRangeInternal(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colour,
},
content = new Container
{
RelativeSizeAxes = Axes.Both,
},
t1 = new SpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
t2 = new SpriteText
{
Rotation = 90,
Anchor = Anchor.CentreRight,
Origin = Anchor.TopCentre
},
t3 = new SpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre
},
t4 = new SpriteText
{
Rotation = -90,
Anchor = Anchor.CentreLeft,
Origin = Anchor.TopCentre
}
});
Masking = true;
}
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
{
t1.Text = (Padding.Top > 0 ? $"p{Padding.Top}" : string.Empty) + (Margin.Top > 0 ? $"m{Margin.Top}" : string.Empty);
t2.Text = (Padding.Right > 0 ? $"p{Padding.Right}" : string.Empty) + (Margin.Right > 0 ? $"m{Margin.Right}" : string.Empty);
t3.Text = (Padding.Bottom > 0 ? $"p{Padding.Bottom}" : string.Empty) + (Margin.Bottom > 0 ? $"m{Margin.Bottom}" : string.Empty);
t4.Text = (Padding.Left > 0 ? $"p{Padding.Left}" : string.Empty) + (Margin.Left > 0 ? $"m{Margin.Left}" : string.Empty);
return base.Invalidate(invalidation, source, shallPropagate);
}
protected override bool OnDrag(InputState state)
{
Position += state.Mouse.Delta;
return true;
}
protected override bool OnDragEnd(InputState state) => true;
protected override bool OnDragStart(InputState state) => true;
}
}
}

View File

@@ -1,168 +1,168 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCasePathInput : TestCase
{
private const float path_width = 50;
private const float path_radius = path_width / 2;
private readonly Path path;
private readonly TestPoint testPoint;
private readonly SpriteText text;
public TestCasePathInput()
{
Children = new Drawable[]
{
path = new HoverablePath(),
testPoint = new TestPoint(),
text = new SpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre }
};
testHorizontalPath();
testDiagonalPath();
testVShaped();
testOverlapping();
}
private void testHorizontalPath()
{
addPath("Horizontal path", new Vector2(100), new Vector2(300, 100));
// Left out
test(new Vector2(40, 100), false);
// Left in
test(new Vector2(80, 100), true);
// Cap out
test(new Vector2(60), false);
// Cap in
test(new Vector2(70), true);
//Right out
test(new Vector2(360, 100), false);
// Centre
test(new Vector2(200, 100), true);
// Top out
test(new Vector2(190, 40), false);
// Top in
test(new Vector2(190, 60), true);
}
private void testDiagonalPath()
{
addPath("Diagonal path", new Vector2(300), new Vector2(100));
// Top-left out
test(new Vector2(50), false);
// Top-left in
test(new Vector2(80), true);
// Left out
test(new Vector2(145, 235), false);
// Left in
test(new Vector2(170, 235), true);
// Cap out
test(new Vector2(355, 300), false);
// Cap in
test(new Vector2(340, 300), true);
}
private void testVShaped()
{
addPath("V-shaped", new Vector2(100), new Vector2(300), new Vector2(500, 100));
// Intersection out
test(new Vector2(300, 225), false);
// Intersection in
test(new Vector2(300, 240), true);
// Bottom cap out
test(new Vector2(300, 355), false);
// Bottom cap in
test(new Vector2(300, 340), true);
}
private void testOverlapping()
{
addPath("Overlapping", new Vector2(100), new Vector2(600), new Vector2(800, 300), new Vector2(100, 400));
// Left intersection out
test(new Vector2(250, 325), false);
// Left intersection in
test(new Vector2(260, 325), true);
// Top intersection out
test(new Vector2(380, 300), false);
// Top intersection in
test(new Vector2(380, 320), true);
// Triangle left intersection out
test(new Vector2(475, 400), false);
// Triangle left intersection in
test(new Vector2(460, 400), true);
// Triangle right intersection out
test(new Vector2(690, 370), false);
// Triangle right intersection in
test(new Vector2(700, 370), true);
// Triangle bottom intersection out
test(new Vector2(590, 515), false);
// Triangle bottom intersection in
test(new Vector2(590, 525), true);
// Centre intersection in
test(new Vector2(370, 360), true);
}
protected override bool OnMouseMove(InputState state)
{
text.Text = path.ToLocalSpace(state.Mouse.NativeState.Position).ToString();
return base.OnMouseMove(state);
}
private void addPath(string name, params Vector2[] vertices) => AddStep(name, () =>
{
path.PathWidth = path_width;
path.Positions = vertices.ToList();
});
private void test(Vector2 position, bool shouldReceiveMouseInput)
{
AddAssert($"Test @ {position} = {shouldReceiveMouseInput}", () =>
{
testPoint.Position = position;
return path.ReceiveMouseInputAt(path.ToScreenSpace(position)) == shouldReceiveMouseInput;
});
}
private class TestPoint : CircularContainer
{
public TestPoint()
{
Origin = Anchor.Centre;
Size = new Vector2(5);
Colour = Color4.Red;
Masking = true;
InternalChild = new Box { RelativeSizeAxes = Axes.Both };
}
}
private class HoverablePath : Path
{
protected override bool OnHover(InputState state)
{
Colour = Color4.Green;
return true;
}
protected override void OnHoverLost(InputState state)
{
Colour = Color4.White;
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCasePathInput : TestCase
{
private const float path_width = 50;
private const float path_radius = path_width / 2;
private readonly Path path;
private readonly TestPoint testPoint;
private readonly SpriteText text;
public TestCasePathInput()
{
Children = new Drawable[]
{
path = new HoverablePath(),
testPoint = new TestPoint(),
text = new SpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre }
};
testHorizontalPath();
testDiagonalPath();
testVShaped();
testOverlapping();
}
private void testHorizontalPath()
{
addPath("Horizontal path", new Vector2(100), new Vector2(300, 100));
// Left out
test(new Vector2(40, 100), false);
// Left in
test(new Vector2(80, 100), true);
// Cap out
test(new Vector2(60), false);
// Cap in
test(new Vector2(70), true);
//Right out
test(new Vector2(360, 100), false);
// Centre
test(new Vector2(200, 100), true);
// Top out
test(new Vector2(190, 40), false);
// Top in
test(new Vector2(190, 60), true);
}
private void testDiagonalPath()
{
addPath("Diagonal path", new Vector2(300), new Vector2(100));
// Top-left out
test(new Vector2(50), false);
// Top-left in
test(new Vector2(80), true);
// Left out
test(new Vector2(145, 235), false);
// Left in
test(new Vector2(170, 235), true);
// Cap out
test(new Vector2(355, 300), false);
// Cap in
test(new Vector2(340, 300), true);
}
private void testVShaped()
{
addPath("V-shaped", new Vector2(100), new Vector2(300), new Vector2(500, 100));
// Intersection out
test(new Vector2(300, 225), false);
// Intersection in
test(new Vector2(300, 240), true);
// Bottom cap out
test(new Vector2(300, 355), false);
// Bottom cap in
test(new Vector2(300, 340), true);
}
private void testOverlapping()
{
addPath("Overlapping", new Vector2(100), new Vector2(600), new Vector2(800, 300), new Vector2(100, 400));
// Left intersection out
test(new Vector2(250, 325), false);
// Left intersection in
test(new Vector2(260, 325), true);
// Top intersection out
test(new Vector2(380, 300), false);
// Top intersection in
test(new Vector2(380, 320), true);
// Triangle left intersection out
test(new Vector2(475, 400), false);
// Triangle left intersection in
test(new Vector2(460, 400), true);
// Triangle right intersection out
test(new Vector2(690, 370), false);
// Triangle right intersection in
test(new Vector2(700, 370), true);
// Triangle bottom intersection out
test(new Vector2(590, 515), false);
// Triangle bottom intersection in
test(new Vector2(590, 525), true);
// Centre intersection in
test(new Vector2(370, 360), true);
}
protected override bool OnMouseMove(InputState state)
{
text.Text = path.ToLocalSpace(state.Mouse.NativeState.Position).ToString();
return base.OnMouseMove(state);
}
private void addPath(string name, params Vector2[] vertices) => AddStep(name, () =>
{
path.PathWidth = path_width;
path.Positions = vertices.ToList();
});
private void test(Vector2 position, bool shouldReceiveMouseInput)
{
AddAssert($"Test @ {position} = {shouldReceiveMouseInput}", () =>
{
testPoint.Position = position;
return path.ReceiveMouseInputAt(path.ToScreenSpace(position)) == shouldReceiveMouseInput;
});
}
private class TestPoint : CircularContainer
{
public TestPoint()
{
Origin = Anchor.Centre;
Size = new Vector2(5);
Colour = Color4.Red;
Masking = true;
InternalChild = new Box { RelativeSizeAxes = Axes.Both };
}
}
private class HoverablePath : Path
{
protected override bool OnHover(InputState state)
{
Colour = Color4.Green;
return true;
}
protected override void OnHoverLost(InputState state)
{
Colour = Color4.White;
}
}
}
}

View File

@@ -1,92 +1,92 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("ensure validity of drawables when receiving certain values")]
public class TestCasePropertyBoundaries : TestCase
{
[BackgroundDependencyLoader]
private void load()
{
testPositiveScale();
testZeroScale();
testNegativeScale();
}
private void testPositiveScale()
{
var box = new Box
{
Size = new Vector2(100),
Scale = new Vector2(2)
};
Add(box);
AddAssert("Box is loaded", () => box.LoadState >= LoadState.Ready);
AddAssert("Box is present", () => box.IsPresent);
AddAssert("Box has valid draw matrix", () => checkDrawInfo(box.DrawInfo));
}
private void testZeroScale()
{
var box = new Box
{
Size = new Vector2(100),
Scale = new Vector2(0)
};
Add(box);
AddAssert("Box is loaded", () => box.LoadState >= LoadState.Ready);
AddAssert("Box is present", () => !box.IsPresent);
AddAssert("Box has valid draw matrix", () => checkDrawInfo(box.DrawInfo));
}
private void testNegativeScale()
{
var box = new Box
{
Size = new Vector2(100),
Scale = new Vector2(-2)
};
Add(box);
AddAssert("Box is loaded", () => box.LoadState >= LoadState.Ready);
AddAssert("Box is present", () => box.IsPresent);
AddAssert("Box has valid draw matrix", () => checkDrawInfo(box.DrawInfo));
}
private bool checkDrawInfo(DrawInfo drawInfo)
{
return checkFloat(drawInfo.Matrix.M11)
&& checkFloat(drawInfo.Matrix.M12)
&& checkFloat(drawInfo.Matrix.M13)
&& checkFloat(drawInfo.Matrix.M21)
&& checkFloat(drawInfo.Matrix.M22)
&& checkFloat(drawInfo.Matrix.M23)
&& checkFloat(drawInfo.Matrix.M31)
&& checkFloat(drawInfo.Matrix.M32)
&& checkFloat(drawInfo.Matrix.M33)
&& checkFloat(drawInfo.MatrixInverse.M11)
&& checkFloat(drawInfo.MatrixInverse.M12)
&& checkFloat(drawInfo.MatrixInverse.M13)
&& checkFloat(drawInfo.MatrixInverse.M21)
&& checkFloat(drawInfo.MatrixInverse.M22)
&& checkFloat(drawInfo.MatrixInverse.M23)
&& checkFloat(drawInfo.MatrixInverse.M31)
&& checkFloat(drawInfo.MatrixInverse.M32)
&& checkFloat(drawInfo.MatrixInverse.M33);
}
private bool checkFloat(float value) => !float.IsNaN(value) && !float.IsInfinity(value) && !float.IsNegativeInfinity(value);
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("ensure validity of drawables when receiving certain values")]
public class TestCasePropertyBoundaries : TestCase
{
[BackgroundDependencyLoader]
private void load()
{
testPositiveScale();
testZeroScale();
testNegativeScale();
}
private void testPositiveScale()
{
var box = new Box
{
Size = new Vector2(100),
Scale = new Vector2(2)
};
Add(box);
AddAssert("Box is loaded", () => box.LoadState >= LoadState.Ready);
AddAssert("Box is present", () => box.IsPresent);
AddAssert("Box has valid draw matrix", () => checkDrawInfo(box.DrawInfo));
}
private void testZeroScale()
{
var box = new Box
{
Size = new Vector2(100),
Scale = new Vector2(0)
};
Add(box);
AddAssert("Box is loaded", () => box.LoadState >= LoadState.Ready);
AddAssert("Box is present", () => !box.IsPresent);
AddAssert("Box has valid draw matrix", () => checkDrawInfo(box.DrawInfo));
}
private void testNegativeScale()
{
var box = new Box
{
Size = new Vector2(100),
Scale = new Vector2(-2)
};
Add(box);
AddAssert("Box is loaded", () => box.LoadState >= LoadState.Ready);
AddAssert("Box is present", () => box.IsPresent);
AddAssert("Box has valid draw matrix", () => checkDrawInfo(box.DrawInfo));
}
private bool checkDrawInfo(DrawInfo drawInfo)
{
return checkFloat(drawInfo.Matrix.M11)
&& checkFloat(drawInfo.Matrix.M12)
&& checkFloat(drawInfo.Matrix.M13)
&& checkFloat(drawInfo.Matrix.M21)
&& checkFloat(drawInfo.Matrix.M22)
&& checkFloat(drawInfo.Matrix.M23)
&& checkFloat(drawInfo.Matrix.M31)
&& checkFloat(drawInfo.Matrix.M32)
&& checkFloat(drawInfo.Matrix.M33)
&& checkFloat(drawInfo.MatrixInverse.M11)
&& checkFloat(drawInfo.MatrixInverse.M12)
&& checkFloat(drawInfo.MatrixInverse.M13)
&& checkFloat(drawInfo.MatrixInverse.M21)
&& checkFloat(drawInfo.MatrixInverse.M22)
&& checkFloat(drawInfo.MatrixInverse.M23)
&& checkFloat(drawInfo.MatrixInverse.M31)
&& checkFloat(drawInfo.MatrixInverse.M32)
&& checkFloat(drawInfo.MatrixInverse.M33);
}
private bool checkFloat(float value) => !float.IsNaN(value) && !float.IsInfinity(value) && !float.IsNegativeInfinity(value);
}
}

View File

@@ -1,178 +1,178 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.MathUtils;
using osu.Framework.Physics;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseRigidBody : TestCase
{
private readonly TestRigidBodySimulation sim;
private float restitutionBacking;
private float restitution
{
get { return restitutionBacking; }
set
{
restitutionBacking = value;
if (sim == null)
return;
foreach (var d in sim.Children)
d.Restitution = value;
sim.Restitution = value;
}
}
private float frictionBacking;
private float friction
{
get { return frictionBacking; }
set
{
frictionBacking = value;
if (sim == null)
return;
foreach (var d in sim.Children)
d.FrictionCoefficient = value;
sim.FrictionCoefficient = value;
}
}
public TestCaseRigidBody()
{
Child = sim = new TestRigidBodySimulation { RelativeSizeAxes = Axes.Both };
AddStep("Reset bodies", reset);
AddSliderStep("Simulation speed", 0f, 1f, 0.5f, v => sim.SimulationSpeed = v);
AddSliderStep("Restitution", -1f, 1f, 1f, v => restitution = v);
AddSliderStep("Friction", -1f, 5f, 0f, v => friction = v);
reset();
}
private bool overlapsAny(Drawable d)
{
foreach (var other in sim.Children)
if (other.ScreenSpaceDrawQuad.AABB.IntersectsWith(d.ScreenSpaceDrawQuad.AABB))
return true;
return false;
}
private void generateN(int n, Func<RigidBodyContainer<Drawable>> generate)
{
for (int i = 0; i < n; i++)
{
RigidBodyContainer<Drawable> d;
do
{
d = generate();
}
while (overlapsAny(d));
sim.Add(d);
}
}
private void reset()
{
sim.Clear();
Random random = new Random(1337);
// Add a textbox... because we can.
generateN(3, () => new RigidBodyContainer<Drawable>
{
Position = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 1000,
Size = new Vector2(1, 0.1f + 0.2f * (float)random.NextDouble()) * (150 + 150 * (float)random.NextDouble()),
Rotation = (float)random.NextDouble() * 360,
Child = new TextBox
{
RelativeSizeAxes = Axes.Both,
PlaceholderText = "Text box fun!",
},
});
// Boxes
generateN(10, () => new TestRigidBody
{
Position = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 1000,
Size = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 200,
Rotation = (float)random.NextDouble() * 360,
Colour = new Color4(253, 253, 253, 255),
});
// Circles
generateN(5, () =>
{
Vector2 size = new Vector2((float)random.NextDouble()) * 200;
return new TestRigidBody
{
Position = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 1000,
Size = size,
Rotation = (float)random.NextDouble() * 360,
CornerRadius = size.X / 2,
Colour = new Color4(253, 253, 253, 255),
Masking = true,
};
});
// Totally random stuff
generateN(10, () =>
{
Vector2 size = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 200;
return new TestRigidBody
{
Position = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 1000,
Size = size,
Rotation = (float)random.NextDouble() * 360,
Shear = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 2 - new Vector2(1),
CornerRadius = (float)random.NextDouble() * Math.Min(size.X, size.Y) / 2,
Colour = new Color4(253, 253, 253, 255),
Masking = true,
};
});
// Set appropriate properties
foreach (var d in sim.Children)
{
d.Mass = Math.Max(0.01f, d.ScreenSpaceDrawQuad.Area);
d.FrictionCoefficient = friction;
d.Restitution = restitution;
}
}
private class TestRigidBody : RigidBodyContainer<Drawable>
{
public TestRigidBody()
{
Child = new Box { RelativeSizeAxes = Axes.Both };
}
}
private class TestRigidBodySimulation : RigidBodySimulation
{
protected override void LoadComplete()
{
base.LoadComplete();
foreach (var d in Children)
d.ApplyImpulse(new Vector2(RNG.NextSingle() - 0.5f, RNG.NextSingle() - 0.5f) * 100, d.Centre + new Vector2(RNG.NextSingle() - 0.5f, RNG.NextSingle() - 0.5f) * 100);
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.MathUtils;
using osu.Framework.Physics;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseRigidBody : TestCase
{
private readonly TestRigidBodySimulation sim;
private float restitutionBacking;
private float restitution
{
get { return restitutionBacking; }
set
{
restitutionBacking = value;
if (sim == null)
return;
foreach (var d in sim.Children)
d.Restitution = value;
sim.Restitution = value;
}
}
private float frictionBacking;
private float friction
{
get { return frictionBacking; }
set
{
frictionBacking = value;
if (sim == null)
return;
foreach (var d in sim.Children)
d.FrictionCoefficient = value;
sim.FrictionCoefficient = value;
}
}
public TestCaseRigidBody()
{
Child = sim = new TestRigidBodySimulation { RelativeSizeAxes = Axes.Both };
AddStep("Reset bodies", reset);
AddSliderStep("Simulation speed", 0f, 1f, 0.5f, v => sim.SimulationSpeed = v);
AddSliderStep("Restitution", -1f, 1f, 1f, v => restitution = v);
AddSliderStep("Friction", -1f, 5f, 0f, v => friction = v);
reset();
}
private bool overlapsAny(Drawable d)
{
foreach (var other in sim.Children)
if (other.ScreenSpaceDrawQuad.AABB.IntersectsWith(d.ScreenSpaceDrawQuad.AABB))
return true;
return false;
}
private void generateN(int n, Func<RigidBodyContainer<Drawable>> generate)
{
for (int i = 0; i < n; i++)
{
RigidBodyContainer<Drawable> d;
do
{
d = generate();
}
while (overlapsAny(d));
sim.Add(d);
}
}
private void reset()
{
sim.Clear();
Random random = new Random(1337);
// Add a textbox... because we can.
generateN(3, () => new RigidBodyContainer<Drawable>
{
Position = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 1000,
Size = new Vector2(1, 0.1f + 0.2f * (float)random.NextDouble()) * (150 + 150 * (float)random.NextDouble()),
Rotation = (float)random.NextDouble() * 360,
Child = new TextBox
{
RelativeSizeAxes = Axes.Both,
PlaceholderText = "Text box fun!",
},
});
// Boxes
generateN(10, () => new TestRigidBody
{
Position = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 1000,
Size = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 200,
Rotation = (float)random.NextDouble() * 360,
Colour = new Color4(253, 253, 253, 255),
});
// Circles
generateN(5, () =>
{
Vector2 size = new Vector2((float)random.NextDouble()) * 200;
return new TestRigidBody
{
Position = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 1000,
Size = size,
Rotation = (float)random.NextDouble() * 360,
CornerRadius = size.X / 2,
Colour = new Color4(253, 253, 253, 255),
Masking = true,
};
});
// Totally random stuff
generateN(10, () =>
{
Vector2 size = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 200;
return new TestRigidBody
{
Position = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 1000,
Size = size,
Rotation = (float)random.NextDouble() * 360,
Shear = new Vector2((float)random.NextDouble(), (float)random.NextDouble()) * 2 - new Vector2(1),
CornerRadius = (float)random.NextDouble() * Math.Min(size.X, size.Y) / 2,
Colour = new Color4(253, 253, 253, 255),
Masking = true,
};
});
// Set appropriate properties
foreach (var d in sim.Children)
{
d.Mass = Math.Max(0.01f, d.ScreenSpaceDrawQuad.Area);
d.FrictionCoefficient = friction;
d.Restitution = restitution;
}
}
private class TestRigidBody : RigidBodyContainer<Drawable>
{
public TestRigidBody()
{
Child = new Box { RelativeSizeAxes = Axes.Both };
}
}
private class TestRigidBodySimulation : RigidBodySimulation
{
protected override void LoadComplete()
{
base.LoadComplete();
foreach (var d in Children)
d.ApplyImpulse(new Vector2(RNG.NextSingle() - 0.5f, RNG.NextSingle() - 0.5f) * 100, d.Centre + new Vector2(RNG.NextSingle() - 0.5f, RNG.NextSingle() - 0.5f) * 100);
}
}
}
}

View File

@@ -1,117 +1,117 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.MathUtils;
using osu.Framework.Screens;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseScreen : TestCase
{
public TestCaseScreen()
{
Add(new TestScreen());
}
private class TestScreen : Screen
{
public int Sequence;
private Button popButton;
private const int transition_time = 500;
protected override void OnEntering(Screen last)
{
if (last != null)
{
//only show the pop button if we are entered form another screen.
popButton.Alpha = 1;
}
Content.MoveTo(new Vector2(0, -DrawSize.Y));
Content.MoveTo(Vector2.Zero, transition_time, Easing.OutQuint);
}
protected override bool OnExiting(Screen next)
{
Content.MoveTo(new Vector2(0, -DrawSize.Y), transition_time, Easing.OutQuint);
return base.OnExiting(next);
}
protected override void OnSuspending(Screen next)
{
Content.MoveTo(new Vector2(0, DrawSize.Y), transition_time, Easing.OutQuint);
}
protected override void OnResuming(Screen last)
{
Content.MoveTo(Vector2.Zero, transition_time, Easing.OutQuint);
}
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = new Color4(
Math.Max(0.5f, RNG.NextSingle()),
Math.Max(0.5f, RNG.NextSingle()),
Math.Max(0.5f, RNG.NextSingle()),
1),
},
new SpriteText
{
Text = $@"Mode {Sequence}",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
TextSize = 50,
},
popButton = new Button
{
Text = @"Pop",
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.1f),
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
BackgroundColour = Color4.Red,
Alpha = 0,
Action = Exit
},
new Button
{
Text = @"Push",
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.1f),
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
BackgroundColour = Color4.YellowGreen,
Action = delegate
{
Push(new TestScreen
{
Sequence = Sequence + 1,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
}
}
};
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.MathUtils;
using osu.Framework.Screens;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseScreen : TestCase
{
public TestCaseScreen()
{
Add(new TestScreen());
}
private class TestScreen : Screen
{
public int Sequence;
private Button popButton;
private const int transition_time = 500;
protected override void OnEntering(Screen last)
{
if (last != null)
{
//only show the pop button if we are entered form another screen.
popButton.Alpha = 1;
}
Content.MoveTo(new Vector2(0, -DrawSize.Y));
Content.MoveTo(Vector2.Zero, transition_time, Easing.OutQuint);
}
protected override bool OnExiting(Screen next)
{
Content.MoveTo(new Vector2(0, -DrawSize.Y), transition_time, Easing.OutQuint);
return base.OnExiting(next);
}
protected override void OnSuspending(Screen next)
{
Content.MoveTo(new Vector2(0, DrawSize.Y), transition_time, Easing.OutQuint);
}
protected override void OnResuming(Screen last)
{
Content.MoveTo(Vector2.Zero, transition_time, Easing.OutQuint);
}
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = new Color4(
Math.Max(0.5f, RNG.NextSingle()),
Math.Max(0.5f, RNG.NextSingle()),
Math.Max(0.5f, RNG.NextSingle()),
1),
},
new SpriteText
{
Text = $@"Mode {Sequence}",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
TextSize = 50,
},
popButton = new Button
{
Text = @"Pop",
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.1f),
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
BackgroundColour = Color4.Red,
Alpha = 0,
Action = Exit
},
new Button
{
Text = @"Push",
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.1f),
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
BackgroundColour = Color4.YellowGreen,
Action = delegate
{
Push(new TestScreen
{
Sequence = Sequence + 1,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
}
}
};
}
}
}
}

View File

@@ -1,131 +1,131 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
using osu.Framework.Threading;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseScrollableFlow : TestCase
{
private readonly ScheduledDelegate boxCreator;
private ScrollContainer scroll;
private FillFlowContainer flow;
private void createArea(Direction dir)
{
Axes scrollAxis = dir == Direction.Horizontal ? Axes.X : Axes.Y;
Children = new[]
{
scroll = new ScrollContainer(dir)
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
flow = new FillFlowContainer
{
LayoutDuration = 100,
LayoutEasing = Easing.Out,
Spacing = new Vector2(1, 1),
RelativeSizeAxes = Axes.Both & ~scrollAxis,
AutoSizeAxes = scrollAxis,
Padding = new MarginPadding(5)
}
},
},
};
}
private void createAreaBoth()
{
Children = new[]
{
new ScrollContainer(Direction.Horizontal)
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = 150 },
Children = new[]
{
scroll = new ScrollContainer
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Children = new[]
{
flow = new FillFlowContainer
{
LayoutDuration = 100,
LayoutEasing = Easing.Out,
Spacing = new Vector2(1, 1),
Size = new Vector2(1000, 0),
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(5)
}
}
}
},
},
};
scroll.ScrollContent.AutoSizeAxes = Axes.None;
scroll.ScrollContent.RelativeSizeAxes = Axes.None;
scroll.ScrollContent.AutoSizeAxes = Axes.Both;
}
public TestCaseScrollableFlow()
{
Direction scrollDir;
createArea(scrollDir = Direction.Vertical);
AddStep("Vertical", delegate { createArea(scrollDir = Direction.Vertical); });
AddStep("Horizontal", delegate { createArea(scrollDir = Direction.Horizontal); });
AddStep("Both", createAreaBoth);
AddStep("Dragger Anchor 1", delegate { scroll.ScrollbarAnchor = scrollDir == Direction.Vertical ? Anchor.TopRight : Anchor.BottomLeft; });
AddStep("Dragger Anchor 2", delegate { scroll.ScrollbarAnchor = Anchor.TopLeft; });
AddStep("Dragger Visible", delegate { scroll.ScrollbarVisible = !scroll.ScrollbarVisible; });
AddStep("Dragger Overlap", delegate { scroll.ScrollbarOverlapsContent = !scroll.ScrollbarOverlapsContent; });
boxCreator?.Cancel();
boxCreator = Scheduler.AddDelayed(delegate
{
if (Parent == null) return;
Box box;
Container container = new Container
{
Size = new Vector2(80, 80),
Children = new[]
{
box = new Box
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
}
}
};
flow.Add(container);
container.FadeInFromZero(1000);
double displayTime = RNG.Next(0, 20000);
box.Delay(displayTime).ScaleTo(0.5f, 4000).RotateTo((RNG.NextSingle() - 0.5f) * 90, 4000);
container.Delay(displayTime).FadeOut(4000).Expire();
}, 100, true);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
using osu.Framework.Threading;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseScrollableFlow : TestCase
{
private readonly ScheduledDelegate boxCreator;
private ScrollContainer scroll;
private FillFlowContainer flow;
private void createArea(Direction dir)
{
Axes scrollAxis = dir == Direction.Horizontal ? Axes.X : Axes.Y;
Children = new[]
{
scroll = new ScrollContainer(dir)
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
flow = new FillFlowContainer
{
LayoutDuration = 100,
LayoutEasing = Easing.Out,
Spacing = new Vector2(1, 1),
RelativeSizeAxes = Axes.Both & ~scrollAxis,
AutoSizeAxes = scrollAxis,
Padding = new MarginPadding(5)
}
},
},
};
}
private void createAreaBoth()
{
Children = new[]
{
new ScrollContainer(Direction.Horizontal)
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = 150 },
Children = new[]
{
scroll = new ScrollContainer
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Children = new[]
{
flow = new FillFlowContainer
{
LayoutDuration = 100,
LayoutEasing = Easing.Out,
Spacing = new Vector2(1, 1),
Size = new Vector2(1000, 0),
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(5)
}
}
}
},
},
};
scroll.ScrollContent.AutoSizeAxes = Axes.None;
scroll.ScrollContent.RelativeSizeAxes = Axes.None;
scroll.ScrollContent.AutoSizeAxes = Axes.Both;
}
public TestCaseScrollableFlow()
{
Direction scrollDir;
createArea(scrollDir = Direction.Vertical);
AddStep("Vertical", delegate { createArea(scrollDir = Direction.Vertical); });
AddStep("Horizontal", delegate { createArea(scrollDir = Direction.Horizontal); });
AddStep("Both", createAreaBoth);
AddStep("Dragger Anchor 1", delegate { scroll.ScrollbarAnchor = scrollDir == Direction.Vertical ? Anchor.TopRight : Anchor.BottomLeft; });
AddStep("Dragger Anchor 2", delegate { scroll.ScrollbarAnchor = Anchor.TopLeft; });
AddStep("Dragger Visible", delegate { scroll.ScrollbarVisible = !scroll.ScrollbarVisible; });
AddStep("Dragger Overlap", delegate { scroll.ScrollbarOverlapsContent = !scroll.ScrollbarOverlapsContent; });
boxCreator?.Cancel();
boxCreator = Scheduler.AddDelayed(delegate
{
if (Parent == null) return;
Box box;
Container container = new Container
{
Size = new Vector2(80, 80),
Children = new[]
{
box = new Box
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
}
}
};
flow.Add(container);
container.FadeInFromZero(1000);
double displayTime = RNG.Next(0, 20000);
box.Delay(displayTime).ScaleTo(0.5f, 4000).RotateTo((RNG.NextSingle() - 0.5f) * 90, 4000);
container.Delay(displayTime).FadeOut(4000).Expire();
}, 100, true);
}
}
}

View File

@@ -1,191 +1,191 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
public class TestCaseSearchContainer : TestCase
{
public TestCaseSearchContainer()
{
SearchContainer<HeaderContainer> search;
TextBox textBox;
Children = new Drawable[] {
textBox = new TextBox
{
Size = new Vector2(300, 40),
},
search = new SearchContainer<HeaderContainer>
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 40 },
Children = new[]
{
new HeaderContainer
{
AutoSizeAxes = Axes.Both,
Children = new[]
{
new HeaderContainer("Subsection 1")
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new SearchableText
{
Text = "test",
},
new SearchableText
{
Text = "TEST",
},
new SearchableText
{
Text = "123",
},
new SearchableText
{
Text = "444",
},
new FilterableFlowContainer
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Children = new []
{
new SpriteText
{
Text = "multi",
},
new SpriteText
{
Text = "piece",
},
new SpriteText
{
Text = "container",
},
}
},
new SearchableText
{
Text = "öüäéèêáàâ",
}
}
},
new HeaderContainer("Subsection 2")
{
AutoSizeAxes = Axes.Both,
Children = new[]
{
new SearchableText
{
Text = "?!()[]{}"
},
new SearchableText
{
Text = "@€$"
},
},
},
},
}
}
}
};
new Dictionary<string, int>
{
{ "test", 2 },
{ "sUbSeCtIoN 1", 6 },
{ "€", 1 },
{ "èê", 1 },
{ "321", 0 },
{ "mul pi", 1},
{ "header", 8 }
}.ToList().ForEach(term =>
{
AddStep("Search term: " + term.Key, () => search.SearchTerm = term.Key);
AddAssert("Visible end-children: " + term.Value, () => term.Value == search.Children.SelectMany(container => container.Children.Cast<Container>()).SelectMany(container => container.Children).Count(drawable => drawable.IsPresent));
});
textBox.Current.ValueChanged += newValue => search.SearchTerm = newValue;
}
private class HeaderContainer : Container, IHasFilterableChildren
{
public IEnumerable<string> FilterTerms => header.FilterTerms;
public bool MatchingFilter
{
set
{
if (value)
this.FadeIn();
else
this.FadeOut();
}
}
public IEnumerable<IFilterable> FilterableChildren => Children.OfType<IFilterable>();
protected override Container<Drawable> Content => flowContainer;
private readonly SearchableText header;
private readonly FillFlowContainer flowContainer;
public HeaderContainer(string headerText = "Header")
{
AddInternal(header = new SearchableText
{
Text = headerText,
});
AddInternal(flowContainer = new FillFlowContainer
{
Margin = new MarginPadding { Top = header.TextSize, Left = 30 },
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
});
}
}
private class FilterableFlowContainer : FillFlowContainer, IFilterable
{
public IEnumerable<string> FilterTerms => Children.OfType<IHasFilterTerms>().SelectMany(d => d.FilterTerms);
public bool MatchingFilter
{
set
{
if (value)
Show();
else
Hide();
}
}
}
private class SearchableText : SpriteText, IFilterable
{
public bool MatchingFilter
{
set
{
if (value)
Show();
else
Hide();
}
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
public class TestCaseSearchContainer : TestCase
{
public TestCaseSearchContainer()
{
SearchContainer<HeaderContainer> search;
TextBox textBox;
Children = new Drawable[] {
textBox = new TextBox
{
Size = new Vector2(300, 40),
},
search = new SearchContainer<HeaderContainer>
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 40 },
Children = new[]
{
new HeaderContainer
{
AutoSizeAxes = Axes.Both,
Children = new[]
{
new HeaderContainer("Subsection 1")
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new SearchableText
{
Text = "test",
},
new SearchableText
{
Text = "TEST",
},
new SearchableText
{
Text = "123",
},
new SearchableText
{
Text = "444",
},
new FilterableFlowContainer
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Children = new []
{
new SpriteText
{
Text = "multi",
},
new SpriteText
{
Text = "piece",
},
new SpriteText
{
Text = "container",
},
}
},
new SearchableText
{
Text = "öüäéèêáàâ",
}
}
},
new HeaderContainer("Subsection 2")
{
AutoSizeAxes = Axes.Both,
Children = new[]
{
new SearchableText
{
Text = "?!()[]{}"
},
new SearchableText
{
Text = "@€$"
},
},
},
},
}
}
}
};
new Dictionary<string, int>
{
{ "test", 2 },
{ "sUbSeCtIoN 1", 6 },
{ "€", 1 },
{ "èê", 1 },
{ "321", 0 },
{ "mul pi", 1},
{ "header", 8 }
}.ToList().ForEach(term =>
{
AddStep("Search term: " + term.Key, () => search.SearchTerm = term.Key);
AddAssert("Visible end-children: " + term.Value, () => term.Value == search.Children.SelectMany(container => container.Children.Cast<Container>()).SelectMany(container => container.Children).Count(drawable => drawable.IsPresent));
});
textBox.Current.ValueChanged += newValue => search.SearchTerm = newValue;
}
private class HeaderContainer : Container, IHasFilterableChildren
{
public IEnumerable<string> FilterTerms => header.FilterTerms;
public bool MatchingFilter
{
set
{
if (value)
this.FadeIn();
else
this.FadeOut();
}
}
public IEnumerable<IFilterable> FilterableChildren => Children.OfType<IFilterable>();
protected override Container<Drawable> Content => flowContainer;
private readonly SearchableText header;
private readonly FillFlowContainer flowContainer;
public HeaderContainer(string headerText = "Header")
{
AddInternal(header = new SearchableText
{
Text = headerText,
});
AddInternal(flowContainer = new FillFlowContainer
{
Margin = new MarginPadding { Top = header.TextSize, Left = 30 },
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
});
}
}
private class FilterableFlowContainer : FillFlowContainer, IFilterable
{
public IEnumerable<string> FilterTerms => Children.OfType<IHasFilterTerms>().SelectMany(d => d.FilterTerms);
public bool MatchingFilter
{
set
{
if (value)
Show();
else
Hide();
}
}
}
private class SearchableText : SpriteText, IFilterable
{
public bool MatchingFilter
{
set
{
if (value)
Show();
else
Hide();
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,68 +1,68 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Configuration;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseSliderbar : TestCase
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly BindableDouble sliderBarValue; //keep a reference to avoid GC of the bindable
private readonly SpriteText sliderbarText;
public TestCaseSliderbar()
{
sliderBarValue = new BindableDouble(8)
{
MinValue = -10,
MaxValue = 10
};
sliderBarValue.ValueChanged += sliderBarValueChanged;
sliderbarText = new SpriteText
{
Text = $"Selected value: {sliderBarValue.Value}",
Position = new Vector2(25, 0)
};
SliderBar<double> sliderBar = new BasicSliderBar<double>
{
Size = new Vector2(200, 10),
Position = new Vector2(25, 25),
Color = Color4.White,
SelectionColor = Color4.Pink,
KeyboardStep = 1
};
sliderBar.Current.BindTo(sliderBarValue);
Add(sliderBar);
Add(sliderbarText);
Add(sliderBar = new BasicSliderBar<double>
{
Size = new Vector2(200, 10),
RangePadding = 20,
Position = new Vector2(25, 45),
Color = Color4.White,
SelectionColor = Color4.Pink,
KeyboardStep = 1,
});
sliderBar.Current.BindTo(sliderBarValue);
AddSliderStep("Value", -10.0, 10.0, 0.0, v => sliderBarValue.Value = v);
}
private void sliderBarValueChanged(double newValue)
{
sliderbarText.Text = $"Selected value: {newValue:N}";
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Configuration;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseSliderbar : TestCase
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly BindableDouble sliderBarValue; //keep a reference to avoid GC of the bindable
private readonly SpriteText sliderbarText;
public TestCaseSliderbar()
{
sliderBarValue = new BindableDouble(8)
{
MinValue = -10,
MaxValue = 10
};
sliderBarValue.ValueChanged += sliderBarValueChanged;
sliderbarText = new SpriteText
{
Text = $"Selected value: {sliderBarValue.Value}",
Position = new Vector2(25, 0)
};
SliderBar<double> sliderBar = new BasicSliderBar<double>
{
Size = new Vector2(200, 10),
Position = new Vector2(25, 25),
Color = Color4.White,
SelectionColor = Color4.Pink,
KeyboardStep = 1
};
sliderBar.Current.BindTo(sliderBarValue);
Add(sliderBar);
Add(sliderbarText);
Add(sliderBar = new BasicSliderBar<double>
{
Size = new Vector2(200, 10),
RangePadding = 20,
Position = new Vector2(25, 45),
Color = Color4.White,
SelectionColor = Color4.Pink,
KeyboardStep = 1,
});
sliderBar.Current.BindTo(sliderBarValue);
AddSliderStep("Value", -10.0, 10.0, 0.0, v => sliderBarValue.Value = v);
}
private void sliderBarValueChanged(double newValue)
{
sliderbarText.Text = $"Selected value: {newValue:N}";
}
}
}

View File

@@ -1,57 +1,57 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseSmoothedEdges : GridTestCase
{
public TestCaseSmoothedEdges() : base(2, 2)
{
Vector2[] smoothnesses =
{
new Vector2(0, 0),
new Vector2(0, 2),
new Vector2(1, 1),
new Vector2(2, 2),
};
for (int i = 0; i < Rows * Cols; ++i)
{
Cell(i).AddRange(new Drawable[]
{
new SpriteText
{
Text = $"{nameof(Sprite.EdgeSmoothness)}={smoothnesses[i]}",
TextSize = 20,
},
boxes[i] = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(0.5f),
EdgeSmoothness = smoothnesses[i],
},
});
}
}
private readonly Box[] boxes = new Box[4];
protected override void Update()
{
base.Update();
foreach (Box box in boxes)
box.Rotation += 0.01f;
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseSmoothedEdges : GridTestCase
{
public TestCaseSmoothedEdges() : base(2, 2)
{
Vector2[] smoothnesses =
{
new Vector2(0, 0),
new Vector2(0, 2),
new Vector2(1, 1),
new Vector2(2, 2),
};
for (int i = 0; i < Rows * Cols; ++i)
{
Cell(i).AddRange(new Drawable[]
{
new SpriteText
{
Text = $"{nameof(Sprite.EdgeSmoothness)}={smoothnesses[i]}",
TextSize = 20,
},
boxes[i] = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(0.5f),
EdgeSmoothness = smoothnesses[i],
},
});
}
}
private readonly Box[] boxes = new Box[4];
protected override void Update()
{
base.Update();
foreach (Box box in boxes)
box.Rotation += 0.01f;
}
}
}

View File

@@ -1,63 +1,63 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
public class TestCaseSpriteText : TestCase
{
public TestCaseSpriteText()
{
FillFlowContainer flow;
Children = new Drawable[]
{
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
flow = new FillFlowContainer
{
Anchor = Anchor.TopLeft,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
}
}
}
};
flow.Add(new SpriteText
{
Text = @"the quick red fox jumps over the lazy brown dog"
});
flow.Add(new SpriteText
{
Text = @"THE QUICK RED FOX JUMPS OVER THE LAZY BROWN DOG"
});
flow.Add(new SpriteText
{
Text = @"0123456789!@#$%^&*()_-+-[]{}.,<>;'\"
});
for (int i = 1; i <= 200; i++)
{
SpriteText text = new SpriteText
{
Text = $@"Font testy at size {i}",
AllowMultiline = true,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
TextSize = i
};
flow.Add(text);
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
namespace osu.Framework.Tests.Visual
{
public class TestCaseSpriteText : TestCase
{
public TestCaseSpriteText()
{
FillFlowContainer flow;
Children = new Drawable[]
{
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
flow = new FillFlowContainer
{
Anchor = Anchor.TopLeft,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
}
}
}
};
flow.Add(new SpriteText
{
Text = @"the quick red fox jumps over the lazy brown dog"
});
flow.Add(new SpriteText
{
Text = @"THE QUICK RED FOX JUMPS OVER THE LAZY BROWN DOG"
});
flow.Add(new SpriteText
{
Text = @"0123456789!@#$%^&*()_-+-[]{}.,<>;'\"
});
for (int i = 1; i <= 200; i++)
{
SpriteText text = new SpriteText
{
Text = $@"Font testy at size {i}",
AllowMultiline = true,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
TextSize = i
};
flow.Add(text);
}
}
}
}

View File

@@ -1,179 +1,179 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseTabControl : TestCase
{
public TestCaseTabControl()
{
List<KeyValuePair<string, TestEnum>> items = new List<KeyValuePair<string, TestEnum>>();
foreach (var val in (TestEnum[])Enum.GetValues(typeof(TestEnum)))
items.Add(new KeyValuePair<string, TestEnum>(val.GetDescription(), val));
StyledTabControl simpleTabcontrol = new StyledTabControl
{
Position = new Vector2(200, 50),
Size = new Vector2(200, 30),
};
items.AsEnumerable().ForEach(item => simpleTabcontrol.AddItem(item.Value));
StyledTabControl pinnedAndAutoSort = new StyledTabControl
{
Position = new Vector2(500, 50),
Size = new Vector2(200, 30),
AutoSort = true
};
items.GetRange(0, 7).AsEnumerable().ForEach(item => pinnedAndAutoSort.AddItem(item.Value));
pinnedAndAutoSort.PinItem(TestEnum.Test5);
Add(simpleTabcontrol);
Add(pinnedAndAutoSort);
var nextTest = new Func<TestEnum>(() => items.AsEnumerable()
.Select(item => item.Value)
.FirstOrDefault(test => !pinnedAndAutoSort.Items.Contains(test)));
Stack<TestEnum> pinned = new Stack<TestEnum>();
AddStep("AddItem", () =>
{
var item = nextTest.Invoke();
if (!pinnedAndAutoSort.Items.Contains(item))
pinnedAndAutoSort.AddItem(item);
});
AddStep("RemoveItem", () =>
{
if (pinnedAndAutoSort.Items.Any())
{
pinnedAndAutoSort.RemoveItem(pinnedAndAutoSort.Items.First());
}
});
AddStep("PinItem", () =>
{
var item = nextTest.Invoke();
if (!pinnedAndAutoSort.Items.Contains(item))
{
pinned.Push(item);
pinnedAndAutoSort.AddItem(item);
pinnedAndAutoSort.PinItem(item);
}
});
AddStep("UnpinItem", () =>
{
if (pinned.Count > 0) pinnedAndAutoSort.UnpinItem(pinned.Pop());
});
}
private class StyledTabControl : TabControl<TestEnum>
{
protected override Dropdown<TestEnum> CreateDropdown() => new StyledDropdown();
protected override TabItem<TestEnum> CreateTabItem(TestEnum value) => new StyledTabItem(value);
}
private class StyledTabItem : TabItem<TestEnum>
{
private readonly SpriteText text;
public override bool IsRemovable => true;
public StyledTabItem(TestEnum value) : base(value)
{
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
text = new SpriteText
{
Margin = new MarginPadding(2),
Text = value.ToString(),
TextSize = 18
}
};
}
protected override void OnActivated() => text.Colour = Color4.MediumPurple;
protected override void OnDeactivated() => text.Colour = Color4.White;
}
private class StyledDropdown : Dropdown<TestEnum>
{
protected override DropdownMenu CreateMenu() => new StyledDropdownMenu();
protected override DropdownHeader CreateHeader() => new StyledDropdownHeader();
public StyledDropdown()
{
Menu.Anchor = Anchor.TopRight;
Menu.Origin = Anchor.TopRight;
Header.Anchor = Anchor.TopRight;
Header.Origin = Anchor.TopRight;
}
private class StyledDropdownMenu : DropdownMenu
{
public StyledDropdownMenu()
{
ScrollbarVisible = false;
CornerRadius = 4;
}
}
}
private class StyledDropdownHeader : DropdownHeader
{
protected internal override string Label { get; set; }
public StyledDropdownHeader()
{
Background.Hide(); // don't need a background
RelativeSizeAxes = Axes.None;
AutoSizeAxes = Axes.X;
Foreground.RelativeSizeAxes = Axes.None;
Foreground.AutoSizeAxes = Axes.Both;
Foreground.Children = new[]
{
new Box { Width = 20, Height = 20 }
};
}
}
private enum TestEnum
{
Test0,
Test1,
Test2,
Test3,
Test4,
Test5,
Test6,
Test7,
Test8,
Test9,
Test10,
Test11,
Test12
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseTabControl : TestCase
{
public TestCaseTabControl()
{
List<KeyValuePair<string, TestEnum>> items = new List<KeyValuePair<string, TestEnum>>();
foreach (var val in (TestEnum[])Enum.GetValues(typeof(TestEnum)))
items.Add(new KeyValuePair<string, TestEnum>(val.GetDescription(), val));
StyledTabControl simpleTabcontrol = new StyledTabControl
{
Position = new Vector2(200, 50),
Size = new Vector2(200, 30),
};
items.AsEnumerable().ForEach(item => simpleTabcontrol.AddItem(item.Value));
StyledTabControl pinnedAndAutoSort = new StyledTabControl
{
Position = new Vector2(500, 50),
Size = new Vector2(200, 30),
AutoSort = true
};
items.GetRange(0, 7).AsEnumerable().ForEach(item => pinnedAndAutoSort.AddItem(item.Value));
pinnedAndAutoSort.PinItem(TestEnum.Test5);
Add(simpleTabcontrol);
Add(pinnedAndAutoSort);
var nextTest = new Func<TestEnum>(() => items.AsEnumerable()
.Select(item => item.Value)
.FirstOrDefault(test => !pinnedAndAutoSort.Items.Contains(test)));
Stack<TestEnum> pinned = new Stack<TestEnum>();
AddStep("AddItem", () =>
{
var item = nextTest.Invoke();
if (!pinnedAndAutoSort.Items.Contains(item))
pinnedAndAutoSort.AddItem(item);
});
AddStep("RemoveItem", () =>
{
if (pinnedAndAutoSort.Items.Any())
{
pinnedAndAutoSort.RemoveItem(pinnedAndAutoSort.Items.First());
}
});
AddStep("PinItem", () =>
{
var item = nextTest.Invoke();
if (!pinnedAndAutoSort.Items.Contains(item))
{
pinned.Push(item);
pinnedAndAutoSort.AddItem(item);
pinnedAndAutoSort.PinItem(item);
}
});
AddStep("UnpinItem", () =>
{
if (pinned.Count > 0) pinnedAndAutoSort.UnpinItem(pinned.Pop());
});
}
private class StyledTabControl : TabControl<TestEnum>
{
protected override Dropdown<TestEnum> CreateDropdown() => new StyledDropdown();
protected override TabItem<TestEnum> CreateTabItem(TestEnum value) => new StyledTabItem(value);
}
private class StyledTabItem : TabItem<TestEnum>
{
private readonly SpriteText text;
public override bool IsRemovable => true;
public StyledTabItem(TestEnum value) : base(value)
{
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
text = new SpriteText
{
Margin = new MarginPadding(2),
Text = value.ToString(),
TextSize = 18
}
};
}
protected override void OnActivated() => text.Colour = Color4.MediumPurple;
protected override void OnDeactivated() => text.Colour = Color4.White;
}
private class StyledDropdown : Dropdown<TestEnum>
{
protected override DropdownMenu CreateMenu() => new StyledDropdownMenu();
protected override DropdownHeader CreateHeader() => new StyledDropdownHeader();
public StyledDropdown()
{
Menu.Anchor = Anchor.TopRight;
Menu.Origin = Anchor.TopRight;
Header.Anchor = Anchor.TopRight;
Header.Origin = Anchor.TopRight;
}
private class StyledDropdownMenu : DropdownMenu
{
public StyledDropdownMenu()
{
ScrollbarVisible = false;
CornerRadius = 4;
}
}
}
private class StyledDropdownHeader : DropdownHeader
{
protected internal override string Label { get; set; }
public StyledDropdownHeader()
{
Background.Hide(); // don't need a background
RelativeSizeAxes = Axes.None;
AutoSizeAxes = Axes.X;
Foreground.RelativeSizeAxes = Axes.None;
Foreground.AutoSizeAxes = Axes.Both;
Foreground.Children = new[]
{
new Box { Width = 20, Height = 20 }
};
}
}
private enum TestEnum
{
Test0,
Test1,
Test2,
Test3,
Test4,
Test5,
Test6,
Test7,
Test8,
Test9,
Test10,
Test11,
Test12
}
}
}

View File

@@ -1,143 +1,143 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
public class TestCaseTextBox : TestCase
{
public TestCaseTextBox()
{
FillFlowContainer textBoxes = new FillFlowContainer
{
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 50),
Padding = new MarginPadding
{
Top = 50,
},
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.9f, 1)
};
Add(textBoxes);
textBoxes.Add(new TextBox
{
Size = new Vector2(100, 16),
TabbableContentContainer = textBoxes
});
textBoxes.Add(new TextBox
{
Text = @"Limited length",
Size = new Vector2(200, 20),
LengthLimit = 20,
TabbableContentContainer = textBoxes
});
textBoxes.Add(new TextBox
{
Text = @"Box with some more text",
Size = new Vector2(500, 30),
TabbableContentContainer = textBoxes
});
textBoxes.Add(new TextBox
{
PlaceholderText = @"Placeholder text",
Size = new Vector2(500, 30),
TabbableContentContainer = textBoxes
});
textBoxes.Add(new TextBox
{
Text = @"prefilled placeholder",
PlaceholderText = @"Placeholder text",
Size = new Vector2(500, 30),
TabbableContentContainer = textBoxes
});
textBoxes.Add(new TextBox
{
Text = "Readonly textbox",
Size = new Vector2(500, 30),
ReadOnly = true,
TabbableContentContainer = textBoxes
});
FillFlowContainer otherTextBoxes = new FillFlowContainer
{
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 50),
Padding = new MarginPadding
{
Top = 50,
Left = 500
},
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f, 1)
};
otherTextBoxes.Add(new TextBox
{
PlaceholderText = @"Textbox in separate container",
Size = new Vector2(500, 30),
TabbableContentContainer = otherTextBoxes
});
otherTextBoxes.Add(new PasswordTextBox
{
PlaceholderText = @"Password textbox",
Text = "Secret ;)",
Size = new Vector2(500, 30),
TabbableContentContainer = otherTextBoxes
});
FillFlowContainer nestedTextBoxes = new FillFlowContainer
{
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 50),
Margin = new MarginPadding { Left = 50 },
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f, 1)
};
nestedTextBoxes.Add(new TextBox
{
PlaceholderText = @"Nested textbox 1",
Size = new Vector2(457, 30),
TabbableContentContainer = otherTextBoxes
});
nestedTextBoxes.Add(new TextBox
{
PlaceholderText = @"Nested textbox 2",
Size = new Vector2(457, 30),
TabbableContentContainer = otherTextBoxes
});
nestedTextBoxes.Add(new TextBox
{
PlaceholderText = @"Nested textbox 3",
Size = new Vector2(457, 30),
TabbableContentContainer = otherTextBoxes
});
otherTextBoxes.Add(nestedTextBoxes);
Add(otherTextBoxes);
//textBoxes.Add(tb = new PasswordTextBox(@"", 14, Vector2.Zero, 300));
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
namespace osu.Framework.Tests.Visual
{
public class TestCaseTextBox : TestCase
{
public TestCaseTextBox()
{
FillFlowContainer textBoxes = new FillFlowContainer
{
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 50),
Padding = new MarginPadding
{
Top = 50,
},
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.9f, 1)
};
Add(textBoxes);
textBoxes.Add(new TextBox
{
Size = new Vector2(100, 16),
TabbableContentContainer = textBoxes
});
textBoxes.Add(new TextBox
{
Text = @"Limited length",
Size = new Vector2(200, 20),
LengthLimit = 20,
TabbableContentContainer = textBoxes
});
textBoxes.Add(new TextBox
{
Text = @"Box with some more text",
Size = new Vector2(500, 30),
TabbableContentContainer = textBoxes
});
textBoxes.Add(new TextBox
{
PlaceholderText = @"Placeholder text",
Size = new Vector2(500, 30),
TabbableContentContainer = textBoxes
});
textBoxes.Add(new TextBox
{
Text = @"prefilled placeholder",
PlaceholderText = @"Placeholder text",
Size = new Vector2(500, 30),
TabbableContentContainer = textBoxes
});
textBoxes.Add(new TextBox
{
Text = "Readonly textbox",
Size = new Vector2(500, 30),
ReadOnly = true,
TabbableContentContainer = textBoxes
});
FillFlowContainer otherTextBoxes = new FillFlowContainer
{
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 50),
Padding = new MarginPadding
{
Top = 50,
Left = 500
},
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f, 1)
};
otherTextBoxes.Add(new TextBox
{
PlaceholderText = @"Textbox in separate container",
Size = new Vector2(500, 30),
TabbableContentContainer = otherTextBoxes
});
otherTextBoxes.Add(new PasswordTextBox
{
PlaceholderText = @"Password textbox",
Text = "Secret ;)",
Size = new Vector2(500, 30),
TabbableContentContainer = otherTextBoxes
});
FillFlowContainer nestedTextBoxes = new FillFlowContainer
{
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 50),
Margin = new MarginPadding { Left = 50 },
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f, 1)
};
nestedTextBoxes.Add(new TextBox
{
PlaceholderText = @"Nested textbox 1",
Size = new Vector2(457, 30),
TabbableContentContainer = otherTextBoxes
});
nestedTextBoxes.Add(new TextBox
{
PlaceholderText = @"Nested textbox 2",
Size = new Vector2(457, 30),
TabbableContentContainer = otherTextBoxes
});
nestedTextBoxes.Add(new TextBox
{
PlaceholderText = @"Nested textbox 3",
Size = new Vector2(457, 30),
TabbableContentContainer = otherTextBoxes
});
otherTextBoxes.Add(nestedTextBoxes);
Add(otherTextBoxes);
//textBoxes.Add(tb = new PasswordTextBox(@"", 14, Vector2.Zero, 300));
}
}
}

View File

@@ -1,212 +1,212 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("word-wrap and paragraphs")]
public class TestCaseTextFlow : TestCase
{
public TestCaseTextFlow()
{
FillFlowContainer flow;
Children = new Drawable[]
{
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
flow = new FillFlowContainer
{
Anchor = Anchor.TopLeft,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
}
}
}
};
FillFlowContainer paragraphContainer;
TextFlowContainer textFlowContainer;
flow.Add(paragraphContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Width = 0.5f,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
textFlowContainer = new TextFlowContainer
{
FirstLineIndent = 5,
ContentIndent = 10,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
}
}
});
textFlowContainer.AddText("the considerably swift vermilion reynard bounds above the slothful mahogany hound.", t => t.Colour = Color4.Yellow);
textFlowContainer.AddText("\nTHE ", t => t.Colour = Color4.Red);
textFlowContainer.AddText("CONSIDERABLY", t => t.Colour = Color4.Pink);
textFlowContainer.AddText(" SWIFT VERMILION REYNARD BOUNDS ABOVE THE SLOTHFUL MAHOGANY HOUND!!", t => t.Colour = Color4.Red);
textFlowContainer.AddText("\n\n0123456789!@#$%^&*()_-+-[]{}.,<>;'\\\\", t => t.Colour = Color4.Blue);
var textSize = 48f;
textFlowContainer.AddParagraph("Multiple Text Sizes", t =>
{
t.TextSize = textSize;
textSize -= 12f;
});
textFlowContainer.AddText("\nI'm a paragraph\nnewlines are cool", t => t.Colour = Color4.Beige);
textFlowContainer.AddText(" (and so are inline styles!)", t => t.Colour = Color4.Yellow);
textFlowContainer.AddParagraph("There's 2 line breaks\n\ninside this paragraph!", t => t.Colour = Color4.GreenYellow);
textFlowContainer.AddParagraph("Make\nTextFlowContainer\ngreat\nagain!", t => t.Colour = Color4.Red);
paragraphContainer.Add(new TextFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Text =
@"osu! is a freeware rhythm game developed by Dean ""peppy"" Herbert, originally for Microsoft Windows. The game has also been ported to macOS, iOS, Android, and Windows Phone.[1] Its game play is based on commercial titles including Osu! Tatakae! Ouendan, Elite Beat Agents, Taiko no Tatsujin, beatmania IIDX, O2Jam, and DJMax.
osu! is written in C# on the .NET Framework. On August 28, 2016, osu!'s source code was open-sourced under the MIT License. [2] [3] Dubbed as ""Lazer"", the project aims to make osu! available to more platforms and transparent. [4] The community includes over 9 million registered users, with a total of 6 billion ranked plays.[5]"
});
paragraphContainer.Add(new TestCaseCustomText
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Placeholders = new Drawable[]
{
new LineBaseBox
{
Colour = Color4.Purple,
LineBaseHeight = 25f,
Size = new Vector2(25, 25)
}.WithEffect(new OutlineEffect
{
Strength = 20f,
PadExtent = true,
BlurSigma = new Vector2(5f),
Colour = Color4.White
})
},
Text = "Test icons [RedBox] interleaved\n[GreenBox] with other [0] text, also [[0]] escaping stuff is possible."
});
paragraphContainer.Add(new Container
{
Size = new Vector2(300),
Children = new Drawable[]
{
new Box
{
Name = "Background",
RelativeSizeAxes = Axes.Both,
Alpha = 0.1f
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.TopLeft,
Text = "TopLeft"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.TopCentre,
Text = "TopCentre"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.TopRight,
Text = "TopRight"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.BottomLeft,
Text = "BottomLeft"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.BottomCentre,
Text = "BottomCentre"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.BottomRight,
Text = "BottomRight"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.CentreLeft,
Text = "CentreLeft"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.Centre,
Text = "Centre"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.CentreRight,
Text = "CentreRight"
}
}
});
AddStep(@"resize paragraph 1", () => { paragraphContainer.Width = 1f; });
AddStep(@"resize paragraph 2", () => { paragraphContainer.Width = 0.6f; });
AddStep(@"header inset", () => { textFlowContainer.FirstLineIndent += 2; });
AddStep(@"body inset", () => { textFlowContainer.ContentIndent += 4; });
AddToggleStep(@"Zero paragraph spacing", state => textFlowContainer.ParagraphSpacing = state ? 0 : 0.5f);
AddToggleStep(@"Non-zero line spacing", state => textFlowContainer.LineSpacing = state ? 1 : 0);
}
private class LineBaseBox : Box, IHasLineBaseHeight
{
public float LineBaseHeight { get; set; }
}
private class TestCaseCustomText : CustomizableTextContainer
{
public TestCaseCustomText()
{
AddIconFactory("RedBox", makeRedBox);
AddIconFactory("GreenBox", makeGreenBox);
}
private Drawable makeGreenBox() => new LineBaseBox
{
Colour = Color4.Green,
LineBaseHeight = 25f,
Size = new Vector2(25, 20)
};
private Drawable makeRedBox() => new LineBaseBox
{
Colour = Color4.Red,
LineBaseHeight = 10f,
Size = new Vector2(25, 25)
};
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
[System.ComponentModel.Description("word-wrap and paragraphs")]
public class TestCaseTextFlow : TestCase
{
public TestCaseTextFlow()
{
FillFlowContainer flow;
Children = new Drawable[]
{
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
flow = new FillFlowContainer
{
Anchor = Anchor.TopLeft,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
}
}
}
};
FillFlowContainer paragraphContainer;
TextFlowContainer textFlowContainer;
flow.Add(paragraphContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Width = 0.5f,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
textFlowContainer = new TextFlowContainer
{
FirstLineIndent = 5,
ContentIndent = 10,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
}
}
});
textFlowContainer.AddText("the considerably swift vermilion reynard bounds above the slothful mahogany hound.", t => t.Colour = Color4.Yellow);
textFlowContainer.AddText("\nTHE ", t => t.Colour = Color4.Red);
textFlowContainer.AddText("CONSIDERABLY", t => t.Colour = Color4.Pink);
textFlowContainer.AddText(" SWIFT VERMILION REYNARD BOUNDS ABOVE THE SLOTHFUL MAHOGANY HOUND!!", t => t.Colour = Color4.Red);
textFlowContainer.AddText("\n\n0123456789!@#$%^&*()_-+-[]{}.,<>;'\\\\", t => t.Colour = Color4.Blue);
var textSize = 48f;
textFlowContainer.AddParagraph("Multiple Text Sizes", t =>
{
t.TextSize = textSize;
textSize -= 12f;
});
textFlowContainer.AddText("\nI'm a paragraph\nnewlines are cool", t => t.Colour = Color4.Beige);
textFlowContainer.AddText(" (and so are inline styles!)", t => t.Colour = Color4.Yellow);
textFlowContainer.AddParagraph("There's 2 line breaks\n\ninside this paragraph!", t => t.Colour = Color4.GreenYellow);
textFlowContainer.AddParagraph("Make\nTextFlowContainer\ngreat\nagain!", t => t.Colour = Color4.Red);
paragraphContainer.Add(new TextFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Text =
@"osu! is a freeware rhythm game developed by Dean ""peppy"" Herbert, originally for Microsoft Windows. The game has also been ported to macOS, iOS, Android, and Windows Phone.[1] Its game play is based on commercial titles including Osu! Tatakae! Ouendan, Elite Beat Agents, Taiko no Tatsujin, beatmania IIDX, O2Jam, and DJMax.
osu! is written in C# on the .NET Framework. On August 28, 2016, osu!'s source code was open-sourced under the MIT License. [2] [3] Dubbed as ""Lazer"", the project aims to make osu! available to more platforms and transparent. [4] The community includes over 9 million registered users, with a total of 6 billion ranked plays.[5]"
});
paragraphContainer.Add(new TestCaseCustomText
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Placeholders = new Drawable[]
{
new LineBaseBox
{
Colour = Color4.Purple,
LineBaseHeight = 25f,
Size = new Vector2(25, 25)
}.WithEffect(new OutlineEffect
{
Strength = 20f,
PadExtent = true,
BlurSigma = new Vector2(5f),
Colour = Color4.White
})
},
Text = "Test icons [RedBox] interleaved\n[GreenBox] with other [0] text, also [[0]] escaping stuff is possible."
});
paragraphContainer.Add(new Container
{
Size = new Vector2(300),
Children = new Drawable[]
{
new Box
{
Name = "Background",
RelativeSizeAxes = Axes.Both,
Alpha = 0.1f
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.TopLeft,
Text = "TopLeft"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.TopCentre,
Text = "TopCentre"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.TopRight,
Text = "TopRight"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.BottomLeft,
Text = "BottomLeft"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.BottomCentre,
Text = "BottomCentre"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.BottomRight,
Text = "BottomRight"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.CentreLeft,
Text = "CentreLeft"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.Centre,
Text = "Centre"
},
new TextFlowContainer
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.CentreRight,
Text = "CentreRight"
}
}
});
AddStep(@"resize paragraph 1", () => { paragraphContainer.Width = 1f; });
AddStep(@"resize paragraph 2", () => { paragraphContainer.Width = 0.6f; });
AddStep(@"header inset", () => { textFlowContainer.FirstLineIndent += 2; });
AddStep(@"body inset", () => { textFlowContainer.ContentIndent += 4; });
AddToggleStep(@"Zero paragraph spacing", state => textFlowContainer.ParagraphSpacing = state ? 0 : 0.5f);
AddToggleStep(@"Non-zero line spacing", state => textFlowContainer.LineSpacing = state ? 1 : 0);
}
private class LineBaseBox : Box, IHasLineBaseHeight
{
public float LineBaseHeight { get; set; }
}
private class TestCaseCustomText : CustomizableTextContainer
{
public TestCaseCustomText()
{
AddIconFactory("RedBox", makeRedBox);
AddIconFactory("GreenBox", makeGreenBox);
}
private Drawable makeGreenBox() => new LineBaseBox
{
Colour = Color4.Green,
LineBaseHeight = 25f,
Size = new Vector2(25, 20)
};
private Drawable makeRedBox() => new LineBaseBox
{
Colour = Color4.Red,
LineBaseHeight = 10f,
Size = new Vector2(25, 25)
};
}
}
}

View File

@@ -1,212 +1,212 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseTooltip : TestCase
{
private readonly Container testContainer;
public TestCaseTooltip()
{
Add(testContainer = new Container
{
RelativeSizeAxes = Axes.Both,
});
AddToggleStep("Cursor-less tooltip", generateTest);
generateTest(false);
}
private TooltipBox makeBox(Anchor anchor)
{
return new TooltipBox
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.2f),
Anchor = anchor,
Origin = anchor,
Colour = Color4.Blue,
TooltipText = $"{anchor}",
};
}
private void generateTest(bool cursorlessTooltip)
{
testContainer.Clear();
CursorContainer cursor = null;
if (!cursorlessTooltip)
{
cursor = new RectangleCursorContainer();
testContainer.Add(cursor);
}
TooltipContainer ttc;
testContainer.Add(ttc = new TooltipContainer(cursor)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Children = new[]
{
new TooltipBox
{
TooltipText = "Outer Tooltip",
Colour = Color4.CornflowerBlue,
Size = new Vector2(300, 300),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
new TooltipBox
{
TooltipText = "Inner Tooltip",
Size = new Vector2(150, 150),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
}
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
new TooltipSpriteText("this text has a tooltip!"),
new TooltipSpriteText("this one too!"),
new CustomTooltipSpriteText("this text has an empty tooltip!", string.Empty),
new CustomTooltipSpriteText("this text has a nulled tooltip!", null),
new TooltipTextbox
{
Text = "with real time updates!",
Size = new Vector2(400, 30),
},
new TooltipContainer
{
AutoSizeAxes = Axes.Both,
Child = new TooltipSpriteText("Nested tooltip; uses no cursor in all cases!"),
},
new TooltipTooltipContainer("This tooltip container has a tooltip itself!")
{
AutoSizeAxes = Axes.Both,
Child = new Container
{
AutoSizeAxes = Axes.Both,
Child = new TooltipSpriteText("Nested tooltip; uses no cursor in all cases; parent TooltipContainer has a tooltip"),
}
},
new Container
{
Child = new FillFlowContainer
{
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 8),
Children = new[]
{
new Container
{
Child = new Container
{
Child = new TooltipSpriteText("Tooltip within containers with zero size; i.e. parent is never hovered."),
}
},
new Container
{
Child = new TooltipSpriteText("Other tooltip within containers with zero size; different nesting; overlap."),
}
}
}
}
},
}
}
});
ttc.Add(makeBox(Anchor.BottomLeft));
ttc.Add(makeBox(Anchor.TopRight));
ttc.Add(makeBox(Anchor.BottomRight));
}
private class CustomTooltipSpriteText : Container, IHasTooltip
{
private readonly string tooltipText;
public string TooltipText => tooltipText;
public CustomTooltipSpriteText(string displayedText, string tooltipText)
{
this.tooltipText = tooltipText;
AutoSizeAxes = Axes.Both;
Children = new[]
{
new SpriteText
{
Text = displayedText,
}
};
}
}
private class TooltipSpriteText : CustomTooltipSpriteText
{
public TooltipSpriteText(string tooltipText)
: base(tooltipText, tooltipText)
{
}
}
private class TooltipTooltipContainer : TooltipContainer, IHasTooltip
{
public string TooltipText { get; set; }
public TooltipTooltipContainer(string tooltipText)
{
TooltipText = tooltipText;
}
}
private class TooltipTextbox : TextBox, IHasTooltip
{
public string TooltipText => Text;
}
private class TooltipBox : Box, IHasTooltip
{
public string TooltipText { get; set; }
public override bool HandleKeyboardInput => true;
public override bool HandleMouseInput => true;
}
private class RectangleCursorContainer : CursorContainer
{
protected override Drawable CreateCursor() => new RectangleCursor();
private class RectangleCursor : Box
{
public RectangleCursor()
{
Size = new Vector2(20, 40);
}
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseTooltip : TestCase
{
private readonly Container testContainer;
public TestCaseTooltip()
{
Add(testContainer = new Container
{
RelativeSizeAxes = Axes.Both,
});
AddToggleStep("Cursor-less tooltip", generateTest);
generateTest(false);
}
private TooltipBox makeBox(Anchor anchor)
{
return new TooltipBox
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.2f),
Anchor = anchor,
Origin = anchor,
Colour = Color4.Blue,
TooltipText = $"{anchor}",
};
}
private void generateTest(bool cursorlessTooltip)
{
testContainer.Clear();
CursorContainer cursor = null;
if (!cursorlessTooltip)
{
cursor = new RectangleCursorContainer();
testContainer.Add(cursor);
}
TooltipContainer ttc;
testContainer.Add(ttc = new TooltipContainer(cursor)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Children = new[]
{
new TooltipBox
{
TooltipText = "Outer Tooltip",
Colour = Color4.CornflowerBlue,
Size = new Vector2(300, 300),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
new TooltipBox
{
TooltipText = "Inner Tooltip",
Size = new Vector2(150, 150),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
}
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
new TooltipSpriteText("this text has a tooltip!"),
new TooltipSpriteText("this one too!"),
new CustomTooltipSpriteText("this text has an empty tooltip!", string.Empty),
new CustomTooltipSpriteText("this text has a nulled tooltip!", null),
new TooltipTextbox
{
Text = "with real time updates!",
Size = new Vector2(400, 30),
},
new TooltipContainer
{
AutoSizeAxes = Axes.Both,
Child = new TooltipSpriteText("Nested tooltip; uses no cursor in all cases!"),
},
new TooltipTooltipContainer("This tooltip container has a tooltip itself!")
{
AutoSizeAxes = Axes.Both,
Child = new Container
{
AutoSizeAxes = Axes.Both,
Child = new TooltipSpriteText("Nested tooltip; uses no cursor in all cases; parent TooltipContainer has a tooltip"),
}
},
new Container
{
Child = new FillFlowContainer
{
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 8),
Children = new[]
{
new Container
{
Child = new Container
{
Child = new TooltipSpriteText("Tooltip within containers with zero size; i.e. parent is never hovered."),
}
},
new Container
{
Child = new TooltipSpriteText("Other tooltip within containers with zero size; different nesting; overlap."),
}
}
}
}
},
}
}
});
ttc.Add(makeBox(Anchor.BottomLeft));
ttc.Add(makeBox(Anchor.TopRight));
ttc.Add(makeBox(Anchor.BottomRight));
}
private class CustomTooltipSpriteText : Container, IHasTooltip
{
private readonly string tooltipText;
public string TooltipText => tooltipText;
public CustomTooltipSpriteText(string displayedText, string tooltipText)
{
this.tooltipText = tooltipText;
AutoSizeAxes = Axes.Both;
Children = new[]
{
new SpriteText
{
Text = displayedText,
}
};
}
}
private class TooltipSpriteText : CustomTooltipSpriteText
{
public TooltipSpriteText(string tooltipText)
: base(tooltipText, tooltipText)
{
}
}
private class TooltipTooltipContainer : TooltipContainer, IHasTooltip
{
public string TooltipText { get; set; }
public TooltipTooltipContainer(string tooltipText)
{
TooltipText = tooltipText;
}
}
private class TooltipTextbox : TextBox, IHasTooltip
{
public string TooltipText => Text;
}
private class TooltipBox : Box, IHasTooltip
{
public string TooltipText { get; set; }
public override bool HandleKeyboardInput => true;
public override bool HandleMouseInput => true;
}
private class RectangleCursorContainer : CursorContainer
{
protected override Drawable CreateCursor() => new RectangleCursor();
private class RectangleCursor : Box
{
public RectangleCursor()
{
Size = new Vector2(20, 40);
}
}
}
}
}

View File

@@ -1,385 +1,385 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Testing;
using osu.Framework.Timing;
namespace osu.Framework.Tests.Visual
{
public class TestCaseTransformRewinding : TestCase
{
private const double interval = 250;
private const int interval_count = 4;
private static double intervalAt(int sequence) => interval * sequence;
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("Basic scale", () => boxTest(box =>
{
box.Scale = Vector2.One;
box.ScaleTo(0, interval * 4);
}));
AddStep("Scale sequence", () => boxTest(box =>
{
box.Scale = Vector2.One;
box.ScaleTo(0.75f, interval).Then()
.ScaleTo(0.5f, interval).Then()
.ScaleTo(0.25f, interval).Then()
.ScaleTo(0, interval);
}));
AddStep("Basic movement", () => boxTest(box =>
{
box.Scale = new Vector2(0.25f);
box.Anchor = Anchor.TopLeft;
box.Origin = Anchor.TopLeft;
box.MoveTo(new Vector2(0.75f, 0), interval).Then()
.MoveTo(new Vector2(0.75f, 0.75f), interval).Then()
.MoveTo(new Vector2(0, 0.75f), interval).Then()
.MoveTo(new Vector2(0), interval);
}));
AddStep("Move sequence", () => boxTest(box =>
{
box.Scale = new Vector2(0.25f);
box.Anchor = Anchor.TopLeft;
box.Origin = Anchor.TopLeft;
box.ScaleTo(0.5f, interval).MoveTo(new Vector2(0.5f), interval)
.Then()
.ScaleTo(0.1f, interval).MoveTo(new Vector2(0, 0.75f), interval)
.Then()
.ScaleTo(1f, interval).MoveTo(new Vector2(0, 0), interval)
.Then()
.FadeTo(0, interval);
}));
AddStep("Same type in type", () => boxTest(box =>
{
box.ScaleTo(0.5f, interval * 4);
box.Delay(interval * 2).ScaleTo(1, interval);
}));
AddStep("Same type partial overlap", () => boxTest(box =>
{
box.ScaleTo(0.5f, interval * 2);
box.Delay(interval).ScaleTo(1, interval * 2);
}));
AddStep("Start in middle of sequence", () => boxTest(box =>
{
box.Alpha = 0;
box.Delay(interval * 2).FadeInFromZero(interval);
box.ScaleTo(0.9f, interval * 4);
}, 750));
AddStep("Loop sequence", () => boxTest(box => { box.RotateTo(0).RotateTo(90, interval).Loop(); }));
AddStep("Start in middle of loop sequence", () => boxTest(box => { box.RotateTo(0).RotateTo(90, interval).Loop(); }, 750));
}
private Box box;
private void boxTest(Action<Box> action, int startTime = 0)
{
Clear();
Add(new AnimationContainer(startTime)
{
Child = box = new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Both,
Scale = new Vector2(0.25f),
},
ExaminableDrawable = box,
});
action(box);
}
private class AnimationContainer : Container
{
public override bool RemoveCompletedTransforms => false;
protected override Container<Drawable> Content => content;
private readonly Container content;
private readonly SpriteText minTimeText;
private readonly SpriteText currentTimeText;
private readonly SpriteText maxTimeText;
private readonly Tick seekingTick;
private readonly WrappingTimeContainer wrapping;
public Box ExaminableDrawable;
private readonly FlowContainer<DrawableTransform> transforms;
public AnimationContainer(int startTime = 0)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
InternalChild = wrapping = new WrappingTimeContainer(startTime)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Container
{
FillMode = FillMode.Fit,
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(0.6f),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.DarkGray,
},
content = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
},
}
},
transforms = new FillFlowContainer<DrawableTransform>
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Spacing = Vector2.One,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0.2f,
},
new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f, 0.1f),
Children = new Drawable[]
{
minTimeText = new SpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
},
currentTimeText = new SpriteText
{
RelativePositionAxes = Axes.X,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomCentre,
Y = -10,
},
maxTimeText = new SpriteText
{
Anchor = Anchor.BottomRight,
Origin = Anchor.TopRight,
},
seekingTick = new Tick(0, false),
new Tick(0),
new Tick(1),
new Tick(2),
new Tick(3),
new Tick(4),
}
}
}
};
}
private int displayedTransformsCount;
protected override void Update()
{
base.Update();
double time = wrapping.Time.Current;
minTimeText.Text = wrapping.MinTime.ToString("n0");
currentTimeText.Text = time.ToString("n0");
seekingTick.X = currentTimeText.X = (float)(time / (wrapping.MaxTime - wrapping.MinTime));
maxTimeText.Text = wrapping.MaxTime.ToString("n0");
maxTimeText.Colour = time > wrapping.MaxTime ? Color4.Gray : (wrapping.Time.Elapsed > 0 ? Color4.Blue : Color4.Red);
minTimeText.Colour = time < wrapping.MinTime ? Color4.Gray : (content.Time.Elapsed > 0 ? Color4.Blue : Color4.Red);
if (ExaminableDrawable.Transforms.Count != displayedTransformsCount)
{
transforms.Clear();
foreach (var t in ExaminableDrawable.Transforms)
transforms.Add(new DrawableTransform(t));
displayedTransformsCount = ExaminableDrawable.Transforms.Count;
}
}
private class DrawableTransform : CompositeDrawable
{
private readonly Transform transform;
private readonly Box applied;
private readonly Box appliedToEnd;
private readonly SpriteText text;
private const float height = 15;
public DrawableTransform(Transform transform)
{
this.transform = transform;
RelativeSizeAxes = Axes.X;
Height = height;
InternalChildren = new Drawable[]
{
applied = new Box { Size = new Vector2(height) },
appliedToEnd = new Box { X = height + 2, Size = new Vector2(height) },
text = new SpriteText { X = (height + 2) * 2, TextSize = height },
};
}
protected override void Update()
{
base.Update();
applied.Colour = transform.Applied ? Color4.Green : Color4.Red;
appliedToEnd.Colour = transform.AppliedToEnd ? Color4.Green : Color4.Red;
text.Text = transform.ToString();
}
}
private class Tick : Box
{
private readonly int tick;
private readonly bool colouring;
public Tick(int tick, bool colouring = true)
{
this.tick = tick;
this.colouring = colouring;
Anchor = Anchor.BottomLeft;
Origin = Anchor.BottomCentre;
Size = new Vector2(1, 10);
Colour = Color4.White;
RelativePositionAxes = Axes.X;
X = (float)tick / interval_count;
}
protected override void Update()
{
base.Update();
if (colouring)
Colour = Time.Current > tick * interval ? Color4.Yellow : Color4.White;
}
}
}
private class WrappingTimeContainer : Container
{
// Padding, in milliseconds, at each end of maxima of the clock time
private const double time_padding = 50;
public double MinTime => clock.MinTime + time_padding;
public double MaxTime => clock.MaxTime - time_padding;
private readonly ReversibleClock clock;
public WrappingTimeContainer(double startTime)
{
clock = new ReversibleClock(startTime);
}
[BackgroundDependencyLoader]
private void load()
{
// Replace the game clock, but keep it as a reference
clock.SetSource(Clock);
Clock = clock;
}
protected override void LoadComplete()
{
base.LoadComplete();
clock.MinTime = -time_padding;
clock.MaxTime = intervalAt(interval_count) + time_padding;
}
private class ReversibleClock : IFrameBasedClock
{
private readonly double startTime;
public double MinTime;
public double MaxTime = 1000;
private IFrameBasedClock trackingClock;
private bool reversed;
public ReversibleClock(double startTime)
{
this.startTime = startTime;
}
public void SetSource(IFrameBasedClock trackingClock)
{
this.trackingClock = new FramedOffsetClock(trackingClock) { Offset = -trackingClock.CurrentTime + startTime };
}
public double CurrentTime { get; private set; }
public double Rate => trackingClock.Rate;
public bool IsRunning => trackingClock.IsRunning;
public double ElapsedFrameTime => (reversed ? -1 : 1) * trackingClock.ElapsedFrameTime;
public double AverageFrameTime => trackingClock.AverageFrameTime;
public double FramesPerSecond => trackingClock.FramesPerSecond;
public FrameTimeInfo TimeInfo => new FrameTimeInfo { Current = CurrentTime, Elapsed = ElapsedFrameTime };
public void ProcessFrame()
{
trackingClock.ProcessFrame();
// There are two iterations, when iteration % 2 == 0 : not reversed
int iteration = (int)(trackingClock.CurrentTime / (MaxTime - MinTime));
reversed = iteration % 2 == 1;
double iterationTime = trackingClock.CurrentTime % (MaxTime - MinTime);
if (reversed)
CurrentTime = MaxTime - iterationTime;
else
CurrentTime = MinTime + iterationTime;
}
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Testing;
using osu.Framework.Timing;
namespace osu.Framework.Tests.Visual
{
public class TestCaseTransformRewinding : TestCase
{
private const double interval = 250;
private const int interval_count = 4;
private static double intervalAt(int sequence) => interval * sequence;
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("Basic scale", () => boxTest(box =>
{
box.Scale = Vector2.One;
box.ScaleTo(0, interval * 4);
}));
AddStep("Scale sequence", () => boxTest(box =>
{
box.Scale = Vector2.One;
box.ScaleTo(0.75f, interval).Then()
.ScaleTo(0.5f, interval).Then()
.ScaleTo(0.25f, interval).Then()
.ScaleTo(0, interval);
}));
AddStep("Basic movement", () => boxTest(box =>
{
box.Scale = new Vector2(0.25f);
box.Anchor = Anchor.TopLeft;
box.Origin = Anchor.TopLeft;
box.MoveTo(new Vector2(0.75f, 0), interval).Then()
.MoveTo(new Vector2(0.75f, 0.75f), interval).Then()
.MoveTo(new Vector2(0, 0.75f), interval).Then()
.MoveTo(new Vector2(0), interval);
}));
AddStep("Move sequence", () => boxTest(box =>
{
box.Scale = new Vector2(0.25f);
box.Anchor = Anchor.TopLeft;
box.Origin = Anchor.TopLeft;
box.ScaleTo(0.5f, interval).MoveTo(new Vector2(0.5f), interval)
.Then()
.ScaleTo(0.1f, interval).MoveTo(new Vector2(0, 0.75f), interval)
.Then()
.ScaleTo(1f, interval).MoveTo(new Vector2(0, 0), interval)
.Then()
.FadeTo(0, interval);
}));
AddStep("Same type in type", () => boxTest(box =>
{
box.ScaleTo(0.5f, interval * 4);
box.Delay(interval * 2).ScaleTo(1, interval);
}));
AddStep("Same type partial overlap", () => boxTest(box =>
{
box.ScaleTo(0.5f, interval * 2);
box.Delay(interval).ScaleTo(1, interval * 2);
}));
AddStep("Start in middle of sequence", () => boxTest(box =>
{
box.Alpha = 0;
box.Delay(interval * 2).FadeInFromZero(interval);
box.ScaleTo(0.9f, interval * 4);
}, 750));
AddStep("Loop sequence", () => boxTest(box => { box.RotateTo(0).RotateTo(90, interval).Loop(); }));
AddStep("Start in middle of loop sequence", () => boxTest(box => { box.RotateTo(0).RotateTo(90, interval).Loop(); }, 750));
}
private Box box;
private void boxTest(Action<Box> action, int startTime = 0)
{
Clear();
Add(new AnimationContainer(startTime)
{
Child = box = new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Both,
Scale = new Vector2(0.25f),
},
ExaminableDrawable = box,
});
action(box);
}
private class AnimationContainer : Container
{
public override bool RemoveCompletedTransforms => false;
protected override Container<Drawable> Content => content;
private readonly Container content;
private readonly SpriteText minTimeText;
private readonly SpriteText currentTimeText;
private readonly SpriteText maxTimeText;
private readonly Tick seekingTick;
private readonly WrappingTimeContainer wrapping;
public Box ExaminableDrawable;
private readonly FlowContainer<DrawableTransform> transforms;
public AnimationContainer(int startTime = 0)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
InternalChild = wrapping = new WrappingTimeContainer(startTime)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Container
{
FillMode = FillMode.Fit,
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(0.6f),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.DarkGray,
},
content = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
},
}
},
transforms = new FillFlowContainer<DrawableTransform>
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Spacing = Vector2.One,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0.2f,
},
new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f, 0.1f),
Children = new Drawable[]
{
minTimeText = new SpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
},
currentTimeText = new SpriteText
{
RelativePositionAxes = Axes.X,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomCentre,
Y = -10,
},
maxTimeText = new SpriteText
{
Anchor = Anchor.BottomRight,
Origin = Anchor.TopRight,
},
seekingTick = new Tick(0, false),
new Tick(0),
new Tick(1),
new Tick(2),
new Tick(3),
new Tick(4),
}
}
}
};
}
private int displayedTransformsCount;
protected override void Update()
{
base.Update();
double time = wrapping.Time.Current;
minTimeText.Text = wrapping.MinTime.ToString("n0");
currentTimeText.Text = time.ToString("n0");
seekingTick.X = currentTimeText.X = (float)(time / (wrapping.MaxTime - wrapping.MinTime));
maxTimeText.Text = wrapping.MaxTime.ToString("n0");
maxTimeText.Colour = time > wrapping.MaxTime ? Color4.Gray : (wrapping.Time.Elapsed > 0 ? Color4.Blue : Color4.Red);
minTimeText.Colour = time < wrapping.MinTime ? Color4.Gray : (content.Time.Elapsed > 0 ? Color4.Blue : Color4.Red);
if (ExaminableDrawable.Transforms.Count != displayedTransformsCount)
{
transforms.Clear();
foreach (var t in ExaminableDrawable.Transforms)
transforms.Add(new DrawableTransform(t));
displayedTransformsCount = ExaminableDrawable.Transforms.Count;
}
}
private class DrawableTransform : CompositeDrawable
{
private readonly Transform transform;
private readonly Box applied;
private readonly Box appliedToEnd;
private readonly SpriteText text;
private const float height = 15;
public DrawableTransform(Transform transform)
{
this.transform = transform;
RelativeSizeAxes = Axes.X;
Height = height;
InternalChildren = new Drawable[]
{
applied = new Box { Size = new Vector2(height) },
appliedToEnd = new Box { X = height + 2, Size = new Vector2(height) },
text = new SpriteText { X = (height + 2) * 2, TextSize = height },
};
}
protected override void Update()
{
base.Update();
applied.Colour = transform.Applied ? Color4.Green : Color4.Red;
appliedToEnd.Colour = transform.AppliedToEnd ? Color4.Green : Color4.Red;
text.Text = transform.ToString();
}
}
private class Tick : Box
{
private readonly int tick;
private readonly bool colouring;
public Tick(int tick, bool colouring = true)
{
this.tick = tick;
this.colouring = colouring;
Anchor = Anchor.BottomLeft;
Origin = Anchor.BottomCentre;
Size = new Vector2(1, 10);
Colour = Color4.White;
RelativePositionAxes = Axes.X;
X = (float)tick / interval_count;
}
protected override void Update()
{
base.Update();
if (colouring)
Colour = Time.Current > tick * interval ? Color4.Yellow : Color4.White;
}
}
}
private class WrappingTimeContainer : Container
{
// Padding, in milliseconds, at each end of maxima of the clock time
private const double time_padding = 50;
public double MinTime => clock.MinTime + time_padding;
public double MaxTime => clock.MaxTime - time_padding;
private readonly ReversibleClock clock;
public WrappingTimeContainer(double startTime)
{
clock = new ReversibleClock(startTime);
}
[BackgroundDependencyLoader]
private void load()
{
// Replace the game clock, but keep it as a reference
clock.SetSource(Clock);
Clock = clock;
}
protected override void LoadComplete()
{
base.LoadComplete();
clock.MinTime = -time_padding;
clock.MaxTime = intervalAt(interval_count) + time_padding;
}
private class ReversibleClock : IFrameBasedClock
{
private readonly double startTime;
public double MinTime;
public double MaxTime = 1000;
private IFrameBasedClock trackingClock;
private bool reversed;
public ReversibleClock(double startTime)
{
this.startTime = startTime;
}
public void SetSource(IFrameBasedClock trackingClock)
{
this.trackingClock = new FramedOffsetClock(trackingClock) { Offset = -trackingClock.CurrentTime + startTime };
}
public double CurrentTime { get; private set; }
public double Rate => trackingClock.Rate;
public bool IsRunning => trackingClock.IsRunning;
public double ElapsedFrameTime => (reversed ? -1 : 1) * trackingClock.ElapsedFrameTime;
public double AverageFrameTime => trackingClock.AverageFrameTime;
public double FramesPerSecond => trackingClock.FramesPerSecond;
public FrameTimeInfo TimeInfo => new FrameTimeInfo { Current = CurrentTime, Elapsed = ElapsedFrameTime };
public void ProcessFrame()
{
trackingClock.ProcessFrame();
// There are two iterations, when iteration % 2 == 0 : not reversed
int iteration = (int)(trackingClock.CurrentTime / (MaxTime - MinTime));
reversed = iteration % 2 == 1;
double iterationTime = trackingClock.CurrentTime % (MaxTime - MinTime);
if (reversed)
CurrentTime = MaxTime - iterationTime;
else
CurrentTime = MinTime + iterationTime;
}
}
}
}
}

View File

@@ -1,178 +1,178 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseTransformSequence : GridTestCase
{
private readonly Container[] boxes;
public TestCaseTransformSequence()
: base(3, 3)
{
boxes = new Container[Rows * Cols];
}
protected override void LoadComplete()
{
base.LoadComplete();
testFinish();
testClear();
}
private void testFinish()
{
AddStep("Animate", delegate
{
setup();
animate();
});
AddStep($"{nameof(FinishTransforms)}", delegate
{
foreach (var box in boxes)
box.FinishTransforms();
});
AddAssert("finalize triggered", () => finalizeTriggered);
}
private void testClear()
{
AddStep("Animate", delegate
{
setup();
animate();
});
AddStep($"{nameof(ClearTransforms)}", delegate
{
foreach (var box in boxes)
box.ClearTransforms();
});
AddAssert("finalize triggered", () => finalizeTriggered);
}
private void setup()
{
finalizeTriggered = false;
string[] labels =
{
"Spin after 2 seconds",
"Loop(1 sec pause; 1 sec rotate)",
"Complex transform 1 (should end in sync with CT2)",
"Complex transform 2 (should end in sync with CT1)",
$"Red on {nameof(TransformSequence<Container>)}.{nameof(TransformSequence<Container>.OnAbort)}",
$"Red on {nameof(TransformSequence<Container>)}.{nameof(TransformSequence<Container>.Finally)}",
"Red after instant transform",
"Red after instant transform 1 sec in the past",
"Red after 1 sec transform 1 sec in the past",
};
for (int i = 0; i < Rows * Cols; ++i)
{
Cell(i).Children = new Drawable[]
{
new SpriteText
{
Text = labels[i],
TextSize = 20,
},
boxes[i] = new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.25f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 20,
Colour = Color4.Blue,
},
Child = new Box
{
RelativeSizeAxes = Axes.Both,
}
}
};
}
}
private bool finalizeTriggered;
private void animate()
{
boxes[0].Delay(500).Then(500).Then(500).Then(
b => b.Delay(500).Spin(1000, RotationDirection.CounterClockwise)
);
boxes[1].Delay(1000).Loop(1000, 10, b => b.RotateTo(0).RotateTo(340, 1000));
boxes[2].RotateTo(0).ScaleTo(1).RotateTo(360, 1000)
.Then(1000,
b => b.RotateTo(0, 1000),
b => b.ScaleTo(2, 500)
)
.Then().RotateTo(360, 1000).ScaleTo(0.5f, 1000)
.Then().FadeEdgeEffectTo(Color4.Red, 1000).ScaleTo(2, 500);
boxes[3].RotateTo(0).ScaleTo(1).RotateTo(360, 500)
.Then(1000,
b => b.RotateTo(0),
b => b.ScaleTo(2)
)
.Then(
b => b.Loop(500, 2, d => d.RotateTo(0).RotateTo(360, 1000)).Delay(500).ScaleTo(0.5f, 500)
)
.Then().FadeEdgeEffectTo(Color4.Red, 1000).ScaleTo(2, 500)
.Finally(_ => finalizeTriggered = true);
boxes[4].RotateTo(0).ScaleTo(1).RotateTo(360, 500)
.Then(1000,
b => b.RotateTo(0),
b => b.ScaleTo(2)
)
.Then(
b => b.Loop(500, 2, d => d.RotateTo(0).RotateTo(360, 1000)),
b => b.ScaleTo(0.5f, 500)
)
.OnAbort(b => b.FadeEdgeEffectTo(Color4.Red, 1000));
boxes[5].RotateTo(0).ScaleTo(1).RotateTo(360, 500)
.Then(1000,
b => b.RotateTo(0),
b => b.ScaleTo(2)
)
.Then(
b => b.Loop(500, 2, d => d.RotateTo(0).RotateTo(360, 1000)),
b => b.ScaleTo(0.5f, 500)
)
.Finally(b => b.FadeEdgeEffectTo(Color4.Red, 1000));
boxes[6].RotateTo(200)
.Finally(b => b.FadeEdgeEffectTo(Color4.Red, 1000));
boxes[7].Delay(-1000).RotateTo(200)
.Finally(b => b.FadeEdgeEffectTo(Color4.Red, 1000));
boxes[8].Delay(-1000).RotateTo(200, 1000)
.Finally(b => b.FadeEdgeEffectTo(Color4.Red, 1000));
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseTransformSequence : GridTestCase
{
private readonly Container[] boxes;
public TestCaseTransformSequence()
: base(3, 3)
{
boxes = new Container[Rows * Cols];
}
protected override void LoadComplete()
{
base.LoadComplete();
testFinish();
testClear();
}
private void testFinish()
{
AddStep("Animate", delegate
{
setup();
animate();
});
AddStep($"{nameof(FinishTransforms)}", delegate
{
foreach (var box in boxes)
box.FinishTransforms();
});
AddAssert("finalize triggered", () => finalizeTriggered);
}
private void testClear()
{
AddStep("Animate", delegate
{
setup();
animate();
});
AddStep($"{nameof(ClearTransforms)}", delegate
{
foreach (var box in boxes)
box.ClearTransforms();
});
AddAssert("finalize triggered", () => finalizeTriggered);
}
private void setup()
{
finalizeTriggered = false;
string[] labels =
{
"Spin after 2 seconds",
"Loop(1 sec pause; 1 sec rotate)",
"Complex transform 1 (should end in sync with CT2)",
"Complex transform 2 (should end in sync with CT1)",
$"Red on {nameof(TransformSequence<Container>)}.{nameof(TransformSequence<Container>.OnAbort)}",
$"Red on {nameof(TransformSequence<Container>)}.{nameof(TransformSequence<Container>.Finally)}",
"Red after instant transform",
"Red after instant transform 1 sec in the past",
"Red after 1 sec transform 1 sec in the past",
};
for (int i = 0; i < Rows * Cols; ++i)
{
Cell(i).Children = new Drawable[]
{
new SpriteText
{
Text = labels[i],
TextSize = 20,
},
boxes[i] = new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.25f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 20,
Colour = Color4.Blue,
},
Child = new Box
{
RelativeSizeAxes = Axes.Both,
}
}
};
}
}
private bool finalizeTriggered;
private void animate()
{
boxes[0].Delay(500).Then(500).Then(500).Then(
b => b.Delay(500).Spin(1000, RotationDirection.CounterClockwise)
);
boxes[1].Delay(1000).Loop(1000, 10, b => b.RotateTo(0).RotateTo(340, 1000));
boxes[2].RotateTo(0).ScaleTo(1).RotateTo(360, 1000)
.Then(1000,
b => b.RotateTo(0, 1000),
b => b.ScaleTo(2, 500)
)
.Then().RotateTo(360, 1000).ScaleTo(0.5f, 1000)
.Then().FadeEdgeEffectTo(Color4.Red, 1000).ScaleTo(2, 500);
boxes[3].RotateTo(0).ScaleTo(1).RotateTo(360, 500)
.Then(1000,
b => b.RotateTo(0),
b => b.ScaleTo(2)
)
.Then(
b => b.Loop(500, 2, d => d.RotateTo(0).RotateTo(360, 1000)).Delay(500).ScaleTo(0.5f, 500)
)
.Then().FadeEdgeEffectTo(Color4.Red, 1000).ScaleTo(2, 500)
.Finally(_ => finalizeTriggered = true);
boxes[4].RotateTo(0).ScaleTo(1).RotateTo(360, 500)
.Then(1000,
b => b.RotateTo(0),
b => b.ScaleTo(2)
)
.Then(
b => b.Loop(500, 2, d => d.RotateTo(0).RotateTo(360, 1000)),
b => b.ScaleTo(0.5f, 500)
)
.OnAbort(b => b.FadeEdgeEffectTo(Color4.Red, 1000));
boxes[5].RotateTo(0).ScaleTo(1).RotateTo(360, 500)
.Then(1000,
b => b.RotateTo(0),
b => b.ScaleTo(2)
)
.Then(
b => b.Loop(500, 2, d => d.RotateTo(0).RotateTo(360, 1000)),
b => b.ScaleTo(0.5f, 500)
)
.Finally(b => b.FadeEdgeEffectTo(Color4.Red, 1000));
boxes[6].RotateTo(200)
.Finally(b => b.FadeEdgeEffectTo(Color4.Red, 1000));
boxes[7].Delay(-1000).RotateTo(200)
.Finally(b => b.FadeEdgeEffectTo(Color4.Red, 1000));
boxes[8].Delay(-1000).RotateTo(200, 1000)
.Finally(b => b.FadeEdgeEffectTo(Color4.Red, 1000));
}
}
}

View File

@@ -1,189 +1,189 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseTriangles : TestCase
{
private readonly Container testContainer;
public TestCaseTriangles()
{
Add(testContainer = new Container
{
RelativeSizeAxes = Axes.Both,
});
string[] testNames = { @"Bounding box / input" };
for (int i = 0; i < testNames.Length; i++)
{
int test = i;
AddStep(testNames[i], delegate { loadTest(test); });
}
loadTest(0);
addCrosshair();
}
private void addCrosshair()
{
Add(new Box
{
Colour = Color4.Black,
Size = new Vector2(22, 4),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
Add(new Box
{
Colour = Color4.Black,
Size = new Vector2(4, 22),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
Add(new Box
{
Colour = Color4.WhiteSmoke,
Size = new Vector2(20, 2),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
Add(new Box
{
Colour = Color4.WhiteSmoke,
Size = new Vector2(2, 20),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
}
private void loadTest(int testType)
{
testContainer.Clear();
Triangle triangle;
switch (testType)
{
case 0:
Container box;
testContainer.Add(box = new InfofulBoxAutoSize
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
addCornerMarkers(box);
box.AddRange(new[]
{
new DraggableTriangle
{
//chameleon = true,
Position = new Vector2(0, 0),
Size = new Vector2(25, 25),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.Blue,
},
triangle = new DraggableTriangle
{
Size = new Vector2(250, 250),
Alpha = 0.5f,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.DarkSeaGreen,
}
});
triangle.OnUpdate += delegate { triangle.Rotation += 0.05f; };
break;
}
#if DEBUG
//if (toggleDebugAutosize.State)
// testContainer.Children.FindAll(c => c.HasAutosizeChildren).ForEach(c => c.AutoSizeDebug = true);
#endif
}
private void addCornerMarkers(Container box, int size = 50, Color4? colour = null)
{
box.Add(new DraggableTriangle
{
//chameleon = true,
Size = new Vector2(size, size),
Origin = Anchor.TopLeft,
Anchor = Anchor.TopLeft,
AllowDrag = false,
Depth = -2,
Colour = colour ?? Color4.Red,
});
box.Add(new DraggableTriangle
{
//chameleon = true,
Size = new Vector2(size, size),
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
AllowDrag = false,
Depth = -2,
Colour = colour ?? Color4.Red,
});
box.Add(new DraggableTriangle
{
//chameleon = true,
Size = new Vector2(size, size),
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
AllowDrag = false,
Depth = -2,
Colour = colour ?? Color4.Red,
});
box.Add(new DraggableTriangle
{
//chameleon = true,
Size = new Vector2(size, size),
Origin = Anchor.BottomRight,
Anchor = Anchor.BottomRight,
AllowDrag = false,
Depth = -2,
Colour = colour ?? Color4.Red,
});
}
}
internal class DraggableTriangle : Triangle
{
public bool AllowDrag = true;
protected override bool OnDrag(InputState state)
{
if (!AllowDrag) return false;
Position += state.Mouse.Delta;
return true;
}
protected override bool OnDragEnd(InputState state)
{
return true;
}
protected override bool OnDragStart(InputState state) => AllowDrag;
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Testing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual
{
public class TestCaseTriangles : TestCase
{
private readonly Container testContainer;
public TestCaseTriangles()
{
Add(testContainer = new Container
{
RelativeSizeAxes = Axes.Both,
});
string[] testNames = { @"Bounding box / input" };
for (int i = 0; i < testNames.Length; i++)
{
int test = i;
AddStep(testNames[i], delegate { loadTest(test); });
}
loadTest(0);
addCrosshair();
}
private void addCrosshair()
{
Add(new Box
{
Colour = Color4.Black,
Size = new Vector2(22, 4),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
Add(new Box
{
Colour = Color4.Black,
Size = new Vector2(4, 22),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
Add(new Box
{
Colour = Color4.WhiteSmoke,
Size = new Vector2(20, 2),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
Add(new Box
{
Colour = Color4.WhiteSmoke,
Size = new Vector2(2, 20),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
}
private void loadTest(int testType)
{
testContainer.Clear();
Triangle triangle;
switch (testType)
{
case 0:
Container box;
testContainer.Add(box = new InfofulBoxAutoSize
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
addCornerMarkers(box);
box.AddRange(new[]
{
new DraggableTriangle
{
//chameleon = true,
Position = new Vector2(0, 0),
Size = new Vector2(25, 25),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.Blue,
},
triangle = new DraggableTriangle
{
Size = new Vector2(250, 250),
Alpha = 0.5f,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.DarkSeaGreen,
}
});
triangle.OnUpdate += delegate { triangle.Rotation += 0.05f; };
break;
}
#if DEBUG
//if (toggleDebugAutosize.State)
// testContainer.Children.FindAll(c => c.HasAutosizeChildren).ForEach(c => c.AutoSizeDebug = true);
#endif
}
private void addCornerMarkers(Container box, int size = 50, Color4? colour = null)
{
box.Add(new DraggableTriangle
{
//chameleon = true,
Size = new Vector2(size, size),
Origin = Anchor.TopLeft,
Anchor = Anchor.TopLeft,
AllowDrag = false,
Depth = -2,
Colour = colour ?? Color4.Red,
});
box.Add(new DraggableTriangle
{
//chameleon = true,
Size = new Vector2(size, size),
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
AllowDrag = false,
Depth = -2,
Colour = colour ?? Color4.Red,
});
box.Add(new DraggableTriangle
{
//chameleon = true,
Size = new Vector2(size, size),
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
AllowDrag = false,
Depth = -2,
Colour = colour ?? Color4.Red,
});
box.Add(new DraggableTriangle
{
//chameleon = true,
Size = new Vector2(size, size),
Origin = Anchor.BottomRight,
Anchor = Anchor.BottomRight,
AllowDrag = false,
Depth = -2,
Colour = colour ?? Color4.Red,
});
}
}
internal class DraggableTriangle : Triangle
{
public bool AllowDrag = true;
protected override bool OnDrag(InputState state)
{
if (!AllowDrag) return false;
Position += state.Mouse.Delta;
return true;
}
protected override bool OnDragEnd(InputState state)
{
return true;
}
protected override bool OnDragStart(InputState state) => AllowDrag;
}
}

View File

@@ -1,90 +1,90 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
using System;
namespace osu.Framework.Tests.Visual
{
public class TestCaseWaveform : FrameworkTestCase
{
private readonly List<WaveformGraph> waveforms = new List<WaveformGraph>();
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Waveform),
typeof(WaveformGraph),
typeof(DataStreamFileProcedures)
};
public TestCaseWaveform()
{
FillFlowContainer flow;
Add(flow = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10)
});
for (int i = 1; i <= 16; i *= 2)
{
var newDisplay = new WaveformGraph
{
RelativeSizeAxes = Axes.Both,
Resolution = 1f / i
};
waveforms.Add(newDisplay);
flow.Add(new Container
{
RelativeSizeAxes = Axes.X,
Height = 100,
Children = new Drawable[]
{
newDisplay,
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.75f
},
new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding(4),
Text = $"Resolution: {1f / i:0.00}"
}
}
}
}
});
}
}
[BackgroundDependencyLoader]
private void load(Game game)
{
var waveform = new Waveform(game.Resources.GetStream("Tracks/sample-track.mp3"));
waveforms.ForEach(w => w.Waveform = waveform);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
using System;
namespace osu.Framework.Tests.Visual
{
public class TestCaseWaveform : FrameworkTestCase
{
private readonly List<WaveformGraph> waveforms = new List<WaveformGraph>();
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Waveform),
typeof(WaveformGraph),
typeof(DataStreamFileProcedures)
};
public TestCaseWaveform()
{
FillFlowContainer flow;
Add(flow = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10)
});
for (int i = 1; i <= 16; i *= 2)
{
var newDisplay = new WaveformGraph
{
RelativeSizeAxes = Axes.Both,
Resolution = 1f / i
};
waveforms.Add(newDisplay);
flow.Add(new Container
{
RelativeSizeAxes = Axes.X,
Height = 100,
Children = new Drawable[]
{
newDisplay,
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.75f
},
new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding(4),
Text = $"Resolution: {1f / i:0.00}"
}
}
}
}
});
}
}
[BackgroundDependencyLoader]
private void load(Game game)
{
var waveform = new Waveform(game.Resources.GetStream("Tracks/sample-track.mp3"));
waveforms.ForEach(w => w.Waveform = waveform);
}
}
}

View File

@@ -1,34 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Framework.Testing;
namespace osu.Framework.Tests
{
internal class VisualTestGame : TestGame
{
[BackgroundDependencyLoader]
private void load()
{
Child = new DrawSizePreservingFillContainer
{
Children = new Drawable[]
{
new TestBrowser(),
new CursorContainer(),
},
};
}
public override void SetHost(GameHost host)
{
base.SetHost(host);
host.Window.CursorState |= CursorState.Hidden;
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Framework.Testing;
namespace osu.Framework.Tests
{
internal class VisualTestGame : TestGame
{
[BackgroundDependencyLoader]
private void load()
{
Child = new DrawSizePreservingFillContainer
{
Children = new Drawable[]
{
new TestBrowser(),
new CursorContainer(),
},
};
}
public override void SetHost(GameHost host)
{
base.SetHost(host);
host.Window.CursorState |= CursorState.Hidden;
}
}
}

View File

@@ -1,28 +1,28 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Allocation
{
/// <summary>
/// Marks a method as the loader-Method of a <see cref="osu.Framework.Graphics.Drawable"/>, allowing for automatic injection of dependencies via the parameters of the method.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class BackgroundDependencyLoader : Attribute
{
/// <summary>
/// True if nulls are allowed to be passed to the method marked with this attribute.
/// </summary>
public bool PermitNulls { get; private set; }
/// <summary>
/// Marks this method as the initializer for a class in the context of dependency injection.
/// </summary>
/// <param name="permitNulls">If true, the initializer may be passed null for the dependencies we can't fulfill.</param>
public BackgroundDependencyLoader(bool permitNulls = false)
{
PermitNulls = permitNulls;
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Allocation
{
/// <summary>
/// Marks a method as the loader-Method of a <see cref="osu.Framework.Graphics.Drawable"/>, allowing for automatic injection of dependencies via the parameters of the method.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class BackgroundDependencyLoader : Attribute
{
/// <summary>
/// True if nulls are allowed to be passed to the method marked with this attribute.
/// </summary>
public bool PermitNulls { get; private set; }
/// <summary>
/// Marks this method as the initializer for a class in the context of dependency injection.
/// </summary>
/// <param name="permitNulls">If true, the initializer may be passed null for the dependencies we can't fulfill.</param>
public BackgroundDependencyLoader(bool permitNulls = false)
{
PermitNulls = permitNulls;
}
}
}

View File

@@ -1,76 +1,76 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
namespace osu.Framework.Allocation
{
/// <summary>
/// A stack of buffers (arrays with elements of type <see cref="T"/>) which allows bypassing the
/// garbage collector and expensive allocations when buffers can be frequently re-used.
/// The stack nature ensures that the most recently used buffers remain hot in memory, while
/// at the same time guaranteeing a certain degree of order preservation.
/// </summary>
public class BufferStack<T>
{
private readonly int maxAmountBuffers;
private readonly Stack<T[]> freeDataBuffers = new Stack<T[]>();
private readonly HashSet<T[]> usedDataBuffers = new HashSet<T[]>();
/// <summary>
/// Creates a new buffer stack containing a given maximum amount of buffers.
/// </summary>
/// <param name="maxAmountBuffers">The maximum amount of buffers to be contained within the buffer stack.</param>
public BufferStack(int maxAmountBuffers)
{
this.maxAmountBuffers = maxAmountBuffers;
}
private T[] findFreeBuffer(int minimumLength)
{
T[] buffer = null;
if (freeDataBuffers.Count > 0)
buffer = freeDataBuffers.Pop();
if (buffer == null || buffer.Length < minimumLength)
buffer = new T[minimumLength];
if (usedDataBuffers.Count < maxAmountBuffers)
usedDataBuffers.Add(buffer);
return buffer;
}
private void returnFreeBuffer(T[] buffer)
{
if (usedDataBuffers.Remove(buffer))
// We are here if the element was successfully found and removed
freeDataBuffers.Push(buffer);
}
/// <summary>
/// Reserve a buffer from the buffer stack. If no free buffers are available, a new one is allocated.
/// </summary>
/// <param name="minimumLength">The minimum length required of the reserved buffer.</param>
/// <returns>The reserved buffer.</returns>
public T[] ReserveBuffer(int minimumLength)
{
T[] buffer;
lock (freeDataBuffers)
buffer = findFreeBuffer(minimumLength);
return buffer;
}
/// <summary>
/// Frees a previously reserved buffer for future reservations.
/// </summary>
/// <param name="buffer">The buffer to be freed. If the buffer has not previously been reserved then this method does nothing.</param>
public void FreeBuffer(T[] buffer)
{
lock (freeDataBuffers)
returnFreeBuffer(buffer);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
namespace osu.Framework.Allocation
{
/// <summary>
/// A stack of buffers (arrays with elements of type <see cref="T"/>) which allows bypassing the
/// garbage collector and expensive allocations when buffers can be frequently re-used.
/// The stack nature ensures that the most recently used buffers remain hot in memory, while
/// at the same time guaranteeing a certain degree of order preservation.
/// </summary>
public class BufferStack<T>
{
private readonly int maxAmountBuffers;
private readonly Stack<T[]> freeDataBuffers = new Stack<T[]>();
private readonly HashSet<T[]> usedDataBuffers = new HashSet<T[]>();
/// <summary>
/// Creates a new buffer stack containing a given maximum amount of buffers.
/// </summary>
/// <param name="maxAmountBuffers">The maximum amount of buffers to be contained within the buffer stack.</param>
public BufferStack(int maxAmountBuffers)
{
this.maxAmountBuffers = maxAmountBuffers;
}
private T[] findFreeBuffer(int minimumLength)
{
T[] buffer = null;
if (freeDataBuffers.Count > 0)
buffer = freeDataBuffers.Pop();
if (buffer == null || buffer.Length < minimumLength)
buffer = new T[minimumLength];
if (usedDataBuffers.Count < maxAmountBuffers)
usedDataBuffers.Add(buffer);
return buffer;
}
private void returnFreeBuffer(T[] buffer)
{
if (usedDataBuffers.Remove(buffer))
// We are here if the element was successfully found and removed
freeDataBuffers.Push(buffer);
}
/// <summary>
/// Reserve a buffer from the buffer stack. If no free buffers are available, a new one is allocated.
/// </summary>
/// <param name="minimumLength">The minimum length required of the reserved buffer.</param>
/// <returns>The reserved buffer.</returns>
public T[] ReserveBuffer(int minimumLength)
{
T[] buffer;
lock (freeDataBuffers)
buffer = findFreeBuffer(minimumLength);
return buffer;
}
/// <summary>
/// Frees a previously reserved buffer for future reservations.
/// </summary>
/// <param name="buffer">The buffer to be freed. If the buffer has not previously been reserved then this method does nothing.</param>
public void FreeBuffer(T[] buffer)
{
lock (freeDataBuffers)
returnFreeBuffer(buffer);
}
}
}

View File

@@ -1,196 +1,196 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Extensions.TypeExtensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using osu.Framework.Extensions.ExceptionExtensions;
namespace osu.Framework.Allocation
{
/// <summary>
/// Hierarchically caches dependencies and can inject those automatically into types registered for dependency injection.
/// </summary>
public class DependencyContainer : IReadOnlyDependencyContainer
{
private delegate object ObjectActivator(DependencyContainer dc, object instance);
private readonly ConcurrentDictionary<Type, ObjectActivator> activators = new ConcurrentDictionary<Type, ObjectActivator>();
private readonly ConcurrentDictionary<Type, object> cache = new ConcurrentDictionary<Type, object>();
private readonly IReadOnlyDependencyContainer parentContainer;
/// <summary>
/// Create a new DependencyContainer instance.
/// </summary>
/// <param name="parent">An optional parent container which we should use as a fallback for cache lookups.</param>
public DependencyContainer(IReadOnlyDependencyContainer parent = null)
{
parentContainer = parent;
}
private MethodInfo getLoaderMethod(Type type)
{
var loaderMethods = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(
mi => mi.GetCustomAttribute<BackgroundDependencyLoader>() != null).ToArray();
if (loaderMethods.Length == 0)
return null;
if (loaderMethods.Length == 1)
return loaderMethods[0];
throw new InvalidOperationException($"The type {type.ReadableName()} has more than one method marked with the {nameof(BackgroundDependencyLoader)}-Attribute. Any given type can only have one such method.");
}
private void register(Type type, bool lazy)
{
if (activators.ContainsKey(type))
throw new InvalidOperationException($@"Type {type.FullName} can not be registered twice");
var initialize = getLoaderMethod(type);
var constructor = type.GetConstructor(new Type[] { });
var initializerMethods = new List<MethodInfo>();
for (Type parent = type.BaseType; parent != typeof(object); parent = parent?.BaseType)
{
var init = getLoaderMethod(parent);
if (init != null)
initializerMethods.Insert(0, init);
}
if (initialize != null)
initializerMethods.Add(initialize);
var initializers = initializerMethods.Select(initializer =>
{
var permitNull = initializer.GetCustomAttribute<BackgroundDependencyLoader>().PermitNulls;
var parameters = initializer.GetParameters().Select(p => p.ParameterType)
.Select(t => new Func<object>(() =>
{
var val = Get(t);
if (val == null && !permitNull)
{
throw new InvalidOperationException(
$@"Type {t.FullName} is not registered, and is a dependency of {type.FullName}");
}
return val;
})).ToList();
// Test that we already have all the dependencies registered
if (!lazy)
parameters.ForEach(p => p());
return new Action<object>(instance =>
{
var p = parameters.Select(pa => pa()).ToArray();
try
{
initializer.Invoke(instance, p);
}
catch (TargetInvocationException e)
{
new RecursiveLoadException(e.GetLastInvocation(), initializer).Rethrow();
}
});
}).ToList();
activators[type] = (container, instance) =>
{
if (instance == null)
{
if (constructor == null)
throw new InvalidOperationException($@"Type {type.FullName} must have a parameterless constructor to initialize one from scratch.");
instance = Activator.CreateInstance(type);
}
initializers.ForEach(init => init(instance));
return instance;
};
}
/// <summary>
/// Registers a type and configures a default allocator for it that injects its
/// dependencies.
/// </summary>
public void Register<T>(bool lazy = false) where T : class => register(typeof(T), lazy);
/// <summary>
/// Registers a type that allocates with a custom allocator.
/// </summary>
public void Register<T>(Func<DependencyContainer, T> activator) where T : class
{
var type = typeof(T);
if (activators.ContainsKey(type))
throw new InvalidOperationException($@"Type {typeof(T).FullName} is already registered");
activators[type] = (d, i) => i ?? activator(d);
}
/// <summary>
/// Caches an instance of a type as its most derived type. This instance will be returned each time you <see cref="Get(Type)"/>.
/// </summary>
/// <param name="instance">The instance to cache.</param>
public void Cache<T>(T instance)
where T : class
{
if (instance == null) throw new ArgumentNullException(nameof(instance));
cache[instance.GetType()] = instance;
}
/// <summary>
/// Caches an instance of a type as a type of <typeparamref name="T"/>. This instance will be returned each time you <see cref="Get(Type)"/>.
/// </summary>
/// <param name="instance">The instance to cache. Must be or derive from <typeparamref name="T"/>.</param>
public void CacheAs<T>(T instance)
where T : class
{
cache[typeof(T)] = instance ?? throw new ArgumentNullException(nameof(instance));
}
/// <summary>
/// Retrieves a cached dependency of <paramref name="type"/> if it exists. If not, then the parent
/// <see cref="IReadOnlyDependencyContainer"/> is recursively queried. If no parent contains
/// <paramref name="type"/>, then null is returned.
/// </summary>
/// <param name="type">The dependency type to query for.</param>
/// <returns>The requested dependency, or null if not found.</returns>
public object Get(Type type)
{
if (cache.TryGetValue(type, out object ret))
return ret;
return parentContainer?.Get(type);
//we don't ever want to instantiate for now, as this breaks expectations when using permitNull.
//need to revisit this when/if it is required.
//if (!activators.ContainsKey(type))
// return null; // Or an exception?
//object instance = activators[type](this, null);
//if (cacheable.Contains(type))
// cache[type] = instance;
//return instance;
}
/// <summary>
/// Injects dependencies into the given instance.
/// </summary>
/// <typeparam name="T">The type of the instance to inject dependencies into.</typeparam>
/// <param name="instance">The instance to inject dependencies into.</param>
/// <param name="autoRegister">True if the instance should be automatically registered as injectable if it isn't already.</param>
/// <param name="lazy">True if the dependencies should be initialized lazily.</param>
public void Inject<T>(T instance, bool autoRegister = true, bool lazy = false) where T : class
{
var type = instance.GetType();
// TODO: consider using parentContainer for activator lookups as a potential performance improvement.
lock (activators)
if (autoRegister && !activators.ContainsKey(type))
register(type, lazy);
if (!activators.TryGetValue(type, out ObjectActivator activator))
throw new InvalidOperationException("DI Initialisation failed badly.");
activator(this, instance);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Extensions.TypeExtensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using osu.Framework.Extensions.ExceptionExtensions;
namespace osu.Framework.Allocation
{
/// <summary>
/// Hierarchically caches dependencies and can inject those automatically into types registered for dependency injection.
/// </summary>
public class DependencyContainer : IReadOnlyDependencyContainer
{
private delegate object ObjectActivator(DependencyContainer dc, object instance);
private readonly ConcurrentDictionary<Type, ObjectActivator> activators = new ConcurrentDictionary<Type, ObjectActivator>();
private readonly ConcurrentDictionary<Type, object> cache = new ConcurrentDictionary<Type, object>();
private readonly IReadOnlyDependencyContainer parentContainer;
/// <summary>
/// Create a new DependencyContainer instance.
/// </summary>
/// <param name="parent">An optional parent container which we should use as a fallback for cache lookups.</param>
public DependencyContainer(IReadOnlyDependencyContainer parent = null)
{
parentContainer = parent;
}
private MethodInfo getLoaderMethod(Type type)
{
var loaderMethods = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(
mi => mi.GetCustomAttribute<BackgroundDependencyLoader>() != null).ToArray();
if (loaderMethods.Length == 0)
return null;
if (loaderMethods.Length == 1)
return loaderMethods[0];
throw new InvalidOperationException($"The type {type.ReadableName()} has more than one method marked with the {nameof(BackgroundDependencyLoader)}-Attribute. Any given type can only have one such method.");
}
private void register(Type type, bool lazy)
{
if (activators.ContainsKey(type))
throw new InvalidOperationException($@"Type {type.FullName} can not be registered twice");
var initialize = getLoaderMethod(type);
var constructor = type.GetConstructor(new Type[] { });
var initializerMethods = new List<MethodInfo>();
for (Type parent = type.BaseType; parent != typeof(object); parent = parent?.BaseType)
{
var init = getLoaderMethod(parent);
if (init != null)
initializerMethods.Insert(0, init);
}
if (initialize != null)
initializerMethods.Add(initialize);
var initializers = initializerMethods.Select(initializer =>
{
var permitNull = initializer.GetCustomAttribute<BackgroundDependencyLoader>().PermitNulls;
var parameters = initializer.GetParameters().Select(p => p.ParameterType)
.Select(t => new Func<object>(() =>
{
var val = Get(t);
if (val == null && !permitNull)
{
throw new InvalidOperationException(
$@"Type {t.FullName} is not registered, and is a dependency of {type.FullName}");
}
return val;
})).ToList();
// Test that we already have all the dependencies registered
if (!lazy)
parameters.ForEach(p => p());
return new Action<object>(instance =>
{
var p = parameters.Select(pa => pa()).ToArray();
try
{
initializer.Invoke(instance, p);
}
catch (TargetInvocationException e)
{
new RecursiveLoadException(e.GetLastInvocation(), initializer).Rethrow();
}
});
}).ToList();
activators[type] = (container, instance) =>
{
if (instance == null)
{
if (constructor == null)
throw new InvalidOperationException($@"Type {type.FullName} must have a parameterless constructor to initialize one from scratch.");
instance = Activator.CreateInstance(type);
}
initializers.ForEach(init => init(instance));
return instance;
};
}
/// <summary>
/// Registers a type and configures a default allocator for it that injects its
/// dependencies.
/// </summary>
public void Register<T>(bool lazy = false) where T : class => register(typeof(T), lazy);
/// <summary>
/// Registers a type that allocates with a custom allocator.
/// </summary>
public void Register<T>(Func<DependencyContainer, T> activator) where T : class
{
var type = typeof(T);
if (activators.ContainsKey(type))
throw new InvalidOperationException($@"Type {typeof(T).FullName} is already registered");
activators[type] = (d, i) => i ?? activator(d);
}
/// <summary>
/// Caches an instance of a type as its most derived type. This instance will be returned each time you <see cref="Get(Type)"/>.
/// </summary>
/// <param name="instance">The instance to cache.</param>
public void Cache<T>(T instance)
where T : class
{
if (instance == null) throw new ArgumentNullException(nameof(instance));
cache[instance.GetType()] = instance;
}
/// <summary>
/// Caches an instance of a type as a type of <typeparamref name="T"/>. This instance will be returned each time you <see cref="Get(Type)"/>.
/// </summary>
/// <param name="instance">The instance to cache. Must be or derive from <typeparamref name="T"/>.</param>
public void CacheAs<T>(T instance)
where T : class
{
cache[typeof(T)] = instance ?? throw new ArgumentNullException(nameof(instance));
}
/// <summary>
/// Retrieves a cached dependency of <paramref name="type"/> if it exists. If not, then the parent
/// <see cref="IReadOnlyDependencyContainer"/> is recursively queried. If no parent contains
/// <paramref name="type"/>, then null is returned.
/// </summary>
/// <param name="type">The dependency type to query for.</param>
/// <returns>The requested dependency, or null if not found.</returns>
public object Get(Type type)
{
if (cache.TryGetValue(type, out object ret))
return ret;
return parentContainer?.Get(type);
//we don't ever want to instantiate for now, as this breaks expectations when using permitNull.
//need to revisit this when/if it is required.
//if (!activators.ContainsKey(type))
// return null; // Or an exception?
//object instance = activators[type](this, null);
//if (cacheable.Contains(type))
// cache[type] = instance;
//return instance;
}
/// <summary>
/// Injects dependencies into the given instance.
/// </summary>
/// <typeparam name="T">The type of the instance to inject dependencies into.</typeparam>
/// <param name="instance">The instance to inject dependencies into.</param>
/// <param name="autoRegister">True if the instance should be automatically registered as injectable if it isn't already.</param>
/// <param name="lazy">True if the dependencies should be initialized lazily.</param>
public void Inject<T>(T instance, bool autoRegister = true, bool lazy = false) where T : class
{
var type = instance.GetType();
// TODO: consider using parentContainer for activator lookups as a potential performance improvement.
lock (activators)
if (autoRegister && !activators.ContainsKey(type))
register(type, lazy);
if (!activators.TryGetValue(type, out ObjectActivator activator))
throw new InvalidOperationException("DI Initialisation failed badly.");
activator(this, instance);
}
}
}

View File

@@ -1,42 +1,42 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Allocation
{
/// <summary>
/// Read-only interface into a dependency container capable of injective and retrieving dependencies based
/// on types.
/// </summary>
public interface IReadOnlyDependencyContainer
{
/// <summary>
/// Retrieves a cached dependency of <paramref name="type"/> if it exists and null otherwise.
/// </summary>
/// <param name="type">The dependency type to query for.</param>
/// <returns>The requested dependency, or null if not found.</returns>
object Get(Type type);
/// <summary>
/// Injects dependencies into the given instance.
/// </summary>
/// <typeparam name="T">The type of the instance to inject dependencies into.</typeparam>
/// <param name="instance">The instance to inject dependencies into.</param>
/// <param name="autoRegister">True if the instance should be automatically registered as injectable if it isn't already.</param>
/// <param name="lazy">True if the dependencies should be initialized lazily.</param>
void Inject<T>(T instance, bool autoRegister = true, bool lazy = false) where T : class;
}
public static class ReadOnlyDependencyContainerExtensions
{
/// <summary>
/// Retrieves a cached dependency of type <typeparamref name="T"/> if it exists and null otherwise.
/// </summary>
/// <typeparam name="T">The dependency type to query for.</typeparam>
/// <param name="container">The <see cref="IReadOnlyDependencyContainer"/> to query.</param>
/// <returns>The requested dependency, or null if not found.</returns>
public static T Get<T>(this IReadOnlyDependencyContainer container) where T : class =>
(T)container.Get(typeof(T));
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Allocation
{
/// <summary>
/// Read-only interface into a dependency container capable of injective and retrieving dependencies based
/// on types.
/// </summary>
public interface IReadOnlyDependencyContainer
{
/// <summary>
/// Retrieves a cached dependency of <paramref name="type"/> if it exists and null otherwise.
/// </summary>
/// <param name="type">The dependency type to query for.</param>
/// <returns>The requested dependency, or null if not found.</returns>
object Get(Type type);
/// <summary>
/// Injects dependencies into the given instance.
/// </summary>
/// <typeparam name="T">The type of the instance to inject dependencies into.</typeparam>
/// <param name="instance">The instance to inject dependencies into.</param>
/// <param name="autoRegister">True if the instance should be automatically registered as injectable if it isn't already.</param>
/// <param name="lazy">True if the dependencies should be initialized lazily.</param>
void Inject<T>(T instance, bool autoRegister = true, bool lazy = false) where T : class;
}
public static class ReadOnlyDependencyContainerExtensions
{
/// <summary>
/// Retrieves a cached dependency of type <typeparamref name="T"/> if it exists and null otherwise.
/// </summary>
/// <typeparam name="T">The dependency type to query for.</typeparam>
/// <param name="container">The <see cref="IReadOnlyDependencyContainer"/> to query.</param>
/// <returns>The requested dependency, or null if not found.</returns>
public static T Get<T>(this IReadOnlyDependencyContainer container) where T : class =>
(T)container.Get(typeof(T));
}
}

View File

@@ -1,39 +1,39 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Allocation
{
/// <summary>
/// Instances of this class capture an action for later cleanup. When a method returns an instance of this class, the appropriate usage is:
/// <code>using (SomeMethod())
/// {
/// // ...
/// }</code>
/// The using block will automatically dispose the returned instance, doing the necessary cleanup work.
/// </summary>
public class InvokeOnDisposal : IDisposable
{
private readonly Action action;
/// <summary>
/// Constructs a new instance, capturing the given action to be run during disposal.
/// </summary>
/// <param name="action">The action to invoke during disposal.</param>
public InvokeOnDisposal(Action action) => this.action = action ?? throw new ArgumentNullException(nameof(action));
#region IDisposable Support
/// <summary>
/// Disposes this instance, calling the initially captured action.
/// </summary>
public void Dispose()
{
//no isDisposed check here so we can reuse these instances multiple times to save on allocations.
action();
}
#endregion
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Allocation
{
/// <summary>
/// Instances of this class capture an action for later cleanup. When a method returns an instance of this class, the appropriate usage is:
/// <code>using (SomeMethod())
/// {
/// // ...
/// }</code>
/// The using block will automatically dispose the returned instance, doing the necessary cleanup work.
/// </summary>
public class InvokeOnDisposal : IDisposable
{
private readonly Action action;
/// <summary>
/// Constructs a new instance, capturing the given action to be run during disposal.
/// </summary>
/// <param name="action">The action to invoke during disposal.</param>
public InvokeOnDisposal(Action action) => this.action = action ?? throw new ArgumentNullException(nameof(action));
#region IDisposable Support
/// <summary>
/// Disposes this instance, calling the initially captured action.
/// </summary>
public void Dispose()
{
//no isDisposed check here so we can reuse these instances multiple times to save on allocations.
action();
}
#endregion
}
}

View File

@@ -1,59 +1,59 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
namespace osu.Framework.Allocation
{
public class ObjectStack<T> where T : new()
{
private readonly int maxAmountObjects;
private readonly Stack<T> freeObjects = new Stack<T>();
private int usedObjects;
public ObjectStack(int maxAmountObjects = -1)
{
this.maxAmountObjects = maxAmountObjects;
}
private T findFreeObject()
{
T o = freeObjects.Count > 0 ? freeObjects.Pop() : new T();
if (maxAmountObjects == -1 || usedObjects < maxAmountObjects)
usedObjects++;
return o;
}
private void returnFreeObject(T o)
{
if (usedObjects-- > 0)
// We are here if the element was successfully found and removed
freeObjects.Push(o);
}
/// <summary>
/// Reserve an object from the pool. This is used to avoid excessive amounts of heap allocations.
/// </summary>
/// <returns>The reserved object.</returns>
public T ReserveObject()
{
T o;
lock (freeObjects)
o = findFreeObject();
return o;
}
/// <summary>
/// Frees a previously reserved object for future reservations.
/// </summary>
/// <param name="o">The object to be freed. If the object has not previously been reserved then this method does nothing.</param>
public void FreeObject(T o)
{
lock (freeObjects)
returnFreeObject(o);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
namespace osu.Framework.Allocation
{
public class ObjectStack<T> where T : new()
{
private readonly int maxAmountObjects;
private readonly Stack<T> freeObjects = new Stack<T>();
private int usedObjects;
public ObjectStack(int maxAmountObjects = -1)
{
this.maxAmountObjects = maxAmountObjects;
}
private T findFreeObject()
{
T o = freeObjects.Count > 0 ? freeObjects.Pop() : new T();
if (maxAmountObjects == -1 || usedObjects < maxAmountObjects)
usedObjects++;
return o;
}
private void returnFreeObject(T o)
{
if (usedObjects-- > 0)
// We are here if the element was successfully found and removed
freeObjects.Push(o);
}
/// <summary>
/// Reserve an object from the pool. This is used to avoid excessive amounts of heap allocations.
/// </summary>
/// <returns>The reserved object.</returns>
public T ReserveObject()
{
T o;
lock (freeObjects)
o = findFreeObject();
return o;
}
/// <summary>
/// Frees a previously reserved object for future reservations.
/// </summary>
/// <param name="o">The object to be freed. If the object has not previously been reserved then this method does nothing.</param>
public void FreeObject(T o)
{
lock (freeObjects)
returnFreeObject(o);
}
}
}

View File

@@ -1,31 +1,31 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Allocation
{
public class ObjectUsage<T> : IDisposable
{
public T Object;
public int Index;
public long FrameId;
internal Action<ObjectUsage<T>, UsageType> Finish;
public UsageType Usage;
public void Dispose()
{
Finish?.Invoke(this, Usage);
}
}
public enum UsageType
{
None,
Read,
Write
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Allocation
{
public class ObjectUsage<T> : IDisposable
{
public T Object;
public int Index;
public long FrameId;
internal Action<ObjectUsage<T>, UsageType> Finish;
public UsageType Usage;
public void Dispose()
{
Finish?.Invoke(this, Usage);
}
}
public enum UsageType
{
None,
Read,
Write
}
}

View File

@@ -1,74 +1,74 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Linq;
using System.Reflection;
using System.Text;
using osu.Framework.Graphics.Containers;
namespace osu.Framework.Allocation
{
/// <summary>
/// The exception that is re-thrown by <see cref="DependencyContainer"/> when a loader invocation fails.
/// This exception type builds a readablestacktrace message since loader invocations tend to be long recursive reflection calls.
/// </summary>
public class RecursiveLoadException : Exception
{
/// <summary>
/// Types that are ignored for the custom stack traces. The initializers for these typically invoke
/// initializers in user code where the problem actually lies.
/// </summary>
private static readonly Type[] blacklist =
{
typeof(Container),
typeof(Container<>),
typeof(CompositeDrawable)
};
private readonly StringBuilder traceBuilder;
private readonly Exception original;
public RecursiveLoadException(Exception original, MethodInfo loaderMethod)
{
var recursive = original as RecursiveLoadException;
this.original = recursive?.original ?? original;
traceBuilder = recursive?.traceBuilder ?? new StringBuilder();
// Find the location of the load method
var loaderLocation = $"{loaderMethod.DeclaringType}.{loaderMethod.Name}";
if (recursive == null)
{
var lines = original.StackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
// Write all lines from the original exception until the loader method is hit
foreach (var o in lines)
{
traceBuilder.AppendLine(o);
if (o.Contains(loaderLocation))
break;
}
}
else if (!blacklist.Contains(loaderMethod.DeclaringType))
traceBuilder.AppendLine($" at {loaderLocation} ()");
stackTrace = traceBuilder.ToString();
}
public override string Message => original.Message;
private readonly string stackTrace;
public override string StackTrace => stackTrace;
public override string ToString()
{
var builder = new StringBuilder();
builder.AppendLine($"{original.GetType()}: {original.Message}");
builder.AppendLine(stackTrace);
return builder.ToString();
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Linq;
using System.Reflection;
using System.Text;
using osu.Framework.Graphics.Containers;
namespace osu.Framework.Allocation
{
/// <summary>
/// The exception that is re-thrown by <see cref="DependencyContainer"/> when a loader invocation fails.
/// This exception type builds a readablestacktrace message since loader invocations tend to be long recursive reflection calls.
/// </summary>
public class RecursiveLoadException : Exception
{
/// <summary>
/// Types that are ignored for the custom stack traces. The initializers for these typically invoke
/// initializers in user code where the problem actually lies.
/// </summary>
private static readonly Type[] blacklist =
{
typeof(Container),
typeof(Container<>),
typeof(CompositeDrawable)
};
private readonly StringBuilder traceBuilder;
private readonly Exception original;
public RecursiveLoadException(Exception original, MethodInfo loaderMethod)
{
var recursive = original as RecursiveLoadException;
this.original = recursive?.original ?? original;
traceBuilder = recursive?.traceBuilder ?? new StringBuilder();
// Find the location of the load method
var loaderLocation = $"{loaderMethod.DeclaringType}.{loaderMethod.Name}";
if (recursive == null)
{
var lines = original.StackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
// Write all lines from the original exception until the loader method is hit
foreach (var o in lines)
{
traceBuilder.AppendLine(o);
if (o.Contains(loaderLocation))
break;
}
}
else if (!blacklist.Contains(loaderMethod.DeclaringType))
traceBuilder.AppendLine($" at {loaderLocation} ()");
stackTrace = traceBuilder.ToString();
}
public override string Message => original.Message;
private readonly string stackTrace;
public override string StackTrace => stackTrace;
public override string ToString()
{
var builder = new StringBuilder();
builder.AppendLine($"{original.GetType()}: {original.Message}");
builder.AppendLine(stackTrace);
return builder.ToString();
}
}
}

View File

@@ -1,115 +1,115 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace osu.Framework.Allocation
{
/// <summary>
/// A key-value store which supports cleaning up items after a specified expiry time.
/// </summary>
public class TimedExpiryCache<TKey, TValue> : IDisposable
{
private readonly ConcurrentDictionary<TKey, TimedObject<TValue>> dictionary = new ConcurrentDictionary<TKey, TimedObject<TValue>>();
/// <summary>
/// Time in milliseconds after last access after which items will be cleaned up.
/// </summary>
public int ExpiryTime = 5000;
/// <summary>
/// The interval in milliseconds between checks for expiry.
/// </summary>
public readonly int CheckPeriod = 5000;
private readonly Timer timer;
public TimedExpiryCache()
{
timer = new Timer(checkExpiry, null, 0, CheckPeriod);
}
internal void Add(TKey key, TValue value)
{
dictionary.TryAdd(key, new TimedObject<TValue>(value));
}
private void checkExpiry(object state)
{
var now = DateTimeOffset.Now;
foreach (var v in dictionary)
{
if ((now - v.Value.LastAccessTime).TotalMilliseconds > ExpiryTime)
dictionary.TryRemove(v.Key, out _);
}
}
public bool TryGetValue(TKey key, out TValue value)
{
if (!dictionary.TryGetValue(key, out TimedObject<TValue> timed))
{
value = default(TValue);
return false;
}
value = timed.Value;
return true;
}
#region IDisposable Support
private bool isDisposed;
protected virtual void Dispose(bool disposing)
{
if (!isDisposed)
{
isDisposed = true;
timer.Dispose();
}
}
~TimedExpiryCache()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
private class TimedObject<T>
{
public DateTimeOffset LastAccessTime;
private readonly T value;
public T Value
{
get
{
updateAccessTime();
return value;
}
}
public TimedObject(T value)
{
this.value = value;
updateAccessTime();
}
private void updateAccessTime()
{
LastAccessTime = DateTimeOffset.Now;
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace osu.Framework.Allocation
{
/// <summary>
/// A key-value store which supports cleaning up items after a specified expiry time.
/// </summary>
public class TimedExpiryCache<TKey, TValue> : IDisposable
{
private readonly ConcurrentDictionary<TKey, TimedObject<TValue>> dictionary = new ConcurrentDictionary<TKey, TimedObject<TValue>>();
/// <summary>
/// Time in milliseconds after last access after which items will be cleaned up.
/// </summary>
public int ExpiryTime = 5000;
/// <summary>
/// The interval in milliseconds between checks for expiry.
/// </summary>
public readonly int CheckPeriod = 5000;
private readonly Timer timer;
public TimedExpiryCache()
{
timer = new Timer(checkExpiry, null, 0, CheckPeriod);
}
internal void Add(TKey key, TValue value)
{
dictionary.TryAdd(key, new TimedObject<TValue>(value));
}
private void checkExpiry(object state)
{
var now = DateTimeOffset.Now;
foreach (var v in dictionary)
{
if ((now - v.Value.LastAccessTime).TotalMilliseconds > ExpiryTime)
dictionary.TryRemove(v.Key, out _);
}
}
public bool TryGetValue(TKey key, out TValue value)
{
if (!dictionary.TryGetValue(key, out TimedObject<TValue> timed))
{
value = default(TValue);
return false;
}
value = timed.Value;
return true;
}
#region IDisposable Support
private bool isDisposed;
protected virtual void Dispose(bool disposing)
{
if (!isDisposed)
{
isDisposed = true;
timer.Dispose();
}
}
~TimedExpiryCache()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
private class TimedObject<T>
{
public DateTimeOffset LastAccessTime;
private readonly T value;
public T Value
{
get
{
updateAccessTime();
return value;
}
}
public TimedObject(T value)
{
this.value = value;
updateAccessTime();
}
private void updateAccessTime()
{
LastAccessTime = DateTimeOffset.Now;
}
}
}
}

View File

@@ -1,91 +1,91 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Threading;
namespace osu.Framework.Allocation
{
/// <summary>
/// Handles triple-buffering of any object type.
/// Thread safety assumes at most one writer and one reader.
/// </summary>
public class TripleBuffer<T>
{
private readonly ObjectUsage<T>[] buffers = new ObjectUsage<T>[3];
private int read;
private int write;
private int lastWrite = -1;
private long currentFrame;
private readonly Action<ObjectUsage<T>, UsageType> finishDelegate;
public TripleBuffer()
{
//caching the delegate means we only have to allocate it once, rather than once per created buffer.
finishDelegate = finish;
}
public ObjectUsage<T> Get(UsageType usage)
{
switch (usage)
{
case UsageType.Write:
lock (buffers)
{
while (buffers[write]?.Usage == UsageType.Read || write == lastWrite)
write = (write + 1) % 3;
}
if (buffers[write] == null)
{
buffers[write] = new ObjectUsage<T>
{
Finish = finishDelegate,
Usage = UsageType.Write,
Index = write,
};
}
else
{
buffers[write].Usage = UsageType.Write;
}
buffers[write].FrameId = Interlocked.Increment(ref currentFrame);
return buffers[write];
case UsageType.Read:
if (lastWrite < 0) return null;
lock (buffers)
{
read = lastWrite;
buffers[read].Usage = UsageType.Read;
}
return buffers[read];
}
return null;
}
private void finish(ObjectUsage<T> obj, UsageType type)
{
switch (type)
{
case UsageType.Read:
lock (buffers)
buffers[read].Usage = UsageType.None;
break;
case UsageType.Write:
lock (buffers)
{
buffers[write].Usage = UsageType.None;
lastWrite = write;
}
break;
}
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Threading;
namespace osu.Framework.Allocation
{
/// <summary>
/// Handles triple-buffering of any object type.
/// Thread safety assumes at most one writer and one reader.
/// </summary>
public class TripleBuffer<T>
{
private readonly ObjectUsage<T>[] buffers = new ObjectUsage<T>[3];
private int read;
private int write;
private int lastWrite = -1;
private long currentFrame;
private readonly Action<ObjectUsage<T>, UsageType> finishDelegate;
public TripleBuffer()
{
//caching the delegate means we only have to allocate it once, rather than once per created buffer.
finishDelegate = finish;
}
public ObjectUsage<T> Get(UsageType usage)
{
switch (usage)
{
case UsageType.Write:
lock (buffers)
{
while (buffers[write]?.Usage == UsageType.Read || write == lastWrite)
write = (write + 1) % 3;
}
if (buffers[write] == null)
{
buffers[write] = new ObjectUsage<T>
{
Finish = finishDelegate,
Usage = UsageType.Write,
Index = write,
};
}
else
{
buffers[write].Usage = UsageType.Write;
}
buffers[write].FrameId = Interlocked.Increment(ref currentFrame);
return buffers[write];
case UsageType.Read:
if (lastWrite < 0) return null;
lock (buffers)
{
read = lastWrite;
buffers[read].Usage = UsageType.Read;
}
return buffers[read];
}
return null;
}
private void finish(ObjectUsage<T> obj, UsageType type)
{
switch (type)
{
case UsageType.Read:
lock (buffers)
buffers[read].Usage = UsageType.None;
break;
case UsageType.Write:
lock (buffers)
{
buffers[write].Usage = UsageType.None;
lastWrite = write;
}
break;
}
}
}
}

View File

@@ -1,148 +1,148 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Configuration;
namespace osu.Framework.Audio
{
public class AdjustableAudioComponent : AudioComponent
{
private readonly HashSet<BindableDouble> volumeAdjustments = new HashSet<BindableDouble>();
private readonly HashSet<BindableDouble> balanceAdjustments = new HashSet<BindableDouble>();
private readonly HashSet<BindableDouble> frequencyAdjustments = new HashSet<BindableDouble>();
/// <summary>
/// Global volume of this component.
/// </summary>
public readonly BindableDouble Volume = new BindableDouble(1)
{
MinValue = 0,
MaxValue = 1
};
protected readonly BindableDouble VolumeCalculated = new BindableDouble(1)
{
MinValue = 0,
MaxValue = 1
};
/// <summary>
/// Playback balance of this sample (-1 .. 1 where 0 is centered)
/// </summary>
public readonly BindableDouble Balance = new BindableDouble
{
MinValue = -1,
MaxValue = 1
};
protected readonly BindableDouble BalanceCalculated = new BindableDouble
{
MinValue = -1,
MaxValue = 1
};
/// <summary>
/// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency.
/// </summary>
public readonly BindableDouble Frequency = new BindableDouble(1);
protected readonly BindableDouble FrequencyCalculated = new BindableDouble(1);
protected AdjustableAudioComponent()
{
Volume.ValueChanged += InvalidateState;
Balance.ValueChanged += InvalidateState;
Frequency.ValueChanged += InvalidateState;
}
internal void InvalidateState(double newValue = 0)
{
EnqueueAction(OnStateChanged);
}
internal virtual void OnStateChanged()
{
VolumeCalculated.Value = volumeAdjustments.Aggregate(Volume.Value, (current, adj) => current * adj);
BalanceCalculated.Value = balanceAdjustments.Aggregate(Balance.Value, (current, adj) => current + adj);
FrequencyCalculated.Value = frequencyAdjustments.Aggregate(Frequency.Value, (current, adj) => current * adj);
}
public void AddAdjustmentDependency(AdjustableAudioComponent component)
{
AddAdjustment(AdjustableProperty.Balance, component.BalanceCalculated);
AddAdjustment(AdjustableProperty.Frequency, component.FrequencyCalculated);
AddAdjustment(AdjustableProperty.Volume, component.VolumeCalculated);
}
public void RemoveAdjustmentDependency(AdjustableAudioComponent component)
{
RemoveAdjustment(AdjustableProperty.Balance, component.BalanceCalculated);
RemoveAdjustment(AdjustableProperty.Frequency, component.FrequencyCalculated);
RemoveAdjustment(AdjustableProperty.Volume, component.VolumeCalculated);
}
public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
{
switch (type)
{
case AdjustableProperty.Balance:
if (balanceAdjustments.Contains(adjustBindable))
throw new ArgumentException("An adjustable binding may only be registered once.");
balanceAdjustments.Add(adjustBindable);
break;
case AdjustableProperty.Frequency:
if (frequencyAdjustments.Contains(adjustBindable))
throw new ArgumentException("An adjustable binding may only be registered once.");
frequencyAdjustments.Add(adjustBindable);
break;
case AdjustableProperty.Volume:
if (volumeAdjustments.Contains(adjustBindable))
throw new ArgumentException("An adjustable binding may only be registered once.");
volumeAdjustments.Add(adjustBindable);
break;
}
InvalidateState();
}
public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
{
switch (type)
{
case AdjustableProperty.Balance:
balanceAdjustments.Remove(adjustBindable);
break;
case AdjustableProperty.Frequency:
frequencyAdjustments.Remove(adjustBindable);
break;
case AdjustableProperty.Volume:
volumeAdjustments.Remove(adjustBindable);
break;
}
InvalidateState();
}
protected override void Dispose(bool disposing)
{
volumeAdjustments.Clear();
balanceAdjustments.Clear();
frequencyAdjustments.Clear();
base.Dispose(disposing);
}
}
public enum AdjustableProperty
{
Volume,
Balance,
Frequency
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Configuration;
namespace osu.Framework.Audio
{
public class AdjustableAudioComponent : AudioComponent
{
private readonly HashSet<BindableDouble> volumeAdjustments = new HashSet<BindableDouble>();
private readonly HashSet<BindableDouble> balanceAdjustments = new HashSet<BindableDouble>();
private readonly HashSet<BindableDouble> frequencyAdjustments = new HashSet<BindableDouble>();
/// <summary>
/// Global volume of this component.
/// </summary>
public readonly BindableDouble Volume = new BindableDouble(1)
{
MinValue = 0,
MaxValue = 1
};
protected readonly BindableDouble VolumeCalculated = new BindableDouble(1)
{
MinValue = 0,
MaxValue = 1
};
/// <summary>
/// Playback balance of this sample (-1 .. 1 where 0 is centered)
/// </summary>
public readonly BindableDouble Balance = new BindableDouble
{
MinValue = -1,
MaxValue = 1
};
protected readonly BindableDouble BalanceCalculated = new BindableDouble
{
MinValue = -1,
MaxValue = 1
};
/// <summary>
/// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency.
/// </summary>
public readonly BindableDouble Frequency = new BindableDouble(1);
protected readonly BindableDouble FrequencyCalculated = new BindableDouble(1);
protected AdjustableAudioComponent()
{
Volume.ValueChanged += InvalidateState;
Balance.ValueChanged += InvalidateState;
Frequency.ValueChanged += InvalidateState;
}
internal void InvalidateState(double newValue = 0)
{
EnqueueAction(OnStateChanged);
}
internal virtual void OnStateChanged()
{
VolumeCalculated.Value = volumeAdjustments.Aggregate(Volume.Value, (current, adj) => current * adj);
BalanceCalculated.Value = balanceAdjustments.Aggregate(Balance.Value, (current, adj) => current + adj);
FrequencyCalculated.Value = frequencyAdjustments.Aggregate(Frequency.Value, (current, adj) => current * adj);
}
public void AddAdjustmentDependency(AdjustableAudioComponent component)
{
AddAdjustment(AdjustableProperty.Balance, component.BalanceCalculated);
AddAdjustment(AdjustableProperty.Frequency, component.FrequencyCalculated);
AddAdjustment(AdjustableProperty.Volume, component.VolumeCalculated);
}
public void RemoveAdjustmentDependency(AdjustableAudioComponent component)
{
RemoveAdjustment(AdjustableProperty.Balance, component.BalanceCalculated);
RemoveAdjustment(AdjustableProperty.Frequency, component.FrequencyCalculated);
RemoveAdjustment(AdjustableProperty.Volume, component.VolumeCalculated);
}
public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
{
switch (type)
{
case AdjustableProperty.Balance:
if (balanceAdjustments.Contains(adjustBindable))
throw new ArgumentException("An adjustable binding may only be registered once.");
balanceAdjustments.Add(adjustBindable);
break;
case AdjustableProperty.Frequency:
if (frequencyAdjustments.Contains(adjustBindable))
throw new ArgumentException("An adjustable binding may only be registered once.");
frequencyAdjustments.Add(adjustBindable);
break;
case AdjustableProperty.Volume:
if (volumeAdjustments.Contains(adjustBindable))
throw new ArgumentException("An adjustable binding may only be registered once.");
volumeAdjustments.Add(adjustBindable);
break;
}
InvalidateState();
}
public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
{
switch (type)
{
case AdjustableProperty.Balance:
balanceAdjustments.Remove(adjustBindable);
break;
case AdjustableProperty.Frequency:
frequencyAdjustments.Remove(adjustBindable);
break;
case AdjustableProperty.Volume:
volumeAdjustments.Remove(adjustBindable);
break;
}
InvalidateState();
}
protected override void Dispose(bool disposing)
{
volumeAdjustments.Clear();
balanceAdjustments.Clear();
frequencyAdjustments.Clear();
base.Dispose(disposing);
}
}
public enum AdjustableProperty
{
Volume,
Balance,
Frequency
}
}

View File

@@ -1,82 +1,82 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using System.Linq;
namespace osu.Framework.Audio
{
/// <summary>
/// A collection of audio components which need central property control.
/// </summary>
public class AudioCollectionManager<T> : AdjustableAudioComponent
where T : AdjustableAudioComponent
{
protected List<T> Items = new List<T>();
public void AddItem(T item)
{
RegisterItem(item);
AddItemToList(item);
}
public void AddItemToList(T item)
{
EnqueueAction(delegate
{
if (Items.Contains(item)) return;
Items.Add(item);
});
}
public void RegisterItem(T item)
{
EnqueueAction(() => item.AddAdjustmentDependency(this));
}
public void UnregisterItem(T item)
{
EnqueueAction(() => item.RemoveAdjustmentDependency(this));
}
internal override void OnStateChanged()
{
base.OnStateChanged();
foreach (var item in Items)
item.OnStateChanged();
}
public virtual void UpdateDevice(int deviceIndex)
{
foreach (var item in Items.OfType<IBassAudio>())
item.UpdateDevice(deviceIndex);
}
protected override void UpdateChildren()
{
base.UpdateChildren();
for (int i = 0; i < Items.Count; i++)
{
var item = Items[i];
if (!item.IsAlive)
{
Items.RemoveAt(i--);
continue;
}
item.Update();
}
}
public override void Dispose()
{
// we need to queue disposal of our Items before enqueueing the main dispose.
foreach (var i in Items)
i.Dispose();
base.Dispose();
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using System.Linq;
namespace osu.Framework.Audio
{
/// <summary>
/// A collection of audio components which need central property control.
/// </summary>
public class AudioCollectionManager<T> : AdjustableAudioComponent
where T : AdjustableAudioComponent
{
protected List<T> Items = new List<T>();
public void AddItem(T item)
{
RegisterItem(item);
AddItemToList(item);
}
public void AddItemToList(T item)
{
EnqueueAction(delegate
{
if (Items.Contains(item)) return;
Items.Add(item);
});
}
public void RegisterItem(T item)
{
EnqueueAction(() => item.AddAdjustmentDependency(this));
}
public void UnregisterItem(T item)
{
EnqueueAction(() => item.RemoveAdjustmentDependency(this));
}
internal override void OnStateChanged()
{
base.OnStateChanged();
foreach (var item in Items)
item.OnStateChanged();
}
public virtual void UpdateDevice(int deviceIndex)
{
foreach (var item in Items.OfType<IBassAudio>())
item.UpdateDevice(deviceIndex);
}
protected override void UpdateChildren()
{
base.UpdateChildren();
for (int i = 0; i < Items.Count; i++)
{
var item = Items[i];
if (!item.IsAlive)
{
Items.RemoveAt(i--);
continue;
}
item.Update();
}
}
public override void Dispose()
{
// we need to queue disposal of our Items before enqueueing the main dispose.
foreach (var i in Items)
i.Dispose();
base.Dispose();
}
}
}

View File

@@ -1,105 +1,105 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using osu.Framework.Development;
using osu.Framework.Statistics;
namespace osu.Framework.Audio
{
public class AudioComponent : IDisposable, IUpdateable
{
/// <summary>
/// Audio operations will be run on a separate dedicated thread, so we need to schedule any audio API calls using this queue.
/// </summary>
protected ConcurrentQueue<Task> PendingActions = new ConcurrentQueue<Task>();
protected Task EnqueueAction(Action action)
{
var task = new Task(action);
if (ThreadSafety.IsAudioThread)
{
task.RunSynchronously();
return task;
}
if (!acceptingActions)
// we don't want consumers to block on operations after we are disposed.
return Task.CompletedTask;
PendingActions.Enqueue(task);
return task;
}
private bool acceptingActions = true;
~AudioComponent()
{
Dispose(false);
}
/// <summary>
/// Run each loop of the audio thread after queued actions to allow components to update anything they need to.
/// </summary>
protected virtual void UpdateState()
{
}
protected virtual void UpdateChildren()
{
}
/// <summary>
/// Updates this audio component. Always runs on the audio thread.
/// </summary>
public void Update()
{
ThreadSafety.EnsureNotUpdateThread();
if (IsDisposed)
throw new ObjectDisposedException(ToString(), "Can not update disposed audio components.");
FrameStatistics.Add(StatisticsCounterType.TasksRun, PendingActions.Count);
FrameStatistics.Increment(StatisticsCounterType.Components);
while (!IsDisposed && PendingActions.TryDequeue(out Task task))
task.RunSynchronously();
if (!IsDisposed)
UpdateState();
UpdateChildren();
}
/// <summary>
/// This component has completed playback and is now in a stopped state.
/// </summary>
public virtual bool HasCompleted => !IsAlive;
/// <summary>
/// This component has completed all processing and is ready to be removed from its parent.
/// </summary>
public virtual bool IsAlive => !IsDisposed;
public virtual bool IsLoaded => true;
#region IDisposable Support
protected volatile bool IsDisposed; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
IsDisposed = true;
}
public virtual void Dispose()
{
acceptingActions = false;
PendingActions.Enqueue(new Task(() => Dispose(true)));
}
#endregion
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using osu.Framework.Development;
using osu.Framework.Statistics;
namespace osu.Framework.Audio
{
public class AudioComponent : IDisposable, IUpdateable
{
/// <summary>
/// Audio operations will be run on a separate dedicated thread, so we need to schedule any audio API calls using this queue.
/// </summary>
protected ConcurrentQueue<Task> PendingActions = new ConcurrentQueue<Task>();
protected Task EnqueueAction(Action action)
{
var task = new Task(action);
if (ThreadSafety.IsAudioThread)
{
task.RunSynchronously();
return task;
}
if (!acceptingActions)
// we don't want consumers to block on operations after we are disposed.
return Task.CompletedTask;
PendingActions.Enqueue(task);
return task;
}
private bool acceptingActions = true;
~AudioComponent()
{
Dispose(false);
}
/// <summary>
/// Run each loop of the audio thread after queued actions to allow components to update anything they need to.
/// </summary>
protected virtual void UpdateState()
{
}
protected virtual void UpdateChildren()
{
}
/// <summary>
/// Updates this audio component. Always runs on the audio thread.
/// </summary>
public void Update()
{
ThreadSafety.EnsureNotUpdateThread();
if (IsDisposed)
throw new ObjectDisposedException(ToString(), "Can not update disposed audio components.");
FrameStatistics.Add(StatisticsCounterType.TasksRun, PendingActions.Count);
FrameStatistics.Increment(StatisticsCounterType.Components);
while (!IsDisposed && PendingActions.TryDequeue(out Task task))
task.RunSynchronously();
if (!IsDisposed)
UpdateState();
UpdateChildren();
}
/// <summary>
/// This component has completed playback and is now in a stopped state.
/// </summary>
public virtual bool HasCompleted => !IsAlive;
/// <summary>
/// This component has completed all processing and is ready to be removed from its parent.
/// </summary>
public virtual bool IsAlive => !IsDisposed;
public virtual bool IsLoaded => true;
#region IDisposable Support
protected volatile bool IsDisposed; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
IsDisposed = true;
}
public virtual void Dispose()
{
acceptingActions = false;
PendingActions.Enqueue(new Task(() => Dispose(true)));
}
#endregion
}
}

View File

@@ -1,378 +1,378 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using ManagedBass;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.IO.Stores;
using osu.Framework.Threading;
using System.Linq;
using System.Diagnostics;
using osu.Framework.Extensions.TypeExtensions;
namespace osu.Framework.Audio
{
public class AudioManager : AudioCollectionManager<AdjustableAudioComponent>
{
/// <summary>
/// The manager component responsible for audio tracks (e.g. songs).
/// </summary>
public TrackManager Track => GetTrackManager();
/// <summary>
/// The manager component responsible for audio samples (e.g. sound effects).
/// </summary>
public SampleManager Sample => GetSampleManager();
/// <summary>
/// The thread audio operations (mainly Bass calls) are ran on.
/// </summary>
internal readonly AudioThread Thread;
private List<DeviceInfo> audioDevices = new List<DeviceInfo>();
private List<string> audioDeviceNames = new List<string>();
/// <summary>
/// The names of all available audio devices.
/// </summary>
public IEnumerable<string> AudioDeviceNames => audioDeviceNames;
/// <summary>
/// Is fired whenever a new audio device is discovered and provides its name.
/// </summary>
public event Action<string> OnNewDevice;
/// <summary>
/// Is fired whenever an audio device is lost and provides its name.
/// </summary>
public event Action<string> OnLostDevice;
/// <summary>
/// The preferred audio device we should use. A value of
/// <see cref="string.Empty"/> denotes the OS default.
/// </summary>
public readonly Bindable<string> AudioDevice = new Bindable<string>();
private string currentAudioDevice;
/// <summary>
/// Volume of all samples played game-wide.
/// </summary>
public readonly BindableDouble VolumeSample = new BindableDouble(1)
{
MinValue = 0,
MaxValue = 1
};
/// <summary>
/// Volume of all tracks played game-wide.
/// </summary>
public readonly BindableDouble VolumeTrack = new BindableDouble(1)
{
MinValue = 0,
MaxValue = 1
};
private Scheduler scheduler => Thread.Scheduler;
private Scheduler eventScheduler => EventScheduler ?? scheduler;
/// <summary>
/// The scheduler used for invoking publicly exposed delegate events.
/// </summary>
public Scheduler EventScheduler;
private readonly Lazy<TrackManager> globalTrackManager;
private readonly Lazy<SampleManager> globalSampleManager;
/// <summary>
/// Constructs an AudioManager given a track resource store, and a sample resource store.
/// </summary>
/// <param name="trackStore">The resource store containing all audio tracks to be used in the future.</param>
/// <param name="sampleStore">The sample store containing all audio samples to be used in the future.</param>
public AudioManager(ResourceStore<byte[]> trackStore, ResourceStore<byte[]> sampleStore)
{
AudioDevice.ValueChanged += onDeviceChanged;
trackStore.AddExtension(@"mp3");
sampleStore.AddExtension(@"wav");
sampleStore.AddExtension(@"mp3");
Thread = new AudioThread(Update);
Thread.Start();
globalTrackManager = new Lazy<TrackManager>(() => GetTrackManager(trackStore));
globalSampleManager = new Lazy<SampleManager>(() => GetSampleManager(sampleStore));
scheduler.Add(() =>
{
try
{
setAudioDevice();
}
catch
{
}
});
scheduler.AddDelayed(delegate
{
updateAvailableAudioDevices();
checkAudioDeviceChanged();
}, 1000, true);
}
protected override void Dispose(bool disposing)
{
OnNewDevice = null;
OnLostDevice = null;
base.Dispose(disposing);
}
private void onDeviceChanged(string newDevice)
{
scheduler.Add(() => setAudioDevice(string.IsNullOrEmpty(newDevice) ? null : newDevice));
}
/// <summary>
/// Returns a list of the names of recognized audio devices.
/// </summary>
/// <remarks>
/// The No Sound device that is in the list of Audio Devices that are stored internally is not returned.
/// Regarding the .Skip(1) as implementation for removing "No Sound", see http://bass.radio42.com/help/html/e5a666b4-1bdd-d1cb-555e-ce041997d52f.htm.
/// </remarks>
/// <returns>A list of the names of recognized audio devices.</returns>
private IEnumerable<string> getDeviceNames(List<DeviceInfo> devices) => devices.Skip(1).Select(d => d.Name);
/// <summary>
/// Obtains the <see cref="TrackManager"/> corresponding to a given resource store.
/// Returns the global <see cref="TrackManager"/> if no resource store is passed.
/// </summary>
/// <param name="store">The <see cref="T:ResourceStore"/> of which to retrieve the <see cref="TrackManager"/>.</param>
public TrackManager GetTrackManager(ResourceStore<byte[]> store = null)
{
if (store == null) return globalTrackManager.Value;
TrackManager tm = new TrackManager(store);
AddItem(tm);
tm.AddAdjustment(AdjustableProperty.Volume, VolumeTrack);
VolumeTrack.ValueChanged += tm.InvalidateState;
return tm;
}
/// <summary>
/// Obtains the <see cref="SampleManager"/> corresponding to a given resource store.
/// Returns the global <see cref="SampleManager"/> if no resource store is passed.
/// </summary>
/// <param name="store">The <see cref="T:ResourceStore"/> of which to retrieve the <see cref="SampleManager"/>.</param>
public SampleManager GetSampleManager(IResourceStore<byte[]> store = null)
{
if (store == null) return globalSampleManager.Value;
SampleManager sm = new SampleManager(store);
AddItem(sm);
sm.AddAdjustment(AdjustableProperty.Volume, VolumeSample);
VolumeSample.ValueChanged += sm.InvalidateState;
return sm;
}
private List<DeviceInfo> getAllDevices()
{
int deviceCount = Bass.DeviceCount;
List<DeviceInfo> info = new List<DeviceInfo>();
for (int i = 0; i < deviceCount; i++)
info.Add(Bass.GetDeviceInfo(i));
return info;
}
private bool setAudioDevice(string preferredDevice = null)
{
updateAvailableAudioDevices();
string oldDevice = currentAudioDevice;
string newDevice = preferredDevice;
if (string.IsNullOrEmpty(newDevice))
newDevice = audioDevices.Find(df => df.IsDefault).Name;
bool oldDeviceValid = Bass.CurrentDevice >= 0;
if (oldDeviceValid)
{
DeviceInfo oldDeviceInfo = Bass.GetDeviceInfo(Bass.CurrentDevice);
oldDeviceValid &= oldDeviceInfo.IsEnabled && oldDeviceInfo.IsInitialized;
}
if (newDevice == oldDevice)
{
//check the old device is still valid
if (oldDeviceValid)
return true;
}
if (string.IsNullOrEmpty(newDevice))
return false;
int newDeviceIndex = audioDevices.FindIndex(df => df.Name == newDevice);
DeviceInfo newDeviceInfo = new DeviceInfo();
try
{
if (newDeviceIndex >= 0)
newDeviceInfo = Bass.GetDeviceInfo(newDeviceIndex);
//we may have previously initialised this device.
}
catch
{
}
if (oldDeviceValid && (newDeviceInfo.Driver == null || !newDeviceInfo.IsEnabled))
{
//handles the case we are trying to load a user setting which is currently unavailable,
//and we have already fallen back to a sane default.
return true;
}
if (!Bass.Init(newDeviceIndex) && Bass.LastError != Errors.Already)
{
//the new device didn't go as planned. we need another option.
if (preferredDevice == null)
{
//we're fucked. the default device won't initialise.
currentAudioDevice = null;
return false;
}
//let's try again using the default device.
return setAudioDevice();
}
if (Bass.LastError == Errors.Already)
{
// We check if the initialization error is that we already initialized the device
// If it is, it means we can just tell Bass to use the already initialized device without much
// other fuzz.
Bass.CurrentDevice = newDeviceIndex;
Bass.Free();
Bass.Init(newDeviceIndex);
}
Trace.Assert(Bass.LastError == Errors.OK);
//we have successfully initialised a new device.
currentAudioDevice = newDevice;
UpdateDevice(newDeviceIndex);
Bass.PlaybackBufferLength = 100;
Bass.UpdatePeriod = 5;
return true;
}
public override void UpdateDevice(int deviceIndex)
{
Sample.UpdateDevice(deviceIndex);
Track.UpdateDevice(deviceIndex);
}
private void updateAvailableAudioDevices()
{
var currentDeviceList = getAllDevices().Where(d => d.IsEnabled).ToList();
var currentDeviceNames = getDeviceNames(currentDeviceList).ToList();
var newDevices = currentDeviceNames.Except(audioDeviceNames).ToList();
var lostDevices = audioDeviceNames.Except(currentDeviceNames).ToList();
if (newDevices.Count > 0 || lostDevices.Count > 0)
{
eventScheduler.Add(delegate
{
foreach (var d in newDevices)
OnNewDevice?.Invoke(d);
foreach (var d in lostDevices)
OnLostDevice?.Invoke(d);
});
}
audioDevices = currentDeviceList;
audioDeviceNames = currentDeviceNames;
}
private void checkAudioDeviceChanged()
{
try
{
if (AudioDevice.Value == string.Empty)
{
// use default device
var device = Bass.GetDeviceInfo(Bass.CurrentDevice);
if (!device.IsDefault && !setAudioDevice())
{
if (!device.IsEnabled || !setAudioDevice(device.Name))
{
foreach (var d in getAllDevices())
{
if (d.Name == device.Name || !d.IsEnabled)
continue;
if (setAudioDevice(d.Name))
break;
}
}
}
}
else
{
// use whatever is the preferred device
var device = Bass.GetDeviceInfo(Bass.CurrentDevice);
if (device.Name == AudioDevice.Value)
{
if (!device.IsEnabled && !setAudioDevice())
{
foreach (var d in getAllDevices())
{
if (d.Name == device.Name || !d.IsEnabled)
continue;
if (setAudioDevice(d.Name))
break;
}
}
}
else
{
var preferredDevice = getAllDevices().SingleOrDefault(d => d.Name == AudioDevice.Value);
if (preferredDevice.Name == AudioDevice.Value && preferredDevice.IsEnabled)
setAudioDevice(preferredDevice.Name);
else if (!device.IsEnabled && !setAudioDevice())
{
foreach (var d in getAllDevices())
{
if (d.Name == device.Name || !d.IsEnabled)
continue;
if (setAudioDevice(d.Name))
break;
}
}
}
}
}
catch
{
}
}
public override string ToString() => $@"{GetType().ReadableName()} ({currentAudioDevice})";
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using ManagedBass;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.IO.Stores;
using osu.Framework.Threading;
using System.Linq;
using System.Diagnostics;
using osu.Framework.Extensions.TypeExtensions;
namespace osu.Framework.Audio
{
public class AudioManager : AudioCollectionManager<AdjustableAudioComponent>
{
/// <summary>
/// The manager component responsible for audio tracks (e.g. songs).
/// </summary>
public TrackManager Track => GetTrackManager();
/// <summary>
/// The manager component responsible for audio samples (e.g. sound effects).
/// </summary>
public SampleManager Sample => GetSampleManager();
/// <summary>
/// The thread audio operations (mainly Bass calls) are ran on.
/// </summary>
internal readonly AudioThread Thread;
private List<DeviceInfo> audioDevices = new List<DeviceInfo>();
private List<string> audioDeviceNames = new List<string>();
/// <summary>
/// The names of all available audio devices.
/// </summary>
public IEnumerable<string> AudioDeviceNames => audioDeviceNames;
/// <summary>
/// Is fired whenever a new audio device is discovered and provides its name.
/// </summary>
public event Action<string> OnNewDevice;
/// <summary>
/// Is fired whenever an audio device is lost and provides its name.
/// </summary>
public event Action<string> OnLostDevice;
/// <summary>
/// The preferred audio device we should use. A value of
/// <see cref="string.Empty"/> denotes the OS default.
/// </summary>
public readonly Bindable<string> AudioDevice = new Bindable<string>();
private string currentAudioDevice;
/// <summary>
/// Volume of all samples played game-wide.
/// </summary>
public readonly BindableDouble VolumeSample = new BindableDouble(1)
{
MinValue = 0,
MaxValue = 1
};
/// <summary>
/// Volume of all tracks played game-wide.
/// </summary>
public readonly BindableDouble VolumeTrack = new BindableDouble(1)
{
MinValue = 0,
MaxValue = 1
};
private Scheduler scheduler => Thread.Scheduler;
private Scheduler eventScheduler => EventScheduler ?? scheduler;
/// <summary>
/// The scheduler used for invoking publicly exposed delegate events.
/// </summary>
public Scheduler EventScheduler;
private readonly Lazy<TrackManager> globalTrackManager;
private readonly Lazy<SampleManager> globalSampleManager;
/// <summary>
/// Constructs an AudioManager given a track resource store, and a sample resource store.
/// </summary>
/// <param name="trackStore">The resource store containing all audio tracks to be used in the future.</param>
/// <param name="sampleStore">The sample store containing all audio samples to be used in the future.</param>
public AudioManager(ResourceStore<byte[]> trackStore, ResourceStore<byte[]> sampleStore)
{
AudioDevice.ValueChanged += onDeviceChanged;
trackStore.AddExtension(@"mp3");
sampleStore.AddExtension(@"wav");
sampleStore.AddExtension(@"mp3");
Thread = new AudioThread(Update);
Thread.Start();
globalTrackManager = new Lazy<TrackManager>(() => GetTrackManager(trackStore));
globalSampleManager = new Lazy<SampleManager>(() => GetSampleManager(sampleStore));
scheduler.Add(() =>
{
try
{
setAudioDevice();
}
catch
{
}
});
scheduler.AddDelayed(delegate
{
updateAvailableAudioDevices();
checkAudioDeviceChanged();
}, 1000, true);
}
protected override void Dispose(bool disposing)
{
OnNewDevice = null;
OnLostDevice = null;
base.Dispose(disposing);
}
private void onDeviceChanged(string newDevice)
{
scheduler.Add(() => setAudioDevice(string.IsNullOrEmpty(newDevice) ? null : newDevice));
}
/// <summary>
/// Returns a list of the names of recognized audio devices.
/// </summary>
/// <remarks>
/// The No Sound device that is in the list of Audio Devices that are stored internally is not returned.
/// Regarding the .Skip(1) as implementation for removing "No Sound", see http://bass.radio42.com/help/html/e5a666b4-1bdd-d1cb-555e-ce041997d52f.htm.
/// </remarks>
/// <returns>A list of the names of recognized audio devices.</returns>
private IEnumerable<string> getDeviceNames(List<DeviceInfo> devices) => devices.Skip(1).Select(d => d.Name);
/// <summary>
/// Obtains the <see cref="TrackManager"/> corresponding to a given resource store.
/// Returns the global <see cref="TrackManager"/> if no resource store is passed.
/// </summary>
/// <param name="store">The <see cref="T:ResourceStore"/> of which to retrieve the <see cref="TrackManager"/>.</param>
public TrackManager GetTrackManager(ResourceStore<byte[]> store = null)
{
if (store == null) return globalTrackManager.Value;
TrackManager tm = new TrackManager(store);
AddItem(tm);
tm.AddAdjustment(AdjustableProperty.Volume, VolumeTrack);
VolumeTrack.ValueChanged += tm.InvalidateState;
return tm;
}
/// <summary>
/// Obtains the <see cref="SampleManager"/> corresponding to a given resource store.
/// Returns the global <see cref="SampleManager"/> if no resource store is passed.
/// </summary>
/// <param name="store">The <see cref="T:ResourceStore"/> of which to retrieve the <see cref="SampleManager"/>.</param>
public SampleManager GetSampleManager(IResourceStore<byte[]> store = null)
{
if (store == null) return globalSampleManager.Value;
SampleManager sm = new SampleManager(store);
AddItem(sm);
sm.AddAdjustment(AdjustableProperty.Volume, VolumeSample);
VolumeSample.ValueChanged += sm.InvalidateState;
return sm;
}
private List<DeviceInfo> getAllDevices()
{
int deviceCount = Bass.DeviceCount;
List<DeviceInfo> info = new List<DeviceInfo>();
for (int i = 0; i < deviceCount; i++)
info.Add(Bass.GetDeviceInfo(i));
return info;
}
private bool setAudioDevice(string preferredDevice = null)
{
updateAvailableAudioDevices();
string oldDevice = currentAudioDevice;
string newDevice = preferredDevice;
if (string.IsNullOrEmpty(newDevice))
newDevice = audioDevices.Find(df => df.IsDefault).Name;
bool oldDeviceValid = Bass.CurrentDevice >= 0;
if (oldDeviceValid)
{
DeviceInfo oldDeviceInfo = Bass.GetDeviceInfo(Bass.CurrentDevice);
oldDeviceValid &= oldDeviceInfo.IsEnabled && oldDeviceInfo.IsInitialized;
}
if (newDevice == oldDevice)
{
//check the old device is still valid
if (oldDeviceValid)
return true;
}
if (string.IsNullOrEmpty(newDevice))
return false;
int newDeviceIndex = audioDevices.FindIndex(df => df.Name == newDevice);
DeviceInfo newDeviceInfo = new DeviceInfo();
try
{
if (newDeviceIndex >= 0)
newDeviceInfo = Bass.GetDeviceInfo(newDeviceIndex);
//we may have previously initialised this device.
}
catch
{
}
if (oldDeviceValid && (newDeviceInfo.Driver == null || !newDeviceInfo.IsEnabled))
{
//handles the case we are trying to load a user setting which is currently unavailable,
//and we have already fallen back to a sane default.
return true;
}
if (!Bass.Init(newDeviceIndex) && Bass.LastError != Errors.Already)
{
//the new device didn't go as planned. we need another option.
if (preferredDevice == null)
{
//we're fucked. the default device won't initialise.
currentAudioDevice = null;
return false;
}
//let's try again using the default device.
return setAudioDevice();
}
if (Bass.LastError == Errors.Already)
{
// We check if the initialization error is that we already initialized the device
// If it is, it means we can just tell Bass to use the already initialized device without much
// other fuzz.
Bass.CurrentDevice = newDeviceIndex;
Bass.Free();
Bass.Init(newDeviceIndex);
}
Trace.Assert(Bass.LastError == Errors.OK);
//we have successfully initialised a new device.
currentAudioDevice = newDevice;
UpdateDevice(newDeviceIndex);
Bass.PlaybackBufferLength = 100;
Bass.UpdatePeriod = 5;
return true;
}
public override void UpdateDevice(int deviceIndex)
{
Sample.UpdateDevice(deviceIndex);
Track.UpdateDevice(deviceIndex);
}
private void updateAvailableAudioDevices()
{
var currentDeviceList = getAllDevices().Where(d => d.IsEnabled).ToList();
var currentDeviceNames = getDeviceNames(currentDeviceList).ToList();
var newDevices = currentDeviceNames.Except(audioDeviceNames).ToList();
var lostDevices = audioDeviceNames.Except(currentDeviceNames).ToList();
if (newDevices.Count > 0 || lostDevices.Count > 0)
{
eventScheduler.Add(delegate
{
foreach (var d in newDevices)
OnNewDevice?.Invoke(d);
foreach (var d in lostDevices)
OnLostDevice?.Invoke(d);
});
}
audioDevices = currentDeviceList;
audioDeviceNames = currentDeviceNames;
}
private void checkAudioDeviceChanged()
{
try
{
if (AudioDevice.Value == string.Empty)
{
// use default device
var device = Bass.GetDeviceInfo(Bass.CurrentDevice);
if (!device.IsDefault && !setAudioDevice())
{
if (!device.IsEnabled || !setAudioDevice(device.Name))
{
foreach (var d in getAllDevices())
{
if (d.Name == device.Name || !d.IsEnabled)
continue;
if (setAudioDevice(d.Name))
break;
}
}
}
}
else
{
// use whatever is the preferred device
var device = Bass.GetDeviceInfo(Bass.CurrentDevice);
if (device.Name == AudioDevice.Value)
{
if (!device.IsEnabled && !setAudioDevice())
{
foreach (var d in getAllDevices())
{
if (d.Name == device.Name || !d.IsEnabled)
continue;
if (setAudioDevice(d.Name))
break;
}
}
}
else
{
var preferredDevice = getAllDevices().SingleOrDefault(d => d.Name == AudioDevice.Value);
if (preferredDevice.Name == AudioDevice.Value && preferredDevice.IsEnabled)
setAudioDevice(preferredDevice.Name);
else if (!device.IsEnabled && !setAudioDevice())
{
foreach (var d in getAllDevices())
{
if (d.Name == device.Name || !d.IsEnabled)
continue;
if (setAudioDevice(d.Name))
break;
}
}
}
}
}
catch
{
}
}
public override string ToString() => $@"{GetType().ReadableName()} ({currentAudioDevice})";
}
}

View File

@@ -1,10 +1,10 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
namespace osu.Framework.Audio
{
internal interface IBassAudio
{
void UpdateDevice(int deviceIndex);
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
namespace osu.Framework.Audio
{
internal interface IBassAudio
{
void UpdateDevice(int deviceIndex);
}
}

View File

@@ -1,13 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
namespace osu.Framework.Audio
{
public interface IHasPitchAdjust
{
/// <summary>
/// The pitch this track is playing at, relative to original.
/// </summary>
double PitchAdjust { get; set; }
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
namespace osu.Framework.Audio
{
public interface IHasPitchAdjust
{
/// <summary>
/// The pitch this track is playing at, relative to original.
/// </summary>
double PitchAdjust { get; set; }
}
}

View File

@@ -1,21 +1,21 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
namespace osu.Framework.Audio.Sample
{
public abstract class Sample : AudioComponent
{
public const int DEFAULT_CONCURRENCY = 2;
protected readonly int PlaybackConcurrency;
/// <summary>
/// Construct a new sample.
/// </summary>
/// <param name="playbackConcurrency">How many instances of this sample should be allowed to playback concurrently before stopping the longest playing.</param>
protected Sample(int playbackConcurrency = DEFAULT_CONCURRENCY)
{
PlaybackConcurrency = playbackConcurrency;
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
namespace osu.Framework.Audio.Sample
{
public abstract class Sample : AudioComponent
{
public const int DEFAULT_CONCURRENCY = 2;
protected readonly int PlaybackConcurrency;
/// <summary>
/// Construct a new sample.
/// </summary>
/// <param name="playbackConcurrency">How many instances of this sample should be allowed to playback concurrently before stopping the longest playing.</param>
protected Sample(int playbackConcurrency = DEFAULT_CONCURRENCY)
{
PlaybackConcurrency = playbackConcurrency;
}
}
}

View File

@@ -1,40 +1,40 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using ManagedBass;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace osu.Framework.Audio.Sample
{
internal class SampleBass : Sample, IBassAudio
{
private volatile int sampleId;
public override bool IsLoaded => sampleId != 0;
public SampleBass(byte[] data, ConcurrentQueue<Task> customPendingActions = null, int concurrency = DEFAULT_CONCURRENCY)
: base(concurrency)
{
if (customPendingActions != null)
PendingActions = customPendingActions;
EnqueueAction(() => { sampleId = Bass.SampleLoad(data, 0, data.Length, PlaybackConcurrency, BassFlags.Default | BassFlags.SampleOverrideLongestPlaying); });
}
protected override void Dispose(bool disposing)
{
Bass.SampleFree(sampleId);
base.Dispose(disposing);
}
void IBassAudio.UpdateDevice(int deviceIndex)
{
if (IsLoaded)
// counter-intuitively, this is the correct API to use to migrate a sample to a new device.
Bass.ChannelSetDevice(sampleId, deviceIndex);
}
public int CreateChannel() => Bass.SampleGetChannel(sampleId);
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using ManagedBass;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace osu.Framework.Audio.Sample
{
internal class SampleBass : Sample, IBassAudio
{
private volatile int sampleId;
public override bool IsLoaded => sampleId != 0;
public SampleBass(byte[] data, ConcurrentQueue<Task> customPendingActions = null, int concurrency = DEFAULT_CONCURRENCY)
: base(concurrency)
{
if (customPendingActions != null)
PendingActions = customPendingActions;
EnqueueAction(() => { sampleId = Bass.SampleLoad(data, 0, data.Length, PlaybackConcurrency, BassFlags.Default | BassFlags.SampleOverrideLongestPlaying); });
}
protected override void Dispose(bool disposing)
{
Bass.SampleFree(sampleId);
base.Dispose(disposing);
}
void IBassAudio.UpdateDevice(int deviceIndex)
{
if (IsLoaded)
// counter-intuitively, this is the correct API to use to migrate a sample to a new device.
Bass.ChannelSetDevice(sampleId, deviceIndex);
}
public int CreateChannel() => Bass.SampleGetChannel(sampleId);
}
}

View File

@@ -1,56 +1,56 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Statistics;
using System;
namespace osu.Framework.Audio.Sample
{
public abstract class SampleChannel : AdjustableAudioComponent
{
protected bool WasStarted;
protected Sample Sample { get; set; }
private readonly Action<SampleChannel> onPlay;
protected SampleChannel(Sample sample, Action<SampleChannel> onPlay)
{
Sample = sample ?? throw new ArgumentNullException(nameof(sample));
this.onPlay = onPlay;
}
public virtual void Play(bool restart = true)
{
if (IsDisposed)
throw new ObjectDisposedException(ToString(), "Can not play disposed samples.");
onPlay(this);
WasStarted = true;
}
public virtual void Stop()
{
if (IsDisposed)
throw new ObjectDisposedException(ToString(), "Can not stop disposed samples.");
}
protected override void Dispose(bool disposing)
{
Stop();
base.Dispose(disposing);
}
protected override void UpdateState()
{
FrameStatistics.Increment(StatisticsCounterType.SChannels);
base.UpdateState();
}
public abstract bool Playing { get; }
public virtual bool Played => WasStarted && !Playing;
public override bool IsAlive => base.IsAlive && !Played;
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Statistics;
using System;
namespace osu.Framework.Audio.Sample
{
public abstract class SampleChannel : AdjustableAudioComponent
{
protected bool WasStarted;
protected Sample Sample { get; set; }
private readonly Action<SampleChannel> onPlay;
protected SampleChannel(Sample sample, Action<SampleChannel> onPlay)
{
Sample = sample ?? throw new ArgumentNullException(nameof(sample));
this.onPlay = onPlay;
}
public virtual void Play(bool restart = true)
{
if (IsDisposed)
throw new ObjectDisposedException(ToString(), "Can not play disposed samples.");
onPlay(this);
WasStarted = true;
}
public virtual void Stop()
{
if (IsDisposed)
throw new ObjectDisposedException(ToString(), "Can not stop disposed samples.");
}
protected override void Dispose(bool disposing)
{
Stop();
base.Dispose(disposing);
}
protected override void UpdateState()
{
FrameStatistics.Increment(StatisticsCounterType.SChannels);
base.UpdateState();
}
public abstract bool Playing { get; }
public virtual bool Played => WasStarted && !Playing;
public override bool IsAlive => base.IsAlive && !Played;
}
}

View File

@@ -1,97 +1,97 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using ManagedBass;
namespace osu.Framework.Audio.Sample
{
internal class SampleChannelBass : SampleChannel, IBassAudio
{
private volatile int channel;
private volatile bool playing;
public override bool IsLoaded => Sample.IsLoaded;
private float initialFrequency;
public SampleChannelBass(Sample sample, Action<SampleChannel> onPlay)
: base(sample, onPlay)
{
}
void IBassAudio.UpdateDevice(int deviceIndex)
{
// Channels created from samples can not be migrated, so we need to ensure
// a new channel is created after switching the device. We do not need to
// manually free the channel, because our Bass.Free call upon switching devices
// takes care of that.
channel = 0;
}
internal override void OnStateChanged()
{
base.OnStateChanged();
if (channel != 0)
{
Bass.ChannelSetAttribute(channel, ChannelAttribute.Volume, VolumeCalculated);
Bass.ChannelSetAttribute(channel, ChannelAttribute.Pan, BalanceCalculated);
Bass.ChannelSetAttribute(channel, ChannelAttribute.Frequency, initialFrequency * FrequencyCalculated);
}
}
public override void Play(bool restart = true)
{
EnqueueAction(() =>
{
if (!IsLoaded)
{
channel = 0;
return;
}
// We are creating a new channel for every playback, since old channels may
// be overridden when too many other channels are created from the same sample.
channel = ((SampleBass)Sample).CreateChannel();
Bass.ChannelGetAttribute(channel, ChannelAttribute.Frequency, out initialFrequency);
});
InvalidateState();
EnqueueAction(() =>
{
if (channel != 0)
Bass.ChannelPlay(channel, restart);
});
// Needs to happen on the main thread such that
// Played does not become true for a short moment.
playing = true;
base.Play(restart);
}
protected override void UpdateState()
{
base.UpdateState();
playing = channel != 0 && Bass.ChannelIsActive(channel) != 0;
}
public override void Stop()
{
if (channel == 0) return;
base.Stop();
EnqueueAction(() =>
{
Bass.ChannelStop(channel);
// ChannelStop frees the channel.
channel = 0;
});
}
public override bool Playing => playing;
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using ManagedBass;
namespace osu.Framework.Audio.Sample
{
internal class SampleChannelBass : SampleChannel, IBassAudio
{
private volatile int channel;
private volatile bool playing;
public override bool IsLoaded => Sample.IsLoaded;
private float initialFrequency;
public SampleChannelBass(Sample sample, Action<SampleChannel> onPlay)
: base(sample, onPlay)
{
}
void IBassAudio.UpdateDevice(int deviceIndex)
{
// Channels created from samples can not be migrated, so we need to ensure
// a new channel is created after switching the device. We do not need to
// manually free the channel, because our Bass.Free call upon switching devices
// takes care of that.
channel = 0;
}
internal override void OnStateChanged()
{
base.OnStateChanged();
if (channel != 0)
{
Bass.ChannelSetAttribute(channel, ChannelAttribute.Volume, VolumeCalculated);
Bass.ChannelSetAttribute(channel, ChannelAttribute.Pan, BalanceCalculated);
Bass.ChannelSetAttribute(channel, ChannelAttribute.Frequency, initialFrequency * FrequencyCalculated);
}
}
public override void Play(bool restart = true)
{
EnqueueAction(() =>
{
if (!IsLoaded)
{
channel = 0;
return;
}
// We are creating a new channel for every playback, since old channels may
// be overridden when too many other channels are created from the same sample.
channel = ((SampleBass)Sample).CreateChannel();
Bass.ChannelGetAttribute(channel, ChannelAttribute.Frequency, out initialFrequency);
});
InvalidateState();
EnqueueAction(() =>
{
if (channel != 0)
Bass.ChannelPlay(channel, restart);
});
// Needs to happen on the main thread such that
// Played does not become true for a short moment.
playing = true;
base.Play(restart);
}
protected override void UpdateState()
{
base.UpdateState();
playing = channel != 0 && Bass.ChannelIsActive(channel) != 0;
}
public override void Stop()
{
if (channel == 0) return;
base.Stop();
EnqueueAction(() =>
{
Bass.ChannelStop(channel);
// ChannelStop frees the channel.
channel = 0;
});
}
public override bool Playing => playing;
}
}

View File

@@ -1,70 +1,70 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Concurrent;
using System.IO;
using osu.Framework.IO.Stores;
using osu.Framework.Statistics;
using System.Linq;
namespace osu.Framework.Audio.Sample
{
public class SampleManager : AudioCollectionManager<SampleChannel>, IResourceStore<SampleChannel>
{
private readonly IResourceStore<byte[]> store;
private readonly ConcurrentDictionary<string, Sample> sampleCache = new ConcurrentDictionary<string, Sample>();
/// <summary>
/// How many instances of a single sample should be allowed to playback concurrently before stopping the longest playing.
/// </summary>
public int PlaybackConcurrency { get; set; } = Sample.DEFAULT_CONCURRENCY;
public SampleManager(IResourceStore<byte[]> store)
{
this.store = store;
}
public SampleChannel Get(string name)
{
if (string.IsNullOrEmpty(name)) return null;
lock (sampleCache)
{
SampleChannel channel = null;
if (!sampleCache.TryGetValue(name, out Sample sample))
{
byte[] data = store.Get(name);
sample = sampleCache[name] = data == null ? null : new SampleBass(data, PendingActions, PlaybackConcurrency);
}
if (sample != null)
{
channel = new SampleChannelBass(sample, AddItemToList);
RegisterItem(channel);
}
return channel;
}
}
public override void UpdateDevice(int deviceIndex)
{
foreach (var sample in sampleCache.Values.OfType<IBassAudio>())
sample.UpdateDevice(deviceIndex);
base.UpdateDevice(deviceIndex);
}
protected override void UpdateState()
{
FrameStatistics.Add(StatisticsCounterType.Samples, sampleCache.Count);
base.UpdateState();
}
public Stream GetStream(string name)
{
return store.GetStream(name);
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Concurrent;
using System.IO;
using osu.Framework.IO.Stores;
using osu.Framework.Statistics;
using System.Linq;
namespace osu.Framework.Audio.Sample
{
public class SampleManager : AudioCollectionManager<SampleChannel>, IResourceStore<SampleChannel>
{
private readonly IResourceStore<byte[]> store;
private readonly ConcurrentDictionary<string, Sample> sampleCache = new ConcurrentDictionary<string, Sample>();
/// <summary>
/// How many instances of a single sample should be allowed to playback concurrently before stopping the longest playing.
/// </summary>
public int PlaybackConcurrency { get; set; } = Sample.DEFAULT_CONCURRENCY;
public SampleManager(IResourceStore<byte[]> store)
{
this.store = store;
}
public SampleChannel Get(string name)
{
if (string.IsNullOrEmpty(name)) return null;
lock (sampleCache)
{
SampleChannel channel = null;
if (!sampleCache.TryGetValue(name, out Sample sample))
{
byte[] data = store.Get(name);
sample = sampleCache[name] = data == null ? null : new SampleBass(data, PendingActions, PlaybackConcurrency);
}
if (sample != null)
{
channel = new SampleChannelBass(sample, AddItemToList);
RegisterItem(channel);
}
return channel;
}
}
public override void UpdateDevice(int deviceIndex)
{
foreach (var sample in sampleCache.Values.OfType<IBassAudio>())
sample.UpdateDevice(deviceIndex);
base.UpdateDevice(deviceIndex);
}
protected override void UpdateState()
{
FrameStatistics.Add(StatisticsCounterType.Samples, sampleCache.Count);
base.UpdateState();
}
public Stream GetStream(string name)
{
return store.GetStream(name);
}
}
}

View File

@@ -1,87 +1,87 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.IO;
using System.Runtime.InteropServices;
using ManagedBass;
namespace osu.Framework.Audio.Track
{
internal class DataStreamFileProcedures
{
private byte[] readBuffer = new byte[32768];
private readonly Stream dataStream;
public FileProcedures BassProcedures => new FileProcedures
{
Close = ac_Close,
Length = ac_Length,
Read = ac_Read,
Seek = ac_Seek
};
public DataStreamFileProcedures(Stream data)
{
dataStream = data;
}
private void ac_Close(IntPtr user)
{
//manually handle closing of stream
}
private long ac_Length(IntPtr user)
{
if (dataStream == null) return 0;
try
{
return dataStream.Length;
}
catch
{
}
return 0;
}
private int ac_Read(IntPtr buffer, int length, IntPtr user)
{
if (dataStream == null) return 0;
try
{
if (length > readBuffer.Length)
readBuffer = new byte[length];
if (!dataStream.CanRead)
return 0;
int readBytes = dataStream.Read(readBuffer, 0, length);
Marshal.Copy(readBuffer, 0, buffer, readBytes);
return readBytes;
}
catch
{
}
return 0;
}
private bool ac_Seek(long offset, IntPtr user)
{
if (dataStream == null) return false;
try
{
return dataStream.Seek(offset, SeekOrigin.Begin) == offset;
}
catch
{
}
return false;
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.IO;
using System.Runtime.InteropServices;
using ManagedBass;
namespace osu.Framework.Audio.Track
{
internal class DataStreamFileProcedures
{
private byte[] readBuffer = new byte[32768];
private readonly Stream dataStream;
public FileProcedures BassProcedures => new FileProcedures
{
Close = ac_Close,
Length = ac_Length,
Read = ac_Read,
Seek = ac_Seek
};
public DataStreamFileProcedures(Stream data)
{
dataStream = data;
}
private void ac_Close(IntPtr user)
{
//manually handle closing of stream
}
private long ac_Length(IntPtr user)
{
if (dataStream == null) return 0;
try
{
return dataStream.Length;
}
catch
{
}
return 0;
}
private int ac_Read(IntPtr buffer, int length, IntPtr user)
{
if (dataStream == null) return 0;
try
{
if (length > readBuffer.Length)
readBuffer = new byte[length];
if (!dataStream.CanRead)
return 0;
int readBytes = dataStream.Read(readBuffer, 0, length);
Marshal.Copy(readBuffer, 0, buffer, readBytes);
return readBytes;
}
catch
{
}
return 0;
}
private bool ac_Seek(long offset, IntPtr user)
{
if (dataStream == null) return false;
try
{
return dataStream.Seek(offset, SeekOrigin.Begin) == offset;
}
catch
{
}
return false;
}
}
}

View File

@@ -1,134 +1,134 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Configuration;
using osu.Framework.Statistics;
using osu.Framework.Timing;
using System;
namespace osu.Framework.Audio.Track
{
public abstract class Track : AdjustableAudioComponent, IAdjustableClock
{
/// <summary>
/// Is this track capable of producing audio?
/// </summary>
public virtual bool IsDummyDevice => true;
/// <summary>
/// States if this track should repeat.
/// </summary>
public bool Looping { get; set; }
/// <summary>
/// The speed of track playback. Does not affect pitch, but will reduce playback quality due to skipped frames.
/// </summary>
public readonly BindableDouble Tempo = new BindableDouble(1);
protected Track()
{
Tempo.ValueChanged += InvalidateState;
}
/// <summary>
/// Reset this track to a logical default state.
/// </summary>
public virtual void Reset()
{
Volume.Value = 1;
ResetSpeedAdjustments();
Stop();
Seek(0);
}
/// <summary>
/// Restarts this track from the beginning while retaining adjustments.
/// </summary>
public virtual void Restart()
{
Stop();
Seek(0);
Start();
}
public virtual void ResetSpeedAdjustments()
{
Frequency.Value = 1;
Tempo.Value = 1;
}
/// <summary>
/// Current position in milliseconds.
/// </summary>
public abstract double CurrentTime { get; }
private double length;
/// <summary>
/// Length of the track in milliseconds.
/// </summary>
public double Length
{
get => length;
set
{
if (value < 0)
throw new ArgumentException("Track length must be >= 0.", nameof(value));
length = value;
}
}
public virtual int? Bitrate => null;
/// <summary>
/// Seek to a new position.
/// </summary>
/// <param name="seek">New position in milliseconds</param>
/// <returns>Whether the seek was successful.</returns>
public abstract bool Seek(double seek);
public virtual void Start()
{
if (IsDisposed)
throw new ObjectDisposedException(ToString(), "Can not start disposed tracks.");
}
public virtual void Stop()
{
}
public abstract bool IsRunning { get; }
/// <summary>
/// Overall playback rate (1 is 100%, -1 is reversed at 100%).
/// </summary>
public virtual double Rate
{
get { return Frequency * Tempo; }
set { Tempo.Value = value; }
}
public bool IsReversed => Rate < 0;
public override bool HasCompleted => IsLoaded && !IsRunning && CurrentTime >= Length;
/// <summary>
/// Current amplitude of stereo channels where 1 is full volume and 0 is silent.
/// LeftChannel and RightChannel represent the maximum current amplitude of all of the left and right channels respectively.
/// The most recent values are returned. Synchronisation between channels should not be expected.
/// </summary>
public virtual TrackAmplitudes CurrentAmplitudes => new TrackAmplitudes();
protected override void UpdateState()
{
FrameStatistics.Increment(StatisticsCounterType.Tracks);
if (Looping && HasCompleted)
Restart();
base.UpdateState();
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Configuration;
using osu.Framework.Statistics;
using osu.Framework.Timing;
using System;
namespace osu.Framework.Audio.Track
{
public abstract class Track : AdjustableAudioComponent, IAdjustableClock
{
/// <summary>
/// Is this track capable of producing audio?
/// </summary>
public virtual bool IsDummyDevice => true;
/// <summary>
/// States if this track should repeat.
/// </summary>
public bool Looping { get; set; }
/// <summary>
/// The speed of track playback. Does not affect pitch, but will reduce playback quality due to skipped frames.
/// </summary>
public readonly BindableDouble Tempo = new BindableDouble(1);
protected Track()
{
Tempo.ValueChanged += InvalidateState;
}
/// <summary>
/// Reset this track to a logical default state.
/// </summary>
public virtual void Reset()
{
Volume.Value = 1;
ResetSpeedAdjustments();
Stop();
Seek(0);
}
/// <summary>
/// Restarts this track from the beginning while retaining adjustments.
/// </summary>
public virtual void Restart()
{
Stop();
Seek(0);
Start();
}
public virtual void ResetSpeedAdjustments()
{
Frequency.Value = 1;
Tempo.Value = 1;
}
/// <summary>
/// Current position in milliseconds.
/// </summary>
public abstract double CurrentTime { get; }
private double length;
/// <summary>
/// Length of the track in milliseconds.
/// </summary>
public double Length
{
get => length;
set
{
if (value < 0)
throw new ArgumentException("Track length must be >= 0.", nameof(value));
length = value;
}
}
public virtual int? Bitrate => null;
/// <summary>
/// Seek to a new position.
/// </summary>
/// <param name="seek">New position in milliseconds</param>
/// <returns>Whether the seek was successful.</returns>
public abstract bool Seek(double seek);
public virtual void Start()
{
if (IsDisposed)
throw new ObjectDisposedException(ToString(), "Can not start disposed tracks.");
}
public virtual void Stop()
{
}
public abstract bool IsRunning { get; }
/// <summary>
/// Overall playback rate (1 is 100%, -1 is reversed at 100%).
/// </summary>
public virtual double Rate
{
get { return Frequency * Tempo; }
set { Tempo.Value = value; }
}
public bool IsReversed => Rate < 0;
public override bool HasCompleted => IsLoaded && !IsRunning && CurrentTime >= Length;
/// <summary>
/// Current amplitude of stereo channels where 1 is full volume and 0 is silent.
/// LeftChannel and RightChannel represent the maximum current amplitude of all of the left and right channels respectively.
/// The most recent values are returned. Synchronisation between channels should not be expected.
/// </summary>
public virtual TrackAmplitudes CurrentAmplitudes => new TrackAmplitudes();
protected override void UpdateState()
{
FrameStatistics.Increment(StatisticsCounterType.Tracks);
if (Looping && HasCompleted)
Restart();
base.UpdateState();
}
}
}

View File

@@ -1,22 +1,22 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Audio.Track
{
public struct TrackAmplitudes
{
public float LeftChannel;
public float RightChannel;
public float Maximum => Math.Max(LeftChannel, RightChannel);
public float Average => (LeftChannel + RightChannel) / 2;
/// <summary>
/// 256 length array of bins containing the average frequency of both channels at every ~78Hz step of the audible spectrum (0Hz - 20,000Hz).
/// </summary>
public float[] FrequencyAmplitudes;
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Audio.Track
{
public struct TrackAmplitudes
{
public float LeftChannel;
public float RightChannel;
public float Maximum => Math.Max(LeftChannel, RightChannel);
public float Average => (LeftChannel + RightChannel) / 2;
/// <summary>
/// 256 length array of bins containing the average frequency of both channels at every ~78Hz step of the audible spectrum (0Hz - 20,000Hz).
/// </summary>
public float[] FrequencyAmplitudes;
}
}

View File

@@ -1,248 +1,248 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.IO;
using System.Threading;
using ManagedBass;
using ManagedBass.Fx;
using OpenTK;
using osu.Framework.IO;
using System.Diagnostics;
using System.Threading.Tasks;
namespace osu.Framework.Audio.Track
{
public class TrackBass : Track, IBassAudio, IHasPitchAdjust
{
private AsyncBufferStream dataStream;
/// <summary>
/// Should this track only be used for preview purposes? This suggests it has not yet been fully loaded.
/// </summary>
public bool Preview { get; private set; }
/// <summary>
/// The handle for this track, if there is one.
/// </summary>
private int activeStream;
/// <summary>
/// The handle for adjusting tempo.
/// </summary>
private int tempoAdjustStream;
/// <summary>
/// This marks if the track is paused, or stopped to the end.
/// </summary>
private bool isPlayed;
private volatile bool isLoaded;
public override bool IsLoaded => isLoaded;
public TrackBass(Stream data, bool quick = false)
{
EnqueueAction(() =>
{
Preview = quick;
if (data == null)
throw new ArgumentNullException(nameof(data));
//encapsulate incoming stream with async buffer if it isn't already.
dataStream = data as AsyncBufferStream ?? new AsyncBufferStream(data, quick ? 8 : -1);
var procs = new DataStreamFileProcedures(dataStream);
BassFlags flags = Preview ? 0 : BassFlags.Decode | BassFlags.Prescan | BassFlags.Float;
activeStream = Bass.CreateStream(StreamSystem.NoBuffer, flags, procs.BassProcedures, IntPtr.Zero);
if (!Preview)
{
// We assign the BassFlags.Decode streams to the device "bass_nodevice" to prevent them from getting
// cleaned up during a Bass.Free call. This is necessary for seamless switching between audio devices.
// Further, we provide the flag BassFlags.FxFreeSource such that freeing the activeStream also frees
// all parent decoding streams.
const int bass_nodevice = 0x20000;
Bass.ChannelSetDevice(activeStream, bass_nodevice);
tempoAdjustStream = BassFx.TempoCreate(activeStream, BassFlags.Decode | BassFlags.FxFreeSource);
Bass.ChannelSetDevice(activeStream, bass_nodevice);
activeStream = BassFx.ReverseCreate(tempoAdjustStream, 5f, BassFlags.Default | BassFlags.FxFreeSource);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.TempoUseQuickAlgorithm, 1);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.TempoOverlapMilliseconds, 4);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.TempoSequenceMilliseconds, 30);
}
Length = Bass.ChannelBytes2Seconds(activeStream, Bass.ChannelGetLength(activeStream)) * 1000;
Bass.ChannelGetAttribute(activeStream, ChannelAttribute.Frequency, out float frequency);
initialFrequency = frequency;
bitrate = (int)Bass.ChannelGetAttribute(activeStream, ChannelAttribute.Bitrate);
isLoaded = true;
});
InvalidateState();
}
void IBassAudio.UpdateDevice(int deviceIndex)
{
Bass.ChannelSetDevice(activeStream, deviceIndex);
Trace.Assert(Bass.LastError == Errors.OK);
}
protected override void UpdateState()
{
isRunning = Bass.ChannelIsActive(activeStream) == PlaybackState.Playing;
double currentTimeLocal = Bass.ChannelBytes2Seconds(activeStream, Bass.ChannelGetPosition(activeStream)) * 1000;
Interlocked.Exchange(ref currentTime, currentTimeLocal == Length && !isPlayed ? 0 : currentTimeLocal);
var leftChannel = isPlayed ? Bass.ChannelGetLevelLeft(activeStream) / 32768f : -1;
var rightChannel = isPlayed ? Bass.ChannelGetLevelRight(activeStream) / 32768f : -1;
if (leftChannel >= 0 && rightChannel >= 0)
{
currentAmplitudes.LeftChannel = leftChannel;
currentAmplitudes.RightChannel = rightChannel;
float[] tempFrequencyData = new float[256];
Bass.ChannelGetData(activeStream, tempFrequencyData, (int)DataFlags.FFT512);
currentAmplitudes.FrequencyAmplitudes = tempFrequencyData;
}
else
{
currentAmplitudes.LeftChannel = 0;
currentAmplitudes.RightChannel = 0;
currentAmplitudes.FrequencyAmplitudes = new float[256];
}
base.UpdateState();
}
protected override void Dispose(bool disposing)
{
if (activeStream != 0)
{
isRunning = false;
Bass.ChannelStop(activeStream);
Bass.StreamFree(activeStream);
}
activeStream = 0;
dataStream?.Dispose();
dataStream = null;
base.Dispose(disposing);
}
public override bool IsDummyDevice => false;
public override void Stop()
{
base.Stop();
StopAsync().Wait();
}
public async Task StopAsync()
{
await EnqueueAction(() =>
{
if (Bass.ChannelIsActive(activeStream) == PlaybackState.Playing)
Bass.ChannelPause(activeStream);
isPlayed = false;
});
}
private int direction;
private void setDirection(bool reverse)
{
direction = reverse ? -1 : 1;
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.ReverseDirection, direction);
}
public override void Start()
{
base.Start();
StartAsync().Wait();
}
public async Task StartAsync()
{
await EnqueueAction(() =>
{
if (Bass.ChannelPlay(activeStream))
isPlayed = true;
else
isRunning = false;
});
}
public override bool Seek(double seek) => SeekAsync(seek).Result;
public async Task<bool> SeekAsync(double seek)
{
// At this point the track may not yet be loaded which is indicated by a 0 length.
// In that case we still want to return true, hence the conservative length.
double conservativeLength = Length == 0 ? double.MaxValue : Length;
double conservativeClamped = MathHelper.Clamp(seek, 0, conservativeLength);
await EnqueueAction(() =>
{
double clamped = MathHelper.Clamp(seek, 0, Length);
if (clamped != CurrentTime)
{
long pos = Bass.ChannelSeconds2Bytes(activeStream, clamped / 1000d);
Bass.ChannelSetPosition(activeStream, pos);
}
});
return conservativeClamped == seek;
}
private double currentTime;
public override double CurrentTime => currentTime;
private volatile bool isRunning;
public override bool IsRunning => isRunning;
internal override void OnStateChanged()
{
base.OnStateChanged();
setDirection(FrequencyCalculated.Value < 0);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Volume, VolumeCalculated);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Pan, BalanceCalculated);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Frequency, bassFreq);
Bass.ChannelSetAttribute(tempoAdjustStream, ChannelAttribute.Tempo, (Math.Abs(Tempo) - 1) * 100);
}
private volatile float initialFrequency;
private int bassFreq => (int)MathHelper.Clamp(Math.Abs(initialFrequency * FrequencyCalculated), 100, 100000);
private volatile int bitrate;
public override int? Bitrate => bitrate;
public double PitchAdjust
{
get { return Frequency.Value; }
set { Frequency.Value = value; }
}
private TrackAmplitudes currentAmplitudes;
public override TrackAmplitudes CurrentAmplitudes => currentAmplitudes;
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.IO;
using System.Threading;
using ManagedBass;
using ManagedBass.Fx;
using OpenTK;
using osu.Framework.IO;
using System.Diagnostics;
using System.Threading.Tasks;
namespace osu.Framework.Audio.Track
{
public class TrackBass : Track, IBassAudio, IHasPitchAdjust
{
private AsyncBufferStream dataStream;
/// <summary>
/// Should this track only be used for preview purposes? This suggests it has not yet been fully loaded.
/// </summary>
public bool Preview { get; private set; }
/// <summary>
/// The handle for this track, if there is one.
/// </summary>
private int activeStream;
/// <summary>
/// The handle for adjusting tempo.
/// </summary>
private int tempoAdjustStream;
/// <summary>
/// This marks if the track is paused, or stopped to the end.
/// </summary>
private bool isPlayed;
private volatile bool isLoaded;
public override bool IsLoaded => isLoaded;
public TrackBass(Stream data, bool quick = false)
{
EnqueueAction(() =>
{
Preview = quick;
if (data == null)
throw new ArgumentNullException(nameof(data));
//encapsulate incoming stream with async buffer if it isn't already.
dataStream = data as AsyncBufferStream ?? new AsyncBufferStream(data, quick ? 8 : -1);
var procs = new DataStreamFileProcedures(dataStream);
BassFlags flags = Preview ? 0 : BassFlags.Decode | BassFlags.Prescan | BassFlags.Float;
activeStream = Bass.CreateStream(StreamSystem.NoBuffer, flags, procs.BassProcedures, IntPtr.Zero);
if (!Preview)
{
// We assign the BassFlags.Decode streams to the device "bass_nodevice" to prevent them from getting
// cleaned up during a Bass.Free call. This is necessary for seamless switching between audio devices.
// Further, we provide the flag BassFlags.FxFreeSource such that freeing the activeStream also frees
// all parent decoding streams.
const int bass_nodevice = 0x20000;
Bass.ChannelSetDevice(activeStream, bass_nodevice);
tempoAdjustStream = BassFx.TempoCreate(activeStream, BassFlags.Decode | BassFlags.FxFreeSource);
Bass.ChannelSetDevice(activeStream, bass_nodevice);
activeStream = BassFx.ReverseCreate(tempoAdjustStream, 5f, BassFlags.Default | BassFlags.FxFreeSource);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.TempoUseQuickAlgorithm, 1);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.TempoOverlapMilliseconds, 4);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.TempoSequenceMilliseconds, 30);
}
Length = Bass.ChannelBytes2Seconds(activeStream, Bass.ChannelGetLength(activeStream)) * 1000;
Bass.ChannelGetAttribute(activeStream, ChannelAttribute.Frequency, out float frequency);
initialFrequency = frequency;
bitrate = (int)Bass.ChannelGetAttribute(activeStream, ChannelAttribute.Bitrate);
isLoaded = true;
});
InvalidateState();
}
void IBassAudio.UpdateDevice(int deviceIndex)
{
Bass.ChannelSetDevice(activeStream, deviceIndex);
Trace.Assert(Bass.LastError == Errors.OK);
}
protected override void UpdateState()
{
isRunning = Bass.ChannelIsActive(activeStream) == PlaybackState.Playing;
double currentTimeLocal = Bass.ChannelBytes2Seconds(activeStream, Bass.ChannelGetPosition(activeStream)) * 1000;
Interlocked.Exchange(ref currentTime, currentTimeLocal == Length && !isPlayed ? 0 : currentTimeLocal);
var leftChannel = isPlayed ? Bass.ChannelGetLevelLeft(activeStream) / 32768f : -1;
var rightChannel = isPlayed ? Bass.ChannelGetLevelRight(activeStream) / 32768f : -1;
if (leftChannel >= 0 && rightChannel >= 0)
{
currentAmplitudes.LeftChannel = leftChannel;
currentAmplitudes.RightChannel = rightChannel;
float[] tempFrequencyData = new float[256];
Bass.ChannelGetData(activeStream, tempFrequencyData, (int)DataFlags.FFT512);
currentAmplitudes.FrequencyAmplitudes = tempFrequencyData;
}
else
{
currentAmplitudes.LeftChannel = 0;
currentAmplitudes.RightChannel = 0;
currentAmplitudes.FrequencyAmplitudes = new float[256];
}
base.UpdateState();
}
protected override void Dispose(bool disposing)
{
if (activeStream != 0)
{
isRunning = false;
Bass.ChannelStop(activeStream);
Bass.StreamFree(activeStream);
}
activeStream = 0;
dataStream?.Dispose();
dataStream = null;
base.Dispose(disposing);
}
public override bool IsDummyDevice => false;
public override void Stop()
{
base.Stop();
StopAsync().Wait();
}
public async Task StopAsync()
{
await EnqueueAction(() =>
{
if (Bass.ChannelIsActive(activeStream) == PlaybackState.Playing)
Bass.ChannelPause(activeStream);
isPlayed = false;
});
}
private int direction;
private void setDirection(bool reverse)
{
direction = reverse ? -1 : 1;
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.ReverseDirection, direction);
}
public override void Start()
{
base.Start();
StartAsync().Wait();
}
public async Task StartAsync()
{
await EnqueueAction(() =>
{
if (Bass.ChannelPlay(activeStream))
isPlayed = true;
else
isRunning = false;
});
}
public override bool Seek(double seek) => SeekAsync(seek).Result;
public async Task<bool> SeekAsync(double seek)
{
// At this point the track may not yet be loaded which is indicated by a 0 length.
// In that case we still want to return true, hence the conservative length.
double conservativeLength = Length == 0 ? double.MaxValue : Length;
double conservativeClamped = MathHelper.Clamp(seek, 0, conservativeLength);
await EnqueueAction(() =>
{
double clamped = MathHelper.Clamp(seek, 0, Length);
if (clamped != CurrentTime)
{
long pos = Bass.ChannelSeconds2Bytes(activeStream, clamped / 1000d);
Bass.ChannelSetPosition(activeStream, pos);
}
});
return conservativeClamped == seek;
}
private double currentTime;
public override double CurrentTime => currentTime;
private volatile bool isRunning;
public override bool IsRunning => isRunning;
internal override void OnStateChanged()
{
base.OnStateChanged();
setDirection(FrequencyCalculated.Value < 0);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Volume, VolumeCalculated);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Pan, BalanceCalculated);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Frequency, bassFreq);
Bass.ChannelSetAttribute(tempoAdjustStream, ChannelAttribute.Tempo, (Math.Abs(Tempo) - 1) * 100);
}
private volatile float initialFrequency;
private int bassFreq => (int)MathHelper.Clamp(Math.Abs(initialFrequency * FrequencyCalculated), 100, 100000);
private volatile int bitrate;
public override int? Bitrate => bitrate;
public double PitchAdjust
{
get { return Frequency.Value; }
set { Frequency.Value = value; }
}
private TrackAmplitudes currentAmplitudes;
public override TrackAmplitudes CurrentAmplitudes => currentAmplitudes;
}
}

View File

@@ -1,26 +1,26 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.IO.Stores;
namespace osu.Framework.Audio.Track
{
public class TrackManager : AudioCollectionManager<Track>
{
private readonly IResourceStore<byte[]> store;
public TrackManager(IResourceStore<byte[]> store)
{
this.store = store;
}
public Track Get(string name)
{
if (string.IsNullOrEmpty(name)) return null;
TrackBass track = new TrackBass(store.GetStream(name));
AddItem(track);
return track;
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.IO.Stores;
namespace osu.Framework.Audio.Track
{
public class TrackManager : AudioCollectionManager<Track>
{
private readonly IResourceStore<byte[]> store;
public TrackManager(IResourceStore<byte[]> store)
{
this.store = store;
}
public Track Get(string name)
{
if (string.IsNullOrEmpty(name)) return null;
TrackBass track = new TrackBass(store.GetStream(name));
AddItem(track);
return track;
}
}
}

View File

@@ -1,92 +1,92 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Timing;
using OpenTK;
namespace osu.Framework.Audio.Track
{
public class TrackVirtual : Track
{
private readonly StopwatchClock clock = new StopwatchClock();
private double seekOffset;
public TrackVirtual()
{
Length = double.PositiveInfinity;
}
public override bool Seek(double seek)
{
double current = CurrentTime;
seekOffset = seek;
lock (clock)
{
if (IsRunning)
clock.Restart();
else
clock.Reset();
}
seekOffset = MathHelper.Clamp(seekOffset, 0, Length);
return current != seekOffset;
}
public override void Start()
{
lock (clock) clock.Start();
}
public override void Reset()
{
lock (clock) clock.Reset();
seekOffset = 0;
base.Reset();
}
public override void Stop()
{
lock (clock) clock.Stop();
}
public override bool IsRunning
{
get
{
lock (clock) return clock.IsRunning;
}
}
public override double CurrentTime
{
get
{
lock (clock) return seekOffset + clock.CurrentTime;
}
}
protected override void UpdateState()
{
base.UpdateState();
lock (clock)
{
if (CurrentTime >= Length)
Stop();
}
}
internal override void OnStateChanged()
{
base.OnStateChanged();
lock (clock)
clock.Rate = Tempo;
}
}
}
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Timing;
using OpenTK;
namespace osu.Framework.Audio.Track
{
public class TrackVirtual : Track
{
private readonly StopwatchClock clock = new StopwatchClock();
private double seekOffset;
public TrackVirtual()
{
Length = double.PositiveInfinity;
}
public override bool Seek(double seek)
{
double current = CurrentTime;
seekOffset = seek;
lock (clock)
{
if (IsRunning)
clock.Restart();
else
clock.Reset();
}
seekOffset = MathHelper.Clamp(seekOffset, 0, Length);
return current != seekOffset;
}
public override void Start()
{
lock (clock) clock.Start();
}
public override void Reset()
{
lock (clock) clock.Reset();
seekOffset = 0;
base.Reset();
}
public override void Stop()
{
lock (clock) clock.Stop();
}
public override bool IsRunning
{
get
{
lock (clock) return clock.IsRunning;
}
}
public override double CurrentTime
{
get
{
lock (clock) return seekOffset + clock.CurrentTime;
}
}
protected override void UpdateState()
{
base.UpdateState();
lock (clock)
{
if (CurrentTime >= Length)
Stop();
}
}
internal override void OnStateChanged()
{
base.OnStateChanged();
lock (clock)
clock.Rate = Tempo;
}
}
}

Some files were not shown because too many files have changed in this diff Show More