Normalize all the line endings

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

514
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,212 +1,212 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing; using osu.Framework.Testing;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Framework.Tests.Visual namespace osu.Framework.Tests.Visual
{ {
[System.ComponentModel.Description("word-wrap and paragraphs")] [System.ComponentModel.Description("word-wrap and paragraphs")]
public class TestCaseTextFlow : TestCase public class TestCaseTextFlow : TestCase
{ {
public TestCaseTextFlow() public TestCaseTextFlow()
{ {
FillFlowContainer flow; FillFlowContainer flow;
Children = new Drawable[] Children = new Drawable[]
{ {
new ScrollContainer new ScrollContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new[] Children = new[]
{ {
flow = new FillFlowContainer flow = new FillFlowContainer
{ {
Anchor = Anchor.TopLeft, Anchor = Anchor.TopLeft,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
} }
} }
} }
}; };
FillFlowContainer paragraphContainer; FillFlowContainer paragraphContainer;
TextFlowContainer textFlowContainer; TextFlowContainer textFlowContainer;
flow.Add(paragraphContainer = new FillFlowContainer flow.Add(paragraphContainer = new FillFlowContainer
{ {
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Width = 0.5f, Width = 0.5f,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
textFlowContainer = new TextFlowContainer textFlowContainer = new TextFlowContainer
{ {
FirstLineIndent = 5, FirstLineIndent = 5,
ContentIndent = 10, ContentIndent = 10,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
} }
} }
}); });
textFlowContainer.AddText("the considerably swift vermilion reynard bounds above the slothful mahogany hound.", t => t.Colour = Color4.Yellow); 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("\nTHE ", t => t.Colour = Color4.Red);
textFlowContainer.AddText("CONSIDERABLY", t => t.Colour = Color4.Pink); 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(" SWIFT VERMILION REYNARD BOUNDS ABOVE THE SLOTHFUL MAHOGANY HOUND!!", t => t.Colour = Color4.Red);
textFlowContainer.AddText("\n\n0123456789!@#$%^&*()_-+-[]{}.,<>;'\\\\", t => t.Colour = Color4.Blue); textFlowContainer.AddText("\n\n0123456789!@#$%^&*()_-+-[]{}.,<>;'\\\\", t => t.Colour = Color4.Blue);
var textSize = 48f; var textSize = 48f;
textFlowContainer.AddParagraph("Multiple Text Sizes", t => textFlowContainer.AddParagraph("Multiple Text Sizes", t =>
{ {
t.TextSize = textSize; t.TextSize = textSize;
textSize -= 12f; textSize -= 12f;
}); });
textFlowContainer.AddText("\nI'm a paragraph\nnewlines are cool", t => t.Colour = Color4.Beige); 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.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("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); textFlowContainer.AddParagraph("Make\nTextFlowContainer\ngreat\nagain!", t => t.Colour = Color4.Red);
paragraphContainer.Add(new TextFlowContainer paragraphContainer.Add(new TextFlowContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Text = 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 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]" 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 paragraphContainer.Add(new TestCaseCustomText
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Placeholders = new Drawable[] Placeholders = new Drawable[]
{ {
new LineBaseBox new LineBaseBox
{ {
Colour = Color4.Purple, Colour = Color4.Purple,
LineBaseHeight = 25f, LineBaseHeight = 25f,
Size = new Vector2(25, 25) Size = new Vector2(25, 25)
}.WithEffect(new OutlineEffect }.WithEffect(new OutlineEffect
{ {
Strength = 20f, Strength = 20f,
PadExtent = true, PadExtent = true,
BlurSigma = new Vector2(5f), BlurSigma = new Vector2(5f),
Colour = Color4.White Colour = Color4.White
}) })
}, },
Text = "Test icons [RedBox] interleaved\n[GreenBox] with other [0] text, also [[0]] escaping stuff is possible." Text = "Test icons [RedBox] interleaved\n[GreenBox] with other [0] text, also [[0]] escaping stuff is possible."
}); });
paragraphContainer.Add(new Container paragraphContainer.Add(new Container
{ {
Size = new Vector2(300), Size = new Vector2(300),
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Box
{ {
Name = "Background", Name = "Background",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Alpha = 0.1f Alpha = 0.1f
}, },
new TextFlowContainer new TextFlowContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.TopLeft, TextAnchor = Anchor.TopLeft,
Text = "TopLeft" Text = "TopLeft"
}, },
new TextFlowContainer new TextFlowContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.TopCentre, TextAnchor = Anchor.TopCentre,
Text = "TopCentre" Text = "TopCentre"
}, },
new TextFlowContainer new TextFlowContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.TopRight, TextAnchor = Anchor.TopRight,
Text = "TopRight" Text = "TopRight"
}, },
new TextFlowContainer new TextFlowContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.BottomLeft, TextAnchor = Anchor.BottomLeft,
Text = "BottomLeft" Text = "BottomLeft"
}, },
new TextFlowContainer new TextFlowContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.BottomCentre, TextAnchor = Anchor.BottomCentre,
Text = "BottomCentre" Text = "BottomCentre"
}, },
new TextFlowContainer new TextFlowContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.BottomRight, TextAnchor = Anchor.BottomRight,
Text = "BottomRight" Text = "BottomRight"
}, },
new TextFlowContainer new TextFlowContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.CentreLeft, TextAnchor = Anchor.CentreLeft,
Text = "CentreLeft" Text = "CentreLeft"
}, },
new TextFlowContainer new TextFlowContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.Centre, TextAnchor = Anchor.Centre,
Text = "Centre" Text = "Centre"
}, },
new TextFlowContainer new TextFlowContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.CentreRight, TextAnchor = Anchor.CentreRight,
Text = "CentreRight" Text = "CentreRight"
} }
} }
}); });
AddStep(@"resize paragraph 1", () => { paragraphContainer.Width = 1f; }); AddStep(@"resize paragraph 1", () => { paragraphContainer.Width = 1f; });
AddStep(@"resize paragraph 2", () => { paragraphContainer.Width = 0.6f; }); AddStep(@"resize paragraph 2", () => { paragraphContainer.Width = 0.6f; });
AddStep(@"header inset", () => { textFlowContainer.FirstLineIndent += 2; }); AddStep(@"header inset", () => { textFlowContainer.FirstLineIndent += 2; });
AddStep(@"body inset", () => { textFlowContainer.ContentIndent += 4; }); AddStep(@"body inset", () => { textFlowContainer.ContentIndent += 4; });
AddToggleStep(@"Zero paragraph spacing", state => textFlowContainer.ParagraphSpacing = state ? 0 : 0.5f); AddToggleStep(@"Zero paragraph spacing", state => textFlowContainer.ParagraphSpacing = state ? 0 : 0.5f);
AddToggleStep(@"Non-zero line spacing", state => textFlowContainer.LineSpacing = state ? 1 : 0); AddToggleStep(@"Non-zero line spacing", state => textFlowContainer.LineSpacing = state ? 1 : 0);
} }
private class LineBaseBox : Box, IHasLineBaseHeight private class LineBaseBox : Box, IHasLineBaseHeight
{ {
public float LineBaseHeight { get; set; } public float LineBaseHeight { get; set; }
} }
private class TestCaseCustomText : CustomizableTextContainer private class TestCaseCustomText : CustomizableTextContainer
{ {
public TestCaseCustomText() public TestCaseCustomText()
{ {
AddIconFactory("RedBox", makeRedBox); AddIconFactory("RedBox", makeRedBox);
AddIconFactory("GreenBox", makeGreenBox); AddIconFactory("GreenBox", makeGreenBox);
} }
private Drawable makeGreenBox() => new LineBaseBox private Drawable makeGreenBox() => new LineBaseBox
{ {
Colour = Color4.Green, Colour = Color4.Green,
LineBaseHeight = 25f, LineBaseHeight = 25f,
Size = new Vector2(25, 20) Size = new Vector2(25, 20)
}; };
private Drawable makeRedBox() => new LineBaseBox private Drawable makeRedBox() => new LineBaseBox
{ {
Colour = Color4.Red, Colour = Color4.Red,
LineBaseHeight = 10f, LineBaseHeight = 10f,
Size = new Vector2(25, 25) Size = new Vector2(25, 25)
}; };
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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