331 changed files with 12947 additions and 3028 deletions
@ -1,7 +1,6 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<packages> |
|||
<package id="Rx-Core" version="2.2.5" targetFramework="net45" /> |
|||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" /> |
|||
<package id="Splat" version="1.6.1" targetFramework="net45" /> |
|||
<package id="StyleCop.MSBuild" version="4.7.49.0" targetFramework="net45" developmentDependency="true" /> |
|||
<package id="Rx-Core" version="2.2.5" targetFramework="net45" userInstalled="true" /> |
|||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" userInstalled="true" /> |
|||
<package id="Splat" version="1.6.2" targetFramework="net45" userInstalled="true" /> |
|||
</packages> |
|||
@ -1,8 +1,7 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<packages> |
|||
<package id="Rx-Core" version="2.2.5" targetFramework="net45" /> |
|||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" /> |
|||
<package id="Rx-Linq" version="2.2.5" targetFramework="net45" /> |
|||
<package id="Splat" version="1.6.1" targetFramework="net45" /> |
|||
<package id="StyleCop.MSBuild" version="4.7.49.0" targetFramework="net45" developmentDependency="true" /> |
|||
<package id="Rx-Core" version="2.2.5" targetFramework="net45" userInstalled="true" /> |
|||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" userInstalled="true" /> |
|||
<package id="Rx-Linq" version="2.2.5" targetFramework="net45" userInstalled="true" /> |
|||
<package id="Splat" version="1.6.2" targetFramework="net45" userInstalled="true" /> |
|||
</packages> |
|||
@ -1,161 +0,0 @@ |
|||
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00 |
|||
# Visual Studio 14 |
|||
VisualStudioVersion = 14.0.22609.0 |
|||
MinimumVisualStudioVersion = 10.0.40219.1 |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Win32", "Windows\Perspex.Win32\Perspex.Win32.csproj", "{811A76CF-1CF6-440F-963B-BBE31BD72A82}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication", "TestApplication\TestApplication.csproj", "{E3A1060B-50D0-44E8-88B6-F44EF2E5BD72}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Direct2D1", "Windows\Perspex.Direct2D1\Perspex.Direct2D1.csproj", "{3E908F67-5543-4879-A1DC-08EACE79B3CD}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Base", "Perspex.Base\Perspex.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.SceneGraph", "Perspex.SceneGraph\Perspex.SceneGraph.csproj", "{EB582467-6ABB-43A1-B052-E981BA910E3A}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Layout", "Perspex.Layout\Perspex.Layout.csproj", "{42472427-4774-4C81-8AFF-9F27B8E31721}" |
|||
EndProject |
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Windows", "Windows", "{B39A8919-9F95-48FE-AD7B-76E08B509888}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Input", "Perspex.Input\Perspex.Input.csproj", "{62024B2D-53EB-4638-B26B-85EEAA54866E}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Interactivity", "Perspex.Interactivity\Perspex.Interactivity.csproj", "{6B0ED19D-A08B-461C-A9D9-A9EE40B0C06B}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Controls", "Perspex.Controls\Perspex.Controls.csproj", "{D2221C82-4A25-4583-9B43-D791E3F6820C}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Styling", "Perspex.Styling\Perspex.Styling.csproj", "{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Styling.UnitTests", "Tests\Perspex.Styling.UnitTests\Perspex.Styling.UnitTests.csproj", "{47ECDF59-DEF8-4C53-87B1-2098A3429059}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Controls.UnitTests", "Tests\Perspex.Controls.UnitTests\Perspex.Controls.UnitTests.csproj", "{5CCB5571-7C30-4E7D-967D-0E2158EBD91F}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Themes.Default", "Perspex.Themes.Default\Perspex.Themes.Default.csproj", "{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.SceneGraph.UnitTests", "Tests\Perspex.SceneGraph.UnitTests\Perspex.SceneGraph.UnitTests.csproj", "{76716382-3159-460E-BDA6-C5715CF606D7}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Application", "Perspex.Application\Perspex.Application.csproj", "{799A7BB5-3C2C-48B6-85A7-406A12C420DA}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Diagnostics", "Perspex.Diagnostics\Perspex.Diagnostics.csproj", "{7062AE20-5DCC-4442-9645-8195BDECE63E}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Base.UnitTests", "Tests\Perspex.Base.UnitTests\Perspex.Base.UnitTests.csproj", "{2905FF23-53FB-45E6-AA49-6AF47A172056}" |
|||
EndProject |
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utils", "Utils", "{2BAFBE53-7FA4-4BB9-976F-9AFCC4F9847D}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NGenerics", "NGenerics\NGenerics.csproj", "{415E048E-4611-4815-9CF2-D774E29079AC}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Layout.UnitTests", "Tests\Perspex.Layout.UnitTests\Perspex.Layout.UnitTests.csproj", "{DB070A10-BF39-4752-8456-86E9D5928478}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Animation", "Perspex.Animation\Perspex.Animation.csproj", "{D211E587-D8BC-45B9-95A4-F297C8FA5200}" |
|||
EndProject |
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{596AF748-EAA9-4698-A78C-98724783AFC1}" |
|||
EndProject |
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Direct2D1.RenderTests", "Tests\Perspex.RenderTests\Perspex.Direct2D1.RenderTests.csproj", "{DABFD304-D6A4-4752-8123-C2CCF7AC7831}" |
|||
EndProject |
|||
Global |
|||
GlobalSection(SolutionConfigurationPlatforms) = preSolution |
|||
Debug|Any CPU = Debug|Any CPU |
|||
Release|Any CPU = Release|Any CPU |
|||
EndGlobalSection |
|||
GlobalSection(ProjectConfigurationPlatforms) = postSolution |
|||
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{E3A1060B-50D0-44E8-88B6-F44EF2E5BD72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{E3A1060B-50D0-44E8-88B6-F44EF2E5BD72}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{E3A1060B-50D0-44E8-88B6-F44EF2E5BD72}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{E3A1060B-50D0-44E8-88B6-F44EF2E5BD72}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{EB582467-6ABB-43A1-B052-E981BA910E3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{EB582467-6ABB-43A1-B052-E981BA910E3A}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{EB582467-6ABB-43A1-B052-E981BA910E3A}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{EB582467-6ABB-43A1-B052-E981BA910E3A}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{42472427-4774-4C81-8AFF-9F27B8E31721}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{42472427-4774-4C81-8AFF-9F27B8E31721}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{42472427-4774-4C81-8AFF-9F27B8E31721}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{42472427-4774-4C81-8AFF-9F27B8E31721}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{62024B2D-53EB-4638-B26B-85EEAA54866E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{62024B2D-53EB-4638-B26B-85EEAA54866E}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{62024B2D-53EB-4638-B26B-85EEAA54866E}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{62024B2D-53EB-4638-B26B-85EEAA54866E}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{6B0ED19D-A08B-461C-A9D9-A9EE40B0C06B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{6B0ED19D-A08B-461C-A9D9-A9EE40B0C06B}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{6B0ED19D-A08B-461C-A9D9-A9EE40B0C06B}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{6B0ED19D-A08B-461C-A9D9-A9EE40B0C06B}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{D2221C82-4A25-4583-9B43-D791E3F6820C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{D2221C82-4A25-4583-9B43-D791E3F6820C}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{D2221C82-4A25-4583-9B43-D791E3F6820C}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{D2221C82-4A25-4583-9B43-D791E3F6820C}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{47ECDF59-DEF8-4C53-87B1-2098A3429059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{47ECDF59-DEF8-4C53-87B1-2098A3429059}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{47ECDF59-DEF8-4C53-87B1-2098A3429059}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{47ECDF59-DEF8-4C53-87B1-2098A3429059}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{5CCB5571-7C30-4E7D-967D-0E2158EBD91F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{5CCB5571-7C30-4E7D-967D-0E2158EBD91F}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{5CCB5571-7C30-4E7D-967D-0E2158EBD91F}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{5CCB5571-7C30-4E7D-967D-0E2158EBD91F}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{76716382-3159-460E-BDA6-C5715CF606D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{76716382-3159-460E-BDA6-C5715CF606D7}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{76716382-3159-460E-BDA6-C5715CF606D7}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{76716382-3159-460E-BDA6-C5715CF606D7}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{7062AE20-5DCC-4442-9645-8195BDECE63E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{7062AE20-5DCC-4442-9645-8195BDECE63E}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{7062AE20-5DCC-4442-9645-8195BDECE63E}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{7062AE20-5DCC-4442-9645-8195BDECE63E}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{2905FF23-53FB-45E6-AA49-6AF47A172056}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{2905FF23-53FB-45E6-AA49-6AF47A172056}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{2905FF23-53FB-45E6-AA49-6AF47A172056}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{2905FF23-53FB-45E6-AA49-6AF47A172056}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{415E048E-4611-4815-9CF2-D774E29079AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{415E048E-4611-4815-9CF2-D774E29079AC}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{415E048E-4611-4815-9CF2-D774E29079AC}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{415E048E-4611-4815-9CF2-D774E29079AC}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{DB070A10-BF39-4752-8456-86E9D5928478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{DB070A10-BF39-4752-8456-86E9D5928478}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{DB070A10-BF39-4752-8456-86E9D5928478}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{DB070A10-BF39-4752-8456-86E9D5928478}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{D211E587-D8BC-45B9-95A4-F297C8FA5200}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{D211E587-D8BC-45B9-95A4-F297C8FA5200}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{D211E587-D8BC-45B9-95A4-F297C8FA5200}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{D211E587-D8BC-45B9-95A4-F297C8FA5200}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{DABFD304-D6A4-4752-8123-C2CCF7AC7831}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{DABFD304-D6A4-4752-8123-C2CCF7AC7831}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{DABFD304-D6A4-4752-8123-C2CCF7AC7831}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{DABFD304-D6A4-4752-8123-C2CCF7AC7831}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
EndGlobalSection |
|||
GlobalSection(SolutionProperties) = preSolution |
|||
HideSolutionNode = FALSE |
|||
EndGlobalSection |
|||
GlobalSection(NestedProjects) = preSolution |
|||
{811A76CF-1CF6-440F-963B-BBE31BD72A82} = {B39A8919-9F95-48FE-AD7B-76E08B509888} |
|||
{3E908F67-5543-4879-A1DC-08EACE79B3CD} = {B39A8919-9F95-48FE-AD7B-76E08B509888} |
|||
{47ECDF59-DEF8-4C53-87B1-2098A3429059} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} |
|||
{5CCB5571-7C30-4E7D-967D-0E2158EBD91F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} |
|||
{76716382-3159-460E-BDA6-C5715CF606D7} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} |
|||
{2905FF23-53FB-45E6-AA49-6AF47A172056} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} |
|||
{415E048E-4611-4815-9CF2-D774E29079AC} = {2BAFBE53-7FA4-4BB9-976F-9AFCC4F9847D} |
|||
{DB070A10-BF39-4752-8456-86E9D5928478} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} |
|||
{DABFD304-D6A4-4752-8123-C2CCF7AC7831} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} |
|||
EndGlobalSection |
|||
EndGlobal |
|||
@ -0,0 +1,58 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="Animation.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Animation |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// Tracks the progress of an animation.
|
|||
/// </summary>
|
|||
public class Animation : IObservable<object>, IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The animation being tracked.
|
|||
/// </summary>
|
|||
private IObservable<object> inner; |
|||
|
|||
/// <summary>
|
|||
/// The disposable used to cancel the animation.
|
|||
/// </summary>
|
|||
private IDisposable subscription; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Animation"/> class.
|
|||
/// </summary>
|
|||
/// <param name="inner">The animation observable being tracked.</param>
|
|||
/// <param name="subscription">A disposable used to cancel the animation.</param>
|
|||
public Animation(IObservable<object> inner, IDisposable subscription) |
|||
{ |
|||
this.inner = inner; |
|||
this.subscription = subscription; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Cancels the animation.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.subscription.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Notifies the provider that an observer is to receive notifications.
|
|||
/// </summary>
|
|||
/// <param name="observer">The observer.</param>
|
|||
/// <returns>
|
|||
/// A reference to an interface that allows observers to stop receiving notifications
|
|||
/// before the provider has finished sending them.
|
|||
/// </returns>
|
|||
public IDisposable Subscribe(IObserver<object> observer) |
|||
{ |
|||
return this.inner.Subscribe(observer); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="Animation`1.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Animation |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// Tracks the progress of an animation.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of the value being animated./</typeparam>
|
|||
public class Animation<T> : IObservable<T>, IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The animation being tracked.
|
|||
/// </summary>
|
|||
private IObservable<T> inner; |
|||
|
|||
/// <summary>
|
|||
/// The disposable used to cancel the animation.
|
|||
/// </summary>
|
|||
private IDisposable subscription; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Animation{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="inner">The animation observable being tracked.</param>
|
|||
/// <param name="subscription">A disposable used to cancel the animation.</param>
|
|||
public Animation(IObservable<T> inner, IDisposable subscription) |
|||
{ |
|||
this.inner = inner; |
|||
this.subscription = subscription; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Cancels the animation.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.subscription.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Notifies the provider that an observer is to receive notifications.
|
|||
/// </summary>
|
|||
/// <param name="observer">The observer.</param>
|
|||
/// <returns>
|
|||
/// A reference to an interface that allows observers to stop receiving notifications
|
|||
/// before the provider has finished sending them.
|
|||
/// </returns>
|
|||
public IDisposable Subscribe(IObserver<T> observer) |
|||
{ |
|||
return this.inner.Subscribe(observer); |
|||
} |
|||
} |
|||
} |
|||
@ -1,18 +1,26 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="PropertyTransition.cs" company="Steven Kirk">
|
|||
// <copyright file="IEasing.cs" company="Steven Kirk">
|
|||
// Copyright 2014 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the interface for easing functions.
|
|||
/// </summary>
|
|||
public interface IEasing |
|||
{ |
|||
/// <summary>
|
|||
/// Returns the value of the transition for the specified progress.
|
|||
/// </summary>
|
|||
/// <param name="progress">The progress of the transition, from 0 to 1.</param>
|
|||
/// <param name="start">The start value of the transition.</param>
|
|||
/// <param name="finish">The end value of the transition.</param>
|
|||
/// <returns>
|
|||
/// A value between <paramref name="start"/> and <paramref name="finish"/> as determined
|
|||
/// by <paramref name="progress"/>.
|
|||
/// </returns>
|
|||
object Ease(double progress, object start, object finish); |
|||
} |
|||
|
|||
public interface IEasing<T> : IEasing |
|||
{ |
|||
T Ease(double progress, T start, T finish); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,27 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="IEasing`1.cs" company="Steven Kirk">
|
|||
// Copyright 2014 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the interface for easing functions.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of the property being transitioned.</typeparam>
|
|||
public interface IEasing<T> : IEasing |
|||
{ |
|||
/// <summary>
|
|||
/// Returns the value of the transition for the specified progress.
|
|||
/// </summary>
|
|||
/// <param name="progress">The progress of the transition, from 0 to 1.</param>
|
|||
/// <param name="start">The start value of the transition.</param>
|
|||
/// <param name="finish">The end value of the transition.</param>
|
|||
/// <returns>
|
|||
/// A value between <paramref name="start"/> and <paramref name="finish"/> as determined
|
|||
/// by <paramref name="progress"/>.
|
|||
/// </returns>
|
|||
T Ease(double progress, T start, T finish); |
|||
} |
|||
} |
|||
@ -1,8 +1,9 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<packages> |
|||
<package id="Rx-Core" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-Linq" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-Main" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-Core" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Rx-Linq" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Rx-Main" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="StyleCop.Analyzers" version="1.0.0-alpha008" targetFramework="portable45-net45+win8" userInstalled="true" /> |
|||
</packages> |
|||
@ -1,10 +1,10 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<packages> |
|||
<package id="Rx-Core" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-Linq" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-Main" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Splat" version="1.6.1" targetFramework="portable-net45+win" /> |
|||
<package id="StyleCop.MSBuild" version="4.7.49.0" targetFramework="portable-net45+win" developmentDependency="true" /> |
|||
<package id="Rx-Core" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Rx-Linq" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Rx-Main" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Splat" version="1.6.2" targetFramework="portable45-net45+win8" userInstalled="true" /> |
|||
<package id="StyleCop.Analyzers" version="1.0.0-alpha008" targetFramework="portable45-net45+win8" userInstalled="true" /> |
|||
</packages> |
|||
@ -0,0 +1,35 @@ |
|||
// --------------------------------------------------------------------
|
|||
// <copyright file="PerspexObjectExtensions.cs" company="Steven Kirk">
|
|||
// Copyright 2014 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Diagnostics |
|||
{ |
|||
public static class PerspexObjectExtensions |
|||
{ |
|||
public static PerspexPropertyValue GetDiagnostic(this PerspexObject o, PerspexProperty property) |
|||
{ |
|||
var set = o.GetSetValues(); |
|||
|
|||
PriorityValue value; |
|||
|
|||
if (set.TryGetValue(property, out value)) |
|||
{ |
|||
return new PerspexPropertyValue( |
|||
property, |
|||
value.Value, |
|||
(BindingPriority)value.ValuePriority, |
|||
value.GetDiagnostic()); |
|||
} |
|||
else |
|||
{ |
|||
return new PerspexPropertyValue( |
|||
property, |
|||
o.GetValue(property), |
|||
BindingPriority.Unset, |
|||
"Unset"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="GlobalSuppressions.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage( |
|||
"StyleCop.CSharp.MaintainabilityRules", |
|||
"SA1401:Fields must be private", |
|||
Justification = "PerspexProperty fields should not be private.")] |
|||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage( |
|||
"StyleCop.CSharp.DocumentationRules", |
|||
"SA1609:Property documentation must have value", |
|||
Justification = "This rule is fscking pointless")] |
|||
@ -0,0 +1,53 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="PerspexObjectExtensions.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex |
|||
{ |
|||
using System; |
|||
using System.Reactive.Linq; |
|||
|
|||
/// <summary>
|
|||
/// Provides extension methods for <see cref="PerspexObject"/> and related classes.
|
|||
/// </summary>
|
|||
public static class PerspexObjectExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Subscribes to a property changed notifications for changes that originate from a
|
|||
/// <typeparamref name="TTarget"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TTarget">The type of the property change sender.</typeparam>
|
|||
/// <param name="observable">The property changed observable.</param>
|
|||
/// <param name="handler">Given a TTarget, returns the handler.</param>
|
|||
/// <returns>A disposable that can be used to terminate the subscription.</returns>
|
|||
public static IDisposable AddClassHandler<TTarget>( |
|||
this IObservable<PerspexPropertyChangedEventArgs> observable, |
|||
Func<TTarget, Action<PerspexPropertyChangedEventArgs>> handler) |
|||
where TTarget : class |
|||
{ |
|||
return observable.Subscribe(e => SubscribeAdapter(e, handler)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Observer method for <see cref="AddClassHandler{TTarget}(IObservable{PerspexPropertyChangedEventArgs},
|
|||
/// Func{TTarget, Action{PerspexPropertyChangedEventArgs}})"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TTarget">The sender type to accept.</typeparam>
|
|||
/// <param name="e">The event args.</param>
|
|||
/// <param name="handler">Given a TTarget, returns the handler.</param>
|
|||
private static void SubscribeAdapter<TTarget>( |
|||
PerspexPropertyChangedEventArgs e, |
|||
Func<TTarget, Action<PerspexPropertyChangedEventArgs>> handler) |
|||
where TTarget : class |
|||
{ |
|||
var target = e.Sender as TTarget; |
|||
|
|||
if (target != null) |
|||
{ |
|||
handler(target)(e); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="PerspexProperty`1.cs" company="Steven Kirk">
|
|||
// Copyright 2014 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// A typed perspex property.
|
|||
/// </summary>
|
|||
/// <typeparam name="TValue">The value type of the property.</typeparam>
|
|||
public class PerspexProperty<TValue> : PerspexProperty |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PerspexProperty{TValue}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="name">The name of the property.</param>
|
|||
/// <param name="ownerType">The type of the class that registers the property.</param>
|
|||
/// <param name="defaultValue">The default value of the property.</param>
|
|||
/// <param name="inherits">Whether the property inherits its value.</param>
|
|||
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
|
|||
/// <param name="coerce">A coercion function.</param>
|
|||
/// <param name="isAttached">Whether the property is an attached property.</param>
|
|||
public PerspexProperty( |
|||
string name, |
|||
Type ownerType, |
|||
TValue defaultValue = default(TValue), |
|||
bool inherits = false, |
|||
BindingMode defaultBindingMode = BindingMode.Default, |
|||
Func<PerspexObject, TValue, TValue> coerce = null, |
|||
bool isAttached = false) |
|||
: base( |
|||
name, |
|||
typeof(TValue), |
|||
ownerType, |
|||
defaultValue, |
|||
inherits, |
|||
defaultBindingMode, |
|||
Convert(coerce), |
|||
isAttached) |
|||
{ |
|||
Contract.Requires<NullReferenceException>(name != null); |
|||
Contract.Requires<NullReferenceException>(ownerType != null); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers the property on another type.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOwner">The type of the additional owner.</typeparam>
|
|||
/// <returns>The property.</returns>
|
|||
public PerspexProperty<TValue> AddOwner<TOwner>() |
|||
{ |
|||
PerspexObject.Register(typeof(TOwner), this); |
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the default value for the property on the specified type.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type.</typeparam>
|
|||
/// <returns>The default value.</returns>
|
|||
public TValue GetDefaultValue<T>() |
|||
{ |
|||
return (TValue)this.GetDefaultValue(typeof(T)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts from a typed coercion function to an untyped.
|
|||
/// </summary>
|
|||
/// <param name="f">The typed coercion function.</param>
|
|||
/// <returns>Te untyped coercion function.</returns>
|
|||
private static Func<PerspexObject, object, object> Convert(Func<PerspexObject, TValue, TValue> f) |
|||
{ |
|||
return f != null ? (o, v) => f(o, (TValue)v) : (Func<PerspexObject, object, object>)null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="PriorityBindingEntry.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// A registered binding in a <see cref="PriorityValue"/>.
|
|||
/// </summary>
|
|||
internal class PriorityBindingEntry : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The binding subscription.
|
|||
/// </summary>
|
|||
private IDisposable subscription; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PriorityBindingEntry"/> class.
|
|||
/// </summary>
|
|||
/// <param name="index">
|
|||
/// The binding index. Later bindings should have higher indexes.
|
|||
/// </param>
|
|||
public PriorityBindingEntry(int index) |
|||
{ |
|||
this.Index = index; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the observable associated with the entry.
|
|||
/// </summary>
|
|||
public IObservable<object> Observable { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a description of the binding.
|
|||
/// </summary>
|
|||
public string Description |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the binding entry index. Later bindings will have higher indexes.
|
|||
/// </summary>
|
|||
public int Index |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The current value of the binding.
|
|||
/// </summary>
|
|||
public object Value |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Starts listening to the binding.
|
|||
/// </summary>
|
|||
/// <param name="binding">The binding.</param>
|
|||
/// <param name="changed">Called when the binding changes.</param>
|
|||
/// <param name="completed">Called when the binding completes.</param>
|
|||
public void Start( |
|||
IObservable<object> binding, |
|||
Action<PriorityBindingEntry> changed, |
|||
Action<PriorityBindingEntry> completed) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(binding != null); |
|||
Contract.Requires<ArgumentNullException>(changed != null); |
|||
Contract.Requires<ArgumentNullException>(completed != null); |
|||
|
|||
if (this.subscription != null) |
|||
{ |
|||
throw new Exception("PriorityValue.Entry.Start() called more than once."); |
|||
} |
|||
|
|||
this.Observable = binding; |
|||
this.Value = PerspexProperty.UnsetValue; |
|||
|
|||
if (binding is IDescription) |
|||
{ |
|||
this.Description = ((IDescription)binding).Description; |
|||
} |
|||
|
|||
this.subscription = binding.Subscribe( |
|||
value => |
|||
{ |
|||
this.Value = value; |
|||
changed(this); |
|||
}, |
|||
() => completed(this)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Ends the binding subscription.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
if (this.subscription != null) |
|||
{ |
|||
this.subscription.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,210 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="PriorityLevel.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Disposables; |
|||
|
|||
/// <summary>
|
|||
/// Determines how the current binding is selected for a <see cref="PriorityLevel"/>.
|
|||
/// </summary>
|
|||
internal enum LevelPrecedenceMode |
|||
{ |
|||
/// <summary>
|
|||
/// The latest fired binding is used as the current value.
|
|||
/// </summary>
|
|||
Latest, |
|||
|
|||
/// <summary>
|
|||
/// The latest added binding is used as the current value.
|
|||
/// </summary>
|
|||
Newest, |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores bindings for a priority level in a <see cref="PriorityValue"/>.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// Each priority level in a <see cref="PriorityValue"/> has a current <see cref="Value"/>,
|
|||
/// a list of <see cref="Bindings"/> and a <see cref="DirectValue"/>. When there are no
|
|||
/// bindings present, or all bindings return <see cref="PerspexProperty.UnsetValue"/> then
|
|||
/// <code>Value</code> will equal <code>DirectValue</code>.
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// When there are bindings present, then the latest added binding that doesn't return
|
|||
/// <code>UnsetValue</code> will take precedence. The active binding is returned by the
|
|||
/// <see cref="ActiveBindingIndex"/> property (which refers to the active binding's
|
|||
/// <see cref="PriorityBindingEntry.Index"/> property rather than the index in
|
|||
/// <code>Bindings</code>).
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// If <code>DirectValue</code> is set while a binding is active, then it will replace the
|
|||
/// current value until the active binding fires again.
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
internal class PriorityLevel |
|||
{ |
|||
/// <summary>
|
|||
/// Method called when current value changes.
|
|||
/// </summary>
|
|||
private Action<PriorityLevel> changed; |
|||
|
|||
/// <summary>
|
|||
/// The current direct value.
|
|||
/// </summary>
|
|||
private object directValue; |
|||
|
|||
/// <summary>
|
|||
/// The index of the next <see cref="PriorityBindingEntry"/>.
|
|||
/// </summary>
|
|||
private int nextIndex; |
|||
|
|||
private LevelPrecedenceMode mode; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PriorityLevel"/> class.
|
|||
/// </summary>
|
|||
/// <param name="priority">The priority.</param>
|
|||
/// <param name="mode">The precedence mode.</param>
|
|||
/// <param name="changed">A method to be called when the current value changes.</param>
|
|||
public PriorityLevel( |
|||
int priority, |
|||
LevelPrecedenceMode mode, |
|||
Action<PriorityLevel> changed) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(changed != null); |
|||
|
|||
this.mode = mode; |
|||
this.changed = changed; |
|||
this.Priority = priority; |
|||
this.Value = this.directValue = PerspexProperty.UnsetValue; |
|||
this.ActiveBindingIndex = -1; |
|||
this.Bindings = new LinkedList<PriorityBindingEntry>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the priority of this level.
|
|||
/// </summary>
|
|||
public int Priority { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the direct value for this priority level.
|
|||
/// </summary>
|
|||
public object DirectValue |
|||
{ |
|||
get |
|||
{ |
|||
return this.directValue; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
this.Value = this.directValue = value; |
|||
this.changed(this); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current binding for the priority level.
|
|||
/// </summary>
|
|||
public object Value { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="PriorityBindingEntry.Index"/> value of the active binding, or -1
|
|||
/// if no binding is active.
|
|||
/// </summary>
|
|||
public int ActiveBindingIndex { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the bindings for the priority level.
|
|||
/// </summary>
|
|||
public LinkedList<PriorityBindingEntry> Bindings { get; } |
|||
|
|||
/// <summary>
|
|||
/// Adds a binding.
|
|||
/// </summary>
|
|||
/// <param name="binding">The binding to add.</param>
|
|||
/// <returns>A disposable used to remove the binding.</returns>
|
|||
public IDisposable Add(IObservable<object> binding) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(binding != null); |
|||
|
|||
var entry = new PriorityBindingEntry(this.nextIndex++); |
|||
var node = this.Bindings.AddFirst(entry); |
|||
|
|||
entry.Start(binding, this.Changed, this.Completed); |
|||
|
|||
return Disposable.Create(() => |
|||
{ |
|||
this.Bindings.Remove(node); |
|||
|
|||
if (entry.Index >= this.ActiveBindingIndex) |
|||
{ |
|||
this.ActivateFirstBinding(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invoked when an entry in <see cref="Bindings"/> changes value.
|
|||
/// </summary>
|
|||
/// <param name="entry">The entry that changed.</param>
|
|||
private void Changed(PriorityBindingEntry entry) |
|||
{ |
|||
if (this.mode == LevelPrecedenceMode.Latest || entry.Index >= this.ActiveBindingIndex) |
|||
{ |
|||
if (entry.Value != PerspexProperty.UnsetValue) |
|||
{ |
|||
this.Value = entry.Value; |
|||
this.ActiveBindingIndex = entry.Index; |
|||
this.changed(this); |
|||
} |
|||
else |
|||
{ |
|||
this.ActivateFirstBinding(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invoked when an entry in <see cref="Bindings"/> completes.
|
|||
/// </summary>
|
|||
/// <param name="entry">The entry that completed.</param>
|
|||
private void Completed(PriorityBindingEntry entry) |
|||
{ |
|||
this.Bindings.Remove(entry); |
|||
|
|||
if (entry.Index >= this.ActiveBindingIndex) |
|||
{ |
|||
this.ActivateFirstBinding(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Activates the first binding that has a value.
|
|||
/// </summary>
|
|||
private void ActivateFirstBinding() |
|||
{ |
|||
foreach (var binding in this.Bindings) |
|||
{ |
|||
if (binding.Value != PerspexProperty.UnsetValue) |
|||
{ |
|||
this.Value = binding.Value; |
|||
this.ActiveBindingIndex = binding.Index; |
|||
this.changed(this); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
this.Value = this.DirectValue; |
|||
this.ActiveBindingIndex = -1; |
|||
this.changed(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="PerspexObservable.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Reactive |
|||
{ |
|||
using System; |
|||
using System.Reactive; |
|||
using System.Reactive.Disposables; |
|||
|
|||
public sealed class PerspexObservable<T> : ObservableBase<T>, IDescription |
|||
{ |
|||
private readonly Func<IObserver<T>, IDisposable> subscribe; |
|||
|
|||
public PerspexObservable(Func<IObserver<T>, IDisposable> subscribe, string description) |
|||
{ |
|||
if (subscribe == null) |
|||
{ |
|||
throw new ArgumentNullException("subscribe"); |
|||
} |
|||
|
|||
this.subscribe = subscribe; |
|||
this.Description = description; |
|||
} |
|||
|
|||
public string Description { get; } |
|||
|
|||
protected override IDisposable SubscribeCore(IObserver<T> observer) |
|||
{ |
|||
return this.subscribe(observer) ?? Disposable.Empty; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="TypeUtilities.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Utilities |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
|
|||
/// <summary>
|
|||
/// Provides utilities for working with types at runtime.
|
|||
/// </summary>
|
|||
internal static class TypeUtilities |
|||
{ |
|||
private static readonly Dictionary<Type, List<Type>> Conversions = new Dictionary<Type, List<Type>>() |
|||
{ |
|||
{ typeof(decimal), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char) } }, |
|||
{ typeof(double), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } }, |
|||
{ typeof(float), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } }, |
|||
{ typeof(ulong), new List<Type> { typeof(byte), typeof(ushort), typeof(uint), typeof(char) } }, |
|||
{ typeof(long), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(char) } }, |
|||
{ typeof(uint), new List<Type> { typeof(byte), typeof(ushort), typeof(char) } }, |
|||
{ typeof(int), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(char) } }, |
|||
{ typeof(ushort), new List<Type> { typeof(byte), typeof(char) } }, |
|||
{ typeof(short), new List<Type> { typeof(byte) } } |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Try to cast a value to a type, using implicit conversions if possible.
|
|||
/// </summary>
|
|||
/// <param name="to">The type to cast to.</param>
|
|||
/// <param name="value">The value to cast.</param>
|
|||
/// <param name="result">If sucessful, contains the cast value.</param>
|
|||
/// <returns>True if the cast was sucessful, otherwise false.</returns>
|
|||
public static bool TryCast(Type to, object value, out object result) |
|||
{ |
|||
Contract.Requires<NullReferenceException>(to != null); |
|||
|
|||
if (value == null) |
|||
{ |
|||
var t = to.GetTypeInfo(); |
|||
result = null; |
|||
return !t.IsValueType || (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(Nullable<>))); |
|||
} |
|||
|
|||
var from = value.GetType(); |
|||
|
|||
if (value == PerspexProperty.UnsetValue) |
|||
{ |
|||
result = value; |
|||
return true; |
|||
} |
|||
else if (to.GetTypeInfo().IsAssignableFrom(from.GetTypeInfo())) |
|||
{ |
|||
result = value; |
|||
return true; |
|||
} |
|||
else if (Conversions.ContainsKey(to) && Conversions[to].Contains(from)) |
|||
{ |
|||
result = Convert.ChangeType(value, to); |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
var cast = from.GetTypeInfo() |
|||
.GetDeclaredMethods("op_Implicit") |
|||
.FirstOrDefault(m => m.ReturnType == to); |
|||
|
|||
if (cast != null) |
|||
{ |
|||
result = cast.Invoke(null, new[] { value }); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
result = null; |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -1,10 +1,11 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<packages> |
|||
<package id="Rx-Core" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-Linq" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-Main" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable-net45+win" /> |
|||
<package id="Splat" version="1.6.1" targetFramework="portable-net45+win" /> |
|||
<package id="StyleCop.MSBuild" version="4.7.49.0" targetFramework="portable-net45+win" developmentDependency="true" /> |
|||
<package id="Rx-Core" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Rx-Linq" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Rx-Main" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
|||
<package id="Serilog" version="1.5.5" targetFramework="portable45-net45+win8" userInstalled="true" /> |
|||
<package id="Splat" version="1.6.2" targetFramework="portable45-net45+win8" userInstalled="true" /> |
|||
<package id="StyleCop.Analyzers" version="1.0.0-alpha008" targetFramework="portable45-net45+win8" userInstalled="true" /> |
|||
</packages> |
|||
@ -0,0 +1,65 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="Deck.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
using System.Collections; |
|||
using Perspex.Animation; |
|||
using Perspex.Controls.Generators; |
|||
using Perspex.Controls.Primitives; |
|||
using Perspex.Controls.Utils; |
|||
using Perspex.Input; |
|||
|
|||
/// <summary>
|
|||
/// A selecting items control that displays a single item that fills the control.
|
|||
/// </summary>
|
|||
public class Deck : SelectingItemsControl |
|||
{ |
|||
public static readonly PerspexProperty<IPageTransition> TransitionProperty = |
|||
PerspexProperty.Register<Deck, IPageTransition>("Transition"); |
|||
|
|||
private static readonly ItemsPanelTemplate PanelTemplate = |
|||
new ItemsPanelTemplate(() => new Panel()); |
|||
|
|||
static Deck() |
|||
{ |
|||
ItemsPanelProperty.OverrideDefaultValue(typeof(Deck), PanelTemplate); |
|||
} |
|||
|
|||
public IPageTransition Transition |
|||
{ |
|||
get { return this.GetValue(TransitionProperty); } |
|||
set { this.SetValue(TransitionProperty, value); } |
|||
} |
|||
|
|||
protected override void ItemsChanged(IEnumerable oldValue, IEnumerable newValue) |
|||
{ |
|||
base.ItemsChanged(oldValue, newValue); |
|||
|
|||
var items = this.Items; |
|||
|
|||
if (items != null && items.Count() > 0) |
|||
{ |
|||
this.SelectedIndex = 0; |
|||
} |
|||
} |
|||
|
|||
protected override void OnKeyDown(KeyEventArgs e) |
|||
{ |
|||
// Ignore key presses.
|
|||
} |
|||
|
|||
protected override void OnPointerPressed(PointerPressEventArgs e) |
|||
{ |
|||
// Ignore pointer presses.
|
|||
} |
|||
|
|||
protected override void OnTemplateApplied() |
|||
{ |
|||
base.OnTemplateApplied(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="GlobalSuppressions.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage( |
|||
"StyleCop.CSharp.MaintainabilityRules", |
|||
"SA1401:Fields must be private", |
|||
Justification = "PerspexProperty fields should not be private.")] |
|||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage( |
|||
"StyleCop.CSharp.DocumentationRules", |
|||
"SA1609:Property documentation must have value", |
|||
Justification = "This rule is fscking pointless")] |
|||
@ -1,13 +1,13 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="IObservableDescription.cs" company="Steven Kirk">
|
|||
// <copyright file="TemplateExtensions.cs" company="Steven Kirk">
|
|||
// Copyright 2014 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex |
|||
namespace Perspex.Controls |
|||
{ |
|||
public interface IObservableDescription |
|||
public interface INamed |
|||
{ |
|||
string Description { get; } |
|||
string Name { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="INavigablePanel.cs" company="Steven Kirk">
|
|||
// Copyright 2014 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
using Perspex.Input; |
|||
|
|||
public interface INavigablePanel |
|||
{ |
|||
Control GetControl(FocusNavigationDirection direction, Control from); |
|||
} |
|||
} |
|||
@ -0,0 +1,230 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="Menu.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Reactive.Disposables; |
|||
using Perspex.Input; |
|||
using Perspex.LogicalTree; |
|||
using Perspex.Rendering; |
|||
using Perspex.Interactivity; |
|||
using Perspex.Controls.Primitives; |
|||
|
|||
/// <summary>
|
|||
/// A top-level menu control.
|
|||
/// </summary>
|
|||
public class Menu : SelectingItemsControl, IFocusScope, IMainMenu |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the default items panel used by a <see cref="Menu"/>.
|
|||
/// </summary>
|
|||
private static readonly ItemsPanelTemplate DefaultPanel = |
|||
new ItemsPanelTemplate(() => new StackPanel { Orientation = Orientation.Horizontal }); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="IsOpen"/> property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<bool> IsOpenProperty = |
|||
PerspexProperty.Register<Menu, bool>(nameof(IsOpen)); |
|||
|
|||
/// <summary>
|
|||
/// Tracks event handlers added to the root of the visual tree.
|
|||
/// </summary>
|
|||
private IDisposable subscription; |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="Menu"/> class.
|
|||
/// </summary>
|
|||
static Menu() |
|||
{ |
|||
ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel); |
|||
MenuItem.ClickEvent.AddClassHandler<Menu>(x => x.OnMenuClick); |
|||
MenuItem.SubmenuOpenedEvent.AddClassHandler<Menu>(x => x.OnSubmenuOpened); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the menu is open.
|
|||
/// </summary>
|
|||
public bool IsOpen |
|||
{ |
|||
get { return this.GetValue(IsOpenProperty); } |
|||
private set { this.SetValue(IsOpenProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the selected <see cref="MenuItem"/> container.
|
|||
/// </summary>
|
|||
private MenuItem SelectedMenuItem |
|||
{ |
|||
get |
|||
{ |
|||
return (this.SelectedItem != null) ? |
|||
(MenuItem)this.ItemContainerGenerator.GetContainerForItem(this.SelectedItem) : |
|||
null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Closes the menu.
|
|||
/// </summary>
|
|||
public void Close() |
|||
{ |
|||
foreach (MenuItem i in this.GetLogicalChildren()) |
|||
{ |
|||
i.IsSubMenuOpen = false; |
|||
} |
|||
|
|||
this.IsOpen = false; |
|||
this.SelectedIndex = -1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Opens the menu in response to the Alt/F10 key.
|
|||
/// </summary>
|
|||
public void Open() |
|||
{ |
|||
this.SelectedIndex = 0; |
|||
this.SelectedMenuItem.Focus(); |
|||
this.IsOpen = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="MenuItem"/> is attached to the visual tree.
|
|||
/// </summary>
|
|||
/// <param name="root">The root of the visual tree.</param>
|
|||
protected override void OnAttachedToVisualTree(IRenderRoot root) |
|||
{ |
|||
base.OnAttachedToVisualTree(root); |
|||
|
|||
var topLevel = root as TopLevel; |
|||
|
|||
topLevel.Deactivated += this.Deactivated; |
|||
|
|||
var pointerPress = topLevel.AddHandler( |
|||
InputElement.PointerPressedEvent, |
|||
this.TopLevelPreviewPointerPress, |
|||
RoutingStrategies.Tunnel); |
|||
|
|||
this.subscription = new CompositeDisposable( |
|||
pointerPress, |
|||
Disposable.Create(() => topLevel.Deactivated -= this.Deactivated)); |
|||
|
|||
var inputRoot = root as IInputRoot; |
|||
|
|||
if (inputRoot != null && inputRoot.AccessKeyHandler != null) |
|||
{ |
|||
inputRoot.AccessKeyHandler.MainMenu = this; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Menu"/> is detached from the visual tree.
|
|||
/// </summary>
|
|||
/// <param name="oldRoot">The root of the visual tree being detached from.</param>
|
|||
protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot) |
|||
{ |
|||
base.OnDetachedFromVisualTree(oldRoot); |
|||
this.subscription.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when a key is pressed within the menu.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
protected override void OnKeyDown(KeyEventArgs e) |
|||
{ |
|||
bool menuWasOpen = this.SelectedMenuItem?.IsSubMenuOpen ?? false; |
|||
|
|||
base.OnKeyDown(e); |
|||
|
|||
if (menuWasOpen) |
|||
{ |
|||
// If a menu item was open and we navigate to a new one with the arrow keys, open
|
|||
// that menu and select the first item.
|
|||
var selection = this.SelectedMenuItem; |
|||
|
|||
if (selection != null && !selection.IsSubMenuOpen) |
|||
{ |
|||
selection.IsSubMenuOpen = true; |
|||
selection.SelectedIndex = 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the menu loses focus.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
protected override void OnLostFocus(RoutedEventArgs e) |
|||
{ |
|||
base.OnLostFocus(e); |
|||
this.SelectedItem = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when a submenu opens somewhere in the menu.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
protected virtual void OnSubmenuOpened(RoutedEventArgs e) |
|||
{ |
|||
var menuItem = e.Source as MenuItem; |
|||
|
|||
if (menuItem != null && menuItem.Parent == this) |
|||
{ |
|||
foreach (var child in this.GetLogicalChildren().OfType<MenuItem>()) |
|||
{ |
|||
if (child != menuItem && child.IsSubMenuOpen) |
|||
{ |
|||
child.IsSubMenuOpen = false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
this.IsOpen = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the top-level window is deactivated.
|
|||
/// </summary>
|
|||
/// <param name="sender">The sender.</param>
|
|||
/// <param name="e">The event args.</param>
|
|||
private void Deactivated(object sender, EventArgs e) |
|||
{ |
|||
this.Close(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when a submenu is clicked somewhere in the menu.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
private void OnMenuClick(RoutedEventArgs e) |
|||
{ |
|||
this.Close(); |
|||
FocusManager.Instance.Focus(null); |
|||
e.Handled = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the pointer is pressed anywhere on the window.
|
|||
/// </summary>
|
|||
/// <param name="sender">The sender.</param>
|
|||
/// <param name="e">The event args.</param>
|
|||
private void TopLevelPreviewPointerPress(object sender, PointerPressEventArgs e) |
|||
{ |
|||
if (this.IsOpen) |
|||
{ |
|||
var control = e.Source as ILogical; |
|||
|
|||
if (!this.IsLogicalParentOf(control)) |
|||
{ |
|||
this.Close(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,547 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="MenuItem.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Windows.Input; |
|||
using Perspex.Controls.Primitives; |
|||
using Perspex.Input; |
|||
using Perspex.Interactivity; |
|||
using Perspex.Controls.Templates; |
|||
using Perspex.Controls.Presenters; |
|||
using Perspex.VisualTree; |
|||
using Perspex.Threading; |
|||
using Splat; |
|||
|
|||
/// <summary>
|
|||
/// A menu item control.
|
|||
/// </summary>
|
|||
public class MenuItem : SelectingItemsControl, ISelectable |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Command"/> property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<ICommand> CommandProperty = |
|||
Button.CommandProperty.AddOwner<MenuItem>(); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="CommandParameter"/> property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<object> CommandParameterProperty = |
|||
Button.CommandParameterProperty.AddOwner<MenuItem>(); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Header"/> property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<object> HeaderProperty = |
|||
HeaderedItemsControl.HeaderProperty.AddOwner<MenuItem>(); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Icon"/> property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<object> IconProperty = |
|||
PerspexProperty.Register<MenuItem, object>(nameof(Icon)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="IsSelected"/> property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<bool> IsSelectedProperty = |
|||
ListBoxItem.IsSelectedProperty.AddOwner<MenuItem>(); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="IsSubMenuOpen"/> property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<bool> IsSubMenuOpenProperty = |
|||
PerspexProperty.Register<MenuItem, bool>(nameof(IsSubMenuOpen)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Click"/> event.
|
|||
/// </summary>
|
|||
public static readonly RoutedEvent<RoutedEventArgs> ClickEvent = |
|||
RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(Click), RoutingStrategies.Bubble); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="SubmenuOpened"/> event.
|
|||
/// </summary>
|
|||
public static readonly RoutedEvent<RoutedEventArgs> SubmenuOpenedEvent = |
|||
RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(SubmenuOpened), RoutingStrategies.Bubble); |
|||
|
|||
/// <summary>
|
|||
/// The timer used to display submenus.
|
|||
/// </summary>
|
|||
private IDisposable submenuTimer; |
|||
|
|||
/// <summary>
|
|||
/// The submenu popup.
|
|||
/// </summary>
|
|||
private Popup popup; |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="MenuItem"/> class.
|
|||
/// </summary>
|
|||
static MenuItem() |
|||
{ |
|||
FocusableProperty.OverrideDefaultValue<MenuItem>(true); |
|||
ClickEvent.AddClassHandler<MenuItem>(x => x.OnClick); |
|||
SubmenuOpenedEvent.AddClassHandler<MenuItem>(x => x.OnSubmenuOpened); |
|||
IsSubMenuOpenProperty.Changed.AddClassHandler<MenuItem>(x => x.SubMenuOpenChanged); |
|||
AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<MenuItem>(x => x.AccessKeyPressed); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Occurs when a <see cref="MenuItem"/> without a submenu is clicked.
|
|||
/// </summary>
|
|||
public event EventHandler<RoutedEventArgs> Click |
|||
{ |
|||
add { this.AddHandler(ClickEvent, value); } |
|||
remove { this.RemoveHandler(ClickEvent, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Occurs when a <see cref="MenuItem"/>'s submenu is opened.
|
|||
/// </summary>
|
|||
public event EventHandler<RoutedEventArgs> SubmenuOpened |
|||
{ |
|||
add { this.AddHandler(SubmenuOpenedEvent, value); } |
|||
remove { this.RemoveHandler(SubmenuOpenedEvent, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the command associated with the menu item.
|
|||
/// </summary>
|
|||
public ICommand Command |
|||
{ |
|||
get { return this.GetValue(CommandProperty); } |
|||
set { this.SetValue(CommandProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the parameter to pass to the <see cref="Command"/> property of a
|
|||
/// <see cref="MenuItem"/>.
|
|||
/// </summary>
|
|||
public object CommandParameter |
|||
{ |
|||
get { return this.GetValue(CommandParameterProperty); } |
|||
set { this.SetValue(CommandParameterProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="MenuItem"/>'s header.
|
|||
/// </summary>
|
|||
public object Header |
|||
{ |
|||
get { return this.GetValue(HeaderProperty); } |
|||
set { this.SetValue(HeaderProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the icon that appears in a <see cref="MenuItem"/>.
|
|||
/// </summary>
|
|||
public object Icon |
|||
{ |
|||
get { return this.GetValue(IconProperty); } |
|||
set { this.SetValue(IconProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the <see cref="MenuItem"/> is currently selected.
|
|||
/// </summary>
|
|||
public bool IsSelected |
|||
{ |
|||
get { return this.GetValue(IsSelectedProperty); } |
|||
set { this.SetValue(IsSelectedProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value that indicates whether the submenu of the <see cref="MenuItem"/> is
|
|||
/// open.
|
|||
/// </summary>
|
|||
public bool IsSubMenuOpen |
|||
{ |
|||
get { return this.GetValue(IsSubMenuOpenProperty); } |
|||
set { this.SetValue(IsSubMenuOpenProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value that indicates whether the <see cref="MenuItem"/> has a submenu.
|
|||
/// </summary>
|
|||
public bool HasSubMenu |
|||
{ |
|||
get { return !this.Classes.Contains(":empty"); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates whether the <see cref="MenuItem"/> is a top-level menu item.
|
|||
/// </summary>
|
|||
public bool IsTopLevel |
|||
{ |
|||
get { return this.Parent is Menu; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="MenuItem"/> is clicked.
|
|||
/// </summary>
|
|||
/// <param name="e">The click event args.</param>
|
|||
protected virtual void OnClick(RoutedEventArgs e) |
|||
{ |
|||
if (this.Command != null) |
|||
{ |
|||
this.Command.Execute(this.CommandParameter); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="MenuItem"/> recieves focus.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
protected override void OnGotFocus(GotFocusEventArgs e) |
|||
{ |
|||
base.OnGotFocus(e); |
|||
this.IsSelected = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when a key is pressed in the <see cref="MenuItem"/>.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
protected override void OnKeyDown(KeyEventArgs e) |
|||
{ |
|||
// Some keypresses we want to pass straight to the parent MenuItem/Menu without giving
|
|||
// this MenuItem the chance to handle them. This is usually e.g. when the submenu is
|
|||
// closed so passing them to the base would try to move the selection in a hidden
|
|||
// submenu.
|
|||
var passStraightToParent = true; |
|||
|
|||
switch (e.Key) |
|||
{ |
|||
case Key.Up: |
|||
passStraightToParent = !this.IsSubMenuOpen; |
|||
break; |
|||
|
|||
case Key.Down: |
|||
if (this.IsTopLevel && this.HasSubMenu && !this.IsSubMenuOpen) |
|||
{ |
|||
this.SelectedIndex = 0; |
|||
this.IsSubMenuOpen = true; |
|||
e.Handled = true; |
|||
} |
|||
|
|||
passStraightToParent = !this.IsSubMenuOpen; |
|||
break; |
|||
|
|||
case Key.Left: |
|||
if (!this.IsTopLevel && this.IsSubMenuOpen) |
|||
{ |
|||
this.IsSubMenuOpen = false; |
|||
e.Handled = true; |
|||
} |
|||
|
|||
passStraightToParent = this.IsTopLevel || !this.IsSubMenuOpen; |
|||
break; |
|||
|
|||
case Key.Right: |
|||
if (!this.IsTopLevel && this.HasSubMenu && !this.IsSubMenuOpen) |
|||
{ |
|||
this.SelectedIndex = 0; |
|||
this.IsSubMenuOpen = true; |
|||
e.Handled = true; |
|||
} |
|||
|
|||
passStraightToParent = this.IsTopLevel || !this.IsSubMenuOpen; |
|||
break; |
|||
|
|||
case Key.Enter: |
|||
if (this.HasSubMenu) |
|||
{ |
|||
goto case Key.Right; |
|||
} |
|||
else |
|||
{ |
|||
this.RaiseEvent(new RoutedEventArgs(ClickEvent)); |
|||
e.Handled = true; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case Key.Escape: |
|||
if (this.IsSubMenuOpen) |
|||
{ |
|||
this.IsSubMenuOpen = false; |
|||
e.Handled = true; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
if (!passStraightToParent) |
|||
{ |
|||
base.OnKeyDown(e); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the pointer enters the <see cref="MenuItem"/>.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
protected override void OnPointerEnter(PointerEventArgs e) |
|||
{ |
|||
base.OnPointerEnter(e); |
|||
|
|||
var menu = this.Parent as Menu; |
|||
|
|||
if (menu != null) |
|||
{ |
|||
if (menu.IsOpen) |
|||
{ |
|||
this.IsSubMenuOpen = true; |
|||
} |
|||
} |
|||
else if (this.HasSubMenu && !this.IsSubMenuOpen) |
|||
{ |
|||
this.submenuTimer = DispatcherTimer.Run( |
|||
() => this.IsSubMenuOpen = true, |
|||
TimeSpan.FromMilliseconds(400)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the pointer leaves the <see cref="MenuItem"/>.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
protected override void OnPointerLeave(PointerEventArgs e) |
|||
{ |
|||
base.OnPointerLeave(e); |
|||
|
|||
if (this.submenuTimer != null) |
|||
{ |
|||
this.submenuTimer.Dispose(); |
|||
this.submenuTimer = null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the pointer is pressed over the <see cref="MenuItem"/>.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
protected override void OnPointerPressed(PointerPressEventArgs e) |
|||
{ |
|||
base.OnPointerPressed(e); |
|||
|
|||
if (!this.HasSubMenu) |
|||
{ |
|||
this.RaiseEvent(new RoutedEventArgs(ClickEvent)); |
|||
} |
|||
else if (this.IsTopLevel) |
|||
{ |
|||
this.IsSubMenuOpen = !this.IsSubMenuOpen; |
|||
} |
|||
else |
|||
{ |
|||
this.IsSubMenuOpen = true; |
|||
} |
|||
|
|||
e.Handled = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when a submenu is opened on this MenuItem or a child MenuItem.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
protected virtual void OnSubmenuOpened(RoutedEventArgs e) |
|||
{ |
|||
var menuItem = e.Source as MenuItem; |
|||
|
|||
if (menuItem != null && menuItem.Parent == this) |
|||
{ |
|||
foreach (var child in this.Items.OfType<MenuItem>()) |
|||
{ |
|||
if (child != menuItem && child.IsSubMenuOpen) |
|||
{ |
|||
child.IsSubMenuOpen = false; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the MenuItem's template has been applied.
|
|||
/// </summary>
|
|||
protected override void OnTemplateApplied() |
|||
{ |
|||
base.OnTemplateApplied(); |
|||
|
|||
this.popup = this.GetTemplateChild<Popup>("popup"); |
|||
this.popup.DependencyResolver = DependencyResolver.Instance; |
|||
this.popup.PopupRootCreated += this.PopupRootCreated; |
|||
this.popup.Opened += this.PopupOpened; |
|||
this.popup.Closed += this.PopupClosed; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the menu item's access key is pressed.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
private void AccessKeyPressed(RoutedEventArgs e) |
|||
{ |
|||
if (this.HasSubMenu) |
|||
{ |
|||
this.SelectedIndex = 0; |
|||
this.IsSubMenuOpen = true; |
|||
} |
|||
else |
|||
{ |
|||
this.RaiseEvent(new RoutedEventArgs(ClickEvent)); |
|||
} |
|||
|
|||
e.Handled = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Closes all submenus of the menu item.
|
|||
/// </summary>
|
|||
private void CloseSubmenus() |
|||
{ |
|||
foreach (var child in this.Items.OfType<MenuItem>()) |
|||
{ |
|||
child.IsSubMenuOpen = false; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="IsSubMenuOpen"/> property changes.
|
|||
/// </summary>
|
|||
/// <param name="e">The property change event.</param>
|
|||
private void SubMenuOpenChanged(PerspexPropertyChangedEventArgs e) |
|||
{ |
|||
var value = (bool)e.NewValue; |
|||
|
|||
if (value) |
|||
{ |
|||
this.RaiseEvent(new RoutedEventArgs(SubmenuOpenedEvent)); |
|||
this.IsSelected = true; |
|||
} |
|||
else |
|||
{ |
|||
this.CloseSubmenus(); |
|||
this.SelectedIndex = -1; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the submenu's <see cref="PopupRoot"/> is created.
|
|||
/// </summary>
|
|||
/// <param name="sender">The event sender.</param>
|
|||
/// <param name="e">The event args.</param>
|
|||
private void PopupRootCreated(object sender, EventArgs e) |
|||
{ |
|||
var popup = (Popup)sender; |
|||
ItemsPresenter presenter = null; |
|||
|
|||
// Our ItemsPresenter is in a Popup which means that it's only created when the
|
|||
// Popup is opened, therefore it wasn't found by ItemsControl.OnTemplateApplied.
|
|||
// Now the Popup has been opened for the first time it should exist, so make sure
|
|||
// the PopupRoot's template is applied and look for the ItemsPresenter.
|
|||
foreach (var c in popup.PopupRoot.GetSelfAndVisualDescendents().OfType<Control>()) |
|||
{ |
|||
if (c.Name == "itemsPresenter" && c is ItemsPresenter) |
|||
{ |
|||
presenter = c as ItemsPresenter; |
|||
break; |
|||
} |
|||
|
|||
c.ApplyTemplate(); |
|||
} |
|||
|
|||
if (presenter != null) |
|||
{ |
|||
// The presenter was found. Set its TemplatedParent so it thinks that it had a
|
|||
// normal birth; may it never know its own perveristy.
|
|||
presenter.TemplatedParent = this; |
|||
this.Presenter = presenter; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the submenu's <see cref="Popup"/> is opened.
|
|||
/// </summary>
|
|||
/// <param name="sender">The event sender.</param>
|
|||
/// <param name="e">The event args.</param>
|
|||
private void PopupOpened(object sender, EventArgs e) |
|||
{ |
|||
var selected = this.SelectedItem; |
|||
|
|||
if (selected != null) |
|||
{ |
|||
var container = this.ItemContainerGenerator.GetContainerForItem(selected); |
|||
|
|||
if (container != null) |
|||
{ |
|||
container.Focus(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the submenu's <see cref="Popup"/> is closed.
|
|||
/// </summary>
|
|||
/// <param name="sender">The event sender.</param>
|
|||
/// <param name="e">The event args.</param>
|
|||
private void PopupClosed(object sender, EventArgs e) |
|||
{ |
|||
this.SelectedItem = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A dependency resolver which returns a <see cref="MenuItemAccessKeyHandler"/>.
|
|||
/// </summary>
|
|||
private class DependencyResolver : IDependencyResolver |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default instance of <see cref="DependencyResolver"/>.
|
|||
/// </summary>
|
|||
public static readonly DependencyResolver Instance = new DependencyResolver(); |
|||
|
|||
/// <summary>
|
|||
/// Disposes of all managed resources.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a service of the specified type.
|
|||
/// </summary>
|
|||
/// <param name="serviceType">The service type.</param>
|
|||
/// <param name="contract">An optional contract.</param>
|
|||
/// <returns>A service of the requested type.</returns>
|
|||
public object GetService(Type serviceType, string contract = null) |
|||
{ |
|||
if (serviceType == typeof(IAccessKeyHandler)) |
|||
{ |
|||
return new MenuItemAccessKeyHandler(); |
|||
} |
|||
else |
|||
{ |
|||
return Locator.Current.GetService(serviceType, contract); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets collection of services of the specified type.
|
|||
/// </summary>
|
|||
/// <param name="serviceType">The service type.</param>
|
|||
/// <param name="contract">An optional contract.</param>
|
|||
/// <returns>A collection of services of the requested type.</returns>
|
|||
public IEnumerable<object> GetServices(Type serviceType, string contract = null) |
|||
{ |
|||
return Locator.Current.GetServices(serviceType, contract); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="MenuItemAccessKeyHandler.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Perspex.Input; |
|||
using Perspex.Interactivity; |
|||
|
|||
/// <summary>
|
|||
/// Handles access keys within a <see cref="MenuItem"/>
|
|||
/// </summary>
|
|||
public class MenuItemAccessKeyHandler : IAccessKeyHandler |
|||
{ |
|||
/// <summary>
|
|||
/// The registered access keys.
|
|||
/// </summary>
|
|||
private List<Tuple<string, IInputElement>> registered = new List<Tuple<string, IInputElement>>(); |
|||
|
|||
/// <summary>
|
|||
/// The window to which the handler belongs.
|
|||
/// </summary>
|
|||
private IInputRoot owner; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the window's main menu.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This property is ignored as a menu item cannot have a main menu.
|
|||
/// </remarks>
|
|||
public IMainMenu MainMenu { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Sets the owner of the access key handler.
|
|||
/// </summary>
|
|||
/// <param name="owner">The owner.</param>
|
|||
/// <remarks>
|
|||
/// This method can only be called once, typically by the owner itself on creation.
|
|||
/// </remarks>
|
|||
public void SetOwner(IInputRoot owner) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(owner != null); |
|||
|
|||
if (this.owner != null) |
|||
{ |
|||
throw new InvalidOperationException("AccessKeyHandler owner has already been set."); |
|||
} |
|||
|
|||
this.owner = owner; |
|||
|
|||
this.owner.AddHandler(InputElement.KeyDownEvent, this.OnKeyDown); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers an input element to be associated with an access key.
|
|||
/// </summary>
|
|||
/// <param name="accessKey">The access key.</param>
|
|||
/// <param name="element">The input element.</param>
|
|||
public void Register(char accessKey, IInputElement element) |
|||
{ |
|||
var existing = this.registered.FirstOrDefault(x => x.Item2 == element); |
|||
|
|||
if (existing != null) |
|||
{ |
|||
this.registered.Remove(existing); |
|||
} |
|||
|
|||
this.registered.Add(Tuple.Create(accessKey.ToString().ToUpper(), element)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Unregisters the access keys associated with the input element.
|
|||
/// </summary>
|
|||
/// <param name="element">The input element.</param>
|
|||
public void Unregister(IInputElement element) |
|||
{ |
|||
foreach (var i in this.registered.Where(x => x.Item2 == element).ToList()) |
|||
{ |
|||
this.registered.Remove(i); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Handles a key being pressed in the menu.
|
|||
/// </summary>
|
|||
/// <param name="sender">The event sender.</param>
|
|||
/// <param name="e">The event args.</param>
|
|||
protected virtual void OnKeyDown(object sender, KeyEventArgs e) |
|||
{ |
|||
if (!string.IsNullOrWhiteSpace(e.Text)) |
|||
{ |
|||
var text = e.Text.ToUpper(); |
|||
var focus = this.registered |
|||
.Where(x => x.Item1 == text && x.Item2.IsEffectivelyVisible) |
|||
.FirstOrDefault()?.Item2; |
|||
|
|||
if (focus != null) |
|||
{ |
|||
focus.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent)); |
|||
} |
|||
|
|||
e.Handled = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="GridLengthsParser.cs" company="Steven Kirk">
|
|||
// Copyright 2014 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Controls.Parsers |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Parses a string of <see cref="GridLength"/>s for <see cref="ColumnDefinitions"/> and
|
|||
/// <see cref="RowDefinitions"/>.
|
|||
/// </summary>
|
|||
public static class GridLengthsParser |
|||
{ |
|||
/// <summary>
|
|||
/// Parses a string of <see cref="GridLength"/>s.
|
|||
/// </summary>
|
|||
/// <param name="s">The string.</param>
|
|||
/// <returns>A collection of <see cref="GridLength"/>s.</returns>
|
|||
public static IEnumerable<GridLength> Parse(string s) |
|||
{ |
|||
var parts = s.Split(',').Select(x => x.ToUpperInvariant().Trim()); |
|||
|
|||
foreach (var part in parts) |
|||
{ |
|||
if (part == "AUTO") |
|||
{ |
|||
yield return GridLength.Auto; |
|||
} |
|||
else if (part.EndsWith("*")) |
|||
{ |
|||
var valueString = part.Substring(0, part.Length - 1).Trim(); |
|||
var value = valueString.Length > 0 ? double.Parse(valueString) : 1; |
|||
yield return new GridLength(value, GridUnitType.Star); |
|||
} |
|||
else if (part.EndsWith("PX")) |
|||
{ |
|||
var value = double.Parse(part.Substring(0, part.Length - 2)); |
|||
yield return new GridLength(value, GridUnitType.Pixel); |
|||
} |
|||
else |
|||
{ |
|||
throw new FormatException("Invalid grid length: " + part); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="PlacementMode.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
public enum PlacementMode |
|||
{ |
|||
Bottom, |
|||
Right, |
|||
} |
|||
} |
|||
@ -0,0 +1,159 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="DeckPresenter.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Controls.Presenters |
|||
{ |
|||
using Perspex.Animation; |
|||
using Perspex.Controls.Generators; |
|||
using Perspex.Controls.Primitives; |
|||
using Perspex.Controls.Utils; |
|||
using Perspex.Input; |
|||
using Perspex.Styling; |
|||
using System; |
|||
using System.Collections; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
|
|||
public class DeckPresenter : Control, IItemsPresenter, ITemplatedControl |
|||
{ |
|||
public static readonly PerspexProperty<IEnumerable> ItemsProperty = |
|||
ItemsControl.ItemsProperty.AddOwner<DeckPresenter>(); |
|||
|
|||
public static readonly PerspexProperty<ItemsPanelTemplate> ItemsPanelProperty = |
|||
ItemsControl.ItemsPanelProperty.AddOwner<DeckPresenter>(); |
|||
|
|||
public static readonly PerspexProperty<object> SelectedItemProperty = |
|||
SelectingItemsControl.SelectedItemProperty.AddOwner<DeckPresenter>(); |
|||
|
|||
public static readonly PerspexProperty<IPageTransition> TransitionProperty = |
|||
Deck.TransitionProperty.AddOwner<DeckPresenter>(); |
|||
|
|||
private bool createdPanel; |
|||
|
|||
public DeckPresenter() |
|||
{ |
|||
this.GetObservableWithHistory(SelectedItemProperty).Subscribe(this.SelectedItemChanged); |
|||
} |
|||
|
|||
public ItemContainerGenerator ItemContainerGenerator |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
public IEnumerable Items |
|||
{ |
|||
get { return this.GetValue(ItemsProperty); } |
|||
set { this.SetValue(ItemsProperty, value); } |
|||
} |
|||
|
|||
public ItemsPanelTemplate ItemsPanel |
|||
{ |
|||
get { return this.GetValue(ItemsPanelProperty); } |
|||
set { this.SetValue(ItemsPanelProperty, value); } |
|||
} |
|||
|
|||
public object SelectedItem |
|||
{ |
|||
get { return this.GetValue(SelectedItemProperty); } |
|||
set { this.SetValue(SelectedItemProperty, value); } |
|||
} |
|||
|
|||
public Panel Panel |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
public IPageTransition Transition |
|||
{ |
|||
get { return this.GetValue(TransitionProperty); } |
|||
set { this.SetValue(TransitionProperty, value); } |
|||
} |
|||
|
|||
public override sealed void ApplyTemplate() |
|||
{ |
|||
if (!this.createdPanel) |
|||
{ |
|||
this.CreatePanel(); |
|||
} |
|||
} |
|||
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
this.Panel.Measure(availableSize); |
|||
return this.Panel.DesiredSize; |
|||
} |
|||
|
|||
protected override Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
this.Panel.Arrange(new Rect(finalSize)); |
|||
return finalSize; |
|||
} |
|||
|
|||
private void CreatePanel() |
|||
{ |
|||
this.ClearVisualChildren(); |
|||
this.Panel = this.ItemsPanel.Build(); |
|||
this.Panel.TemplatedParent = this; |
|||
((IItemsPanel)this.Panel).ChildLogicalParent = this.TemplatedParent as ILogical; |
|||
this.AddVisualChild(this.Panel); |
|||
this.createdPanel = true; |
|||
this.SelectedItemChanged(Tuple.Create<object, object>(null, this.SelectedItem)); |
|||
} |
|||
|
|||
private IItemContainerGenerator GetGenerator() |
|||
{ |
|||
if (this.ItemContainerGenerator == null) |
|||
{ |
|||
ItemsControl i = this.TemplatedParent as ItemsControl; |
|||
this.ItemContainerGenerator = i?.ItemContainerGenerator ?? new ItemContainerGenerator(this); |
|||
} |
|||
|
|||
return this.ItemContainerGenerator; |
|||
} |
|||
|
|||
private async void SelectedItemChanged(Tuple<object, object> value) |
|||
{ |
|||
if (this.createdPanel) |
|||
{ |
|||
var generator = this.GetGenerator(); |
|||
Control from = null; |
|||
Control to = null; |
|||
int fromIndex = -1; |
|||
int toIndex = -1; |
|||
|
|||
if (value.Item1 != null) |
|||
{ |
|||
from = generator.GetContainerForItem(value.Item1); |
|||
fromIndex = this.Items.IndexOf(value.Item1); |
|||
} |
|||
|
|||
if (value.Item2 != null) |
|||
{ |
|||
to = generator.Generate(new[] { value.Item2 }).FirstOrDefault(); |
|||
|
|||
if (to != null) |
|||
{ |
|||
this.Panel.Children.Add(to); |
|||
toIndex = this.Items.IndexOf(value.Item2); |
|||
} |
|||
} |
|||
|
|||
if (this.Transition != null) |
|||
{ |
|||
await this.Transition.Start(from, to, fromIndex < toIndex); |
|||
} |
|||
|
|||
if (from != null) |
|||
{ |
|||
this.Panel.Children.Remove(from); |
|||
generator.Remove(new[] { value.Item1 }); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="IPresenter.cs" company="Steven Kirk">
|
|||
// Copyright 2014 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Controls.Presenters |
|||
{ |
|||
public interface IItemsPresenter : IPresenter |
|||
{ |
|||
Panel Panel { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,191 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="AccessText.cs" company="Steven Kirk">
|
|||
// Copyright 2015 MIT Licence. See licence.md for more information.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace Perspex.Controls.Primitives |
|||
{ |
|||
using System; |
|||
using Perspex.Media; |
|||
using Perspex.Input; |
|||
using Perspex.Rendering; |
|||
|
|||
|
|||
|
|||
/// <summary>
|
|||
/// A text block that displays a character prefixed with an underscore as an access key.
|
|||
/// </summary>
|
|||
public class AccessText : TextBlock |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="ShowAccessKey"/> attached property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<bool> ShowAccessKeyProperty = |
|||
PerspexProperty.RegisterAttached<AccessText, Control, bool>("ShowAccessKey", inherits: true); |
|||
|
|||
/// <summary>
|
|||
/// The access key handler for the current window.
|
|||
/// </summary>
|
|||
private IAccessKeyHandler accessKeys; |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="AccessText"/> class.
|
|||
/// </summary>
|
|||
static AccessText() |
|||
{ |
|||
AffectsRender(ShowAccessKeyProperty); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AccessText"/> class.
|
|||
/// </summary>
|
|||
public AccessText() |
|||
{ |
|||
this.GetObservable(TextProperty).Subscribe(this.TextChanged); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the access key.
|
|||
/// </summary>
|
|||
public char AccessKey |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the access key should be underlined.
|
|||
/// </summary>
|
|||
public bool ShowAccessKey |
|||
{ |
|||
get { return this.GetValue(ShowAccessKeyProperty); } |
|||
set { this.SetValue(ShowAccessKeyProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Renders the <see cref="AccessText"/> to a drawing context.
|
|||
/// </summary>
|
|||
/// <param name="context">The drawing context.</param>
|
|||
public override void Render(IDrawingContext context) |
|||
{ |
|||
base.Render(context); |
|||
|
|||
int underscore = this.Text?.IndexOf('_') ?? -1; |
|||
|
|||
if (underscore != -1 && this.ShowAccessKey) |
|||
{ |
|||
var rect = this.FormattedText.HitTestTextPosition(underscore); |
|||
var offset = new Vector(0, -0.5); |
|||
context.DrawLine( |
|||
new Pen(this.Foreground, 1), |
|||
rect.BottomLeft + offset, |
|||
rect.BottomRight + offset); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates the <see cref="FormattedText"/> used to render the text.
|
|||
/// </summary>
|
|||
/// <param name="constraint">The constraint of the text.</param>
|
|||
/// <returns>A <see cref="FormattedText"/> object.</returns>
|
|||
protected override FormattedText CreateFormattedText(Size constraint) |
|||
{ |
|||
var result = new FormattedText( |
|||
this.StripAccessKey(this.Text), |
|||
this.FontFamily, |
|||
this.FontSize, |
|||
this.FontStyle, |
|||
this.TextAlignment, |
|||
this.FontWeight); |
|||
result.Constraint = constraint; |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures the control.
|
|||
/// </summary>
|
|||
/// <param name="availableSize">The available size for the control.</param>
|
|||
/// <returns>The desired size.</returns>
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
var result = base.MeasureOverride(availableSize); |
|||
return result.WithHeight(result.Height + 1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the control is attached to a visual tree.
|
|||
/// </summary>
|
|||
/// <param name="root">The root of the visual tree.</param>
|
|||
protected override void OnAttachedToVisualTree(IRenderRoot root) |
|||
{ |
|||
base.OnAttachedToVisualTree(root); |
|||
this.accessKeys = (root as IInputRoot)?.AccessKeyHandler; |
|||
|
|||
if (this.accessKeys != null && this.AccessKey != 0) |
|||
{ |
|||
this.accessKeys.Register(this.AccessKey, this); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the control is detached from a visual tree.
|
|||
/// </summary>
|
|||
/// <param name="root">The root of the visual tree.</param>
|
|||
protected override void OnDetachedFromVisualTree(IRenderRoot root) |
|||
{ |
|||
base.OnDetachedFromVisualTree(root); |
|||
|
|||
if (this.accessKeys != null && this.AccessKey != 0) |
|||
{ |
|||
this.accessKeys.Unregister(this); |
|||
this.accessKeys = null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a string with the first underscore stripped.
|
|||
/// </summary>
|
|||
/// <param name="text">The text.</param>
|
|||
/// <returns>The text with the first underscore stripped.</returns>
|
|||
private string StripAccessKey(string text) |
|||
{ |
|||
var position = text.IndexOf('_'); |
|||
|
|||
if (position == -1) |
|||
{ |
|||
return text; |
|||
} |
|||
else |
|||
{ |
|||
return text.Substring(0, position) + text.Substring(position + 1); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Text"/> property changes.
|
|||
/// </summary>
|
|||
/// <param name="text">The new text.</param>
|
|||
private void TextChanged(string text) |
|||
{ |
|||
var key = (char)0; |
|||
|
|||
if (text != null) |
|||
{ |
|||
int underscore = text.IndexOf('_'); |
|||
|
|||
if (underscore != -1 && underscore < text.Length - 1) |
|||
{ |
|||
key = text[underscore + 1]; |
|||
} |
|||
} |
|||
|
|||
this.AccessKey = key; |
|||
|
|||
if (this.accessKeys != null && this.AccessKey != 0) |
|||
{ |
|||
this.accessKeys.Register(this.AccessKey, this); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue