mirror of
https://github.com/SK-la/osu-framework.git
synced 2026-03-13 11:20:31 +00:00
Normalize all the line endings
This commit is contained in:
514
.gitignore
vendored
514
.gitignore
vendored
@@ -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
|
||||
30
.travis.yml
30
.travis.yml
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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})";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user