331 changed files with 12947 additions and 3028 deletions
@ -1,7 +1,6 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<packages> |
<packages> |
||||
<package id="Rx-Core" version="2.2.5" targetFramework="net45" /> |
<package id="Rx-Core" version="2.2.5" targetFramework="net45" userInstalled="true" /> |
||||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" /> |
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" userInstalled="true" /> |
||||
<package id="Splat" version="1.6.1" targetFramework="net45" /> |
<package id="Splat" version="1.6.2" targetFramework="net45" userInstalled="true" /> |
||||
<package id="StyleCop.MSBuild" version="4.7.49.0" targetFramework="net45" developmentDependency="true" /> |
|
||||
</packages> |
</packages> |
||||
@ -1,8 +1,7 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<packages> |
<packages> |
||||
<package id="Rx-Core" version="2.2.5" targetFramework="net45" /> |
<package id="Rx-Core" version="2.2.5" targetFramework="net45" userInstalled="true" /> |
||||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" /> |
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" userInstalled="true" /> |
||||
<package id="Rx-Linq" version="2.2.5" targetFramework="net45" /> |
<package id="Rx-Linq" version="2.2.5" targetFramework="net45" userInstalled="true" /> |
||||
<package id="Splat" version="1.6.1" targetFramework="net45" /> |
<package id="Splat" version="1.6.2" targetFramework="net45" userInstalled="true" /> |
||||
<package id="StyleCop.MSBuild" version="4.7.49.0" targetFramework="net45" developmentDependency="true" /> |
|
||||
</packages> |
</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 2014 MIT Licence. See licence.md for more information.
|
||||
// </copyright>
|
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||
|
|
||||
namespace Perspex.Animation |
namespace Perspex.Animation |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the interface for easing functions.
|
||||
|
/// </summary>
|
||||
public interface IEasing |
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); |
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"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<packages> |
<packages> |
||||
<package id="Rx-Core" 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+win" /> |
<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+win" /> |
<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+win" /> |
<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+win" /> |
<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> |
</packages> |
||||
@ -1,10 +1,10 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<packages> |
<packages> |
||||
<package id="Rx-Core" 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+win" /> |
<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+win" /> |
<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+win" /> |
<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+win" /> |
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
||||
<package id="Splat" version="1.6.1" targetFramework="portable-net45+win" /> |
<package id="Splat" version="1.6.2" targetFramework="portable45-net45+win8" userInstalled="true" /> |
||||
<package id="StyleCop.MSBuild" version="4.7.49.0" targetFramework="portable-net45+win" developmentDependency="true" /> |
<package id="StyleCop.Analyzers" version="1.0.0-alpha008" targetFramework="portable45-net45+win8" userInstalled="true" /> |
||||
</packages> |
</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"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<packages> |
<packages> |
||||
<package id="Rx-Core" 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+win" /> |
<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+win" /> |
<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+win" /> |
<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+win" /> |
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" /> |
||||
<package id="Splat" version="1.6.1" targetFramework="portable-net45+win" /> |
<package id="Serilog" version="1.5.5" targetFramework="portable45-net45+win8" userInstalled="true" /> |
||||
<package id="StyleCop.MSBuild" version="4.7.49.0" targetFramework="portable-net45+win" developmentDependency="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> |
</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 2014 MIT Licence. See licence.md for more information.
|
||||
// </copyright>
|
// </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