diff --git a/Perspex.sln b/Perspex.sln
index 1ddd288c70..5ad71b7946 100644
--- a/Perspex.sln
+++ b/Perspex.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.23107.0
+VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Base", "src\Perspex.Base\Perspex.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
EndProject
@@ -130,9 +130,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Android", "src\Andr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.AndroidTestApplication", "src\Android\Perspex.AndroidTestApplication\Perspex.AndroidTestApplication.csproj", "{FF69B927-C545-49AE-8E16-3D14D621AA12}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "iOS", "iOS", "{0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.iOS", "src\iOS\Perspex.iOS\Perspex.iOS.csproj", "{4488AD85-1495-4809-9AA4-DDFE0A48527E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.iOSTestApplication", "src\iOS\Perspex.iOSTestApplication\Perspex.iOSTestApplication.csproj", "{8C923867-8A8F-4F6B-8B80-47D9E8436166}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.LeakTests", "tests\Perspex.LeakTests\Perspex.LeakTests.csproj", "{E1AA3DBF-9056-4530-9376-18119A7A3FFE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog", "samples\ControlCatalog\ControlCatalog.csproj", "{61BEC86C-F307-4295-B5B8-9428610D7D55}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4
+ src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
src\Skia\Perspex.Skia\Perspex.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
@@ -146,10 +157,12 @@ Global
src\Shared\PlatformSupport\PlatformSupport.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4
src\Skia\Perspex.Skia\Perspex.Skia.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4
+ samples\TestApplicationShared\TestApplicationShared.projitems*{8c923867-8a8f-4f6b-8b80-47d9e8436166}*SharedItemsImports = 4
samples\TestApplicationShared\TestApplicationShared.projitems*{e3a1060b-50d0-44e8-88b6-f44ef2e5bd72}*SharedItemsImports = 4
src\Skia\Perspex.Skia\Perspex.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4
+ src\Shared\PlatformSupport\PlatformSupport.projitems*{e1aa3dbf-9056-4530-9376-18119a7a3ffe}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@@ -1189,6 +1202,98 @@ Global
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Release|Any CPU.Deploy.0 = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Release|iPhone.ActiveCfg = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.AppStore|Any CPU.Build.0 = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.AppStore|iPhone.ActiveCfg = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.AppStore|iPhone.Build.0 = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|iPhone.Build.0 = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|iPhone.Build.0 = AppStore|iPhone
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|Any CPU.ActiveCfg = Debug|iPhone
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|iPhone.ActiveCfg = Debug|iPhone
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|iPhone.Build.0 = Debug|iPhone
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|Any CPU.ActiveCfg = Release|iPhone
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhone.ActiveCfg = Release|iPhone
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhone.Build.0 = Release|iPhone
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|Any CPU.Build.0 = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|iPhone.ActiveCfg = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|iPhone.Build.0 = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|iPhone.Build.0 = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.AppStore|Any CPU.Build.0 = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.AppStore|iPhone.ActiveCfg = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.AppStore|iPhone.Build.0 = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Release|Any CPU.Build.0 = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Release|iPhone.Build.0 = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {61BEC86C-F307-4295-B5B8-9428610D7D55}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1229,5 +1334,9 @@ Global
{78345174-5B52-4A14-B9FD-D5F2428137F0} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{7B92AF71-6287-4693-9DCB-BD5B6E927E23} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F}
{FF69B927-C545-49AE-8E16-3D14D621AA12} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F}
+ {4488AD85-1495-4809-9AA4-DDFE0A48527E} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1}
+ {8C923867-8A8F-4F6B-8B80-47D9E8436166} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1}
+ {E1AA3DBF-9056-4530-9376-18119A7A3FFE} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+ {61BEC86C-F307-4295-B5B8-9428610D7D55} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
EndGlobal
diff --git a/docs/build.md b/docs/build.md
index 41d8f965f3..b3be7581c4 100644
--- a/docs/build.md
+++ b/docs/build.md
@@ -12,12 +12,14 @@ Perspex.Gtk project in Visual Studio.
### Clone the Perspex repository
- git clone https://github.com/grokys/Perspex.git
+ git clone https://github.com/Perspex/Perspex.git
We currently need to build our own private version of ReactiveUI as it doesn't work on mono. This
is linked as a submodule in the git repository, so run:
git submodule update --init
+
+The next step is to download the Skia native libraries. Run ```getnatives.ps1``` PowerShell script which can be found under the folder ```Perspex\src\Skia\```.
## Linux
@@ -43,6 +45,8 @@ We currently need to build our own private version of ReactiveUI as it doesn't w
is linked as a submodule in the git repository, so run:
git submodule update --init
+
+The next step is to download the Skia native libraries. Run ```getnatives.sh``` script which can be found under the folder ```Perspex\src\Skia\```.
### Load the Project in MonoDevelop
diff --git a/nuget/build-appveyor.ps1 b/nuget/build-appveyor.ps1
index ee38e950b7..cbe6980648 100644
--- a/nuget/build-appveyor.ps1
+++ b/nuget/build-appveyor.ps1
@@ -1,3 +1,4 @@
+$ErrorActionPreference = "Stop"
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
Push-Location $dir
@@ -9,6 +10,7 @@ sv version $env:APPVEYOR_BUILD_NUMBER
sv version 9999.0.$version-nightly
sv key $env:myget_key
+. ".\include.ps1"
.\build-version.ps1 $version
sv reponame $env:APPVEYOR_REPO_NAME
@@ -23,10 +25,10 @@ if ([string]::IsNullOrWhiteSpace($pullreq))
if($repobranch -eq "master")
{
echo "Repo branch matched"
- nuget.exe push Perspex.$version.nupkg $key -Source https://www.myget.org/F/perspex-nightly/api/v2/package
- nuget.exe push Perspex.Desktop.$version.nupkg $key -Source https://www.myget.org/F/perspex-nightly/api/v2/package
- nuget.exe push Perspex.Skia.Desktop.$version.nupkg $key -Source https://www.myget.org/F/perspex-nightly/api/v2/package
- nuget.exe push Perspex.Android.$version.nupkg $key -Source https://www.myget.org/F/perspex-nightly/api/v2/package
+ foreach($pkg in $Packages)
+ {
+ nuget.exe push "$($pkg).$($version).nupkg" $key -Source https://www.myget.org/F/perspex-nightly/api/v2/package
+ }
}
}
diff --git a/nuget/build-version.ps1 b/nuget/build-version.ps1
index 65b673385e..59031527d5 100644
--- a/nuget/build-version.ps1
+++ b/nuget/build-version.ps1
@@ -1,9 +1,11 @@
$ErrorActionPreference = "Stop"
-rm -Force -Recurse .\Perspex -ErrorAction SilentlyContinue
-rm -Force -Recurse .\Perspex.Desktop -ErrorAction SilentlyContinue
-rm -Force -Recurse .\Perspex.Skia.Desktop -ErrorAction SilentlyContinue
-rm -Force -Recurse .\Perspex.Android -ErrorAction SilentlyContinue
+. ".\include.ps1"
+
+foreach($pkg in $Packages)
+{
+ rm -Force -Recurse .\$pkg -ErrorAction SilentlyContinue
+}
rm -Force -Recurse *.nupkg -ErrorAction SilentlyContinue
Copy-Item template Perspex -Recurse
@@ -14,11 +16,13 @@ sv skia_root "Perspex.Skia.Desktop"
sv skia_lib "Perspex.Skia.Desktop\lib\net45"
sv skia_native "Perspex.Skia.Desktop\build\net45\native"
sv android "Perspex.Android\lib\MonoAndroid10"
+sv ios "Perspex.iOS\lib\Xamarin.iOS10"
mkdir $lib -ErrorAction SilentlyContinue
mkdir $build -ErrorAction SilentlyContinue
mkdir $skia_lib
mkdir $android
+mkdir $ios
Copy-Item ..\src\Perspex.Animation\bin\Release\Perspex.Animation.dll $lib
@@ -65,17 +69,20 @@ Copy-Item ..\src\Skia\Perspex.Skia.Desktop\bin\Release\Perspex.Skia.Desktop.dll
Copy-Item ..\src\Android\Perspex.Android\bin\Release\Perspex.Android.dll $android
Copy-Item ..\src\Skia\Perspex.Skia.Android\bin\Release\Perspex.Skia.Android.dll $android
-(gc Perspex\Perspex.nuspec).replace('#VERSION#', $args[0]) | sc Perspex\Perspex.nuspec
-(gc Perspex\Perspex.Desktop.nuspec).replace('#VERSION#', $args[0]) | sc Perspex.Desktop\Perspex.Desktop.nuspec
-(gc Perspex\Perspex.Skia.Desktop.nuspec).replace('#VERSION#', $args[0]) | sc Perspex.Skia.Desktop\Perspex.Skia.Desktop.nuspec
-(gc Perspex\Perspex.Android.nuspec).replace('#VERSION#', $args[0]) | sc Perspex.Android\Perspex.Android.nuspec
+Copy-Item ..\src\iOS\Perspex.iOS\bin\iPhone\Release\Perspex.iOS.dll $ios
+Copy-Item ..\src\Skia\Perspex.Skia.iOS\bin\iPhone\Release\Perspex.Skia.iOS.dll $ios
+
+foreach($pkg in $Packages)
+{
+ (gc Perspex\$pkg.nuspec).replace('#VERSION#', $args[0]) | sc $pkg\$pkg.nuspec
+}
-nuget.exe pack Perspex\Perspex.nuspec
-nuget.exe pack Perspex.Desktop\Perspex.Desktop.nuspec
-nuget.exe pack Perspex.Skia.Desktop\Perspex.Skia.Desktop.nuspec
-nuget.exe pack Perspex.Android\Perspex.Android.nuspec
+foreach($pkg in $Packages)
+{
+ nuget.exe pack $pkg\$pkg.nuspec
+}
-rm -Force -Recurse .\Perspex
-rm -Force -Recurse .\Perspex.Desktop
-rm -Force -Recurse .\Perspex.Skia.Desktop
-rm -Force -Recurse .\Perspex.Android
+foreach($pkg in $Packages)
+{
+ rm -Force -Recurse .\$pkg
+}
\ No newline at end of file
diff --git a/nuget/build.ps1 b/nuget/build.ps1
index a26fe74706..2663012664 100644
--- a/nuget/build.ps1
+++ b/nuget/build.ps1
@@ -1 +1 @@
-.\build-version.ps1 0.1.1-alpha2
\ No newline at end of file
+.\build-version.ps1 0.2.0-alpha3
\ No newline at end of file
diff --git a/nuget/include.ps1 b/nuget/include.ps1
new file mode 100644
index 0000000000..f1e6f5d523
--- /dev/null
+++ b/nuget/include.ps1
@@ -0,0 +1 @@
+$Packages = @("Perspex", "Perspex.Desktop", "Perspex.Skia.Desktop", "Perspex.Android", "Perspex.iOS")
\ No newline at end of file
diff --git a/nuget/template/Perspex.Desktop.nuspec b/nuget/template/Perspex.Desktop.nuspec
index 2c78dc9c8c..192afe828d 100644
--- a/nuget/template/Perspex.Desktop.nuspec
+++ b/nuget/template/Perspex.Desktop.nuspec
@@ -21,9 +21,9 @@
-
-
-
+
+
+
diff --git a/nuget/template/Perspex.iOS.nuspec b/nuget/template/Perspex.iOS.nuspec
new file mode 100644
index 0000000000..568ee2bae1
--- /dev/null
+++ b/nuget/template/Perspex.iOS.nuspec
@@ -0,0 +1,27 @@
+
+
+
+ Perspex.iOS
+ #VERSION#
+ Perspex Team
+ stevenk
+ http://opensource.org/licenses/MIT
+ https://github.com/Perspex/Perspex/
+ false
+ The Perspex UI framework
+
+ Copyright 2015
+ Perspex
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/readme.md b/readme.md
index 4f15933968..6eec66c9ae 100644
--- a/readme.md
+++ b/readme.md
@@ -1,14 +1,20 @@
# Perspex
-[](https://gitter.im/grokys/Perspex?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[](https://gitter.im/Perspex/Perspex?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge)
[](https://ci.appveyor.com/project/Perspex/Perspex/branch/master)
-A multi-platform .NET UI framework.
+A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android.

+Desktop platforms:
+

+Mobile platforms:
+
+
+
## NuGet
Perspex is delivered as a NuGet package.
@@ -39,7 +45,7 @@ framework.
As mentioned above, Perspex is still in alpha and as such there's not much documentation yet. You can
take a look at the [getting started page](docs/gettingstarted.md) for an
overview of how to get started but probably the best thing to do for now is to already know a little bit
-about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/grokys/Perspex).
+about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/Perspex/Perspex).
There's also a high-level [architecture document](docs/architecture.md) that is currently a little bit
out of date, and I've also started writing blog posts on Perspex at http://grokys.github.io/.
diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj
index 62914fdc13..0ba9e86d67 100644
--- a/samples/BindingTest/BindingTest.csproj
+++ b/samples/BindingTest/BindingTest.csproj
@@ -79,6 +79,10 @@
MainWindow.paml
+
+ TestUserControl.paml
+
+
@@ -89,6 +93,7 @@
MSBuild:Compile
+
diff --git a/samples/BindingTest/MainWindow.paml b/samples/BindingTest/MainWindow.paml
index 903d1f1050..c782786e42 100644
--- a/samples/BindingTest/MainWindow.paml
+++ b/samples/BindingTest/MainWindow.paml
@@ -1,5 +1,6 @@
+ xmlns:vm="clr-namespace:BindingTest.ViewModels;assembly=BindingTest"
+ xmlns:local="clr-namespace:BindingTest;assembly=BindingTest">
@@ -55,5 +56,10 @@
+
+
+
\ No newline at end of file
diff --git a/samples/BindingTest/TestUserControl.paml b/samples/BindingTest/TestUserControl.paml
new file mode 100644
index 0000000000..2c5609f8c9
--- /dev/null
+++ b/samples/BindingTest/TestUserControl.paml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/samples/BindingTest/TestUserControl.paml.cs b/samples/BindingTest/TestUserControl.paml.cs
new file mode 100644
index 0000000000..1cf4342f19
--- /dev/null
+++ b/samples/BindingTest/TestUserControl.paml.cs
@@ -0,0 +1,18 @@
+using Perspex.Controls;
+using Perspex.Markup.Xaml;
+
+namespace BindingTest
+{
+ public class TestUserControl : UserControl
+ {
+ public TestUserControl()
+ {
+ this.InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ PerspexXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs
index 057666d5c4..650f69bc36 100644
--- a/samples/BindingTest/ViewModels/MainWindowViewModel.cs
+++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs
@@ -32,6 +32,7 @@ namespace BindingTest.ViewModels
public ObservableCollection Items { get; }
public ObservableCollection SelectedItems { get; }
public ReactiveCommand
+
@@ -122,6 +123,22 @@
..\..\packages\Splat.1.6.2\lib\Portable-net45+win+wpa81+wp80\Splat.dll
True
+
+ ..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll
+ True
+
+
+ ..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll
+ True
+
+
+ ..\..\packages\Rx-Linq.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Linq.dll
+ True
+
+
+ ..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll
+ True
+
diff --git a/samples/XamlTestApplicationPcl/packages.config b/samples/XamlTestApplicationPcl/packages.config
index 2115574cc5..749731a770 100644
--- a/samples/XamlTestApplicationPcl/packages.config
+++ b/samples/XamlTestApplicationPcl/packages.config
@@ -1,4 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Perspex.Android/AndroidPlatform.cs b/src/Android/Perspex.Android/AndroidPlatform.cs
index 8196571102..4b02a14aa0 100644
--- a/src/Android/Perspex.Android/AndroidPlatform.cs
+++ b/src/Android/Perspex.Android/AndroidPlatform.cs
@@ -10,10 +10,11 @@ using Perspex.Shared.PlatformSupport;
using Perspex.Skia;
using System;
using System.Collections.Generic;
+using Perspex.Android.Platform.SkiaPlatform;
namespace Perspex.Android
{
- public class AndroidPlatform : IPlatformSettings
+ public class AndroidPlatform : IPlatformSettings, IWindowingPlatform
{
public static readonly AndroidPlatform Instance = new AndroidPlatform();
public Size DoubleClickSize => new Size(4, 4);
@@ -33,11 +34,11 @@ namespace Perspex.Android
.Bind().ToConstant(this)
.Bind().ToConstant(new AndroidThreadingInterface())
.Bind().ToTransient()
- .Bind().ToTransient();
+ .Bind().ToTransient()
+ .Bind().ToConstant(this);
SkiaPlatform.Initialize();
- Application.SuppressPlatformInitialization();
- PerspexLocator.CurrentMutable.Bind().ToSingleton();
+ Application.RegisterPlatformCallback(() => { });
_scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity;
@@ -50,5 +51,20 @@ namespace Perspex.Android
{
SharedPlatform.Register(applicationType.Assembly);
}
+
+ public IWindowImpl CreateWindow()
+ {
+ return new WindowImpl();
+ }
+
+ public IWindowImpl CreateEmbeddableWindow()
+ {
+ throw new NotImplementedException();
+ }
+
+ public IPopupImpl CreatePopup()
+ {
+ throw new NotImplementedException();
+ }
}
}
\ No newline at end of file
diff --git a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs
index b7125a56ed..ed853a4353 100644
--- a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs
+++ b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs
@@ -9,6 +9,7 @@ using Perspex.Input.Raw;
using Perspex.Platform;
using Perspex.Skia.Android;
using System;
+using Perspex.Controls;
namespace Perspex.Android.Platform.SkiaPlatform
{
@@ -94,6 +95,9 @@ namespace Perspex.Android.Platform.SkiaPlatform
this.Visibility = ViewStates.Invisible;
}
+ public void SetSystemDecorations(bool enabled)
+ {
+ }
public void Invalidate(Rect rect)
{
if (Holder?.Surface?.IsValid == true) base.Invalidate();
@@ -123,6 +127,18 @@ namespace Perspex.Android.Platform.SkiaPlatform
this.Visibility = ViewStates.Visible;
}
+ public void BeginMoveDrag()
+ {
+ //Not supported
+ }
+
+ public void BeginResizeDrag(WindowEdge edge)
+ {
+ //Not supported
+ }
+
+ public Point Position { get; set; }
+
public IDisposable ShowDialog()
{
throw new NotImplementedException();
diff --git a/src/Gtk/Perspex.Cairo/CairoPlatform.cs b/src/Gtk/Perspex.Cairo/CairoPlatform.cs
index 5d9a003a93..2c72811bbb 100644
--- a/src/Gtk/Perspex.Cairo/CairoPlatform.cs
+++ b/src/Gtk/Perspex.Cairo/CairoPlatform.cs
@@ -16,7 +16,7 @@ namespace Perspex.Cairo
{
private static readonly CairoPlatform s_instance = new CairoPlatform();
- private static Pango.Context s_pangoContext = CreatePangoContext();
+ private static readonly Pango.Context s_pangoContext = CreatePangoContext();
public static void Initialize() => PerspexLocator.CurrentMutable.Bind().ToConstant(s_instance);
diff --git a/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs b/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs
index a1fee1a2e3..afb024b4cb 100644
--- a/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs
+++ b/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs
@@ -148,6 +148,10 @@ namespace Perspex.Cairo.Media
_context.AppendPath(impl.Path);
using (var b = SetBrush(brush, geometry.Bounds.Size))
{
+ _context.FillRule = impl.FillRule == FillRule.EvenOdd
+ ? Cairo.FillRule.EvenOdd
+ : Cairo.FillRule.Winding;
+
if (pen != null)
_context.FillPreserve();
else
diff --git a/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs b/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs
index 3b08b06812..956a522002 100644
--- a/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs
+++ b/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs
@@ -13,7 +13,7 @@ namespace Perspex.Cairo.Media
public class FormattedTextImpl : IFormattedTextImpl
{
private Size _size;
- private string _text;
+ private readonly string _text;
public FormattedTextImpl(
Pango.Context context,
diff --git a/src/Gtk/Perspex.Cairo/Media/RadialGradientBrushImpl.cs b/src/Gtk/Perspex.Cairo/Media/RadialGradientBrushImpl.cs
index a345f1869d..2d54882324 100644
--- a/src/Gtk/Perspex.Cairo/Media/RadialGradientBrushImpl.cs
+++ b/src/Gtk/Perspex.Cairo/Media/RadialGradientBrushImpl.cs
@@ -15,10 +15,10 @@ namespace Perspex.Cairo
foreach (var stop in brush.GradientStops)
{
- ((LinearGradient)this.PlatformBrush).AddColorStop(stop.Offset, stop.Color.ToCairo());
+ ((RadialGradient)this.PlatformBrush).AddColorStop(stop.Offset, stop.Color.ToCairo());
}
- ((LinearGradient)this.PlatformBrush).Extend = Extend.Pad;
+ ((RadialGradient)this.PlatformBrush).Extend = Extend.Pad;
}
}
}
diff --git a/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs b/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs
index 5e3e279deb..f4cf9294dc 100644
--- a/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs
+++ b/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs
@@ -13,11 +13,13 @@ namespace Perspex.Cairo.Media
public class StreamGeometryContextImpl : IStreamGeometryContextImpl
{
+ private readonly StreamGeometryImpl _target;
private Point _currentPoint;
- public StreamGeometryContextImpl(Cairo.Path path = null)
+ public StreamGeometryContextImpl(StreamGeometryImpl target, Cairo.Path path)
{
+ _target = target;
- _surf = new Cairo.ImageSurface (Cairo.Format.Argb32, 0, 0);
+ _surf = new Cairo.ImageSurface (Cairo.Format.Argb32, 0, 0);
_context = new Cairo.Context (_surf);
this.Path = path;
@@ -42,7 +44,7 @@ namespace Perspex.Cairo.Media
}
}
- public void BezierTo(Point point1, Point point2, Point point3)
+ public void CubicBezierTo(Point point1, Point point2, Point point3)
{
if (this.Path == null)
{
@@ -51,11 +53,11 @@ namespace Perspex.Cairo.Media
}
}
- public void QuadTo(Point control, Point endPoint)
+ public void QuadraticBezierTo(Point control, Point endPoint)
{
if (this.Path == null)
{
- QuadBezierHelper.QuadTo(this, _currentPoint, control, endPoint);
+ QuadBezierHelper.QuadraticBezierTo(this, _currentPoint, control, endPoint);
_currentPoint = endPoint;
}
}
@@ -86,6 +88,12 @@ namespace Perspex.Cairo.Media
}
}
+ public void SetFillRule(FillRule fillRule)
+ {
+ _target.FillRule = fillRule;
+ }
+
+
public void Dispose()
{
_context.Dispose ();
diff --git a/src/Gtk/Perspex.Cairo/Media/StreamGeometryImpl.cs b/src/Gtk/Perspex.Cairo/Media/StreamGeometryImpl.cs
index a408cba039..2dd844c687 100644
--- a/src/Gtk/Perspex.Cairo/Media/StreamGeometryImpl.cs
+++ b/src/Gtk/Perspex.Cairo/Media/StreamGeometryImpl.cs
@@ -14,7 +14,7 @@ namespace Perspex.Cairo.Media
{
public StreamGeometryImpl()
{
- _impl = new StreamGeometryContextImpl(null);
+ _impl = new StreamGeometryContextImpl(this, null);
}
public StreamGeometryImpl(StreamGeometryContextImpl impl)
@@ -50,6 +50,8 @@ namespace Perspex.Cairo.Media
}
}
+ public FillRule FillRule { get; set; }
+
public IStreamGeometryImpl Clone()
{
return new StreamGeometryImpl(_impl);
diff --git a/src/Gtk/Perspex.Cairo/RenderTarget.cs b/src/Gtk/Perspex.Cairo/RenderTarget.cs
index ee02a279bd..e562ccfded 100644
--- a/src/Gtk/Perspex.Cairo/RenderTarget.cs
+++ b/src/Gtk/Perspex.Cairo/RenderTarget.cs
@@ -19,7 +19,7 @@ namespace Perspex.Cairo
public class RenderTarget : IRenderTarget
{
private readonly Surface _surface;
- private Gtk.Window _window;
+ private readonly Gtk.Window _window;
///
/// Initializes a new instance of the class.
diff --git a/src/Gtk/Perspex.Gtk/CursorFactory.cs b/src/Gtk/Perspex.Gtk/CursorFactory.cs
index 80e6b7b1e6..6a250024bf 100644
--- a/src/Gtk/Perspex.Gtk/CursorFactory.cs
+++ b/src/Gtk/Perspex.Gtk/CursorFactory.cs
@@ -21,20 +21,28 @@ namespace Perspex.Gtk
private static readonly Dictionary CursorTypeMapping = new Dictionary
{
- { StandardCursorType.AppStarting, CursorType.Watch },
- { StandardCursorType.Arrow, CursorType.LeftPtr },
- { StandardCursorType.Cross, CursorType.Cross },
- { StandardCursorType.Hand, CursorType.Hand1 },
- { StandardCursorType.Ibeam, CursorType.Xterm },
- { StandardCursorType.No, Gtk.Stock.Cancel},
- { StandardCursorType.SizeAll, CursorType.Sizing },
+ {StandardCursorType.AppStarting, CursorType.Watch},
+ {StandardCursorType.Arrow, CursorType.LeftPtr},
+ {StandardCursorType.Cross, CursorType.Cross},
+ {StandardCursorType.Hand, CursorType.Hand1},
+ {StandardCursorType.Ibeam, CursorType.Xterm},
+ {StandardCursorType.No, Gtk.Stock.Cancel},
+ {StandardCursorType.SizeAll, CursorType.Sizing},
//{ StandardCursorType.SizeNorthEastSouthWest, 32643 },
- { StandardCursorType.SizeNorthSouth, CursorType.SbVDoubleArrow},
+ {StandardCursorType.SizeNorthSouth, CursorType.SbVDoubleArrow},
//{ StandardCursorType.SizeNorthWestSouthEast, 32642 },
- { StandardCursorType.SizeWestEast, CursorType.SbHDoubleArrow },
- { StandardCursorType.UpArrow, CursorType.BasedArrowUp },
- { StandardCursorType.Wait, CursorType.Watch },
- { StandardCursorType.Help, Gtk.Stock.Help }
+ {StandardCursorType.SizeWestEast, CursorType.SbHDoubleArrow},
+ {StandardCursorType.UpArrow, CursorType.BasedArrowUp},
+ {StandardCursorType.Wait, CursorType.Watch},
+ {StandardCursorType.Help, Gtk.Stock.Help},
+ {StandardCursorType.TopSide, CursorType.TopSide},
+ {StandardCursorType.BottomSize, CursorType.BottomSide},
+ {StandardCursorType.LeftSide, CursorType.LeftSide},
+ {StandardCursorType.RightSide, CursorType.RightSide},
+ {StandardCursorType.TopLeftCorner, CursorType.TopLeftCorner},
+ {StandardCursorType.TopRightCorner, CursorType.TopRightCorner},
+ {StandardCursorType.BottomLeftCorner, CursorType.BottomLeftCorner},
+ {StandardCursorType.BottomRightCorner, CursorType.BottomRightCorner}
};
private static readonly Dictionary Cache =
diff --git a/src/Gtk/Perspex.Gtk/GtkPlatform.cs b/src/Gtk/Perspex.Gtk/GtkPlatform.cs
index 16ba905589..b8a6d5c511 100644
--- a/src/Gtk/Perspex.Gtk/GtkPlatform.cs
+++ b/src/Gtk/Perspex.Gtk/GtkPlatform.cs
@@ -14,7 +14,7 @@ namespace Perspex.Gtk
{
using Gtk = global::Gtk;
- public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings
+ public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform
{
private static readonly GtkPlatform s_instance = new GtkPlatform();
private static Thread _uiThread;
@@ -33,8 +33,7 @@ namespace Perspex.Gtk
public static void Initialize()
{
PerspexLocator.CurrentMutable
- .Bind().ToTransient()
- .Bind().ToTransient()
+ .Bind().ToConstant(s_instance)
.Bind().ToSingleton()
.Bind().ToConstant(CursorFactory.Instance)
.Bind().ToConstant(GtkKeyboardDevice.Instance)
@@ -86,5 +85,19 @@ namespace Perspex.Gtk
public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _uiThread;
public event Action Signaled;
+ public IWindowImpl CreateWindow()
+ {
+ return new WindowImpl();
+ }
+
+ public IWindowImpl CreateEmbeddableWindow()
+ {
+ throw new NotSupportedException();
+ }
+
+ public IPopupImpl CreatePopup()
+ {
+ return new PopupImpl();
+ }
}
}
\ No newline at end of file
diff --git a/src/Gtk/Perspex.Gtk/Input/GtkKeyboardDevice.cs b/src/Gtk/Perspex.Gtk/Input/GtkKeyboardDevice.cs
index 3ccec97656..b0b9016ff6 100644
--- a/src/Gtk/Perspex.Gtk/Input/GtkKeyboardDevice.cs
+++ b/src/Gtk/Perspex.Gtk/Input/GtkKeyboardDevice.cs
@@ -214,7 +214,7 @@ namespace Perspex.Gtk
//{ Gdk.Key.?, Key.DeadCharProcessed }
};
- public static GtkKeyboardDevice Instance { get; } = new GtkKeyboardDevice();
+ public new static GtkKeyboardDevice Instance { get; } = new GtkKeyboardDevice();
public static Key ConvertKey(Gdk.Key key)
{
diff --git a/src/Gtk/Perspex.Gtk/PopupImpl.cs b/src/Gtk/Perspex.Gtk/PopupImpl.cs
index 8e432e2448..63e34460b4 100644
--- a/src/Gtk/Perspex.Gtk/PopupImpl.cs
+++ b/src/Gtk/Perspex.Gtk/PopupImpl.cs
@@ -12,10 +12,5 @@ namespace Perspex.Gtk
: base(WindowType.Popup)
{
}
-
- public void SetPosition(Point p)
- {
- Move((int)p.X, (int)p.Y);
- }
}
}
diff --git a/src/Gtk/Perspex.Gtk/WindowImpl.cs b/src/Gtk/Perspex.Gtk/WindowImpl.cs
index 64b1beb779..0435a052ba 100644
--- a/src/Gtk/Perspex.Gtk/WindowImpl.cs
+++ b/src/Gtk/Perspex.Gtk/WindowImpl.cs
@@ -11,6 +11,7 @@ using Perspex.Platform;
using Perspex.Input;
using Perspex.Threading;
using Action = System.Action;
+using WindowEdge = Perspex.Controls.WindowEdge;
namespace Perspex.Gtk
{
@@ -162,6 +163,36 @@ namespace Perspex.Gtk
GdkWindow.Cursor = cursor != null ? new Gdk.Cursor(cursor.Handle) : DefaultCursor;
}
+ public void BeginMoveDrag()
+ {
+ int x, y;
+ ModifierType mod;
+ Screen.RootWindow.GetPointer(out x, out y, out mod);
+ BeginMoveDrag(1, x, y, 0);
+ }
+
+ public void BeginResizeDrag(WindowEdge edge)
+ {
+ int x, y;
+ ModifierType mod;
+ Screen.RootWindow.GetPointer(out x, out y, out mod);
+ BeginResizeDrag((Gdk.WindowEdge) (int) edge, 1, x, y, 0);
+ }
+
+ public Point Position
+ {
+ get
+ {
+ int x, y;
+ GetPosition(out x, out y);
+ return new Point(x, y);
+ }
+ set
+ {
+ Move((int)value.X, (int)value.Y);
+ }
+ }
+
public IDisposable ShowDialog()
{
Modal = true;
@@ -170,6 +201,8 @@ namespace Perspex.Gtk
return Disposable.Empty;
}
+ public void SetSystemDecorations(bool enabled) => Decorated = enabled;
+
void ITopLevelImpl.Activate()
{
Activate();
diff --git a/src/Markup/Perspex.Markup.Xaml/Context/NameScopeWrapper.cs b/src/Markup/Perspex.Markup.Xaml/Context/NameScopeWrapper.cs
index 4cb67ce912..2a5e46825a 100644
--- a/src/Markup/Perspex.Markup.Xaml/Context/NameScopeWrapper.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Context/NameScopeWrapper.cs
@@ -5,9 +5,9 @@ namespace Perspex.Markup.Xaml.Context
{
internal class NameScopeWrapper : OmniXaml.INameScope
{
- private Perspex.INameScope _inner;
+ private readonly Perspex.Controls.INameScope _inner;
- public NameScopeWrapper(Perspex.INameScope inner)
+ public NameScopeWrapper(Perspex.Controls.INameScope inner)
{
_inner = inner;
}
diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexContentPropertyProvider.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexContentPropertyProvider.cs
index bff1db29b5..590ffde841 100644
--- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexContentPropertyProvider.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexContentPropertyProvider.cs
@@ -15,7 +15,7 @@ namespace Perspex.Markup.Xaml.Context
{
public class PerspexContentPropertyProvider : IContentPropertyProvider
{
- private Dictionary _values = new Dictionary();
+ private readonly Dictionary _values = new Dictionary();
public string GetContentPropertyName(Type type)
{
diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs
index 2a9a88219f..788bfd3ced 100644
--- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs
@@ -29,6 +29,8 @@ namespace Perspex.Markup.Xaml.Context
public object Result => _objectAssembler.Result;
+ public InstanceLifeCycleHandler InstanceLifeCycleHandler { get; set; } = new InstanceLifeCycleHandler();
+
public EventHandler XamlSetValueHandler { get; set; }
public IWiringContext WiringContext => _objectAssembler.WiringContext;
diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
index 89203022a9..e42df57d59 100644
--- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
@@ -15,6 +15,7 @@ using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Input;
using Perspex.Markup.Xaml.Converters;
+using Perspex.Markup.Xaml.Data;
using Perspex.Media;
using Perspex.Media.Imaging;
using Perspex.Metadata;
@@ -58,9 +59,10 @@ namespace Perspex.Markup.Xaml.Context
var forcedAssemblies = new[]
{
+ typeof(Binding),
typeof(Control),
- typeof(Style),
typeof(IValueConverter),
+ typeof(Style),
}.Select(t => t.GetTypeInfo().Assembly);
foreach (var nsa in
@@ -88,7 +90,7 @@ namespace Perspex.Markup.Xaml.Context
var typeConverterProvider = new TypeConverterProvider();
var converters = new[]
{
- new TypeConverterRegistration(typeof(Bitmap), new BitmapTypeConverter()),
+ new TypeConverterRegistration(typeof(IBitmap), new BitmapTypeConverter()),
new TypeConverterRegistration(typeof(Brush), new BrushTypeConverter()),
new TypeConverterRegistration(typeof(Color), new ColorTypeConverter()),
new TypeConverterRegistration(typeof(Classes), new ClassesTypeConverter()),
@@ -107,6 +109,7 @@ namespace Perspex.Markup.Xaml.Context
new TypeConverterRegistration(typeof(Thickness), new ThicknessTypeConverter()),
new TypeConverterRegistration(typeof(TimeSpan), new TimeSpanTypeConverter()),
new TypeConverterRegistration(typeof(Uri), new UriTypeConverter()),
+ new TypeConverterRegistration(typeof(Cursor), new CursorTypeConverter())
};
typeConverterProvider.AddAll(converters);
diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
index f6a33aa026..e9ce58c771 100644
--- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
@@ -9,6 +9,8 @@ using System.Runtime.CompilerServices;
using Glass;
using OmniXaml.ObjectAssembler;
using OmniXaml.Typing;
+using Perspex.Controls;
+using Perspex.Data;
using Perspex.Markup.Xaml.Data;
using Perspex.Styling;
@@ -39,9 +41,9 @@ namespace Perspex.Markup.Xaml.Context
public override void SetValue(object instance, object value)
{
- if (value is IXamlBinding)
+ if (value is IBinding)
{
- HandleBinding(instance, (IXamlBinding)value);
+ HandleBinding(instance, (IBinding)value);
}
else if (IsPerspexProperty)
{
@@ -68,35 +70,38 @@ namespace Perspex.Markup.Xaml.Context
po.SetValue(pp, value);
}
- private void HandleBinding(object instance, IXamlBinding binding)
+ private void HandleBinding(object instance, IBinding binding)
{
- if (typeof(IXamlBinding).GetTypeInfo().IsAssignableFrom(_xamlMember.XamlType.UnderlyingType.GetTypeInfo()))
+ if (!(AssignBinding(instance, binding) || ApplyBinding(instance, binding)))
{
- var property = instance.GetType().GetRuntimeProperty(_xamlMember.Name);
+ throw new InvalidOperationException(
+ $"Cannot assign to '{_xamlMember.Name}' on '{instance.GetType()}");
+ }
+ }
- if (property == null || !property.CanWrite)
- {
- throw new InvalidOperationException(
- $"Cannot assign to '{_xamlMember.Name}' on '{instance.GetType()}");
- }
+ private bool AssignBinding(object instance, IBinding binding)
+ {
+ var property = instance.GetType()
+ .GetRuntimeProperties()
+ .FirstOrDefault(x => x.Name == _xamlMember.Name);
+ if (property?.GetCustomAttribute() != null)
+ {
property.SetValue(instance, binding);
+ return true;
}
- else
- {
- ApplyBinding(instance, binding);
- }
+
+ return false;
}
- private void ApplyBinding(object instance, IXamlBinding binding)
+ private bool ApplyBinding(object instance, IBinding binding)
{
- var perspexObject = instance as PerspexObject;
+ var targetControl = instance as IControl;
var attached = _xamlMember as PerspexAttachableXamlMember;
- if (perspexObject == null)
+ if (targetControl == null)
{
- throw new InvalidOperationException(
- $"Cannot bind to an object of type '{instance.GetType()}");
+ return false;
}
PerspexProperty property;
@@ -105,7 +110,7 @@ namespace Perspex.Markup.Xaml.Context
if (attached == null)
{
propertyName = _xamlMember.Name;
- property = PerspexPropertyRegistry.Instance.GetRegistered(perspexObject)
+ property = PerspexPropertyRegistry.Instance.GetRegistered((PerspexObject)targetControl)
.FirstOrDefault(x => x.Name == propertyName);
}
else
@@ -115,18 +120,18 @@ namespace Perspex.Markup.Xaml.Context
propertyName = attached.DeclaringType.UnderlyingType.Name + '.' + _xamlMember.Name;
- property = PerspexPropertyRegistry.Instance.GetRegistered(perspexObject)
+ property = PerspexPropertyRegistry.Instance.GetRegistered((PerspexObject)targetControl)
.Where(x => x.IsAttached && x.OwnerType == attached.DeclaringType.UnderlyingType)
.FirstOrDefault(x => x.Name == _xamlMember.Name);
}
if (property == null)
{
- throw new InvalidOperationException(
- $"Cannot find '{propertyName}' on '{instance.GetType()}");
+ return false;
}
- binding.Bind(perspexObject, property);
+ targetControl.Bind(property, binding);
+ return true;
}
private bool ValueRequiresSpecialHandling(object value)
diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs
index c771b30509..413df6c3ea 100644
--- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs
@@ -5,7 +5,7 @@ using System;
using System.Reflection;
using OmniXaml;
using OmniXaml.Typing;
-using Perspex.Markup.Xaml.Data;
+using Perspex.Controls;
namespace Perspex.Markup.Xaml.Context
{
@@ -20,15 +20,15 @@ namespace Perspex.Markup.Xaml.Context
public override OmniXaml.INameScope GetNamescope(object instance)
{
- var result = this.UnderlyingType as OmniXaml.INameScope;
+ var result = instance as OmniXaml.INameScope;
if (result == null)
{
- var visual = instance as Visual;
+ var control = instance as Control;
- if (visual != null)
+ if (control != null)
{
- var perspexNs = (instance as Perspex.INameScope) ?? NameScope.GetNameScope(visual);
+ var perspexNs = (instance as Perspex.Controls.INameScope) ?? NameScope.GetNameScope(control);
if (perspexNs != null)
{
diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/BitmapTypeConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/BitmapTypeConverter.cs
index 1aa1660079..8d0e1663c1 100644
--- a/src/Markup/Perspex.Markup.Xaml/Converters/BitmapTypeConverter.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Converters/BitmapTypeConverter.cs
@@ -5,6 +5,7 @@ using System;
using System.Globalization;
using OmniXaml.TypeConversion;
using Perspex.Media.Imaging;
+using Perspex.Platform;
namespace Perspex.Markup.Xaml.Converters
{
@@ -22,7 +23,17 @@ namespace Perspex.Markup.Xaml.Converters
public object ConvertFrom(IXamlTypeConverterContext context, CultureInfo culture, object value)
{
- return new Bitmap((string)value);
+ var uri = new Uri((string)value, UriKind.RelativeOrAbsolute);
+ var scheme = uri.IsAbsoluteUri ? uri.Scheme : "file";
+
+ switch (scheme)
+ {
+ case "file":
+ return new Bitmap((string)value);
+ default:
+ var assets = PerspexLocator.Current.GetService();
+ return new Bitmap(assets.Open(uri));
+ }
}
public object ConvertTo(IXamlTypeConverterContext context, CultureInfo culture, object value, Type destinationType)
diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/ClassesTypeConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/ClassesTypeConverter.cs
index 3f49bc04fe..e2e7c4f501 100644
--- a/src/Markup/Perspex.Markup.Xaml/Converters/ClassesTypeConverter.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Converters/ClassesTypeConverter.cs
@@ -4,6 +4,7 @@
using System;
using System.Globalization;
using OmniXaml.TypeConversion;
+using Perspex.Controls;
using Perspex.Styling;
namespace Perspex.Markup.Xaml.Converters
diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/CursorTypeConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/CursorTypeConverter.cs
new file mode 100644
index 0000000000..2310d563c5
--- /dev/null
+++ b/src/Markup/Perspex.Markup.Xaml/Converters/CursorTypeConverter.cs
@@ -0,0 +1,36 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Globalization;
+using OmniXaml.TypeConversion;
+using Perspex.Input;
+using Perspex.Media.Imaging;
+using Perspex.Platform;
+
+namespace Perspex.Markup.Xaml.Converters
+{
+ public class CursorTypeConverter : ITypeConverter
+ {
+ public bool CanConvertFrom(IXamlTypeConverterContext context, Type sourceType)
+ {
+ return sourceType == typeof(string);
+ }
+
+ public bool CanConvertTo(IXamlTypeConverterContext context, Type destinationType)
+ {
+ return false;
+ }
+
+ public object ConvertFrom(IXamlTypeConverterContext context, CultureInfo culture, object value)
+ {
+ var cursor = (StandardCursorType)Enum.Parse(typeof (StandardCursorType), ((string) value).Trim(), true);
+ return new Cursor(cursor);
+ }
+
+ public object ConvertTo(IXamlTypeConverterContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs
index b46364d4c4..7191cf9f63 100644
--- a/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs
@@ -55,13 +55,9 @@ namespace Perspex.Markup.Xaml.Converters
}
// First look for non-attached property on the type and then look for an attached property.
- var property = PerspexPropertyRegistry.Instance.FindRegistered(type, s);
-
- if (property == null)
- {
- property = PerspexPropertyRegistry.Instance.GetAttached(type)
- .FirstOrDefault(x => x.Name == propertyName);
- }
+ var property = PerspexPropertyRegistry.Instance.FindRegistered(type, s) ??
+ PerspexPropertyRegistry.Instance.GetAttached(type)
+ .FirstOrDefault(x => x.Name == propertyName);
if (property == null)
{
diff --git a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs
index 08d7e82387..8a254cbf6e 100644
--- a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs
@@ -2,9 +2,11 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Perspex.Controls;
+using Perspex.Data;
using Perspex.Markup.Data;
namespace Perspex.Markup.Xaml.Data
@@ -12,13 +14,18 @@ namespace Perspex.Markup.Xaml.Data
///
/// A XAML binding.
///
- public class Binding : IXamlBinding
+ public class Binding : IBinding
{
///
/// Gets or sets the to use.
///
public IValueConverter Converter { get; set; }
+ ///
+ /// Gets or sets a parameter to pass to .
+ ///
+ public object ConverterParameter { get; set; }
+
///
/// Gets or sets the name of the element to use as the binding source.
///
@@ -44,66 +51,41 @@ namespace Perspex.Markup.Xaml.Data
///
public string Path { get; set; }
- ///
- /// Applies the binding to a property on an instance.
- ///
- /// The target instance.
- /// The target property.
- public void Bind(IObservablePropertyBag instance, PerspexProperty property)
- {
- Contract.Requires(instance != null);
- Contract.Requires(property != null);
-
- var subject = CreateSubject(
- instance,
- property.PropertyType,
- property == Control.DataContextProperty);
-
- if (subject != null)
- {
- Bind(instance, property, subject);
- }
- }
-
///
/// Creates a subject that can be used to get and set the value of the binding.
///
/// The target instance.
- /// The type of the target property.
- ///
- /// Whether the target property is the DataContext property.
- ///
- /// An .
+ /// The target property. May be null.
+ /// An .
public ISubject CreateSubject(
- IObservablePropertyBag target,
- Type targetType,
- bool targetIsDataContext = false)
+ IPerspexObject target,
+ PerspexProperty targetProperty)
{
Contract.Requires(target != null);
- Contract.Requires(targetType != null);
var pathInfo = ParsePath(Path);
ValidateState(pathInfo);
ExpressionObserver observer;
+ var targetIsDataContext = targetProperty == Control.DataContextProperty;
if (pathInfo.ElementName != null || ElementName != null)
{
- observer = CreateElementSubject(
+ observer = CreateElementObserver(
(IControl)target,
pathInfo.ElementName ?? ElementName,
pathInfo.Path);
}
else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
{
- observer = CreateDataContextSubject(
+ observer = CreateDataContexObserver(
target,
pathInfo.Path,
targetIsDataContext);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
- observer = CreateTemplatedParentSubject(
+ observer = CreateTemplatedParentObserver(
target,
pathInfo.Path);
}
@@ -114,44 +96,9 @@ namespace Perspex.Markup.Xaml.Data
return new ExpressionSubject(
observer,
- targetType,
- Converter ?? DefaultValueConverter.Instance);
- }
-
- ///
- /// Applies a binding subject to a property on an instance.
- ///
- /// The target instance.
- /// The target property.
- /// The binding subject.
- internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject subject)
- {
- Contract.Requires(target != null);
- Contract.Requires(property != null);
- Contract.Requires(subject != null);
-
- var mode = Mode == BindingMode.Default ?
- property.DefaultBindingMode : Mode;
-
- switch (mode)
- {
- case BindingMode.Default:
- case BindingMode.OneWay:
- target.Bind(property, subject, Priority);
- break;
- case BindingMode.TwoWay:
- target.BindTwoWay(property, subject, Priority);
- break;
- case BindingMode.OneTime:
- target.GetObservable(Control.DataContextProperty).Subscribe(dataContext =>
- {
- subject.Take(1).Subscribe(x => target.SetValue(property, x, Priority));
- });
- break;
- case BindingMode.OneWayToSource:
- target.GetObservable(property).Subscribe(subject);
- break;
- }
+ targetProperty?.PropertyType ?? typeof(object),
+ Converter ?? DefaultValueConverter.Instance,
+ ConverterParameter);
}
private static PathInfo ParsePath(string path)
@@ -201,56 +148,55 @@ namespace Perspex.Markup.Xaml.Data
}
}
- private ExpressionObserver CreateDataContextSubject(
- IObservablePropertyBag target,
+ private ExpressionObserver CreateDataContexObserver(
+ IPerspexObject target,
string path,
bool targetIsDataContext)
{
Contract.Requires(target != null);
- var dataContextHost = targetIsDataContext ?
- target.InheritanceParent as IObservablePropertyBag : target;
-
- if (dataContextHost != null)
+ if (!targetIsDataContext)
{
+ var update = target.GetObservable(Control.DataContextProperty)
+ .Skip(1)
+ .Select(_ => Unit.Default);
var result = new ExpressionObserver(
- () => dataContextHost.GetValue(Control.DataContextProperty),
- path);
- dataContextHost.GetObservable(Control.DataContextProperty).Subscribe(x =>
- result.UpdateRoot());
+ () => target.GetValue(Control.DataContextProperty),
+ path,
+ update);
+
return result;
}
else
{
- throw new InvalidOperationException(
- "Cannot bind to DataContext of object with no parent.");
+ return new ExpressionObserver(
+ target.GetObservable(Visual.VisualParentProperty)
+ .OfType()
+ .Select(x => x.GetObservable(Control.DataContextProperty))
+ .Switch(),
+ path);
}
}
- private ExpressionObserver CreateTemplatedParentSubject(
- IObservablePropertyBag target,
+ private ExpressionObserver CreateTemplatedParentObserver(
+ IPerspexObject target,
string path)
{
Contract.Requires(target != null);
+ var update = target.GetObservable(Control.TemplatedParentProperty)
+ .Skip(1)
+ .Select(_ => Unit.Default);
+
var result = new ExpressionObserver(
() => target.GetValue(Control.TemplatedParentProperty),
- path);
-
- if (target.GetValue(Control.TemplatedParentProperty) == null)
- {
- // TemplatedParent should only be set once, so only listen for the first non-null
- // value.
- target.GetObservable(Control.TemplatedParentProperty)
- .Where(x => x != null)
- .Take(1)
- .Subscribe(x => result.UpdateRoot());
- }
+ path,
+ update);
return result;
}
- private ExpressionObserver CreateElementSubject(
+ private ExpressionObserver CreateElementObserver(
IControl target,
string elementName,
string path)
diff --git a/src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs
deleted file mode 100644
index e72fb5eccc..0000000000
--- a/src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) The Perspex Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Reactive.Subjects;
-
-namespace Perspex.Markup.Xaml.Data
-{
- ///
- /// Defines a binding that can be created in XAML markup.
- ///
- public interface IXamlBinding
- {
- ///
- /// Applies the binding to a property on an instance.
- ///
- /// The target instance.
- /// The target property.
- void Bind(IObservablePropertyBag instance, PerspexProperty property);
-
- ///
- /// Creates a subject that can be used to get and set the value of the binding.
- ///
- /// The target instance.
- /// The type of the target property.
- ///
- /// Whether the target property is the DataContext property.
- ///
- /// An .
- ISubject CreateSubject(
- IObservablePropertyBag target,
- Type targetType,
- bool targetIsDataContext = false);
- }
-}
\ No newline at end of file
diff --git a/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs b/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs
index 46a01a166c..ea3760cf83 100644
--- a/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs
@@ -8,6 +8,7 @@ using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Perspex.Controls;
+using Perspex.Data;
using Perspex.Metadata;
namespace Perspex.Markup.Xaml.Data
@@ -15,13 +16,13 @@ namespace Perspex.Markup.Xaml.Data
///
/// A XAML binding that calculates an aggregate value from multiple child .
///
- public class MultiBinding : IXamlBinding
+ public class MultiBinding : IBinding
{
///
/// Gets the collection of child bindings.
///
[Content]
- public IList Bindings { get; set; } = new List();
+ public IList Bindings { get; set; } = new List();
///
/// Gets or sets the to use.
@@ -31,7 +32,7 @@ namespace Perspex.Markup.Xaml.Data
///
/// Gets or sets the binding mode.
///
- public BindingMode Mode { get; set; }
+ public BindingMode Mode { get; set; } = BindingMode.OneWay;
///
/// Gets or sets the binding priority.
@@ -48,9 +49,9 @@ namespace Perspex.Markup.Xaml.Data
///
/// The target instance.
/// The target property.
- public void Bind(IObservablePropertyBag instance, PerspexProperty property)
+ public void Bind(IPerspexObject instance, PerspexProperty property)
{
- var subject = CreateSubject(instance, property.PropertyType);
+ var subject = CreateSubject(instance, property);
if (subject != null)
{
@@ -62,24 +63,19 @@ namespace Perspex.Markup.Xaml.Data
/// Creates a subject that can be used to get and set the value of the binding.
///
/// The target instance.
- /// The type of the target property.
- ///
- /// Whether the target property is the DataContext property.
- ///
- /// An .
- public ISubject CreateSubject(
- IObservablePropertyBag target,
- Type targetType,
- bool targetIsDataContext = false)
+ /// The target property.
+ /// An .
+ public ISubject CreateSubject(IPerspexObject target, PerspexProperty targetProperty)
{
if (Converter == null)
{
throw new NotSupportedException("MultiBinding without Converter not currently supported.");
}
+ var targetType = targetProperty?.PropertyType ?? typeof(object);
var result = new BehaviorSubject(PerspexProperty.UnsetValue);
- var children = Bindings.Select(x => x.CreateSubject(target, typeof(object)));
- var input = Observable.CombineLatest(children).Select(x =>
+ var children = Bindings.Select(x => x.CreateSubject(target, null));
+ var input = children.CombineLatest().Select(x =>
Converter.Convert(x, targetType, null, CultureInfo.CurrentUICulture));
input.Subscribe(result);
return result;
@@ -91,7 +87,7 @@ namespace Perspex.Markup.Xaml.Data
/// The target instance.
/// The target property.
/// The binding subject.
- internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject subject)
+ internal void Bind(IPerspexObject target, PerspexProperty property, ISubject subject)
{
var mode = Mode == BindingMode.Default ?
property.DefaultBindingMode : Mode;
diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs
index c2cdc5efd3..d8a668da08 100644
--- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs
+++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using OmniXaml;
+using Perspex.Data;
using Perspex.Markup.Xaml.Data;
namespace Perspex.Markup.Xaml.MarkupExtensions
@@ -22,15 +23,19 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
return new Binding
{
Converter = Converter,
+ ConverterParameter = ConverterParameter,
ElementName = ElementName,
Mode = Mode,
Path = Path,
+ Priority = Priority,
};
}
public IValueConverter Converter { get; set; }
+ public object ConverterParameter { get; set; }
public string ElementName { get; set; }
public BindingMode Mode { get; set; }
public string Path { get; set; }
+ public BindingPriority Priority { get; set; } = BindingPriority.LocalValue;
}
}
\ No newline at end of file
diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
index b3bd1c7c75..cec5146c71 100644
--- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
+++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using OmniXaml;
+using Perspex.Data;
using Perspex.Markup.Xaml.Data;
namespace Perspex.Markup.Xaml.MarkupExtensions
@@ -24,9 +25,9 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
Converter = Converter,
ElementName = ElementName,
Mode = Mode,
- Priority = BindingPriority.TemplatedParent,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Path = Path,
+ Priority = Priority,
};
}
@@ -34,5 +35,6 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
public string ElementName { get; set; }
public BindingMode Mode { get; set; }
public string Path { get; set; }
+ public BindingPriority Priority { get; set; } = BindingPriority.TemplatedParent;
}
}
\ No newline at end of file
diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML
index 3e3b46ba66..0904dcb071 160000
--- a/src/Markup/Perspex.Markup.Xaml/OmniXAML
+++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML
@@ -1 +1 @@
-Subproject commit 3e3b46ba66941da925092e2977003d0553cfc907
+Subproject commit 0904dcb07175ca0cdf2ae1fda1434c0f1425a53e
diff --git a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs
index ba3e2fa5ac..1823995321 100644
--- a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs
@@ -3,10 +3,12 @@
using System.Collections.Generic;
using System.Globalization;
-using System.Linq;
-using Perspex.Styling;
using Sprache;
+// Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the
+// only reason they have overridden Equals methods is for unit testing.
+#pragma warning disable 659
+
namespace Perspex.Markup.Xaml.Parsers
{
internal class SelectorGrammar
@@ -129,7 +131,8 @@ namespace Perspex.Markup.Xaml.Parsers
public override bool Equals(object obj)
{
- return obj is OfTypeSyntax && ((OfTypeSyntax)obj).TypeName == TypeName;
+ var other = obj as OfTypeSyntax;
+ return other != null && other.TypeName == TypeName && other.Xmlns == Xmlns;
}
}
@@ -141,7 +144,8 @@ namespace Perspex.Markup.Xaml.Parsers
public override bool Equals(object obj)
{
- return obj is IsSyntax && ((IsSyntax)obj).TypeName == TypeName;
+ var other = obj as IsSyntax;
+ return other != null && other.TypeName == TypeName && other.Xmlns == Xmlns;
}
}
diff --git a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs
index 8e727c3ff7..dd1341996e 100644
--- a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs
@@ -14,7 +14,7 @@ namespace Perspex.Markup.Xaml.Parsers
///
public class SelectorParser
{
- private Func _typeResolver;
+ private readonly Func _typeResolver;
///
/// Initializes a new instance of the class.
diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
index 35fde7d0e4..d9cd4c6f63 100644
--- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
+++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
@@ -39,9 +39,9 @@
Properties\SharedAssemblyInfo.cs
+
-
@@ -108,6 +108,7 @@
+
@@ -118,6 +119,7 @@
+
@@ -129,6 +131,7 @@
+
@@ -140,6 +143,7 @@
+
@@ -272,10 +276,6 @@
{D211E587-D8BC-45B9-95A4-F297C8FA5200}
Perspex.Animation
-
- {799A7BB5-3C2C-48B6-85A7-406A12C420DA}
- Perspex.Application
-
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}
Perspex.Base
diff --git a/src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs b/src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs
index cfdd6c1da1..16eb793159 100644
--- a/src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs
+++ b/src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
+using System.Text;
using OmniXaml;
using Perspex.Markup.Xaml.Context;
using Perspex.Platform;
@@ -39,6 +40,8 @@ namespace Perspex.Markup.Xaml
/// The object to load the XAML into.
public static void Load(object obj)
{
+ Contract.Requires(obj != null);
+
var loader = new PerspexXamlLoader();
loader.Load(obj.GetType(), obj);
}
@@ -53,6 +56,8 @@ namespace Perspex.Markup.Xaml
/// The loaded object.
public object Load(Type type, object rootInstance = null)
{
+ Contract.Requires(type != null);
+
// HACK: Currently Visual Studio is forcing us to change the extension of xaml files
// in certain situations, so we try to load .xaml and if that's not found we try .paml.
// Ideally we'd be able to use .xaml everywhere
@@ -85,6 +90,8 @@ namespace Perspex.Markup.Xaml
/// The loaded object.
public object Load(Uri uri, object rootInstance = null)
{
+ Contract.Requires(uri != null);
+
var assetLocator = PerspexLocator.Current.GetService();
if (assetLocator == null)
@@ -99,6 +106,24 @@ namespace Perspex.Markup.Xaml
}
}
+ ///
+ /// Loads XAML from a string.
+ ///
+ /// The string containing the XAML.
+ ///
+ /// The optional instance into which the XAML should be loaded.
+ ///
+ /// The loaded object.
+ public object Load(string xaml, object rootInstance = null)
+ {
+ Contract.Requires(xaml != null);
+
+ using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
+ {
+ return Load(stream, rootInstance);
+ }
+ }
+
///
/// Gets the URI for a type.
///
@@ -108,8 +133,8 @@ namespace Perspex.Markup.Xaml
{
var asm = type.GetTypeInfo().Assembly.GetName().Name;
var typeName = type.FullName;
- yield return new Uri("resource://application/" + asm + "/" + typeName + ".xaml");
- yield return new Uri("resource://application/" + asm + "/" + typeName + ".paml");
+ yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm);
+ yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm);
}
}
diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs
index 37a8f338d5..96e38fd292 100644
--- a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs
@@ -20,10 +20,12 @@ namespace Perspex.Markup.Xaml.Templates
{
if (DataType == null)
{
- throw new InvalidOperationException("DataTemplate must have a DataType.");
+ return true;
+ }
+ else
+ {
+ return DataType.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo());
}
-
- return DataType.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo());
}
public IControl Build(object data)
diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/MemberSelector.cs b/src/Markup/Perspex.Markup.Xaml/Templates/MemberSelector.cs
index 3f1d660cb6..1231fd2a28 100644
--- a/src/Markup/Perspex.Markup.Xaml/Templates/MemberSelector.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Templates/MemberSelector.cs
@@ -14,15 +14,7 @@ namespace Perspex.Markup.Xaml.Templates
{
// TODO: Handle nested property paths, changing values etc.
var property = o.GetType().GetRuntimeProperty(MemberName);
-
- if (property != null)
- {
- return property.GetValue(o);
- }
- else
- {
- return null;
- }
+ return property?.GetValue(o);
}
}
}
diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs
index dc9143be1b..ceabd09bb7 100644
--- a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs
@@ -4,8 +4,10 @@
using System;
using System.Collections;
using System.Reactive.Linq;
+using System.Reflection;
using Perspex.Controls;
using Perspex.Controls.Templates;
+using Perspex.Data;
using Perspex.Markup.Data;
using Perspex.Markup.Xaml.Data;
using Perspex.Metadata;
@@ -19,16 +21,19 @@ namespace Perspex.Markup.Xaml.Templates
[Content]
public TemplateContent Content { get; set; }
+ [AssignBinding]
public Binding ItemsSource { get; set; }
public bool Match(object data)
{
if (DataType == null)
{
- throw new InvalidOperationException("DataTemplate must have a DataType.");
+ return true;
+ }
+ else
+ {
+ return DataType.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo());
}
-
- return DataType == data.GetType();
}
public IEnumerable ItemsSelector(object item)
diff --git a/src/Markup/Perspex.Markup/ControlLocator.cs b/src/Markup/Perspex.Markup/ControlLocator.cs
index 36330d89c5..4887de8a15 100644
--- a/src/Markup/Perspex.Markup/ControlLocator.cs
+++ b/src/Markup/Perspex.Markup/ControlLocator.cs
@@ -25,7 +25,7 @@ namespace Perspex.Markup
var attached = Observable.FromEventPattern(
x => relativeTo.AttachedToVisualTree += x,
x => relativeTo.DetachedFromVisualTree += x)
- .Select(x => x.EventArgs.NameScope)
+ .Select(x => ((IControl)x.Sender).FindNameScope())
.StartWith(relativeTo.FindNameScope());
var detached = Observable.FromEventPattern(
@@ -45,10 +45,12 @@ namespace Perspex.Markup
.OfType();
var unregistered = Observable.FromEventPattern(
x => nameScope.Unregistered += x,
- x => nameScope.Unregistered -= x);
+ x => nameScope.Unregistered -= x)
+ .Where(x => x.EventArgs.Name == name)
+ .Select(_ => (IControl)null);
return registered
.StartWith(nameScope.Find(name))
- .TakeUntil(unregistered);
+ .Merge(unregistered);
}
else
{
diff --git a/src/Markup/Perspex.Markup/Data/ExpressionNode.cs b/src/Markup/Perspex.Markup/Data/ExpressionNode.cs
index 5af8576882..c138050cb4 100644
--- a/src/Markup/Perspex.Markup/Data/ExpressionNode.cs
+++ b/src/Markup/Perspex.Markup/Data/ExpressionNode.cs
@@ -63,10 +63,7 @@ namespace Perspex.Markup.Data
Next.Target = value;
}
- if (_subject != null)
- {
- _subject.OnNext(value);
- }
+ _subject?.OnNext(value);
}
}
diff --git a/src/Markup/Perspex.Markup/Data/ExpressionNodeBuilder.cs b/src/Markup/Perspex.Markup/Data/ExpressionNodeBuilder.cs
index af70bc717e..93b38a438a 100644
--- a/src/Markup/Perspex.Markup/Data/ExpressionNodeBuilder.cs
+++ b/src/Markup/Perspex.Markup/Data/ExpressionNodeBuilder.cs
@@ -20,7 +20,7 @@ namespace Perspex.Markup.Data
if (!reader.End)
{
- throw new ExpressionParseException(reader, "Expected end of expression.");
+ throw new ExpressionParseException(reader.Position, "Expected end of expression.");
}
return node;
diff --git a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs
index d669713156..52e619872f 100644
--- a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs
+++ b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Reactive;
using System.Reactive.Disposables;
+using System.Reactive.Linq;
using System.Reactive.Subjects;
using Perspex.Markup.Data.Plugins;
@@ -29,10 +30,11 @@ namespace Perspex.Markup.Data
private readonly object _root;
private readonly Func _rootGetter;
private readonly IObservable _rootObservable;
+ private readonly IObservable _update;
private IDisposable _rootObserverSubscription;
+ private IDisposable _updateSubscription;
private int _count;
private readonly ExpressionNode _node;
- private ISubject _empty;
///
/// Initializes a new instance of the class.
@@ -78,12 +80,18 @@ namespace Perspex.Markup.Data
///
/// A function which gets the root object.
/// The expression.
- public ExpressionObserver(Func rootGetter, string expression)
+ /// An observable which triggers a re-read of the getter.
+ public ExpressionObserver(
+ Func rootGetter,
+ string expression,
+ IObservable update)
{
Contract.Requires(rootGetter != null);
Contract.Requires(expression != null);
+ Contract.Requires(update != null);
_rootGetter = rootGetter;
+ _update = update;
if (!string.IsNullOrWhiteSpace(expression))
{
@@ -104,7 +112,11 @@ namespace Perspex.Markup.Data
public bool SetValue(object value)
{
IncrementCount();
- UpdateRoot();
+
+ if (_rootGetter != null && _node != null)
+ {
+ _node.Target = _rootGetter();
+ }
try
{
@@ -156,6 +168,11 @@ namespace Perspex.Markup.Data
///
string IDescription.Description => Expression;
+ ///
+ /// Gets the root expression node. Used for testing.
+ ///
+ internal ExpressionNode Node => _node;
+
///
/// Gets the leaf node.
///
@@ -169,24 +186,6 @@ namespace Perspex.Markup.Data
}
}
- ///
- /// Causes the root object to be re-read from the root getter.
- ///
- public void UpdateRoot()
- {
- if (_count > 0 && _rootGetter != null)
- {
- if (_node != null)
- {
- _node.Target = _rootGetter();
- }
- else if (_empty != null)
- {
- _empty.OnNext(_rootGetter());
- }
- }
- }
-
///
protected override IDisposable SubscribeCore(IObserver observer)
{
@@ -202,14 +201,23 @@ namespace Perspex.Markup.Data
subscription.Dispose();
});
}
+ else if (_rootObservable != null)
+ {
+ return _rootObservable.Subscribe(observer);
+ }
else
{
- if (_empty == null)
+ if (_update == null)
{
- _empty = new BehaviorSubject(_rootGetter());
+ return Observable.Never().StartWith(_root).Subscribe(observer);
+ }
+ else
+ {
+ return _update
+ .Select(_ => _rootGetter())
+ .StartWith(_rootGetter())
+ .Subscribe(observer);
}
-
- return _empty.Subscribe(observer);
}
}
@@ -220,6 +228,11 @@ namespace Perspex.Markup.Data
if (_rootGetter != null)
{
_node.Target = _rootGetter();
+
+ if (_update != null)
+ {
+ _updateSubscription = _update.Subscribe(x => _node.Target = _rootGetter());
+ }
}
else if (_rootObservable != null)
{
@@ -242,6 +255,12 @@ namespace Perspex.Markup.Data
_rootObserverSubscription = null;
}
+ if (_updateSubscription != null)
+ {
+ _updateSubscription.Dispose();
+ _updateSubscription = null;
+ }
+
_node.Target = null;
}
}
diff --git a/src/Markup/Perspex.Markup/Data/ExpressionParseException.cs b/src/Markup/Perspex.Markup/Data/ExpressionParseException.cs
index fffeac8a77..d92f42cab7 100644
--- a/src/Markup/Perspex.Markup/Data/ExpressionParseException.cs
+++ b/src/Markup/Perspex.Markup/Data/ExpressionParseException.cs
@@ -6,19 +6,26 @@ using Perspex.Markup.Data.Parsers;
namespace Perspex.Markup.Data
{
+ ///
+ /// Exception thrown when could not parse the provided
+ /// expression string.
+ ///
public class ExpressionParseException : Exception
{
- internal ExpressionParseException(int column, string message)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The column position of the error.
+ /// The exception message.
+ public ExpressionParseException(int column, string message)
: base(message)
{
Column = column;
}
- internal ExpressionParseException(Reader r, string message)
- : this(r.Position, message)
- {
- }
-
+ ///
+ /// Gets the column position at which the error occurred.
+ ///
public int Column { get; }
}
}
diff --git a/src/Markup/Perspex.Markup/Data/ExpressionSubject.cs b/src/Markup/Perspex.Markup/Data/ExpressionSubject.cs
index a39033ec59..c69865bacc 100644
--- a/src/Markup/Perspex.Markup/Data/ExpressionSubject.cs
+++ b/src/Markup/Perspex.Markup/Data/ExpressionSubject.cs
@@ -15,8 +15,8 @@ namespace Perspex.Markup.Data
///
public class ExpressionSubject : ISubject, IDescription
{
- private ExpressionObserver _inner;
- private Type _targetType;
+ private readonly ExpressionObserver _inner;
+ private readonly Type _targetType;
///
/// Initializes a new instance of the class.
@@ -34,7 +34,12 @@ namespace Perspex.Markup.Data
/// The .
/// The type to convert the value to.
/// The value converter to use.
- public ExpressionSubject(ExpressionObserver inner, Type targetType, IValueConverter converter)
+ /// A parameter to pass to .
+ public ExpressionSubject(
+ ExpressionObserver inner,
+ Type targetType,
+ IValueConverter converter,
+ object converterParameter = null)
{
Contract.Requires(inner != null);
Contract.Requires(targetType != null);
@@ -43,6 +48,7 @@ namespace Perspex.Markup.Data
_inner = inner;
_targetType = targetType;
Converter = converter;
+ ConverterParameter = converterParameter;
}
///
@@ -50,6 +56,11 @@ namespace Perspex.Markup.Data
///
public IValueConverter Converter { get; }
+ ///
+ /// Gets a parameter to pass to .
+ ///
+ public object ConverterParameter { get; }
+
///
string IDescription.Description => _inner.Expression;
@@ -70,7 +81,11 @@ namespace Perspex.Markup.Data
if (type != null)
{
- var converted = Converter.ConvertBack(value, type, null, CultureInfo.CurrentUICulture);
+ var converted = Converter.ConvertBack(
+ value,
+ type,
+ ConverterParameter,
+ CultureInfo.CurrentUICulture);
if (converted == PerspexProperty.UnsetValue)
{
@@ -85,7 +100,11 @@ namespace Perspex.Markup.Data
public IDisposable Subscribe(IObserver observer)
{
return _inner
- .Select(x => Converter.Convert(x, _targetType, null, CultureInfo.CurrentUICulture))
+ .Select(x => Converter.Convert(
+ x,
+ _targetType,
+ ConverterParameter,
+ CultureInfo.CurrentUICulture))
.Subscribe(observer);
}
}
diff --git a/src/Markup/Perspex.Markup/Data/IndexerNode.cs b/src/Markup/Perspex.Markup/Data/IndexerNode.cs
index 7279175730..b791e15eaa 100644
--- a/src/Markup/Perspex.Markup/Data/IndexerNode.cs
+++ b/src/Markup/Perspex.Markup/Data/IndexerNode.cs
@@ -12,7 +12,7 @@ namespace Perspex.Markup.Data
{
internal class IndexerNode : ExpressionNode
{
- private int[] _intArgs;
+ private readonly int[] _intArgs;
public IndexerNode(IList arguments)
{
diff --git a/src/Markup/Perspex.Markup/Data/LogicalNotNode.cs b/src/Markup/Perspex.Markup/Data/LogicalNotNode.cs
index fac0d19b4c..2447aee4f2 100644
--- a/src/Markup/Perspex.Markup/Data/LogicalNotNode.cs
+++ b/src/Markup/Perspex.Markup/Data/LogicalNotNode.cs
@@ -16,10 +16,10 @@ namespace Perspex.Markup.Data
public override IDisposable Subscribe(IObserver observer)
{
- return Next.Select(x => Negate(x)).Subscribe(observer);
+ return Next.Select(Negate).Subscribe(observer);
}
- private object Negate(object v)
+ private static object Negate(object v)
{
if (v != PerspexProperty.UnsetValue)
{
diff --git a/src/Markup/Perspex.Markup/Data/Parsers/ArgumentListParser.cs b/src/Markup/Perspex.Markup/Data/Parsers/ArgumentListParser.cs
index 588e3257b5..5b67166739 100644
--- a/src/Markup/Perspex.Markup/Data/Parsers/ArgumentListParser.cs
+++ b/src/Markup/Perspex.Markup/Data/Parsers/ArgumentListParser.cs
@@ -26,14 +26,14 @@ namespace Perspex.Markup.Data.Parsers
}
else
{
- throw new ExpressionParseException(r, "Expected integer.");
+ throw new ExpressionParseException(r.Position, "Expected integer.");
}
r.SkipWhitespace();
if (r.End)
{
- throw new ExpressionParseException(r, "Expected ','.");
+ throw new ExpressionParseException(r.Position, "Expected ','.");
}
else if (r.TakeIf(close))
{
@@ -43,7 +43,7 @@ namespace Perspex.Markup.Data.Parsers
{
if (r.Take() != ',')
{
- throw new ExpressionParseException(r, "Expected ','.");
+ throw new ExpressionParseException(r.Position, "Expected ','.");
}
r.SkipWhitespace();
@@ -57,7 +57,7 @@ namespace Perspex.Markup.Data.Parsers
}
else
{
- throw new ExpressionParseException(r, "Expected ']'.");
+ throw new ExpressionParseException(r.Position, "Expected ']'.");
}
}
diff --git a/src/Markup/Perspex.Markup/Data/Parsers/ExpressionParser.cs b/src/Markup/Perspex.Markup/Data/Parsers/ExpressionParser.cs
index 6f743175c9..2ec7f49f5c 100644
--- a/src/Markup/Perspex.Markup/Data/Parsers/ExpressionParser.cs
+++ b/src/Markup/Perspex.Markup/Data/Parsers/ExpressionParser.cs
@@ -34,7 +34,7 @@ namespace Perspex.Markup.Data.Parsers
if (state == State.BeforeMember)
{
- throw new ExpressionParseException(r, "Unexpected end of expression.");
+ throw new ExpressionParseException(r.Position, "Unexpected end of expression.");
}
for (int n = 0; n < nodes.Count - 1; ++n)
@@ -80,7 +80,7 @@ namespace Perspex.Markup.Data.Parsers
{
if (args.Count == 0)
{
- throw new ExpressionParseException(r, "Indexer may not be empty.");
+ throw new ExpressionParseException(r.Position, "Indexer may not be empty.");
}
nodes.Add(new IndexerNode(args));
diff --git a/src/Markup/Perspex.Markup/Data/Parsers/Reader.cs b/src/Markup/Perspex.Markup/Data/Parsers/Reader.cs
index f82cb34ded..6c86dbf733 100644
--- a/src/Markup/Perspex.Markup/Data/Parsers/Reader.cs
+++ b/src/Markup/Perspex.Markup/Data/Parsers/Reader.cs
@@ -7,7 +7,7 @@ namespace Perspex.Markup.Data.Parsers
{
internal class Reader
{
- private string _s;
+ private readonly string _s;
private int _i;
public Reader(string s)
diff --git a/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs
index f6907fddfd..13970a9054 100644
--- a/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs
+++ b/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs
@@ -3,6 +3,7 @@
using System;
using System.ComponentModel;
+using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
@@ -42,7 +43,7 @@ namespace Perspex.Markup.Data.Plugins
Contract.Requires(propertyName != null);
Contract.Requires(changed != null);
- var p = instance.GetType().GetRuntimeProperty(propertyName);
+ var p = instance.GetType().GetRuntimeProperties().FirstOrDefault(_ => _.Name == propertyName);
if (p != null)
{
@@ -56,9 +57,9 @@ namespace Perspex.Markup.Data.Plugins
private class Accessor : IPropertyAccessor
{
- private object _instance;
- private PropertyInfo _property;
- private Action _changed;
+ private readonly object _instance;
+ private readonly PropertyInfo _property;
+ private readonly Action _changed;
public Accessor(object instance, PropertyInfo property, Action changed)
{
@@ -77,15 +78,9 @@ namespace Perspex.Markup.Data.Plugins
}
}
- public Type PropertyType
- {
- get { return _property.PropertyType; }
- }
+ public Type PropertyType => _property.PropertyType;
- public object Value
- {
- get { return _property.GetValue(_instance); }
- }
+ public object Value => _property.GetValue(_instance);
public void Dispose()
{
diff --git a/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs
index 8e8fba45cb..a5b9f00371 100644
--- a/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs
+++ b/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs
@@ -56,8 +56,8 @@ namespace Perspex.Markup.Data.Plugins
private class Accessor : IPropertyAccessor
{
- private PerspexObject _instance;
- private PerspexProperty _property;
+ private readonly PerspexObject _instance;
+ private readonly PerspexProperty _property;
private IDisposable _subscription;
public Accessor(PerspexObject instance, PerspexProperty property, Action changed)
@@ -70,15 +70,9 @@ namespace Perspex.Markup.Data.Plugins
_subscription = instance.GetObservable(property).Skip(1).Subscribe(changed);
}
- public Type PropertyType
- {
- get { return _property.PropertyType; }
- }
+ public Type PropertyType => _property.PropertyType;
- public object Value
- {
- get { return _instance.GetValue(_property); }
- }
+ public object Value => _instance.GetValue(_property);
public void Dispose()
{
diff --git a/src/Markup/Perspex.Markup/FuncMultiValueConverter.cs b/src/Markup/Perspex.Markup/FuncMultiValueConverter.cs
index b956512357..66165e5876 100644
--- a/src/Markup/Perspex.Markup/FuncMultiValueConverter.cs
+++ b/src/Markup/Perspex.Markup/FuncMultiValueConverter.cs
@@ -16,7 +16,7 @@ namespace Perspex.Markup
/// The output type.
public class FuncMultiValueConverter : IMultiValueConverter
{
- private Func, TOut> _convert;
+ private readonly Func, TOut> _convert;
///
/// Initializes a new instance of the class.
diff --git a/src/Markup/Perspex.Markup/FuncValueConverter.cs b/src/Markup/Perspex.Markup/FuncValueConverter.cs
index 74059a3c3e..2dec0c43a4 100644
--- a/src/Markup/Perspex.Markup/FuncValueConverter.cs
+++ b/src/Markup/Perspex.Markup/FuncValueConverter.cs
@@ -15,7 +15,7 @@ namespace Perspex.Markup
/// The output type.
public class FuncValueConverter : IValueConverter
{
- private Func _convert;
+ private readonly Func _convert;
///
/// Initializes a new instance of the class.
diff --git a/src/Markup/Perspex.Markup/StringConverters.cs b/src/Markup/Perspex.Markup/StringConverters.cs
index da4be76a90..950617a52d 100644
--- a/src/Markup/Perspex.Markup/StringConverters.cs
+++ b/src/Markup/Perspex.Markup/StringConverters.cs
@@ -16,7 +16,7 @@ namespace Perspex.Markup
/// A value converter that returns true if the input string is null or an empty string.
///
public static readonly IValueConverter NullOrEmpty =
- new FuncValueConverter(x => string.IsNullOrEmpty(x));
+ new FuncValueConverter(string.IsNullOrEmpty);
///
/// A value converter that returns true if the input string is not null or empty.
diff --git a/src/Perspex.Animation/Animatable.cs b/src/Perspex.Animation/Animatable.cs
index 33c7f611ad..8e77b28dfe 100644
--- a/src/Perspex.Animation/Animatable.cs
+++ b/src/Perspex.Animation/Animatable.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
+using Perspex.Data;
namespace Perspex.Animation
{
@@ -25,12 +26,7 @@ namespace Perspex.Animation
{
get
{
- if (_propertyTransitions == null)
- {
- _propertyTransitions = new PropertyTransitions();
- }
-
- return _propertyTransitions;
+ return _propertyTransitions ?? (_propertyTransitions = new PropertyTransitions());
}
set
diff --git a/src/Perspex.Animation/Animate.cs b/src/Perspex.Animation/Animate.cs
index 85db3e71b7..1f85ffaadf 100644
--- a/src/Perspex.Animation/Animate.cs
+++ b/src/Perspex.Animation/Animate.cs
@@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
+using Perspex.Data;
using Perspex.Threading;
namespace Perspex.Animation
@@ -96,7 +97,7 @@ namespace Perspex.Animation
/// The duration of the animation.
/// An that can be used to track or stop the animation.
public static Animation Property(
- IObservablePropertyBag target,
+ IPerspexObject target,
PerspexProperty property,
object start,
object finish,
@@ -119,7 +120,7 @@ namespace Perspex.Animation
/// The duration of the animation.
/// An that can be used to track or stop the animation.
public static Animation Property(
- IObservablePropertyBag target,
+ IPerspexObject target,
PerspexProperty property,
T start,
T finish,
diff --git a/src/Perspex.Animation/LinearEasing.cs b/src/Perspex.Animation/LinearEasing.cs
index 716ed2e6f5..57583a505c 100644
--- a/src/Perspex.Animation/LinearEasing.cs
+++ b/src/Perspex.Animation/LinearEasing.cs
@@ -27,9 +27,8 @@ namespace Perspex.Animation
}
else
{
- throw new NotSupportedException(string.Format(
- "Don't know how to create a LinearEasing for type '{0}'.",
- typeof(T).FullName));
+ throw new NotSupportedException(
+ $"Don't know how to create a LinearEasing for type '{typeof(T).FullName}'.");
}
}
}
diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs
index d718d76e71..177c337d04 100644
--- a/src/Perspex.Application/Application.cs
+++ b/src/Perspex.Application/Application.cs
@@ -32,9 +32,9 @@ namespace Perspex
/// method.
/// - Tracks the lifetime of the application.
///
- public class Application : IGlobalDataTemplates, IGlobalStyles
+ public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot
{
- static bool _suppressPlatformInitialization;
+ static Action _platformInitializationCallback;
///
/// The application-global data templates.
@@ -62,9 +62,9 @@ namespace Perspex
Current = this;
}
- public static void SuppressPlatformInitialization()
+ public static void RegisterPlatformCallback(Action cb)
{
- _suppressPlatformInitialization = true;
+ _platformInitializationCallback = cb;
}
///
@@ -87,20 +87,8 @@ namespace Perspex
///
public DataTemplates DataTemplates
{
- get
- {
- if (_dataTemplates == null)
- {
- _dataTemplates = new DataTemplates();
- }
-
- return _dataTemplates;
- }
-
- set
- {
- _dataTemplates = value;
- }
+ get { return _dataTemplates ?? (_dataTemplates = new DataTemplates()); }
+ set { _dataTemplates = value; }
}
///
@@ -147,6 +135,11 @@ namespace Perspex
protected set;
}
+ ///
+ /// Gets the styling parent of the application, which is null.
+ ///
+ IStyleHost IStyleHost.StylingParent => null;
+
///
/// Runs the application's main loop until the is closed.
///
@@ -185,9 +178,11 @@ namespace Perspex
/// The value of Environment.OSVersion.Platform.
protected void InitializeSubsystems(int platformID)
{
- if(_suppressPlatformInitialization)
- return;
- if (platformID == 4 || platformID == 6)
+ if (_platformInitializationCallback != null)
+ {
+ _platformInitializationCallback();
+ }
+ else if (platformID == 4 || platformID == 6)
{
InitializeSubsystem("Perspex.Cairo");
InitializeSubsystem("Perspex.Gtk");
@@ -203,7 +198,7 @@ namespace Perspex
/// Initializes the rendering or windowing subsystem defined by the specified assemblt.
///
/// The name of the assembly.
- protected void InitializeSubsystem(string assemblyName)
+ protected static void InitializeSubsystem(string assemblyName)
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
var platformClassName = assemblyName.Replace("Perspex.", string.Empty) + "Platform";
@@ -212,5 +207,11 @@ namespace Perspex
var init = platformClass.GetRuntimeMethod("Initialize", new Type[0]);
init.Invoke(null, null);
}
+
+ internal static void InitializeWin32Subsystem()
+ {
+ InitializeSubsystem("Perspex.Direct2D1");
+ InitializeSubsystem("Perspex.Win32");
+ }
}
}
diff --git a/src/Perspex.Application/Designer/Design.cs b/src/Perspex.Application/Designer/Design.cs
new file mode 100644
index 0000000000..d27646d6c7
--- /dev/null
+++ b/src/Perspex.Application/Designer/Design.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Perspex.Controls;
+
+namespace Perspex
+{
+ public static class Design
+ {
+ public static bool IsDesignMode { get; internal set; }
+
+ public static readonly PerspexProperty HeightProperty = PerspexProperty
+ .RegisterAttached("Height", typeof (Design));
+
+ public static void SetHeight(Control control, double value)
+ {
+ control.SetValue(HeightProperty, value);
+ }
+
+ public static double GetHeight(Control control)
+ {
+ return control.GetValue(HeightProperty);
+ }
+
+ public static readonly PerspexProperty WidthProperty = PerspexProperty
+ .RegisterAttached("Width", typeof(Design));
+
+ public static void SetWidth(Control control, double value)
+ {
+ control.SetValue(WidthProperty, value);
+ }
+
+ public static double GetWidth(Control control)
+ {
+ return control.GetValue(WidthProperty);
+ }
+
+ public static readonly PerspexProperty DataContextProperty = PerspexProperty
+ .RegisterAttached("DataContext", typeof (Design));
+
+ public static void SetDataContext(Control control, object value)
+ {
+ control.SetValue(DataContextProperty, value);
+ }
+
+ public static object GetDataContext(Control control)
+ {
+ return control.GetValue(DataContextProperty);
+ }
+
+ internal static void ApplyDesignerProperties(Control target, Control source)
+ {
+ if (source.IsSet(WidthProperty))
+ target.Width = source.GetValue(WidthProperty);
+ if (source.IsSet(HeightProperty))
+ target.Height = source.GetValue(HeightProperty);
+ if (source.IsSet(DataContextProperty))
+ target.DataContext = source.GetValue(DataContextProperty);
+ }
+ }
+}
diff --git a/src/Perspex.Application/Designer/DesignerApi.cs b/src/Perspex.Application/Designer/DesignerApi.cs
new file mode 100644
index 0000000000..4a172af87b
--- /dev/null
+++ b/src/Perspex.Application/Designer/DesignerApi.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Perspex.DesignerSupport
+{
+ class DesignerApi
+ {
+ private readonly Dictionary _inner;
+
+ public DesignerApi(Dictionary inner)
+ {
+ _inner = inner;
+ }
+
+ object Get([CallerMemberName] string name = null)
+ {
+ object rv;
+ _inner.TryGetValue(name, out rv);
+ return rv;
+ }
+
+ void Set(object value, [CallerMemberName] string name = null)
+ {
+ _inner[name] = value;
+ }
+
+ public Action UpdateXaml
+ {
+ get { return (Action) Get(); }
+ set {Set(value); }
+ }
+
+ public Action OnResize
+ {
+ get { return (Action) Get(); }
+ set { Set(value);}
+ }
+
+ public Action OnWindowCreated
+ {
+ set { Set(value); }
+ get { return (Action) Get(); }
+ }
+
+ public Action SetScalingFactor
+ {
+ set { Set(value);}
+ get { return (Action) Get(); }
+ }
+
+ }
+}
diff --git a/src/Perspex.Application/Designer/DesignerAssist.cs b/src/Perspex.Application/Designer/DesignerAssist.cs
new file mode 100644
index 0000000000..31f716b8be
--- /dev/null
+++ b/src/Perspex.Application/Designer/DesignerAssist.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using OmniXaml;
+using Perspex.Controls;
+using Perspex.Controls.Platform;
+using Perspex.Markup.Xaml;
+using Perspex.Platform;
+using Perspex.Themes.Default;
+
+namespace Perspex.DesignerSupport
+{
+ class DesignerAssist
+ {
+ class DesignerApp : Application
+ {
+ public DesignerApp()
+ {
+ RegisterServices();
+ //For now we only support windows
+ InitializeSubsystems(2);
+ Styles = new DefaultTheme();
+ }
+ }
+
+ public static DesignerApi Api { get; set; }
+
+ public static void Init(Dictionary shared)
+ {
+ Design.IsDesignMode = true;
+ Api = new DesignerApi(shared) {UpdateXaml = UpdateXaml, SetScalingFactor = SetScalingFactor};
+ Application.RegisterPlatformCallback(Application.InitializeWin32Subsystem);
+
+ var plat = (IPclPlatformWrapper) Activator.CreateInstance(Assembly.Load(new AssemblyName("Perspex.Win32"))
+ .DefinedTypes.First(typeof (IPclPlatformWrapper).GetTypeInfo().IsAssignableFrom).AsType());
+ var app = plat.GetLoadedAssemblies()
+ .SelectMany(a => a.DefinedTypes)
+ .Where(typeof (Application).GetTypeInfo().IsAssignableFrom).FirstOrDefault(t => t.Assembly != typeof (Application).GetTypeInfo().Assembly);
+ if (app == null)
+ new DesignerApp();
+ else
+ Activator.CreateInstance(app.AsType());
+ }
+
+ private static void SetScalingFactor(double factor)
+ {
+ PlatformManager.SetDesignerScalingFactor(factor);
+ if (s_currentWindow != null)
+ s_currentWindow.PlatformImpl.ClientSize = s_currentWindow.ClientSize;
+ }
+
+ static Window s_currentWindow;
+
+ private static void UpdateXaml(string xaml)
+ {
+
+ Window window;
+ Control original;
+ using (PlatformManager.DesignerMode())
+ {
+ original =(Control)((XamlXmlLoader)new PerspexXamlLoader()).Load(new MemoryStream(Encoding.UTF8.GetBytes(xaml)));
+ window = original as Window;
+ if (window == null)
+ {
+ window = new Window() {Content = original};
+ }
+ }
+ s_currentWindow?.Close();
+ s_currentWindow = window;
+ window.Show();
+ Design.ApplyDesignerProperties(window, original);
+ Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle);
+ Api.OnResize?.Invoke();
+ }
+ }
+}
diff --git a/src/Perspex.Application/Perspex.Application.csproj b/src/Perspex.Application/Perspex.Application.csproj
index c5fe995636..bbafdcf1a7 100644
--- a/src/Perspex.Application/Perspex.Application.csproj
+++ b/src/Perspex.Application/Perspex.Application.csproj
@@ -27,6 +27,7 @@
prompt
4
bin\Debug\Perspex.Application.XML
+ CS1591
pdbonly
@@ -39,6 +40,18 @@
+
+ {3e53a01a-b331-47f3-b828-4a5717e77a24}
+ Perspex.Markup.Xaml
+
+
+ {6417e941-21bc-467b-a771-0de389353ce6}
+ Perspex.Markup
+
+
+ {d211e587-d8bc-45b9-95a4-f297c8fa5200}
+ Perspex.Animation
+
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}
Perspex.Base
@@ -67,13 +80,19 @@
{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}
Perspex.Styling
+
+ {3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}
+ Perspex.Themes.Default
+
Properties\SharedAssemblyInfo.cs
-
+
+
+
diff --git a/src/Perspex.Base/Collections/PerspexDictionary.cs b/src/Perspex.Base/Collections/PerspexDictionary.cs
index 7324e653ea..d3da0c031d 100644
--- a/src/Perspex.Base/Collections/PerspexDictionary.cs
+++ b/src/Perspex.Base/Collections/PerspexDictionary.cs
@@ -71,10 +71,7 @@ namespace Perspex.Collections
if (replace)
{
- if (PropertyChanged != null)
- {
- PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]"));
- }
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));
if (CollectionChanged != null)
{
diff --git a/src/Perspex.Base/Collections/PerspexList.cs b/src/Perspex.Base/Collections/PerspexList.cs
index 9c200aedaf..3cb3b34ad0 100644
--- a/src/Perspex.Base/Collections/PerspexList.cs
+++ b/src/Perspex.Base/Collections/PerspexList.cs
@@ -52,7 +52,7 @@ namespace Perspex.Collections
///
///
///
- public class PerspexList : IPerspexList, IList, INotifyCollectionChanged, INotifyPropertyChanged
+ public class PerspexList : IPerspexList, IList
{
private List _inner;
@@ -150,7 +150,8 @@ namespace Perspex.Collections
var e = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Replace,
value,
- old);
+ old,
+ index);
CollectionChanged(this, e);
}
}
@@ -171,7 +172,7 @@ namespace Perspex.Collections
/// Adds an item to the collection.
///
/// The item.
- public void Add(T item)
+ public virtual void Add(T item)
{
Validate?.Invoke(item);
int index = _inner.Count;
@@ -183,7 +184,7 @@ namespace Perspex.Collections
/// Adds multiple items to the collection.
///
/// The items.
- public void AddRange(IEnumerable items)
+ public virtual void AddRange(IEnumerable items)
{
Contract.Requires(items != null);
@@ -247,6 +248,16 @@ namespace Perspex.Collections
return _inner.GetEnumerator();
}
+ ///
+ /// Gets a range of items from the collection.
+ ///
+ /// The first index to remove.
+ /// The number of items to remove.
+ public IEnumerable GetRange(int index, int count)
+ {
+ return _inner.GetRange(index, count);
+ }
+
///
/// Gets the index of the specified item in the collection.
///
@@ -264,7 +275,7 @@ namespace Perspex.Collections
///
/// The index.
/// The item.
- public void Insert(int index, T item)
+ public virtual void Insert(int index, T item)
{
Validate?.Invoke(item);
_inner.Insert(index, item);
@@ -276,7 +287,7 @@ namespace Perspex.Collections
///
/// The index.
/// The items.
- public void InsertRange(int index, IEnumerable items)
+ public virtual void InsertRange(int index, IEnumerable items)
{
Contract.Requires(items != null);
@@ -302,7 +313,7 @@ namespace Perspex.Collections
///
/// The item.
/// True if the item was found and removed, otherwise false.
- public bool Remove(T item)
+ public virtual bool Remove(T item)
{
int index = _inner.IndexOf(item);
@@ -320,7 +331,7 @@ namespace Perspex.Collections
/// Removes multiple items from the collection.
///
/// The items.
- public void RemoveAll(IEnumerable items)
+ public virtual void RemoveAll(IEnumerable items)
{
Contract.Requires(items != null);
@@ -337,7 +348,7 @@ namespace Perspex.Collections
/// Removes the item at the specified index.
///
/// The index.
- public void RemoveAt(int index)
+ public virtual void RemoveAt(int index)
{
T item = _inner[index];
_inner.RemoveAt(index);
@@ -349,7 +360,7 @@ namespace Perspex.Collections
///
/// The first index to remove.
/// The number of items to remove.
- public void RemoveRange(int index, int count)
+ public virtual void RemoveRange(int index, int count)
{
if (count > 0)
{
@@ -437,10 +448,7 @@ namespace Perspex.Collections
///
private void NotifyCountChanged()
{
- if (PropertyChanged != null)
- {
- PropertyChanged(this, new PropertyChangedEventArgs("Count"));
- }
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
}
///
@@ -469,14 +477,9 @@ namespace Perspex.Collections
{
NotifyCollectionChangedEventArgs e;
- if (ResetBehavior == ResetBehavior.Reset)
- {
- e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
- }
- else
- {
- e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t, 0);
- }
+ e = ResetBehavior == ResetBehavior.Reset ?
+ new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset) :
+ new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t, 0);
CollectionChanged(this, e);
}
diff --git a/src/Perspex.Base/Contract.cs b/src/Perspex.Base/Contract.cs
index 95e2976474..ffc47a2123 100644
--- a/src/Perspex.Base/Contract.cs
+++ b/src/Perspex.Base/Contract.cs
@@ -1,12 +1,12 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
using System.Runtime.CompilerServices;
+using JetBrains.Annotations;
namespace Perspex
{
- using System;
-
///
/// A stub of Code Contract's Contract class.
///
@@ -25,6 +25,7 @@ namespace Perspex
///
/// The precondition.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [ContractAnnotation("condition:false=>stop")]
public static void Requires(bool condition) where TException : Exception, new()
{
if (!condition)
diff --git a/src/Perspex.Base/Data/AssignBindingAttribute.cs b/src/Perspex.Base/Data/AssignBindingAttribute.cs
new file mode 100644
index 0000000000..d44b66b58e
--- /dev/null
+++ b/src/Perspex.Base/Data/AssignBindingAttribute.cs
@@ -0,0 +1,20 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Perspex.Data
+{
+ ///
+ /// Signifies that a binding can be assigned to a property.
+ ///
+ ///
+ /// Usually in markup, when a binding is set for a property that property will be bound.
+ /// Applying this attribute to a property indicates that the binding should be assigned to
+ /// the property rather than bound.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public sealed class AssignBindingAttribute : Attribute
+ {
+ }
+}
diff --git a/src/Perspex.Base/Data/BindingMode.cs b/src/Perspex.Base/Data/BindingMode.cs
new file mode 100644
index 0000000000..885f58ed1a
--- /dev/null
+++ b/src/Perspex.Base/Data/BindingMode.cs
@@ -0,0 +1,36 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Perspex.Data
+{
+ ///
+ /// Defines possible binding modes.
+ ///
+ public enum BindingMode
+ {
+ ///
+ /// Uses the default binding mode specified for the property.
+ ///
+ Default,
+
+ ///
+ /// Binds one way from source to target.
+ ///
+ OneWay,
+
+ ///
+ /// Binds two-way with the initial value coming from the target.
+ ///
+ TwoWay,
+
+ ///
+ /// Updates the target when the application starts or when the data context changes.
+ ///
+ OneTime,
+
+ ///
+ /// Binds one way from target to source.
+ ///
+ OneWayToSource,
+ }
+}
diff --git a/src/Perspex.Base/BindingPriority.cs b/src/Perspex.Base/Data/BindingPriority.cs
similarity index 98%
rename from src/Perspex.Base/BindingPriority.cs
rename to src/Perspex.Base/Data/BindingPriority.cs
index e26898d202..055b252fa3 100644
--- a/src/Perspex.Base/BindingPriority.cs
+++ b/src/Perspex.Base/Data/BindingPriority.cs
@@ -1,7 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-namespace Perspex
+namespace Perspex.Data
{
///
/// The priority of a binding.
diff --git a/src/Perspex.Base/Data/IBinding.cs b/src/Perspex.Base/Data/IBinding.cs
new file mode 100644
index 0000000000..025ef7dfb7
--- /dev/null
+++ b/src/Perspex.Base/Data/IBinding.cs
@@ -0,0 +1,33 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Reactive.Subjects;
+
+namespace Perspex.Data
+{
+ ///
+ /// Holds a binding that can be applied to a property on an object.
+ ///
+ public interface IBinding
+ {
+ ///
+ /// Gets the binding mode.
+ ///
+ BindingMode Mode { get; }
+
+ ///
+ /// Gets the binding priority.
+ ///
+ BindingPriority Priority { get; }
+
+ ///
+ /// Creates a subject that can be used to get and set the value of the binding.
+ ///
+ /// The target instance.
+ /// The target property. May be null.
+ /// An .
+ ISubject CreateSubject(
+ IPerspexObject target,
+ PerspexProperty targetProperty);
+ }
+}
diff --git a/src/Perspex.Base/BindingDescriptor.cs b/src/Perspex.Base/Data/IndexerDescriptor.cs
similarity index 64%
rename from src/Perspex.Base/BindingDescriptor.cs
rename to src/Perspex.Base/Data/IndexerDescriptor.cs
index 0006843ae3..94563a5499 100644
--- a/src/Perspex.Base/BindingDescriptor.cs
+++ b/src/Perspex.Base/Data/IndexerDescriptor.cs
@@ -4,43 +4,12 @@
using System;
using System.Reactive;
-namespace Perspex
+namespace Perspex.Data
{
///
- /// Defines possible binding modes.
+ /// Holds a description of a binding for 's [] operator.
///
- public enum BindingMode
- {
- ///
- /// Uses the default binding mode specified for the property.
- ///
- Default,
-
- ///
- /// Binds one way from source to target.
- ///
- OneWay,
-
- ///
- /// Binds two-way with the initial value coming from the target.
- ///
- TwoWay,
-
- ///
- /// Updates the target when the application starts or when the data context changes.
- ///
- OneTime,
-
- ///
- /// Binds one way from target to source.
- ///
- OneWayToSource,
- }
-
- ///
- /// Holds a description of a binding, usually for 's [] operator.
- ///
- public class BindingDescriptor : ObservableBase, IDescription
+ public class IndexerDescriptor : ObservableBase, IDescription
{
///
/// Gets or sets the binding mode.
@@ -78,17 +47,29 @@ namespace Perspex
set;
}
+ ///
+ /// Gets or sets the source observable.
+ ///
+ ///
+ /// If null, then . will be used.
+ ///
+ public IObservable SourceObservable
+ {
+ get;
+ set;
+ }
+
///
/// Gets a description of the binding.
///
- public string Description => string.Format("{0}.{1}", Source?.GetType().Name, Property.Name);
+ public string Description => $"{Source?.GetType().Name}.{Property.Name}";
///
/// Makes a two-way binding.
///
/// The current binding.
/// A two-way binding.
- public static BindingDescriptor operator !(BindingDescriptor binding)
+ public static IndexerDescriptor operator !(IndexerDescriptor binding)
{
return binding.WithMode(BindingMode.TwoWay);
}
@@ -98,7 +79,7 @@ namespace Perspex
///
/// The current binding.
/// A two-way binding.
- public static BindingDescriptor operator ~(BindingDescriptor binding)
+ public static IndexerDescriptor operator ~(IndexerDescriptor binding)
{
return binding.WithMode(BindingMode.TwoWay);
}
@@ -108,7 +89,7 @@ namespace Perspex
///
/// The binding mode.
/// The object that the method was called on.
- public BindingDescriptor WithMode(BindingMode mode)
+ public IndexerDescriptor WithMode(BindingMode mode)
{
Mode = mode;
return this;
@@ -119,7 +100,7 @@ namespace Perspex
///
/// The binding priority.
/// The object that the method was called on.
- public BindingDescriptor WithPriority(BindingPriority priority)
+ public IndexerDescriptor WithPriority(BindingPriority priority)
{
Priority = priority;
return this;
@@ -128,7 +109,7 @@ namespace Perspex
///
protected override IDisposable SubscribeCore(IObserver observer)
{
- return Source.GetObservable(Property).Subscribe(observer);
+ return (SourceObservable ?? Source.GetObservable(Property)).Subscribe(observer);
}
}
}
diff --git a/src/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs b/src/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs
index ccfc37279c..3efc5fbe98 100644
--- a/src/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs
+++ b/src/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs
@@ -1,6 +1,8 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using Perspex.Data;
+
namespace Perspex.Diagnostics
{
///
@@ -27,7 +29,7 @@ namespace Perspex.Diagnostics
{
return new PerspexPropertyValue(
property,
- value.Value,
+ o.GetValue(property),
(BindingPriority)value.ValuePriority,
value.GetDiagnostic());
}
diff --git a/src/Perspex.Base/Diagnostics/PerspexPropertyValue.cs b/src/Perspex.Base/Diagnostics/PerspexPropertyValue.cs
index 99f5111b44..5b1d46bcf1 100644
--- a/src/Perspex.Base/Diagnostics/PerspexPropertyValue.cs
+++ b/src/Perspex.Base/Diagnostics/PerspexPropertyValue.cs
@@ -1,6 +1,8 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using Perspex.Data;
+
namespace Perspex.Diagnostics
{
///
diff --git a/src/Perspex.Base/IObservablePropertyBag.cs b/src/Perspex.Base/IObservablePropertyBag.cs
deleted file mode 100644
index 451cb63a89..0000000000
--- a/src/Perspex.Base/IObservablePropertyBag.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) The Perspex Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Reactive.Subjects;
-
-namespace Perspex
-{
- ///
- /// Interface for getting/setting bindings on an object.
- ///
- public interface IObservablePropertyBag : IPropertyBag
- {
- ///
- /// Binds a to an observable.
- ///
- /// The property.
- /// The observable.
- /// The priority of the binding.
- ///
- /// A disposable which can be used to terminate the binding.
- ///
- IDisposable Bind(
- PerspexProperty property,
- IObservable source,
- BindingPriority priority = BindingPriority.LocalValue);
-
- ///
- /// Binds a to an observable.
- ///
- /// The type of the property.
- /// The property.
- /// The observable.
- /// The priority of the binding.
- ///
- /// A disposable which can be used to terminate the binding.
- ///
- IDisposable Bind(
- PerspexProperty property,
- IObservable source,
- BindingPriority priority = BindingPriority.LocalValue);
-
- ///
- /// Initiates a two-way binding between s.
- ///
- /// The property on this object.
- /// The source object.
- /// The property on the source object.
- /// The priority of the binding.
- ///
- /// A disposable which can be used to terminate the binding.
- ///
- ///
- /// The binding is first carried out from to this.
- ///
- IDisposable BindTwoWay(
- PerspexProperty property,
- PerspexObject source,
- PerspexProperty sourceProperty,
- BindingPriority priority = BindingPriority.LocalValue);
-
- ///
- /// Initiates a two-way binding between a and an
- /// .
- ///
- /// The property on this object.
- /// The subject to bind to.
- /// The priority of the binding.
- ///
- /// A disposable which can be used to terminate the binding.
- ///
- ///
- /// The binding is first carried out from to this.
- ///
- IDisposable BindTwoWay(
- PerspexProperty property,
- ISubject source,
- BindingPriority priority = BindingPriority.LocalValue);
-
- ///
- /// Gets an observable for a .
- ///
- /// The property.
- /// An observable.
- IObservable GetObservable(PerspexProperty property);
-
- ///
- /// Gets an observable for a .
- ///
- /// The type of the property.
- /// The property.
- /// An observable.
- IObservable GetObservable(PerspexProperty property);
- }
-}
\ No newline at end of file
diff --git a/src/Perspex.Base/IPropertyBag.cs b/src/Perspex.Base/IPerspexObject.cs
similarity index 56%
rename from src/Perspex.Base/IPropertyBag.cs
rename to src/Perspex.Base/IPerspexObject.cs
index fe29c349ef..4290bfe792 100644
--- a/src/Perspex.Base/IPropertyBag.cs
+++ b/src/Perspex.Base/IPerspexObject.cs
@@ -1,23 +1,20 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
+using Perspex.Data;
+
namespace Perspex
{
///
/// Interface for getting/setting values on an object.
///
- public interface IPropertyBag
+ public interface IPerspexObject
{
///
- /// Gets the object that inherited values are inherited from.
+ /// Raised when a value changes on this object.
///
- IPropertyBag InheritanceParent { get; }
-
- ///
- /// Clears a 's local value.
- ///
- /// The property.
- void ClearValue(PerspexProperty property);
+ event EventHandler PropertyChanged;
///
/// Gets a value.
@@ -34,13 +31,6 @@ namespace Perspex
/// The value.
T GetValue(PerspexProperty property);
- ///
- /// Checks whether a is registered on this object.
- ///
- /// The property.
- /// True if the property is registered, otherwise false.
- bool IsRegistered(PerspexProperty property);
-
///
/// Checks whether a is set on this object.
///
@@ -54,7 +44,10 @@ namespace Perspex
/// The property.
/// The value.
/// The priority of the value.
- void SetValue(PerspexProperty property, object value, BindingPriority priority = BindingPriority.LocalValue);
+ void SetValue(
+ PerspexProperty property,
+ object value,
+ BindingPriority priority = BindingPriority.LocalValue);
///
/// Sets a value.
@@ -63,6 +56,38 @@ namespace Perspex
/// The property.
/// The value.
/// The priority of the value.
- void SetValue(PerspexProperty property, T value, BindingPriority priority = BindingPriority.LocalValue);
+ void SetValue(
+ PerspexProperty property,
+ T value,
+ BindingPriority priority = BindingPriority.LocalValue);
+
+ ///
+ /// Binds a to an observable.
+ ///
+ /// The property.
+ /// The observable.
+ /// The priority of the binding.
+ ///
+ /// A disposable which can be used to terminate the binding.
+ ///
+ IDisposable Bind(
+ PerspexProperty property,
+ IObservable source,
+ BindingPriority priority = BindingPriority.LocalValue);
+
+ ///
+ /// Binds a to an observable.
+ ///
+ /// The type of the property.
+ /// The property.
+ /// The observable.
+ /// The priority of the binding.
+ ///
+ /// A disposable which can be used to terminate the binding.
+ ///
+ IDisposable Bind(
+ PerspexProperty property,
+ IObservable source,
+ BindingPriority priority = BindingPriority.LocalValue);
}
}
\ No newline at end of file
diff --git a/src/Perspex.Base/Metadata/XmlnsDefinitionAttribute.cs b/src/Perspex.Base/Metadata/XmlnsDefinitionAttribute.cs
index 8e9fddc50b..ad30517dcd 100644
--- a/src/Perspex.Base/Metadata/XmlnsDefinitionAttribute.cs
+++ b/src/Perspex.Base/Metadata/XmlnsDefinitionAttribute.cs
@@ -25,11 +25,11 @@ namespace Perspex.Metadata
///
/// Gets or sets the URL of the XML namespace.
///
- public string XmlNamespace { get; set; }
+ public string XmlNamespace { get; }
///
/// Gets or sets the CLR namespace.
///
- public string ClrNamespace { get; set; }
+ public string ClrNamespace { get; }
}
}
diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj
index 890fa666bf..3a61fbc996 100644
--- a/src/Perspex.Base/Perspex.Base.csproj
+++ b/src/Perspex.Base/Perspex.Base.csproj
@@ -27,6 +27,7 @@
prompt
4
bin\Debug\Perspex.Base.XML
+ CS1591
pdbonly
@@ -41,11 +42,13 @@
Properties\SharedAssemblyInfo.cs
-
+
+
+
+
-
-
+
@@ -53,8 +56,9 @@
+
-
+
@@ -72,6 +76,8 @@
+
+
@@ -83,8 +89,13 @@
+
+
+ ..\..\packages\JetBrains.Annotations.9.2.0\lib\portable-net4+sl5+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\JetBrains.Annotations.PCL328.dll
+ True
+
..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll
diff --git a/src/Perspex.Base/PerspexLocator.cs b/src/Perspex.Base/PerspexLocator.cs
index 8b1129f867..142e78dd8c 100644
--- a/src/Perspex.Base/PerspexLocator.cs
+++ b/src/Perspex.Base/PerspexLocator.cs
@@ -36,7 +36,7 @@ namespace Perspex
public class RegistrationHelper
{
- private PerspexLocator _locator;
+ private readonly PerspexLocator _locator;
public RegistrationHelper(PerspexLocator locator)
{
diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs
index a61e04a8ac..5310651514 100644
--- a/src/Perspex.Base/PerspexObject.cs
+++ b/src/Perspex.Base/PerspexObject.cs
@@ -8,8 +8,7 @@ using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
-using System.Reflection;
-using Perspex.Reactive;
+using Perspex.Data;
using Perspex.Threading;
using Perspex.Utilities;
using Serilog;
@@ -23,7 +22,7 @@ namespace Perspex
///
/// This class is analogous to DependencyObject in WPF.
///
- public class PerspexObject : IObservablePropertyBag, INotifyPropertyChanged
+ public class PerspexObject : IPerspexObject, INotifyPropertyChanged
{
///
/// The parent object that inherited values are inherited from.
@@ -89,11 +88,6 @@ namespace Perspex
remove { _inpcChanged -= value; }
}
- ///
- /// Gets the object that inherited values are inherited from.
- ///
- IPropertyBag IPropertyBag.InheritanceParent => InheritanceParent;
-
///
/// Gets or sets the parent object that inherited values
/// are inherited from.
@@ -159,17 +153,11 @@ namespace Perspex
/// Gets or sets a binding for a .
///
/// The binding information.
- public IObservable this[BindingDescriptor binding]
+ public IObservable this[IndexerDescriptor binding]
{
get
{
- return new BindingDescriptor
- {
- Mode = binding.Mode,
- Priority = binding.Priority,
- Property = binding.Property,
- Source = this,
- };
+ return CreateBindingDescriptor(binding);
}
set
@@ -177,7 +165,7 @@ namespace Perspex
var mode = (binding.Mode == BindingMode.Default) ?
binding.Property.DefaultBindingMode :
binding.Mode;
- var sourceBinding = value as BindingDescriptor;
+ var sourceBinding = value as IndexerDescriptor;
if (sourceBinding == null && mode > BindingMode.OneWay)
{
@@ -194,15 +182,27 @@ namespace Perspex
SetValue(binding.Property, sourceBinding.Source.GetValue(sourceBinding.Property), binding.Priority);
break;
case BindingMode.OneWayToSource:
- sourceBinding.Source.Bind(sourceBinding.Property, GetObservable(binding.Property), binding.Priority);
+ sourceBinding.Source.Bind(sourceBinding.Property, this.GetObservable(binding.Property), binding.Priority);
break;
case BindingMode.TwoWay:
- BindTwoWay(binding.Property, sourceBinding.Source, sourceBinding.Property);
+ var subject = sourceBinding.Source.GetSubject(sourceBinding.Property, sourceBinding.Priority);
+ this.Bind(binding.Property, subject, BindingMode.TwoWay, sourceBinding.Priority);
break;
}
}
}
+ protected virtual IndexerDescriptor CreateBindingDescriptor(IndexerDescriptor source)
+ {
+ return new IndexerDescriptor
+ {
+ Mode = source.Mode,
+ Priority = source.Priority,
+ Property = source.Property,
+ Source = this,
+ };
+ }
+
public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess();
@@ -218,81 +218,6 @@ namespace Perspex
SetValue(property, PerspexProperty.UnsetValue);
}
- ///
- /// Gets an observable for a .
- ///
- /// The property.
- /// An observable.
- public IObservable GetObservable(PerspexProperty property)
- {
- Contract.Requires(property != null);
-
- return new PerspexObservable(
- observer =>
- {
- EventHandler handler = (s, e) =>
- {
- if (e.Property == property)
- {
- observer.OnNext(e.NewValue);
- }
- };
-
- observer.OnNext(GetValue(property));
-
- PropertyChanged += handler;
-
- return Disposable.Create(() =>
- {
- PropertyChanged -= handler;
- });
- },
- GetDescription(property));
- }
-
- ///
- /// Gets an observable for a .
- ///
- /// The property type.
- /// The property.
- /// An observable.
- public IObservable GetObservable(PerspexProperty property)
- {
- Contract.Requires(property != null);
-
- return GetObservable((PerspexProperty)property).Cast();
- }
-
- ///
- /// Gets an observable for a .
- ///
- /// The type of the property.
- /// The property.
- /// An observable which when subscribed pushes the old and new values of the
- /// property each time it is changed.
- public IObservable> GetObservableWithHistory(PerspexProperty property)
- {
- return new PerspexObservable>(
- observer =>
- {
- EventHandler handler = (s, e) =>
- {
- if (e.Property == property)
- {
- observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue));
- }
- };
-
- PropertyChanged += handler;
-
- return Disposable.Create(() =>
- {
- PropertyChanged -= handler;
- });
- },
- GetDescription(property));
- }
-
///
/// Gets a value.
///
@@ -556,67 +481,6 @@ namespace Perspex
}
}
- ///
- /// Initiates a two-way binding between s.
- ///
- /// The property on this object.
- /// The source object.
- /// The property on the source object.
- /// The priority of the binding.
- ///
- /// A disposable which can be used to terminate the binding.
- ///
- ///
- /// The binding is first carried out from to this.
- ///
- public IDisposable BindTwoWay(
- PerspexProperty property,
- PerspexObject source,
- PerspexProperty sourceProperty,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- VerifyAccess();
- _propertyLog.Verbose(
- "Bound two way {Property} to {Binding} with priority {Priority}",
- property,
- source,
- priority);
-
- return new CompositeDisposable(
- Bind(property, source.GetObservable(sourceProperty)),
- source.Bind(sourceProperty, GetObservable(property)));
- }
-
- ///
- /// Initiates a two-way binding between a and an
- /// .
- ///
- /// The property on this object.
- /// The subject to bind to.
- /// The priority of the binding.
- ///
- /// A disposable which can be used to terminate the binding.
- ///
- ///
- /// The binding is first carried out from to this.
- ///
- public IDisposable BindTwoWay(
- PerspexProperty property,
- ISubject source,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- VerifyAccess();
- _propertyLog.Verbose(
- "Bound two way {Property} to {Binding} with priority {Priority}",
- property,
- GetDescription(source),
- priority);
-
- return new CompositeDisposable(
- Bind(property, source),
- GetObservable(property).Subscribe(source));
- }
-
///
/// Forces the specified property to be revalidated.
///
@@ -633,11 +497,6 @@ namespace Perspex
}
///
- bool IPropertyBag.IsRegistered(PerspexProperty property)
- {
- return PerspexPropertyRegistry.Instance.IsRegistered(this, property);
- }
-
///
/// Gets all priority values set on the object.
///
@@ -694,20 +553,14 @@ namespace Perspex
newValue,
priority);
- if (property.Notifying != null)
- {
- property.Notifying(this, true);
- }
+ property.Notifying?.Invoke(this, true);
try
{
OnPropertyChanged(e);
property.NotifyChanged(e);
- if (PropertyChanged != null)
- {
- PropertyChanged(this, e);
- }
+ PropertyChanged?.Invoke(this, e);
if (_inpcChanged != null)
{
@@ -717,10 +570,7 @@ namespace Perspex
}
finally
{
- if (property.Notifying != null)
- {
- property.Notifying(this, false);
- }
+ property.Notifying?.Invoke(this, false);
}
}
@@ -779,7 +629,11 @@ namespace Perspex
validate2 = v => validate(this, v);
}
- PriorityValue result = new PriorityValue(property.Name, property.PropertyType, validate2);
+ PriorityValue result = new PriorityValue(
+ property.Name,
+ property.PropertyType,
+ validate2,
+ _propertyLog);
result.Changed.Subscribe(x =>
{
@@ -866,7 +720,7 @@ namespace Perspex
/// The description.
private string GetDescription(PerspexProperty property)
{
- return string.Format("{0}.{1}", GetType().Name, property.Name);
+ return $"{GetType().Name}.{property.Name}";
}
///
diff --git a/src/Perspex.Base/PerspexObjectExtensions.cs b/src/Perspex.Base/PerspexObjectExtensions.cs
index 7ba3fd33d4..cbfb8e45e7 100644
--- a/src/Perspex.Base/PerspexObjectExtensions.cs
+++ b/src/Perspex.Base/PerspexObjectExtensions.cs
@@ -2,8 +2,12 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Linq;
+using System.Reactive;
+using System.Reactive.Disposables;
using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using Perspex.Data;
+using Perspex.Reactive;
namespace Perspex
{
@@ -12,6 +16,222 @@ namespace Perspex
///
public static class PerspexObjectExtensions
{
+ ///
+ /// Gets an observable for a .
+ ///
+ /// The object.
+ /// The property.
+ /// An observable.
+ public static IObservable GetObservable(this IPerspexObject o, PerspexProperty property)
+ {
+ Contract.Requires(o != null);
+ Contract.Requires(property != null);
+
+ return new PerspexObservable(
+ observer =>
+ {
+ EventHandler handler = (s, e) =>
+ {
+ if (e.Property == property)
+ {
+ observer.OnNext(e.NewValue);
+ }
+ };
+
+ observer.OnNext(o.GetValue(property));
+
+ o.PropertyChanged += handler;
+
+ return Disposable.Create(() =>
+ {
+ o.PropertyChanged -= handler;
+ });
+ },
+ GetDescription(o, property));
+ }
+
+ ///
+ /// Gets an observable for a .
+ ///
+ /// The object.
+ /// The property type.
+ /// The property.
+ /// An observable.
+ public static IObservable GetObservable(this IPerspexObject o, PerspexProperty property)
+ {
+ Contract.Requires(o != null);
+ Contract.Requires(property != null);
+
+ return o.GetObservable((PerspexProperty)property).Cast();
+ }
+
+ ///
+ /// Gets an observable for a .
+ ///
+ /// The object.
+ /// The type of the property.
+ /// The property.
+ ///
+ /// An observable which when subscribed pushes the old and new values of the property each
+ /// time it is changed.
+ ///
+ public static IObservable> GetObservableWithHistory(
+ this IPerspexObject o,
+ PerspexProperty property)
+ {
+ Contract.Requires(o != null);
+ Contract.Requires(property != null);
+
+ return new PerspexObservable>(
+ observer =>
+ {
+ EventHandler handler = (s, e) =>
+ {
+ if (e.Property == property)
+ {
+ observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue));
+ }
+ };
+
+ o.PropertyChanged += handler;
+
+ return Disposable.Create(() =>
+ {
+ o.PropertyChanged -= handler;
+ });
+ },
+ GetDescription(o, property));
+ }
+
+ ///
+ /// Gets a subject for a .
+ ///
+ /// The object.
+ /// The property.
+ ///
+ /// The priority with which binding values are written to the object.
+ ///
+ ///
+ /// An which can be used for two-way binding to/from the
+ /// property.
+ ///
+ public static ISubject GetSubject(
+ this IPerspexObject o,
+ PerspexProperty property,
+ BindingPriority priority = BindingPriority.LocalValue)
+ {
+ // TODO: Subject.Create is not yet in stable Rx : once it is, remove the
+ // AnonymousSubject classes and use Subject.Create.
+ var output = new Subject();
+ var result = new AnonymousSubject(
+ Observer.Create(
+ x => output.OnNext(x),
+ e => output.OnError(e),
+ () => output.OnCompleted()),
+ o.GetObservable(property));
+ o.Bind(property, output, priority);
+ return result;
+ }
+
+ ///
+ /// Gets a subject for a .
+ ///
+ /// The property type.
+ /// The object.
+ /// The property.
+ ///
+ /// The priority with which binding values are written to the object.
+ ///
+ ///
+ /// An which can be used for two-way binding to/from the
+ /// property.
+ ///
+ public static ISubject GetSubject(
+ this IPerspexObject o,
+ PerspexProperty property,
+ BindingPriority priority = BindingPriority.LocalValue)
+ {
+ // TODO: Subject.Create is not yet in stable Rx : once it is, remove the
+ // AnonymousSubject classes from this file and use Subject.Create.
+ var output = new Subject();
+ var result = new AnonymousSubject(
+ Observer.Create(
+ x => output.OnNext(x),
+ e => output.OnError(e),
+ () => output.OnCompleted()),
+ o.GetObservable(property));
+ o.Bind(property, output, priority);
+ return result;
+ }
+
+ ///
+ /// Binds a property on an to an .
+ ///
+ /// The object.
+ /// The property to bind.
+ /// The binding.
+ /// An which can be used to cancel the binding.
+ public static IDisposable Bind(
+ this IPerspexObject o,
+ PerspexProperty property,
+ IBinding binding)
+ {
+ Contract.Requires(o != null);
+ Contract.Requires(property != null);
+ Contract.Requires(binding != null);
+
+ var mode = binding.Mode;
+
+ if (mode == BindingMode.Default)
+ {
+ mode = property.DefaultBindingMode;
+ }
+
+ return o.Bind(
+ property,
+ binding.CreateSubject(o, property),
+ mode,
+ binding.Priority);
+ }
+
+ ///
+ /// Binds a property to a subject according to a .
+ ///
+ /// The object.
+ /// The property to bind.
+ /// The binding source.
+ /// The binding mode.
+ /// The binding priority.
+ /// An which can be used to cancel the binding.
+ public static IDisposable Bind(
+ this IPerspexObject o,
+ PerspexProperty property,
+ ISubject source,
+ BindingMode mode,
+ BindingPriority priority = BindingPriority.LocalValue)
+ {
+ Contract.Requires(o != null);
+ Contract.Requires(property != null);
+ Contract.Requires(source != null);
+
+ switch (mode)
+ {
+ case BindingMode.Default:
+ case BindingMode.OneWay:
+ return o.Bind(property, source, priority);
+ case BindingMode.TwoWay:
+ return new CompositeDisposable(
+ o.Bind(property, source, priority),
+ o.GetObservable(property).Subscribe(source));
+ case BindingMode.OneTime:
+ return source.Take(1).Subscribe(x => o.SetValue(property, x, priority));
+ case BindingMode.OneWayToSource:
+ return o.GetObservable(property).Subscribe(source);
+ default:
+ throw new ArgumentException("Invalid binding mode.");
+ }
+ }
+
///
/// Subscribes to a property changed notifications for changes that originate from a
/// .
@@ -52,6 +272,17 @@ namespace Perspex
return observable.Subscribe(e => SubscribeAdapter(e, handler));
}
+ ///
+ /// Gets a description of a property that van be used in observables.
+ ///
+ /// The object.
+ /// The property
+ /// The description.
+ private static string GetDescription(IPerspexObject o, PerspexProperty property)
+ {
+ return $"{o.GetType().Name}.{property.Name}";
+ }
+
///
/// Observer method for .
diff --git a/src/Perspex.Base/PerspexProperty.cs b/src/Perspex.Base/PerspexProperty.cs
index 94f9baf5ba..27c80ca323 100644
--- a/src/Perspex.Base/PerspexProperty.cs
+++ b/src/Perspex.Base/PerspexProperty.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
using System.Reflection;
+using Perspex.Data;
using Perspex.Utilities;
namespace Perspex
@@ -56,7 +57,7 @@ namespace Perspex
///
/// Gets the ID of the property.
///
- private int _id;
+ private readonly int _id;
///
/// Initializes a new instance of the class.
@@ -323,10 +324,10 @@ namespace Perspex
/// indexer.
///
/// The property.
- /// A describing the binding.
- public static BindingDescriptor operator !(PerspexProperty property)
+ /// A describing the binding.
+ public static IndexerDescriptor operator !(PerspexProperty property)
{
- return new BindingDescriptor
+ return new IndexerDescriptor
{
Priority = BindingPriority.LocalValue,
Property = property,
@@ -338,10 +339,10 @@ namespace Perspex
/// indexer.
///
/// The property.
- /// A describing the binding.
- public static BindingDescriptor operator ~(PerspexProperty property)
+ /// A describing the binding.
+ public static IndexerDescriptor operator ~(PerspexProperty property)
{
- return new BindingDescriptor
+ return new IndexerDescriptor
{
Priority = BindingPriority.TemplatedParent,
Property = property,
@@ -538,7 +539,7 @@ namespace Perspex
public override bool Equals(object obj)
{
var p = obj as PerspexProperty;
- return p != null ? Equals(p) : false;
+ return p != null && Equals(p);
}
///
@@ -557,13 +558,13 @@ namespace Perspex
/// Returns a binding accessor that can be passed to 's []
/// operator to initiate a binding.
///
- /// A .
+ /// A .
///
/// The ! and ~ operators are short forms of this.
///
- public BindingDescriptor Bind()
+ public IndexerDescriptor Bind()
{
- return new BindingDescriptor
+ return new IndexerDescriptor
{
Property = this,
};
diff --git a/src/Perspex.Base/PerspexPropertyChangedEventArgs.cs b/src/Perspex.Base/PerspexPropertyChangedEventArgs.cs
index e16e2242fa..1136b5ce2a 100644
--- a/src/Perspex.Base/PerspexPropertyChangedEventArgs.cs
+++ b/src/Perspex.Base/PerspexPropertyChangedEventArgs.cs
@@ -1,6 +1,8 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using Perspex.Data;
+
namespace Perspex
{
///
diff --git a/src/Perspex.Base/PerspexProperty`1.cs b/src/Perspex.Base/PerspexProperty`1.cs
index 6d97833f0a..dae476da31 100644
--- a/src/Perspex.Base/PerspexProperty`1.cs
+++ b/src/Perspex.Base/PerspexProperty`1.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using Perspex.Data;
namespace Perspex
{
diff --git a/src/Perspex.Application/Platform/IAssetLoader.cs b/src/Perspex.Base/Platform/IAssetLoader.cs
similarity index 100%
rename from src/Perspex.Application/Platform/IAssetLoader.cs
rename to src/Perspex.Base/Platform/IAssetLoader.cs
diff --git a/src/Perspex.Base/PriorityBindingEntry.cs b/src/Perspex.Base/PriorityBindingEntry.cs
index 63f906d450..a17080f0f4 100644
--- a/src/Perspex.Base/PriorityBindingEntry.cs
+++ b/src/Perspex.Base/PriorityBindingEntry.cs
@@ -99,10 +99,7 @@ namespace Perspex
///
public void Dispose()
{
- if (_subscription != null)
- {
- _subscription.Dispose();
- }
+ _subscription?.Dispose();
}
}
}
diff --git a/src/Perspex.Base/PriorityValue.cs b/src/Perspex.Base/PriorityValue.cs
index 0bbdcc389e..e45eba172f 100644
--- a/src/Perspex.Base/PriorityValue.cs
+++ b/src/Perspex.Base/PriorityValue.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Reactive.Subjects;
using System.Text;
using Perspex.Utilities;
+using Serilog;
namespace Perspex
{
@@ -54,19 +55,30 @@ namespace Perspex
///
private readonly Func _validate;
+ ///
+ /// An optional logger.
+ ///
+ private ILogger _logger;
+
///
/// Initializes a new instance of the class.
///
/// The name of the property.
/// The value type.
/// An optional validation function.
- public PriorityValue(string name, Type valueType, Func validate = null)
+ /// An optional logger
+ public PriorityValue(
+ string name,
+ Type valueType,
+ Func validate = null,
+ ILogger logger = null)
{
_name = name;
_valueType = valueType;
_value = PerspexProperty.UnsetValue;
ValuePriority = int.MaxValue;
_validate = validate;
+ _logger = logger;
}
///
@@ -225,9 +237,13 @@ namespace Perspex
_value = value;
_changed.OnNext(Tuple.Create(old, _value));
}
- else
+ else if (_logger != null)
{
- // TODO: Log error.
+ _logger.Error(
+ "Binding produced invalid value for {$Type} {$Property}: {$Value}",
+ _valueType,
+ _name,
+ value);
}
}
diff --git a/src/Perspex.Base/Reactive/AnonymousSubject`1.cs b/src/Perspex.Base/Reactive/AnonymousSubject`1.cs
new file mode 100644
index 0000000000..3bbf2295ff
--- /dev/null
+++ b/src/Perspex.Base/Reactive/AnonymousSubject`1.cs
@@ -0,0 +1,16 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Reactive.Subjects;
+
+namespace Perspex.Reactive
+{
+ public class AnonymousSubject : AnonymousSubject, ISubject
+ {
+ public AnonymousSubject(IObserver observer, IObservable observable)
+ : base(observer, observable)
+ {
+ }
+ }
+}
diff --git a/src/Perspex.Base/Reactive/AnonymousSubject`2.cs b/src/Perspex.Base/Reactive/AnonymousSubject`2.cs
new file mode 100644
index 0000000000..04e58585b3
--- /dev/null
+++ b/src/Perspex.Base/Reactive/AnonymousSubject`2.cs
@@ -0,0 +1,49 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Reactive.Subjects;
+
+namespace Perspex.Reactive
+{
+ public class AnonymousSubject : ISubject
+ {
+ private readonly IObserver _observer;
+ private readonly IObservable _observable;
+
+ public AnonymousSubject(IObserver observer, IObservable observable)
+ {
+ _observer = observer;
+ _observable = observable;
+ }
+
+ public void OnCompleted()
+ {
+ _observer.OnCompleted();
+ }
+
+ public void OnError(Exception error)
+ {
+ if (error == null)
+ throw new ArgumentNullException("error");
+
+ _observer.OnError(error);
+ }
+
+ public void OnNext(T value)
+ {
+ _observer.OnNext(value);
+ }
+
+ public IDisposable Subscribe(IObserver observer)
+ {
+ if (observer == null)
+ throw new ArgumentNullException("observer");
+
+ //
+ // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence.
+ //
+ return _observable.Subscribe/*Unsafe*/(observer);
+ }
+ }
+}
diff --git a/src/Perspex.Base/Reactive/PerspexObservable.cs b/src/Perspex.Base/Reactive/PerspexObservable.cs
index 48193aba74..bd76f544d4 100644
--- a/src/Perspex.Base/Reactive/PerspexObservable.cs
+++ b/src/Perspex.Base/Reactive/PerspexObservable.cs
@@ -22,10 +22,7 @@ namespace Perspex.Reactive
/// The description of the observable.
public PerspexObservable(Func, IDisposable> subscribe, string description)
{
- if (subscribe == null)
- {
- throw new ArgumentNullException("subscribe");
- }
+ Contract.Requires(subscribe != null);
_subscribe = subscribe;
Description = description;
diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs
index 159151dc79..376ca5c0c9 100644
--- a/src/Perspex.Base/Threading/DispatcherTimer.cs
+++ b/src/Perspex.Base/Threading/DispatcherTimer.cs
@@ -18,22 +18,23 @@ namespace Perspex.Threading
private TimeSpan _interval;
+ private readonly Action _raiseTickAction;
+
///
/// Initializes a new instance of the class.
///
- public DispatcherTimer()
+ public DispatcherTimer() : this(DispatcherPriority.Normal)
{
- _priority = DispatcherPriority.Normal;
}
///
/// Initializes a new instance of the class.
///
/// The priority to use.
- /// The dispatcher to use.
public DispatcherTimer(DispatcherPriority priority)
{
_priority = priority;
+ _raiseTickAction = RaiseTick;
}
///
@@ -41,9 +42,8 @@ namespace Perspex.Threading
///
/// The interval at which to tick.
/// The priority to use.
- /// The dispatcher to use.
/// The event to call when the timer ticks.
- public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback)
+ public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback) : this(priority)
{
_priority = priority;
Interval = interval;
@@ -131,9 +131,8 @@ namespace Perspex.Threading
/// An used to cancel the timer.
public static IDisposable Run(Func action, TimeSpan interval, DispatcherPriority priority = DispatcherPriority.Normal)
{
- var timer = new DispatcherTimer(priority);
+ var timer = new DispatcherTimer(priority) { Interval = interval };
- timer.Interval = interval;
timer.Tick += (s, e) =>
{
if (!action())
@@ -172,12 +171,14 @@ namespace Perspex.Threading
}
}
+
+
///
/// Raises the event on the dispatcher thread.
///
private void InternalTick()
{
- Dispatcher.UIThread.InvokeAsync(RaiseTick, _priority);
+ Dispatcher.UIThread.InvokeAsync(_raiseTickAction, _priority);
}
///
@@ -185,10 +186,7 @@ namespace Perspex.Threading
///
private void RaiseTick()
{
- if (Tick != null)
- {
- Tick(this, EventArgs.Empty);
- }
+ Tick?.Invoke(this, EventArgs.Empty);
}
}
}
\ No newline at end of file
diff --git a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs
index 5e751833f9..bcf858db3a 100644
--- a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs
+++ b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs
@@ -12,8 +12,8 @@ namespace Perspex.Threading
{
class ThreadingInterface : IPlatformThreadingInterface
{
- private AutoResetEvent _evnt = new AutoResetEvent(false);
- private JobRunner _timerJobRunner;
+ private readonly AutoResetEvent _evnt = new AutoResetEvent(false);
+ private readonly JobRunner _timerJobRunner;
public ThreadingInterface()
{
diff --git a/src/Perspex.Base/Utilities/WeakTimer.cs b/src/Perspex.Base/Utilities/WeakTimer.cs
new file mode 100644
index 0000000000..f8901a1d74
--- /dev/null
+++ b/src/Perspex.Base/Utilities/WeakTimer.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Perspex.Threading;
+
+namespace Perspex.Utilities
+{
+ public class WeakTimer
+ {
+ public interface IWeakTimerSubscriber
+ {
+ bool Tick();
+ }
+
+ private readonly WeakReference _subscriber;
+ private DispatcherTimer _timer;
+
+ public WeakTimer(IWeakTimerSubscriber subscriber)
+ {
+ _subscriber = new WeakReference(subscriber);
+ _timer = new DispatcherTimer();
+
+ _timer.Tick += delegate { OnTick(); };
+ _timer.Start();
+ }
+
+ private void OnTick()
+ {
+ IWeakTimerSubscriber subscriber;
+ if (!_subscriber.TryGetTarget(out subscriber) || !subscriber.Tick())
+ Stop();
+ }
+
+ public TimeSpan Interval
+ {
+ get { return _timer.Interval; }
+ set { _timer.Interval = value; }
+ }
+
+ public void Start() => _timer.Start();
+
+ public void Stop() => _timer.Stop();
+
+
+ public static WeakTimer StartWeakTimer(IWeakTimerSubscriber subscriber, TimeSpan interval)
+ {
+ var timer = new WeakTimer(subscriber) {Interval = interval};
+ timer.Start();
+ return timer;
+ }
+
+ }
+}
diff --git a/src/Perspex.Base/packages.config b/src/Perspex.Base/packages.config
index 7d2a813bf7..26d3282b7a 100644
--- a/src/Perspex.Base/packages.config
+++ b/src/Perspex.Base/packages.config
@@ -1,5 +1,6 @@
+
diff --git a/src/Perspex.Controls/Button.cs b/src/Perspex.Controls/Button.cs
index a1ca0e11a6..71dbbf22e0 100644
--- a/src/Perspex.Controls/Button.cs
+++ b/src/Perspex.Controls/Button.cs
@@ -134,18 +134,6 @@ namespace Perspex.Controls
set { SetValue(IsDefaultProperty, value); }
}
- ///
- protected override Size MeasureOverride(Size availableSize)
- {
- return base.MeasureOverride(availableSize);
- }
-
- ///
- protected override Size ArrangeOverride(Size finalSize)
- {
- return base.ArrangeOverride(finalSize);
- }
-
///
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
@@ -219,10 +207,7 @@ namespace Perspex.Controls
/// The event args.
protected virtual void OnClick(RoutedEventArgs e)
{
- if (Command != null)
- {
- Command.Execute(CommandParameter);
- }
+ Command?.Execute(CommandParameter);
}
///
@@ -230,7 +215,7 @@ namespace Perspex.Controls
{
base.OnPointerPressed(e);
- Classes.Add(":pressed");
+ PseudoClasses.Add(":pressed");
e.Device.Capture(this);
e.Handled = true;
@@ -246,7 +231,7 @@ namespace Perspex.Controls
base.OnPointerReleased(e);
e.Device.Capture(null);
- Classes.Remove(":pressed");
+ PseudoClasses.Remove(":pressed");
e.Handled = true;
if (ClickMode == ClickMode.Release && Classes.Contains(":pointerover"))
diff --git a/src/Perspex.Controls/Carousel.cs b/src/Perspex.Controls/Carousel.cs
index 97745e1452..919e4ef542 100644
--- a/src/Perspex.Controls/Carousel.cs
+++ b/src/Perspex.Controls/Carousel.cs
@@ -44,6 +44,28 @@ namespace Perspex.Controls
set { SetValue(TransitionProperty, value); }
}
+ ///
+ /// Moves to the next item in the carousel.
+ ///
+ public void Next()
+ {
+ if (SelectedIndex < Items.Count() - 1)
+ {
+ ++SelectedIndex;
+ }
+ }
+
+ ///
+ /// Moves to the previous item in the carousel.
+ ///
+ public void Previous()
+ {
+ if (SelectedIndex > 0)
+ {
+ --SelectedIndex;
+ }
+ }
+
///
protected override void OnKeyDown(KeyEventArgs e)
{
diff --git a/src/Perspex.Controls/Classes.cs b/src/Perspex.Controls/Classes.cs
new file mode 100644
index 0000000000..61f4e8368d
--- /dev/null
+++ b/src/Perspex.Controls/Classes.cs
@@ -0,0 +1,253 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Perspex.Collections;
+
+namespace Perspex.Controls
+{
+ ///
+ /// Holds a collection of style classes for an .
+ ///
+ ///
+ /// Similar to CSS, each control may have any number of styling classes applied.
+ ///
+ public class Classes : PerspexList, IPseudoClasses
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Classes()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The initial items.
+ public Classes(IEnumerable items)
+ : base(items)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The initial items.
+ public Classes(params string[] items)
+ : base(items)
+ {
+ }
+
+ ///
+ /// Adds a style class to the collection.
+ ///
+ /// The class name.
+ ///
+ /// Only standard classes may be added via this method. To add pseudoclasses (classes
+ /// beginning with a ':' character) use the protected
+ /// property.
+ ///
+ public override void Add(string name)
+ {
+ ThrowIfPseudoclass(name, "added");
+
+ if (!Contains(name))
+ {
+ base.Add(name);
+ }
+ }
+
+ ///
+ /// Adds a style classes to the collection.
+ ///
+ /// The class names.
+ ///
+ /// Only standard classes may be added via this method. To add pseudoclasses (classes
+ /// beginning with a ':' character) use the protected
+ /// property.
+ ///
+ public override void AddRange(IEnumerable names)
+ {
+ var c = new List();
+
+ foreach (var name in names)
+ {
+ ThrowIfPseudoclass(name, "added");
+
+ if (!Contains(name))
+ {
+ c.Add(name);
+ }
+ }
+
+ base.AddRange(c);
+ }
+
+ ///
+ /// Inserts a style class into the collection.
+ ///
+ /// The index to insert the class at.
+ /// The class name.
+ ///
+ /// Only standard classes may be added via this method. To add pseudoclasses (classes
+ /// beginning with a ':' character) use the protected
+ /// property.
+ ///
+ public override void Insert(int index, string name)
+ {
+ ThrowIfPseudoclass(name, "added");
+
+ if (!Contains(name))
+ {
+ base.Insert(index, name);
+ }
+ }
+
+ ///
+ /// Inserts style classes into the collection.
+ ///
+ /// The index to insert the class at.
+ /// The class names.
+ ///
+ /// Only standard classes may be added via this method. To add pseudoclasses (classes
+ /// beginning with a ':' character) use the protected
+ /// property.
+ ///
+ public override void InsertRange(int index, IEnumerable names)
+ {
+ var c = new List();
+
+ foreach (var name in names)
+ {
+ ThrowIfPseudoclass(name, "added");
+
+ if (!Contains(name))
+ {
+ c.Add(name);
+ }
+ }
+
+ base.InsertRange(index, c);
+ }
+
+ ///
+ /// Removes a style class from the collection.
+ ///
+ /// The class name.
+ ///
+ /// Only standard classes may be removed via this method. To remove pseudoclasses (classes
+ /// beginning with a ':' character) use the protected
+ /// property.
+ ///
+ public override bool Remove(string name)
+ {
+ ThrowIfPseudoclass(name, "removed");
+ return base.Remove(name);
+ }
+
+ ///
+ /// Removes style classes from the collection.
+ ///
+ /// The class name.
+ ///
+ /// Only standard classes may be removed via this method. To remove pseudoclasses (classes
+ /// beginning with a ':' character) use the protected
+ /// property.
+ ///
+ public override void RemoveAll(IEnumerable names)
+ {
+ var c = new List();
+
+ foreach (var name in names)
+ {
+ ThrowIfPseudoclass(name, "removed");
+
+ if (!Contains(name))
+ {
+ c.Add(name);
+ }
+ }
+
+ base.RemoveAll(c);
+ }
+
+ ///
+ /// Removes a style class from the collection.
+ ///
+ /// The index of the class in the collection.
+ ///
+ /// Only standard classes may be removed via this method. To remove pseudoclasses (classes
+ /// beginning with a ':' character) use the protected
+ /// property.
+ ///
+ public override void RemoveAt(int index)
+ {
+ var name = this[index];
+ ThrowIfPseudoclass(name, "removed");
+ base.RemoveAt(index);
+ }
+
+ ///
+ /// Removes style classes from the collection.
+ ///
+ /// The first index to remove.
+ /// The number of items to remove.
+ public override void RemoveRange(int index, int count)
+ {
+ var names = GetRange(index, count);
+ base.RemoveRange(index, count);
+ }
+
+ ///
+ /// Removes all non-pseudoclasses in the collection and adds a new set.
+ ///
+ /// The new contents of the collection.
+ public void Replace(IList source)
+ {
+ var toRemove = new List();
+
+ foreach (var name in source)
+ {
+ ThrowIfPseudoclass(name, "added");
+ }
+
+ foreach (var name in this)
+ {
+ if (!name.StartsWith(":"))
+ {
+ toRemove.Add(name);
+ }
+ }
+
+ base.RemoveAll(toRemove);
+ base.AddRange(source);
+ }
+
+ ///
+ void IPseudoClasses.Add(string name)
+ {
+ if (!Contains(name))
+ {
+ base.Add(name);
+ }
+ }
+
+ ///
+ bool IPseudoClasses.Remove(string name)
+ {
+ return base.Remove(name);
+ }
+
+ private void ThrowIfPseudoclass(string name, string operation)
+ {
+ if (name.StartsWith(":"))
+ {
+ throw new ArgumentException(
+ $"The pseudoclass '{name}' may only be {operation} by the control itself.");
+ }
+ }
+ }
+}
diff --git a/src/Perspex.Controls/ContentControl.cs b/src/Perspex.Controls/ContentControl.cs
index c4c1c94e31..b6cc7bab95 100644
--- a/src/Perspex.Controls/ContentControl.cs
+++ b/src/Perspex.Controls/ContentControl.cs
@@ -2,7 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using Perspex.Collections;
+using System.Linq;
+using System.Reactive.Linq;
+using Perspex.Controls.Mixins;
using Perspex.Controls.Presenters;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
@@ -14,7 +16,7 @@ namespace Perspex.Controls
///
/// Displays according to a .
///
- public class ContentControl : TemplatedControl, IContentControl, IReparentingHost
+ public class ContentControl : TemplatedControl, IContentControl, IContentPresenterHost
{
///
/// Defines the property.
@@ -35,17 +37,11 @@ namespace Perspex.Controls
PerspexProperty.Register(nameof(VerticalContentAlignment));
///
- /// Initializes static members of the class.
+ /// Initializes static members of the class.
///
static ContentControl()
{
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- public ContentControl()
- {
+ ContentControlMixin.Attach(ContentProperty, x => x.LogicalChildren);
}
///
@@ -61,7 +57,7 @@ namespace Perspex.Controls
///
/// Gets the presenter from the control's template.
///
- public ContentPresenter Presenter
+ public IContentPresenter Presenter
{
get;
private set;
@@ -85,31 +81,10 @@ namespace Perspex.Controls
set { SetValue(VerticalContentAlignmentProperty, value); }
}
- ///
- /// Gets a writeable logical children collection from the host.
- ///
- IPerspexList IReparentingHost.LogicalChildren => LogicalChildren;
-
- ///
- /// Asks the control whether it wants to reparent the logical children of the specified
- /// control.
- ///
- /// The control.
- ///
- /// True if the control wants to reparent its logical children otherwise false.
- ///
- bool IReparentingHost.WillReparentChildrenOf(IControl control)
- {
- return control is IContentPresenter && control.TemplatedParent == this;
- }
-
///
- protected override void OnTemplateApplied(INameScope nameScope)
+ void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
{
- // We allow ContentControls without ContentPresenters in the template. This can be
- // useful for e.g. a simple ToggleButton that displays an image. There's no need to
- // have a ContentPresenter in the visual tree for that.
- Presenter = nameScope.Find("PART_ContentPresenter");
+ Presenter = presenter;
}
}
}
diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs
index b382e82675..43eb87093c 100644
--- a/src/Perspex.Controls/Control.cs
+++ b/src/Perspex.Controls/Control.cs
@@ -2,12 +2,19 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Reactive;
using System.Reactive.Linq;
+using System.Reactive.Subjects;
using Perspex.Collections;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
+using Perspex.Data;
using Perspex.Input;
using Perspex.Interactivity;
+using Perspex.LogicalTree;
using Perspex.Rendering;
using Perspex.Styling;
@@ -58,7 +65,7 @@ namespace Perspex.Controls
/// Defines the property.
///
public static readonly PerspexProperty TemplatedParentProperty =
- PerspexProperty.Register(nameof(TemplatedParent));
+ PerspexProperty.Register(nameof(TemplatedParent), inherits: true);
///
/// Event raised when an element wishes to be scrolled into view.
@@ -70,8 +77,11 @@ namespace Perspex.Controls
private readonly Classes _classes = new Classes();
private DataTemplates _dataTemplates;
private IControl _focusAdorner;
+ private bool _isAttachedToLogicalTree;
private IPerspexList _logicalChildren;
+ private INameScope _nameScope;
private Styles _styles;
+ private Subject _styleDetach = new Subject();
///
/// Initializes static members of the class.
@@ -84,6 +94,33 @@ namespace Perspex.Controls
PseudoClass(IsPointerOverProperty, ":pointerover");
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Control()
+ {
+ _nameScope = this as INameScope;
+ }
+
+ ///
+ /// Raised when the control is attached to a rooted logical tree.
+ ///
+ public event EventHandler AttachedToLogicalTree;
+
+ ///
+ /// Raised when the control is detached from a rooted logical tree.
+ ///
+ public event EventHandler DetachedFromLogicalTree;
+
+ ///
+ /// Occurs when the property changes.
+ ///
+ ///
+ /// This event will be raised when the property has changed and
+ /// all subscribers to that change have been notified.
+ ///
+ public event EventHandler DataContextChanged;
+
///
/// Gets or sets the control's classes.
///
@@ -110,8 +147,7 @@ namespace Perspex.Controls
{
if (_classes != value)
{
- _classes.Clear();
- _classes.Add(value);
+ _classes.Replace(value);
}
}
}
@@ -147,20 +183,8 @@ namespace Perspex.Controls
///
public DataTemplates DataTemplates
{
- get
- {
- if (_dataTemplates == null)
- {
- _dataTemplates = new DataTemplates();
- }
-
- return _dataTemplates;
- }
-
- set
- {
- _dataTemplates = value;
- }
+ get { return _dataTemplates ?? (_dataTemplates = new DataTemplates()); }
+ set { _dataTemplates = value; }
}
///
@@ -173,20 +197,8 @@ namespace Perspex.Controls
///
public Styles Styles
{
- get
- {
- if (_styles == null)
- {
- _styles = new Styles();
- }
-
- return _styles;
- }
-
- set
- {
- _styles = value;
- }
+ get { return _styles ?? (_styles = new Styles()); }
+ set { _styles = value; }
}
///
@@ -212,6 +224,11 @@ namespace Perspex.Controls
internal set { SetValue(TemplatedParentProperty, value); }
}
+ ///
+ /// Gets a value indicating whether the element is attached to a rooted logical tree.
+ ///
+ bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree;
+
///
/// Gets the control's logical parent.
///
@@ -222,6 +239,9 @@ namespace Perspex.Controls
///
IPerspexReadOnlyList ILogical.LogicalChildren => LogicalChildren;
+ ///
+ IPerspexReadOnlyList IStyleable.Classes => Classes;
+
///
/// Gets the type by which the control is styled.
///
@@ -233,6 +253,12 @@ namespace Perspex.Controls
///
Type IStyleable.StyleKey => GetType();
+ ///
+ IObservable IStyleable.StyleDetach => _styleDetach;
+
+ ///
+ IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
+
///
/// Gets a value which indicates whether a change to the is in
/// the process of being notified.
@@ -254,6 +280,8 @@ namespace Perspex.Controls
{
var list = new PerspexList();
list.ResetBehavior = ResetBehavior.Remove;
+ list.Validate = ValidateLogicalChild;
+ list.CollectionChanged += LogicalChildrenCollectionChanged;
_logicalChildren = list;
}
@@ -261,6 +289,12 @@ namespace Perspex.Controls
}
}
+ ///
+ /// Gets the collection in a form that allows adding and removing
+ /// pseudoclasses.
+ ///
+ protected IPseudoClasses PseudoClasses => Classes;
+
///
/// Sets the control's logical parent.
///
@@ -276,7 +310,26 @@ namespace Perspex.Controls
throw new InvalidOperationException("The Control already has a parent.");
}
- SetAndRaise(ParentProperty, ref _parent, (IControl)parent);
+ InheritanceParent = parent as PerspexObject;
+ _parent = (IControl)parent;
+
+ var root = FindStyleRoot(old);
+
+ if (root != null)
+ {
+ var e = new LogicalTreeAttachmentEventArgs(root);
+ OnDetachedFromLogicalTree(e);
+ }
+
+ root = FindStyleRoot(this);
+
+ if (root != null)
+ {
+ var e = new LogicalTreeAttachmentEventArgs(root);
+ OnAttachedToLogicalTree(e);
+ }
+
+ RaisePropertyChanged(ParentProperty, old, _parent, BindingPriority.LocalValue);
}
}
@@ -312,20 +365,90 @@ namespace Perspex.Controls
throw new ArgumentException("Cannot supply an empty className.");
}
- Observable.Merge(property.Changed, property.Initialized)
+ property.Changed.Merge(property.Initialized)
.Subscribe(e =>
{
if (selector((T)e.NewValue))
{
- ((Control)e.Sender).Classes.Add(className);
+ ((Control)e.Sender).PseudoClasses.Add(className);
}
else
{
- ((Control)e.Sender).Classes.Remove(className);
+ ((Control)e.Sender).PseudoClasses.Remove(className);
}
});
}
+ ///
+ /// Called when the control is added to a logical tree.
+ ///
+ /// The event args.
+ ///
+ /// It is vital that if you override this method you call the base implementation;
+ /// failing to do so will cause numerous features to not work as expected.
+ ///
+ protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ // This method can be called when a control is already attached to the logical tree
+ // in the following scenario:
+ // - ListBox gets assigned Items containing ListBoxItem
+ // - ListBox makes ListBoxItem a logical child
+ // - ListBox template gets applied; making its Panel get attached to logical tree
+ // - That AttachedToLogicalTree signal travels down to the ListBoxItem
+ if (!_isAttachedToLogicalTree)
+ {
+ if (_nameScope == null)
+ {
+ _nameScope = NameScope.GetNameScope(this) ?? ((Control)Parent)?._nameScope;
+ }
+
+ if (Name != null)
+ {
+ _nameScope?.Register(Name, this);
+ }
+
+ _isAttachedToLogicalTree = true;
+ PerspexLocator.Current.GetService()?.ApplyStyles(this);
+ AttachedToLogicalTree?.Invoke(this, e);
+ }
+
+ foreach (var child in LogicalChildren.OfType())
+ {
+ child.OnAttachedToLogicalTree(e);
+ }
+ }
+
+ ///
+ /// Called when the control is removed from a logical tree.
+ ///
+ /// The event args.
+ ///
+ /// It is vital that if you override this method you call the base implementation;
+ /// failing to do so will cause numerous features to not work as expected.
+ ///
+ protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ if (!_isAttachedToLogicalTree)
+ {
+ throw new Exception("Logic error: Control is not attached to logical tree");
+ }
+
+ if (Name != null)
+ {
+ _nameScope?.Unregister(Name);
+ }
+
+ _isAttachedToLogicalTree = false;
+ _styleDetach.OnNext(Unit.Default);
+ this.TemplatedParent = null;
+ DetachedFromLogicalTree?.Invoke(this, e);
+
+ foreach (var child in LogicalChildren.OfType())
+ {
+ child.OnDetachedFromLogicalTree(e);
+ }
+ }
+
///
protected override void OnGotFocus(GotFocusEventArgs e)
{
@@ -371,34 +494,13 @@ namespace Perspex.Controls
}
}
- ///
- protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
- {
- base.OnAttachedToVisualTree(e);
-
- IStyler styler = PerspexLocator.Current.GetService();
-
- if (styler != null)
- {
- styler.ApplyStyles(this);
- }
- }
-
///
/// Called when the is changed and all subscribers to that change
/// have been notified.
///
- protected virtual void OnDataContextFinishedChanging()
+ protected virtual void OnDataContextChanged()
{
- }
-
- ///
- /// Makes the control use a different control's logical children as its own.
- ///
- /// The logical children to use.
- protected void RedirectLogicalChildren(IPerspexList collection)
- {
- _logicalChildren = collection;
+ DataContextChanged?.Invoke(this, EventArgs.Empty);
}
///
@@ -413,6 +515,80 @@ namespace Perspex.Controls
if (control != null)
{
control.IsDataContextChanging = notifying;
+
+ if (!notifying)
+ {
+ control.OnDataContextChanged();
+ }
+ }
+ }
+
+ private static IStyleRoot FindStyleRoot(IStyleHost e)
+ {
+ while (e != null)
+ {
+ var root = e as IStyleRoot;
+
+ if (root != null && root.StylingParent == null)
+ {
+ return root;
+ }
+
+ e = e.StylingParent;
+ }
+
+ return null;
+ }
+
+ private static void ValidateLogicalChild(ILogical c)
+ {
+ if (c == null)
+ {
+ throw new ArgumentException("Cannot add null to LogicalChildren.");
+ }
+ }
+
+ private void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ SetLogicalParent(e.NewItems.Cast());
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ ClearLogicalParent(e.OldItems.Cast());
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ ClearLogicalParent(e.OldItems.Cast());
+ SetLogicalParent(e.NewItems.Cast());
+ break;
+
+ case NotifyCollectionChangedAction.Reset:
+ throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection");
+ }
+ }
+
+ private void SetLogicalParent(IEnumerable children)
+ {
+ foreach (var i in children)
+ {
+ if (i.LogicalParent == null)
+ {
+ ((ISetLogicalParent)i).SetParent(this);
+ }
+ }
+ }
+
+ private void ClearLogicalParent(IEnumerable children)
+ {
+ foreach (var i in children)
+ {
+ if (i.LogicalParent == this)
+ {
+ ((ISetLogicalParent)i).SetParent(null);
+ }
}
}
}
diff --git a/src/Perspex.Controls/ControlExtensions.cs b/src/Perspex.Controls/ControlExtensions.cs
index 08514258f7..a04bffde9f 100644
--- a/src/Perspex.Controls/ControlExtensions.cs
+++ b/src/Perspex.Controls/ControlExtensions.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using Perspex.Data;
using Perspex.LogicalTree;
using Perspex.Styling;
@@ -59,16 +60,46 @@ namespace Perspex.Controls
}
///
- /// Finds the name scope for a control.
+ /// Finds the name scope for a control by searching up the logical tree.
///
/// The control.
/// The control's name scope, or null if not found.
public static INameScope FindNameScope(this IControl control)
{
return control.GetSelfAndLogicalAncestors()
- .OfType()
+ .OfType()
.Select(x => (x as INameScope) ?? NameScope.GetNameScope(x))
.FirstOrDefault(x => x != null);
}
+
+ ///
+ /// Adds or removes a pseudoclass depending on a boolean value.
+ ///
+ /// The pseudoclasses collection.
+ /// The name of the pseudoclass to set.
+ /// True to add the pseudoclass or false to remove.
+ public static void Set(this IPseudoClasses classes, string name, bool value)
+ {
+ if (value)
+ {
+ classes.Add(name);
+ }
+ else
+ {
+ classes.Remove(name);
+ }
+ }
+
+ ///
+ /// Sets a pseudoclass depending on an observable trigger.
+ ///
+ /// The pseudoclasses collection.
+ /// The name of the pseudoclass to set.
+ /// The trigger: true adds the pseudoclass, false removes.
+ /// A disposable used to cancel the subscription.
+ public static IDisposable Set(this IPseudoClasses classes, string name, IObservable trigger)
+ {
+ return trigger.Subscribe(x => classes.Set(name, x));
+ }
}
}
diff --git a/src/Perspex.Controls/Decorator.cs b/src/Perspex.Controls/Decorator.cs
index a32e4364f5..3440845c46 100644
--- a/src/Perspex.Controls/Decorator.cs
+++ b/src/Perspex.Controls/Decorator.cs
@@ -71,12 +71,7 @@ namespace Perspex.Controls
protected override Size ArrangeOverride(Size finalSize)
{
Control content = Child;
-
- if (content != null)
- {
- content.Arrange(new Rect(finalSize).Deflate(Padding));
- }
-
+ content?.Arrange(new Rect(finalSize).Deflate(Padding));
return finalSize;
}
@@ -93,14 +88,14 @@ namespace Perspex.Controls
{
((ISetLogicalParent)oldChild).SetParent(null);
LogicalChildren.Clear();
- RemoveVisualChild(oldChild);
+ VisualChildren.Remove(oldChild);
}
if (newChild != null)
{
- AddVisualChild(newChild);
- LogicalChildren.Add(newChild);
((ISetLogicalParent)newChild).SetParent(this);
+ VisualChildren.Add(newChild);
+ LogicalChildren.Add(newChild);
}
}
}
diff --git a/src/Perspex.Controls/DockPanel.cs b/src/Perspex.Controls/DockPanel.cs
index efded15d40..a1224a5272 100644
--- a/src/Perspex.Controls/DockPanel.cs
+++ b/src/Perspex.Controls/DockPanel.cs
@@ -251,18 +251,15 @@
public struct Alignments
{
- private readonly Alignment _horizontal;
- private readonly Alignment _vertical;
-
public Alignments(Alignment horizontal, Alignment vertical)
{
- _horizontal = horizontal;
- _vertical = vertical;
+ Horizontal = horizontal;
+ Vertical = vertical;
}
- public Alignment Horizontal => _horizontal;
+ public Alignment Horizontal { get; }
- public Alignment Vertical => _vertical;
+ public Alignment Vertical { get; }
}
public static class CoordinateMixin
diff --git a/src/Perspex.Controls/DropDown.cs b/src/Perspex.Controls/DropDown.cs
index 31a44ad7d0..8742016d9c 100644
--- a/src/Perspex.Controls/DropDown.cs
+++ b/src/Perspex.Controls/DropDown.cs
@@ -2,11 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Linq;
using Perspex.Controls.Generators;
using Perspex.Controls.Primitives;
using Perspex.Controls.Shapes;
-using Perspex.Controls.Templates;
using Perspex.Input;
using Perspex.Layout;
using Perspex.Media;
@@ -14,75 +12,72 @@ using Perspex.VisualTree;
namespace Perspex.Controls
{
- public class DropDown : SelectingItemsControl, IContentControl
- {
- public static readonly PerspexProperty ContentProperty =
- ContentControl.ContentProperty.AddOwner();
-
- public static readonly PerspexProperty HorizontalContentAlignmentProperty =
- ContentControl.HorizontalContentAlignmentProperty.AddOwner();
-
- public static readonly PerspexProperty VerticalContentAlignmentProperty =
- ContentControl.VerticalContentAlignmentProperty.AddOwner();
+ ///
+ /// A drop-down list control.
+ ///
+ public class DropDown : SelectingItemsControl
+ {
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty IsDropDownOpenProperty =
PerspexProperty.RegisterDirect(
nameof(IsDropDownOpen),
o => o.IsDropDownOpen,
(o, v) => o.IsDropDownOpen = v);
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty SelectionBoxItemProperty =
- PerspexProperty.Register("SelectionBoxItem");
+ PerspexProperty.RegisterDirect("SelectionBoxItem", o => o.SelectionBoxItem);
private bool _isDropDownOpen;
private Popup _popup;
+ private object _selectionBoxItem;
+ ///
+ /// Initializes static members of the class.
+ ///
static DropDown()
{
FocusableProperty.OverrideDefaultValue(true);
SelectedItemProperty.Changed.AddClassHandler(x => x.SelectedItemChanged);
}
- public DropDown()
- {
- Bind(ContentProperty, GetObservable(SelectedItemProperty));
- }
-
- public object Content
- {
- get { return GetValue(ContentProperty); }
- set { SetValue(ContentProperty, value); }
- }
-
- public HorizontalAlignment HorizontalContentAlignment
- {
- get { return GetValue(HorizontalContentAlignmentProperty); }
- set { SetValue(HorizontalContentAlignmentProperty, value); }
- }
-
- public VerticalAlignment VerticalContentAlignment
- {
- get { return GetValue(VerticalContentAlignmentProperty); }
- set { SetValue(VerticalContentAlignmentProperty, value); }
- }
-
+ ///
+ /// Gets or sets a value indicating whether the dropdown is currently open.
+ ///
public bool IsDropDownOpen
{
get { return _isDropDownOpen; }
set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
}
- public object SelectionBoxItem
+ ///
+ /// Gets or sets the item to display as the control's content.
+ ///
+ protected object SelectionBoxItem
{
- get { return GetValue(SelectionBoxItemProperty); }
- set { SetValue(SelectionBoxItemProperty, value); }
+ get { return _selectionBoxItem; }
+ set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); }
}
+ ///
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
- return new ItemContainerGenerator(this, ListBoxItem.ContentProperty);
+ return new ItemContainerGenerator(this, DropDownItem.ContentProperty);
+ }
+
+ ///
+ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToLogicalTree(e);
+ this.UpdateSelectionBoxItem(this.SelectedItem);
}
+ ///
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
@@ -103,21 +98,20 @@ namespace Perspex.Controls
}
}
+ ///
protected override void OnPointerPressed(PointerPressEventArgs e)
{
- if (!IsDropDownOpen)
+ if (!IsDropDownOpen && ((IVisual)e.Source).GetVisualRoot() != typeof(PopupRoot))
{
- if (((IVisual)e.Source).GetVisualAncestors().Last().GetType() != typeof(PopupRoot))
- {
- IsDropDownOpen = true;
- e.Handled = true;
- }
+ IsDropDownOpen = true;
+ e.Handled = true;
}
if (!e.Handled)
{
if (UpdateSelectionFromEventSource(e.Source))
{
+ _popup?.Close();
e.Handled = true;
}
}
@@ -125,14 +119,15 @@ namespace Perspex.Controls
base.OnPointerPressed(e);
}
- protected override void OnTemplateApplied(INameScope nameScope)
+ ///
+ protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
if (_popup != null)
{
_popup.Opened -= PopupOpened;
}
- _popup = nameScope.Get("PART_Popup");
+ _popup = e.NameScope.Get("PART_Popup");
_popup.Opened += PopupOpened;
}
@@ -149,7 +144,19 @@ namespace Perspex.Controls
private void SelectedItemChanged(PerspexPropertyChangedEventArgs e)
{
- var control = e.NewValue as IControl;
+ UpdateSelectionBoxItem(e.NewValue);
+ }
+
+ private void UpdateSelectionBoxItem(object item)
+ {
+ var contentControl = item as IContentControl;
+
+ if (contentControl != null)
+ {
+ item = contentControl.Content;
+ }
+
+ var control = item as IControl;
if (control != null)
{
@@ -169,7 +176,7 @@ namespace Perspex.Controls
}
else
{
- SelectionBoxItem = e.NewValue;
+ SelectionBoxItem = item;
}
}
}
diff --git a/src/Perspex.Controls/DropDownItem.cs b/src/Perspex.Controls/DropDownItem.cs
new file mode 100644
index 0000000000..592e94dd71
--- /dev/null
+++ b/src/Perspex.Controls/DropDownItem.cs
@@ -0,0 +1,12 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Perspex.Controls
+{
+ ///
+ /// A selectable item in a .
+ ///
+ public class DropDownItem : ListBoxItem
+ {
+ }
+}
diff --git a/src/Perspex.Controls/Expander.cs b/src/Perspex.Controls/Expander.cs
new file mode 100644
index 0000000000..5ac926af47
--- /dev/null
+++ b/src/Perspex.Controls/Expander.cs
@@ -0,0 +1,74 @@
+using Perspex.Animation;
+using Perspex.Controls.Primitives;
+
+namespace Perspex.Controls
+{
+ public enum ExpandDirection
+ {
+ Down,
+ Up,
+ Left,
+ Right
+ }
+
+ public class Expander : HeaderedContentControl
+ {
+ public static readonly PerspexProperty IsExpandedProperty =
+ PerspexProperty.Register(nameof(IsExpanded), true);
+
+ public static readonly PerspexProperty ExpandDirectionProperty =
+ PerspexProperty.Register(nameof(ExpandDirection), ExpandDirection.Down);
+
+ public static readonly PerspexProperty ContentTransitionProperty =
+ PerspexProperty.Register(nameof(ContentTransition));
+
+ static Expander()
+ {
+ PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Down, ":down");
+ PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Up, ":up");
+ PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Left, ":left");
+ PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Right, ":right");
+
+ PseudoClass(IsExpandedProperty, ":expanded");
+
+ IsExpandedProperty.Changed.AddClassHandler(x => x.OnIsExpandedChanged);
+ }
+
+ public bool IsExpanded
+ {
+ get { return GetValue(IsExpandedProperty); }
+ set { SetValue(IsExpandedProperty, value); }
+ }
+
+ public ExpandDirection ExpandDirection
+ {
+ get { return GetValue(ExpandDirectionProperty); }
+ set { SetValue(ExpandDirectionProperty, value); }
+ }
+
+ public IPageTransition ContentTransition
+ {
+ get { return GetValue(ContentTransitionProperty); }
+ set { SetValue(ContentTransitionProperty, value); }
+ }
+
+ protected virtual void OnIsExpandedChanged(PerspexPropertyChangedEventArgs e)
+ {
+ IVisual visualContent = Presenter;
+
+ if (Content != null && ContentTransition != null && visualContent != null)
+ {
+ bool forward = ExpandDirection == ExpandDirection.Left ||
+ ExpandDirection == ExpandDirection.Up;
+ if (IsExpanded)
+ {
+ ContentTransition.Start(null, visualContent, forward);
+ }
+ else
+ {
+ ContentTransition.Start(visualContent, null, !forward);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Perspex.Controls/Generators/IItemContainerGenerator.cs b/src/Perspex.Controls/Generators/IItemContainerGenerator.cs
index 7189d7a0c9..85628e3374 100644
--- a/src/Perspex.Controls/Generators/IItemContainerGenerator.cs
+++ b/src/Perspex.Controls/Generators/IItemContainerGenerator.cs
@@ -16,12 +16,17 @@ namespace Perspex.Controls.Generators
///
/// Gets the currently realized containers.
///
- IEnumerable Containers { get; }
+ IEnumerable Containers { get; }
///
- /// Signalled whenever new containers are initialized.
+ /// Signalled whenever new containers are materialized.
///
- IObservable ContainersInitialized { get; }
+ event EventHandler Materialized;
+
+ ///
+ /// Event raised whenever containers are dematerialized.
+ ///
+ event EventHandler Dematerialized;
///
/// Creates container controls for a collection of items.
@@ -32,7 +37,7 @@ namespace Perspex.Controls.Generators
/// The items.
/// An optional member selector.
/// The created controls.
- IEnumerable Materialize(
+ IEnumerable Materialize(
int startingIndex,
IEnumerable items,
IMemberSelector selector);
@@ -45,7 +50,7 @@ namespace Perspex.Controls.Generators
///
/// The the number of items to remove.
/// The removed containers.
- IEnumerable Dematerialize(int startingIndex, int count);
+ IEnumerable Dematerialize(int startingIndex, int count);
///
/// Removes a set of created containers and updates the index of later containers to fill
@@ -56,13 +61,13 @@ namespace Perspex.Controls.Generators
///
/// The the number of items to remove.
/// The removed containers.
- IEnumerable RemoveRange(int startingIndex, int count);
+ IEnumerable RemoveRange(int startingIndex, int count);
///
/// Clears all created containers and returns the removed controls.
///
/// The removed controls.
- IEnumerable Clear();
+ IEnumerable Clear();
///
/// Gets the container control representing the item with the specified index.
diff --git a/src/Perspex.Controls/Generators/ITreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/ITreeItemContainerGenerator.cs
index 37f097618b..fa78433c57 100644
--- a/src/Perspex.Controls/Generators/ITreeItemContainerGenerator.cs
+++ b/src/Perspex.Controls/Generators/ITreeItemContainerGenerator.cs
@@ -11,23 +11,8 @@ namespace Perspex.Controls.Generators
public interface ITreeItemContainerGenerator : IItemContainerGenerator
{
///
- /// Gets the item container for the root of the tree, or null if this generator is itself
- /// the root of the tree.
+ /// Gets the container index for the tree.
///
- ITreeItemContainerGenerator RootGenerator { get; }
-
- ///
- /// Gets the item container for the specified item, anywhere in the tree.
- ///
- /// The item.
- /// The container, or null if not found.
- IControl TreeContainerFromItem(object item);
-
- ///
- /// Gets the item for the specified item container, anywhere in the tree.
- ///
- /// The container.
- /// The item, or null if not found.
- object TreeItemFromContainer(IControl container);
+ TreeContainerIndex Index { get; }
}
}
\ No newline at end of file
diff --git a/src/Perspex.Controls/Generators/ItemContainer.cs b/src/Perspex.Controls/Generators/ItemContainer.cs
new file mode 100644
index 0000000000..86f3795106
--- /dev/null
+++ b/src/Perspex.Controls/Generators/ItemContainer.cs
@@ -0,0 +1,45 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Perspex.Controls.Generators
+{
+ ///
+ /// Holds information about an item container generated by an
+ /// .
+ ///
+ public class ItemContainer
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The container control.
+ /// The item that the container represents.
+ ///
+ /// The index of the item in the collection.
+ ///
+ public ItemContainer(IControl container, object item, int index)
+ {
+ ContainerControl = container;
+ Item = item;
+ Index = index;
+ }
+
+ ///
+ /// Gets the container control.
+ ///
+ ///
+ /// This will be null if is null.
+ ///
+ public IControl ContainerControl { get; }
+
+ ///
+ /// Gets the item that the container represents.
+ ///
+ public object Item { get; }
+
+ ///
+ /// Gets the index of the item in the collection.
+ ///
+ public int Index { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Perspex.Controls/Generators/ItemContainers.cs b/src/Perspex.Controls/Generators/ItemContainerEventArgs.cs
similarity index 62%
rename from src/Perspex.Controls/Generators/ItemContainers.cs
rename to src/Perspex.Controls/Generators/ItemContainerEventArgs.cs
index 9eec0f1147..86e2b890d1 100644
--- a/src/Perspex.Controls/Generators/ItemContainers.cs
+++ b/src/Perspex.Controls/Generators/ItemContainerEventArgs.cs
@@ -1,34 +1,38 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
using System.Collections.Generic;
namespace Perspex.Controls.Generators
{
///
- /// Holds details about a set of item containers in an .
+ /// Provides details for the
+ /// and events.
///
- public class ItemContainers
+ public class ItemContainerEventArgs : EventArgs
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The index of the first container in the source items.
/// The containers.
- public ItemContainers(int startingIndex, IList containers)
+ public ItemContainerEventArgs(
+ int startingIndex,
+ IList containers)
{
StartingIndex = startingIndex;
- Items = containers;
+ Containers = containers;
}
///
- /// Gets the index of the first container in the source items.
+ /// Gets the containers.
///
- public int StartingIndex { get; }
+ public IList Containers { get; }
///
- /// Gets the containers. May contain null entries.
+ /// Gets the index of the first container in the source items.
///
- public IList Items { get; }
+ public int StartingIndex { get; }
}
}
diff --git a/src/Perspex.Controls/Generators/ItemContainerGenerator.cs b/src/Perspex.Controls/Generators/ItemContainerGenerator.cs
index b8b57ddd59..2733de5770 100644
--- a/src/Perspex.Controls/Generators/ItemContainerGenerator.cs
+++ b/src/Perspex.Controls/Generators/ItemContainerGenerator.cs
@@ -15,9 +15,7 @@ namespace Perspex.Controls.Generators
///
public class ItemContainerGenerator : IItemContainerGenerator
{
- private List _containers = new List();
-
- private readonly Subject _containersInitialized = new Subject();
+ private List _containers = new List();
///
/// Initializes a new instance of the class.
@@ -31,10 +29,13 @@ namespace Perspex.Controls.Generators
}
///
- public IEnumerable Containers => _containers;
+ public IEnumerable Containers => _containers;
+
+ ///
+ public event EventHandler Materialized;
///
- public IObservable ContainersInitialized => _containersInitialized;
+ public event EventHandler Dematerialized;
///
/// Gets the owner control.
@@ -42,7 +43,7 @@ namespace Perspex.Controls.Generators
public IControl Owner { get; }
///
- public IEnumerable Materialize(
+ public IEnumerable Materialize(
int startingIndex,
IEnumerable items,
IMemberSelector selector)
@@ -50,25 +51,25 @@ namespace Perspex.Controls.Generators
Contract.Requires(items != null);
int index = startingIndex;
- var result = new List();
+ var result = new List();
foreach (var item in items)
{
var i = selector != null ? selector.Select(item) : item;
- var container = CreateContainer(i);
+ var container = new ItemContainer(CreateContainer(i), item, index++);
result.Add(container);
}
- AddContainers(startingIndex, result);
- _containersInitialized.OnNext(new ItemContainers(startingIndex, result));
+ AddContainers(result);
+ Materialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
return result.Where(x => x != null).ToList();
}
///
- public virtual IEnumerable Dematerialize(int startingIndex, int count)
+ public virtual IEnumerable Dematerialize(int startingIndex, int count)
{
- var result = new List();
+ var result = new List();
for (int i = startingIndex; i < startingIndex + count; ++i)
{
@@ -79,22 +80,31 @@ namespace Perspex.Controls.Generators
}
}
+ Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
+
return result;
}
///
- public virtual IEnumerable RemoveRange(int startingIndex, int count)
+ public virtual IEnumerable RemoveRange(int startingIndex, int count)
{
var result = _containers.GetRange(startingIndex, count);
_containers.RemoveRange(startingIndex, count);
+ Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
return result;
}
///
- public virtual IEnumerable Clear()
+ public virtual IEnumerable Clear()
{
var result = _containers;
- _containers = new List();
+ _containers = new List();
+
+ if (result.Count > 0)
+ {
+ Dematerialized?.Invoke(this, new ItemContainerEventArgs(0, result));
+ }
+
return result;
}
@@ -103,7 +113,7 @@ namespace Perspex.Controls.Generators
{
if (index < _containers.Count)
{
- return _containers[index];
+ return _containers[index]?.ContainerControl;
}
return null;
@@ -112,7 +122,19 @@ namespace Perspex.Controls.Generators
///
public int IndexFromContainer(IControl container)
{
- return _containers.IndexOf(container);
+ var index = 0;
+
+ foreach (var i in _containers)
+ {
+ if (i?.ContainerControl == container)
+ {
+ return index;
+ }
+
+ ++index;
+ }
+
+ return -1;
}
///
@@ -135,33 +157,30 @@ namespace Perspex.Controls.Generators
///
/// Adds a collection of containers to the index.
///
- /// The starting index.
- /// The container.
- protected void AddContainers(int index, IList container)
+ /// The containers.
+ protected void AddContainers(IList containers)
{
- Contract.Requires(container != null);
+ Contract.Requires(containers != null);
- foreach (var c in container)
+ foreach (var c in containers)
{
- while (_containers.Count < index)
+ while (_containers.Count < c.Index)
{
_containers.Add(null);
}
- if (_containers.Count == index)
+ if (_containers.Count == c.Index)
{
_containers.Add(c);
}
- else if (_containers[index] == null)
+ else if (_containers[c.Index] == null)
{
- _containers[index] = c;
+ _containers[c.Index] = c;
}
else
{
throw new InvalidOperationException("Container already created.");
}
-
- ++index;
}
}
@@ -171,7 +190,7 @@ namespace Perspex.Controls.Generators
/// The first index.
/// The number of elements in the range.
/// The containers.
- protected IEnumerable GetContainerRange(int index, int count)
+ protected IEnumerable GetContainerRange(int index, int count)
{
return _containers.GetRange(index, count);
}
diff --git a/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs b/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs
index dcb365b120..1a18c2d154 100644
--- a/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs
+++ b/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs
@@ -24,6 +24,9 @@ namespace Perspex.Controls.Generators
PerspexProperty contentProperty)
: base(owner)
{
+ Contract.Requires(owner != null);
+ Contract.Requires(contentProperty != null);
+
ContentProperty = contentProperty;
}
@@ -48,7 +51,7 @@ namespace Perspex.Controls.Generators
else
{
var result = new T();
- result.SetValue(ContentProperty, Owner.MaterializeDataTemplate(item));
+ result.SetValue(ContentProperty, item);
if (!(item is IControl))
{
diff --git a/src/Perspex.Controls/Generators/TreeContainerIndex.cs b/src/Perspex.Controls/Generators/TreeContainerIndex.cs
new file mode 100644
index 0000000000..9de4ca1050
--- /dev/null
+++ b/src/Perspex.Controls/Generators/TreeContainerIndex.cs
@@ -0,0 +1,87 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Collections.Generic;
+
+namespace Perspex.Controls.Generators
+{
+ ///
+ /// Maintains an index of all item containers currently materialized by a .
+ ///
+ ///
+ /// Each has its own
+ /// that maintains the list of its direct children, but they also share an instance of this
+ /// class in their property which tracks
+ /// the containers materialized for the entire tree.
+ ///
+ public class TreeContainerIndex
+ {
+ private readonly Dictionary _itemToContainer = new Dictionary();
+ private readonly Dictionary _containerToItem = new Dictionary();
+
+ ///
+ /// Gets the currently materialized containers.
+ ///
+ public IEnumerable Items => _containerToItem.Keys;
+
+ ///
+ /// Adds an entry to the index.
+ ///
+ /// The item.
+ /// The item container.
+ public void Add(object item, IControl container)
+ {
+ _itemToContainer.Add(item, container);
+ _containerToItem.Add(container, item);
+ }
+
+ ///
+ /// Removes a container from the index.
+ ///
+ /// The item container.
+ public void Remove(IControl container)
+ {
+ var item = _containerToItem[container];
+ _containerToItem.Remove(container);
+ _itemToContainer.Remove(item);
+ }
+
+ ///
+ /// Removes a set of containers from the index.
+ ///
+ /// The item containers.
+ public void Remove(IEnumerable containers)
+ {
+ foreach (var container in containers)
+ {
+ var item = _containerToItem[container.ContainerControl];
+ _containerToItem.Remove(container.ContainerControl);
+ _itemToContainer.Remove(item);
+ }
+ }
+
+ ///
+ /// Gets the container for an item.
+ ///
+ /// The item.
+ /// The container, or null of not found.
+ public IControl ContainerFromItem(object item)
+ {
+ IControl result;
+ _itemToContainer.TryGetValue(item, out result);
+ return result;
+ }
+
+ ///
+ /// Gets the item for a container.
+ ///
+ /// The container.
+ /// The item, or null of not found.
+ public object ItemFromContainer(IControl container)
+ {
+ object result;
+ _containerToItem.TryGetValue(container, out result);
+ return result;
+ }
+ }
+}
diff --git a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
index 18df4c4183..9feaab3d25 100644
--- a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
+++ b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
@@ -1,6 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
using System.Collections;
using System.Collections.Generic;
using Perspex.Controls.Templates;
@@ -14,9 +15,6 @@ namespace Perspex.Controls.Generators
public class TreeItemContainerGenerator : ItemContainerGenerator, ITreeItemContainerGenerator
where T : class, IControl, new()
{
- private Dictionary _itemToContainer;
- private Dictionary _containerToItem;
-
///
/// Initializes a new instance of the class.
///
@@ -24,34 +22,30 @@ namespace Perspex.Controls.Generators
/// The container's Content property.
/// The container's Items property.
/// The container's IsExpanded property.
- ///
- /// The item container for the root of the tree, or null if this generator is itself the
- /// root of the tree.
- ///
+ /// The container index for the tree
public TreeItemContainerGenerator(
IControl owner,
PerspexProperty contentProperty,
PerspexProperty itemsProperty,
PerspexProperty isExpandedProperty,
- ITreeItemContainerGenerator rootGenerator)
+ TreeContainerIndex index)
: base(owner, contentProperty)
{
+ Contract.Requires(owner != null);
+ Contract.Requires(contentProperty != null);
+ Contract.Requires(itemsProperty != null);
+ Contract.Requires(isExpandedProperty != null);
+ Contract.Requires(index != null);
+
ItemsProperty = itemsProperty;
IsExpandedProperty = isExpandedProperty;
- RootGenerator = rootGenerator;
-
- if (rootGenerator == null)
- {
- _itemToContainer = new Dictionary();
- _containerToItem = new Dictionary();
- }
+ Index = index;
}
///
- /// Gets the item container for the root of the tree, or null if this generator is itself
- /// the root of the tree.
+ /// Gets the container index for the tree.
///
- public ITreeItemContainerGenerator RootGenerator { get; }
+ public TreeContainerIndex Index { get; }
///
/// Gets the item container's Items property.
@@ -63,30 +57,6 @@ namespace Perspex.Controls.Generators
///
protected PerspexProperty IsExpandedProperty { get; }
- ///
- /// Gets the item container for the specified item, anywhere in the tree.
- ///
- /// The item.
- /// The container, or null if not found.
- public IControl TreeContainerFromItem(object item)
- {
- T result;
- _itemToContainer.TryGetValue(item, out result);
- return result;
- }
-
- ///
- /// Gets the item for the specified item container, anywhere in the tree.
- ///
- /// The container.
- /// The item, or null if not found.
- public object TreeItemFromContainer(IControl container)
- {
- object result;
- _containerToItem.TryGetValue(container, out result);
- return result;
- }
-
///
protected override IControl CreateContainer(object item)
{
@@ -107,72 +77,35 @@ namespace Perspex.Controls.Generators
result.SetValue(ContentProperty, template.Build(item));
result.SetValue(ItemsProperty, template.ItemsSelector(item));
- result.SetValue(IsExpandedProperty, template.IsExpanded(item));
if (!(item is IControl))
{
result.DataContext = item;
}
- AddToIndex(item, result);
+ Index.Add(item, result);
return result;
}
}
- public override IEnumerable Clear()
+ public override IEnumerable Clear()
{
- ClearIndex();
- return base.Clear();
+ var items = base.Clear();
+ Index.Remove(items);
+ return items;
}
- public override IEnumerable Dematerialize(int startingIndex, int count)
+ public override IEnumerable Dematerialize(int startingIndex, int count)
{
- RemoveFromIndex(GetContainerRange(startingIndex, count));
+ Index.Remove(GetContainerRange(startingIndex, count));
return base.Dematerialize(startingIndex, count);
}
- private void AddToIndex(object item, T container)
+ public override IEnumerable RemoveRange(int startingIndex, int count)
{
- if (RootGenerator != null)
- {
- ((TreeItemContainerGenerator)RootGenerator).AddToIndex(item, container);
- }
- else
- {
- _itemToContainer.Add(item, container);
- _containerToItem.Add(container, item);
- }
- }
-
- private void RemoveFromIndex(IEnumerable containers)
- {
- if (RootGenerator != null)
- {
- ((TreeItemContainerGenerator)RootGenerator).RemoveFromIndex(containers);
- }
- else
- {
- foreach (var container in containers)
- {
- var item = _containerToItem[container];
- _containerToItem.Remove(container);
- _itemToContainer.Remove(item);
- }
- }
- }
-
- private void ClearIndex()
- {
- if (RootGenerator != null)
- {
- ((TreeItemContainerGenerator)RootGenerator).ClearIndex();
- }
- else
- {
- _containerToItem.Clear();
- _itemToContainer.Clear();
- }
+ Index.Remove(GetContainerRange(startingIndex, count));
+ return base.RemoveRange(startingIndex, count);
}
///
@@ -182,20 +115,9 @@ namespace Perspex.Controls.Generators
/// The template.
private ITreeDataTemplate GetTreeDataTemplate(object item)
{
- IDataTemplate template = Owner.FindDataTemplate(item);
-
- if (template == null)
- {
- template = FuncDataTemplate.Default;
- }
-
- var treeTemplate = template as ITreeDataTemplate;
-
- if (treeTemplate == null)
- {
- treeTemplate = new FuncTreeDataTemplate(typeof(object), template.Build, x => null);
- }
-
+ var template = Owner.FindDataTemplate(item) ?? FuncDataTemplate.Default;
+ var treeTemplate = template as ITreeDataTemplate ??
+ new FuncTreeDataTemplate(typeof(object), template.Build, x => null);
return treeTemplate;
}
}
diff --git a/src/Perspex.Controls/Grid.cs b/src/Perspex.Controls/Grid.cs
index 1cb80e4221..c2da194441 100644
--- a/src/Perspex.Controls/Grid.cs
+++ b/src/Perspex.Controls/Grid.cs
@@ -587,6 +587,11 @@ namespace Perspex.Controls
_rowMatrix = new Segment[rowCount, rowCount];
_colMatrix = new Segment[colCount, colCount];
}
+ else
+ {
+ Array.Clear(_rowMatrix, 0, _rowMatrix.Length);
+ Array.Clear(_colMatrix, 0, _colMatrix.Length);
+ }
}
private void ExpandStarCols(Size availableSize)
diff --git a/src/Perspex.Controls/GridSplitter.cs b/src/Perspex.Controls/GridSplitter.cs
index 9a0ef0b988..57d77e8f8d 100644
--- a/src/Perspex.Controls/GridSplitter.cs
+++ b/src/Perspex.Controls/GridSplitter.cs
@@ -2,35 +2,135 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Perspex.Collections;
using Perspex.Controls.Primitives;
using Perspex.Input;
-using Perspex.Rendering;
using Perspex.VisualTree;
namespace Perspex.Controls
{
+ ///
+ /// Represents the control that redistributes space between columns or rows of a Grid control.
+ ///
+ ///
+ /// Unlike WPF GridSplitter, Perspex GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext.
+ ///
public class GridSplitter : Thumb
{
- private Grid _grid;
+ ///
+ /// Defines the property.
+ ///
+ public static readonly PerspexProperty OrientationProperty =
+ PerspexProperty.Register(nameof(Orientation));
+
+ protected Grid _grid;
+
+ private DefinitionBase _prevDefinition;
+
+ private DefinitionBase _nextDefinition;
+
+ private bool _isResizingColumns;
+
+ private List _definitions;
///
- /// Initializes a new instance of the class.
+ /// Gets or sets the orientation of the GridsSlitter.
///
- public GridSplitter()
+ ///
+ /// if null, it's inferred from column/row definition (should be auto).
+ ///
+ public Orientation Orientation {
+ get
+ {
+ return GetValue(OrientationProperty);
+ }
+ set
+ {
+ SetValue(OrientationProperty, value);
+ }
+ }
+
+ static GridSplitter()
+ {
+ PseudoClass(OrientationProperty, o => o == Perspex.Controls.Orientation.Vertical, ":vertical");
+ PseudoClass(OrientationProperty, o => o == Perspex.Controls.Orientation.Horizontal, ":horizontal");
+ }
+
+ private void GetDeltaConstraints(out double min, out double max)
{
- Cursor = new Cursor(StandardCursorType.SizeWestEast);
+ double prevDefinitionLen = GetActualLength(_prevDefinition);
+ double prevDefinitionMin = GetMinLength(_prevDefinition);
+ double prevDefinitionMax = GetMaxLength(_prevDefinition);
+
+ double nextDefinitionLen = GetActualLength(_nextDefinition);
+ double nextDefinitionMin = GetMinLength(_nextDefinition);
+ double nextDefinitionMax = GetMaxLength(_nextDefinition);
+ // Determine the minimum and maximum the columns can be resized
+ min = -Math.Min(prevDefinitionLen - prevDefinitionMin, nextDefinitionMax - nextDefinitionLen);
+ max = Math.Min(prevDefinitionMax - prevDefinitionLen, nextDefinitionLen - nextDefinitionMin);
}
protected override void OnDragDelta(VectorEventArgs e)
{
- int col = GetValue(Grid.ColumnProperty);
+ var delta = Orientation == Orientation.Vertical ? e.Vector.X : e.Vector.Y;
+ double max;
+ double min;
+ GetDeltaConstraints(out min, out max);
+ delta = Math.Min(Math.Max(delta, min), max);
+ foreach (var definition in _definitions)
+ {
+ if (definition == _prevDefinition)
+ {
+ SetLengthInStars(_prevDefinition, GetActualLength(_prevDefinition) + delta);
+ }
+ else if (definition == _nextDefinition)
+ {
+ SetLengthInStars(_nextDefinition, GetActualLength(_nextDefinition) - delta);
+ }
+ else if (IsStar(definition))
+ {
+ SetLengthInStars(definition, GetActualLength(definition)); // same size but in stars.
+ }
+ }
+ }
+
+ private double GetActualLength(DefinitionBase definition)
+ {
+ var columnDefinition = definition as ColumnDefinition;
+ return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight;
+ }
+
+ private double GetMinLength(DefinitionBase definition)
+ {
+ var columnDefinition = definition as ColumnDefinition;
+ return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight;
+ }
+
+ private double GetMaxLength(DefinitionBase definition)
+ {
+ var columnDefinition = definition as ColumnDefinition;
+ return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight;
+ }
+
+ private bool IsStar(DefinitionBase definition)
+ {
+ var columnDefinition = definition as ColumnDefinition;
+ return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar;
+ }
- if (_grid != null && col > 0)
+ private void SetLengthInStars(DefinitionBase definition, double value)
+ {
+ var columnDefinition = definition as ColumnDefinition;
+ if (columnDefinition != null)
+ {
+ columnDefinition.Width = new GridLength(value, GridUnitType.Star);
+ }
+ else
{
- var size = _grid.ColumnDefinitions[col - 1].ActualWidth + e.Vector.X;
- _grid.ColumnDefinitions[col - 1].Width = new GridLength(
- Math.Max(0, size),
- GridUnitType.Pixel);
+ ((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star);
}
}
@@ -38,6 +138,24 @@ namespace Perspex.Controls
{
base.OnAttachedToVisualTree(e);
_grid = this.GetVisualParent();
+
+ if (Orientation == Orientation.Vertical)
+ {
+ Cursor = new Cursor(StandardCursorType.SizeWestEast);
+ var col = GetValue(Grid.ColumnProperty);
+ _definitions = _grid.ColumnDefinitions.Cast().ToList();
+ _prevDefinition = _definitions[col - 1];
+ _nextDefinition = _definitions[col + 1];
+ }
+ else
+ {
+ Cursor = new Cursor(StandardCursorType.SizeNorthSouth);
+ var row = GetValue(Grid.RowProperty);
+ _definitions = _grid.RowDefinitions.Cast().ToList();
+ _prevDefinition = _definitions[row - 1];
+ _nextDefinition = _definitions[row + 1];
+ }
}
}
}
+
diff --git a/src/Perspex.Controls/HotkeyManager.cs b/src/Perspex.Controls/HotkeyManager.cs
index 6b7fb117c1..d407a7433c 100644
--- a/src/Perspex.Controls/HotkeyManager.cs
+++ b/src/Perspex.Controls/HotkeyManager.cs
@@ -12,7 +12,7 @@ namespace Perspex.Controls
{
public class HotKeyManager
{
- public static PerspexProperty HotKeyProperty
+ public static readonly PerspexProperty HotKeyProperty
= PerspexProperty.RegisterAttached("HotKey", typeof (HotKeyManager));
class HotkeyCommandWrapper : ICommand
@@ -22,7 +22,7 @@ namespace Perspex.Controls
Control = control;
}
- public IControl Control;
+ public readonly IControl Control;
private ICommand GetCommand() => Control.GetValue(Button.CommandProperty);
@@ -42,7 +42,7 @@ namespace Perspex.Controls
private IDisposable _parentSub;
private IDisposable _hotkeySub;
private KeyGesture _hotkey;
- private HotkeyCommandWrapper _wrapper;
+ private readonly HotkeyCommandWrapper _wrapper;
private KeyBinding _binding;
public Manager(IControl control)
diff --git a/src/Perspex.Controls/IControl.cs b/src/Perspex.Controls/IControl.cs
index 21cb044d0a..0e65c599be 100644
--- a/src/Perspex.Controls/IControl.cs
+++ b/src/Perspex.Controls/IControl.cs
@@ -13,6 +13,11 @@ namespace Perspex.Controls
///
public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IStyleable, IStyleHost
{
+ ///
+ /// Gets or sets the control's styling classes.
+ ///
+ new Classes Classes { get; set; }
+
///
/// Gets or sets the control's data context.
///
diff --git a/src/Perspex.SceneGraph/INameScope.cs b/src/Perspex.Controls/INameScope.cs
similarity index 98%
rename from src/Perspex.SceneGraph/INameScope.cs
rename to src/Perspex.Controls/INameScope.cs
index 6e54c90afd..a6c115a4ec 100644
--- a/src/Perspex.SceneGraph/INameScope.cs
+++ b/src/Perspex.Controls/INameScope.cs
@@ -3,7 +3,7 @@
using System;
-namespace Perspex
+namespace Perspex.Controls
{
///
/// Defines a name scope.
diff --git a/src/Perspex.Controls/IPseudoClasses.cs b/src/Perspex.Controls/IPseudoClasses.cs
new file mode 100644
index 0000000000..87e5a9259a
--- /dev/null
+++ b/src/Perspex.Controls/IPseudoClasses.cs
@@ -0,0 +1,25 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Perspex.Controls
+{
+ ///
+ /// Exposes an interface for setting pseudoclasses on a collection.
+ ///
+ public interface IPseudoClasses
+ {
+ ///
+ /// Adds a pseudoclass to the collection.
+ ///
+ /// The pseudoclass name.
+ void Add(string name);
+
+ ///
+ /// Removes a pseudoclass from the collection.
+ ///
+ /// The pseudoclass name.
+ bool Remove(string name);
+ }
+}
diff --git a/src/Perspex.Controls/IReparentingControl.cs b/src/Perspex.Controls/IReparentingControl.cs
deleted file mode 100644
index a7d4b46e64..0000000000
--- a/src/Perspex.Controls/IReparentingControl.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) The Perspex Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using Perspex.Collections;
-
-namespace Perspex.Controls
-{
- ///
- /// A control that can make its visual children the logical children of another control.
- ///
- public interface IReparentingControl : IControl
- {
- ///
- /// Requests that the visual children of the control use another control as their logical
- /// parent.
- ///
- ///
- /// The logical parent for the visual children of the control.
- ///
- ///
- /// The collection to modify.
- ///
- void ReparentLogicalChildren(ILogical logicalParent, IPerspexList children);
- }
-}
\ No newline at end of file
diff --git a/src/Perspex.Controls/IReparentingHost.cs b/src/Perspex.Controls/IReparentingHost.cs
deleted file mode 100644
index 99dcf7d4f1..0000000000
--- a/src/Perspex.Controls/IReparentingHost.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) The Perspex Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using Perspex.Collections;
-
-namespace Perspex.Controls
-{
- ///
- /// A control that can use the visual children of another control as its logical children.
- ///
- public interface IReparentingHost : ILogical
- {
- ///
- /// Gets a writeable logical children collection from the host.
- ///
- new IPerspexList LogicalChildren { get; }
-
- ///
- /// Asks the control whether it wants to reparent the logical children of the specified
- /// control.
- ///
- /// The control.
- ///
- /// True if the control wants to reparent its logical children otherwise false.
- ///
- bool WillReparentChildrenOf(IControl control);
- }
-}
\ No newline at end of file
diff --git a/src/Perspex.Controls/Image.cs b/src/Perspex.Controls/Image.cs
index 0b316689bd..26dc930a06 100644
--- a/src/Perspex.Controls/Image.cs
+++ b/src/Perspex.Controls/Image.cs
@@ -27,7 +27,7 @@ namespace Perspex.Controls
///
/// Gets or sets the bitmap image that will be displayed.
///
- public Bitmap Source
+ public IBitmap Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
@@ -48,7 +48,7 @@ namespace Perspex.Controls
/// The drawing context.
public override void Render(DrawingContext context)
{
- Bitmap source = Source;
+ var source = Source;
if (source != null)
{
diff --git a/src/Perspex.Controls/ItemsControl.cs b/src/Perspex.Controls/ItemsControl.cs
index 5c06dbc04e..42b43daf03 100644
--- a/src/Perspex.Controls/ItemsControl.cs
+++ b/src/Perspex.Controls/ItemsControl.cs
@@ -3,7 +3,7 @@
using System;
using System.Collections;
-using System.Collections.ObjectModel;
+using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -21,7 +21,7 @@ namespace Perspex.Controls
///
/// Displays a collection of items.
///
- public class ItemsControl : TemplatedControl, IReparentingHost
+ public class ItemsControl : TemplatedControl, IItemsPresenterHost
{
///
/// The default value for the property.
@@ -64,7 +64,7 @@ namespace Perspex.Controls
///
public ItemsControl()
{
- Classes.Add(":empty");
+ PseudoClasses.Add(":empty");
SubscribeToItems(_items);
}
@@ -78,6 +78,12 @@ namespace Perspex.Controls
if (_itemContainerGenerator == null)
{
_itemContainerGenerator = CreateItemContainerGenerator();
+
+ if (_itemContainerGenerator != null)
+ {
+ _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e);
+ _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e);
+ }
}
return _itemContainerGenerator;
@@ -118,38 +124,140 @@ namespace Perspex.Controls
public IItemsPresenter Presenter
{
get;
- set;
+ protected set;
}
///
- IPerspexList IReparentingHost.LogicalChildren => LogicalChildren;
+ void IItemsPresenterHost.RegisterItemsPresenter(IItemsPresenter presenter)
+ {
+ Presenter = presenter;
+ }
///
- /// Asks the control whether it wants to reparent the logical children of the specified
- /// control.
+ /// Gets the item at the specified index in a collection.
///
- /// The control.
- ///
- /// True if the control wants to reparent its logical children otherwise false.
- ///
- bool IReparentingHost.WillReparentChildrenOf(IControl control)
+ /// The collection.
+ /// The index.
+ /// The index of the item or -1 if the item was not found.
+ protected static object ElementAt(IEnumerable items, int index)
{
- return control is IItemsPresenter && control.TemplatedParent == this;
+ var typedItems = items?.Cast();
+
+ if (index != -1 && typedItems != null && index < typedItems.Count())
+ {
+ return typedItems.ElementAt(index) ?? null;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Gets the index of an item in a collection.
+ ///
+ /// The collection.
+ /// The item.
+ /// The index of the item or -1 if the item was not found.
+ protected static int IndexOf(IEnumerable items, object item)
+ {
+ if (items != null && item != null)
+ {
+ var list = items as IList;
+
+ if (list != null)
+ {
+ return list.IndexOf(item);
+ }
+ else
+ {
+ int index = 0;
+
+ foreach (var i in items)
+ {
+ if (Equals(i, item))
+ {
+ return index;
+ }
+
+ ++index;
+ }
+ }
+ }
+
+ return -1;
}
///
/// Creates the for the control.
///
- /// An .
+ ///
+ /// An or null.
+ ///
+ ///
+ /// Certain controls such as don't actually create item
+ /// containers; however they want it to be ItemsControls so that they have an Items
+ /// property etc. In this case, a derived class can override this method to return null
+ /// in order to disable the creation of item containers.
+ ///
protected virtual IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator(this);
}
+ ///
+ /// Called when new containers are materialized for the by its
+ /// .
+ ///
+ /// The details of the containers.
+ protected virtual void OnContainersMaterialized(ItemContainerEventArgs e)
+ {
+ var toAdd = new List();
+
+ foreach (var container in e.Containers)
+ {
+ // If the item is its own container, then it will be added to the logical tree when
+ // it was added to the Items collection.
+ if (container.ContainerControl != null && container.ContainerControl != container.Item)
+ {
+ toAdd.Add(container.ContainerControl);
+ }
+ }
+
+ LogicalChildren.AddRange(toAdd);
+ }
+
+ ///
+ /// Called when containers are dematerialized for the by its
+ /// .
+ ///
+ /// The details of the containers.
+ protected virtual void OnContainersDematerialized(ItemContainerEventArgs e)
+ {
+ var toRemove = new List();
+
+ foreach (var container in e.Containers)
+ {
+ // If the item is its own container, then it will be removed from the logical tree
+ // when it is removed from the Items collection.
+ if (container.ContainerControl != container.Item)
+ {
+ toRemove.Add(container.ContainerControl);
+ }
+ }
+
+ LogicalChildren.RemoveAll(toRemove);
+ }
+
///
- protected override void OnTemplateApplied(INameScope nameScope)
+ protected override void OnTemplateChanged(PerspexPropertyChangedEventArgs e)
{
- Presenter = nameScope.Find("PART_ItemsPresenter");
+ base.OnTemplateChanged(e);
+
+ if (e.NewValue == null)
+ {
+ ItemContainerGenerator?.Clear();
+ }
}
///
@@ -165,7 +273,11 @@ namespace Perspex.Controls
incc.CollectionChanged -= ItemsCollectionChanged;
}
+ var oldValue = e.OldValue as IEnumerable;
var newValue = e.NewValue as IEnumerable;
+
+ RemoveControlItemsFromLogicalChildren(oldValue);
+ AddControlItemsToLogicalChildren(newValue);
SubscribeToItems(newValue);
}
@@ -177,16 +289,67 @@ namespace Perspex.Controls
/// The event args.
protected virtual void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ AddControlItemsToLogicalChildren(e.NewItems);
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ RemoveControlItemsFromLogicalChildren(e.OldItems);
+ break;
+ }
+
var collection = sender as ICollection;
+ PseudoClasses.Set(":empty", collection.Count == 0);
+ }
+
+ ///
+ /// Given a collection of items, adds those that are controls to the logical children.
+ ///
+ /// The items.
+ private void AddControlItemsToLogicalChildren(IEnumerable items)
+ {
+ var toAdd = new List();
- if (collection.Count == 0)
+ if (items != null)
{
- Classes.Add(":empty");
+ foreach (var i in items)
+ {
+ var control = i as IControl;
+
+ if (control != null && !LogicalChildren.Contains(control))
+ {
+ toAdd.Add(control);
+ }
+ }
}
- else
+
+ LogicalChildren.AddRange(toAdd);
+ }
+
+ ///
+ /// Given a collection of items, removes those that are controls to from logical children.
+ ///
+ /// The items.
+ private void RemoveControlItemsFromLogicalChildren(IEnumerable items)
+ {
+ var toRemove = new List();
+
+ if (items != null)
{
- Classes.Remove(":empty");
+ foreach (var i in items)
+ {
+ var control = i as IControl;
+
+ if (control != null)
+ {
+ toRemove.Add(control);
+ }
+ }
}
+
+ LogicalChildren.RemoveAll(toRemove);
}
///
@@ -195,14 +358,7 @@ namespace Perspex.Controls
///
private void SubscribeToItems(IEnumerable items)
{
- if (items == null || items.Count() == 0)
- {
- Classes.Add(":empty");
- }
- else
- {
- Classes.Remove(":empty");
- }
+ PseudoClasses.Set(":empty", items == null || items.Count() == 0);
var incc = items as INotifyCollectionChanged;
diff --git a/src/Perspex.Controls/ListBox.cs b/src/Perspex.Controls/ListBox.cs
index 7641e2e54a..3cb66641ac 100644
--- a/src/Perspex.Controls/ListBox.cs
+++ b/src/Perspex.Controls/ListBox.cs
@@ -29,10 +29,7 @@ namespace Perspex.Controls
SelectingItemsControl.SelectionModeProperty;
///
- public new IList SelectedItems
- {
- get { return base.SelectedItems; }
- }
+ public new IList SelectedItems => base.SelectedItems;
///
public new SelectionMode SelectionMode
diff --git a/src/Perspex.Controls/LogicalTreeAttachmentEventArgs.cs b/src/Perspex.Controls/LogicalTreeAttachmentEventArgs.cs
new file mode 100644
index 0000000000..024942cf71
--- /dev/null
+++ b/src/Perspex.Controls/LogicalTreeAttachmentEventArgs.cs
@@ -0,0 +1,31 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Perspex.Styling;
+
+namespace Perspex.Controls
+{
+ ///
+ /// Holds the event arguments for the and
+ /// events.
+ ///
+ public class LogicalTreeAttachmentEventArgs : EventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The root of the logical tree.
+ public LogicalTreeAttachmentEventArgs(IStyleHost root)
+ {
+ Contract.Requires(root != null);
+
+ Root = root;
+ }
+
+ ///
+ /// Gets the root of the logical tree that the control is being attached to or detached from.
+ ///
+ public IStyleHost Root { get; }
+ }
+}
diff --git a/src/Perspex.Controls/Menu.cs b/src/Perspex.Controls/Menu.cs
index 9e9ee451e8..2abe65ae48 100644
--- a/src/Perspex.Controls/Menu.cs
+++ b/src/Perspex.Controls/Menu.cs
@@ -116,7 +116,7 @@ namespace Perspex.Controls
var inputRoot = e.Root as IInputRoot;
- if (inputRoot != null && inputRoot.AccessKeyHandler != null)
+ if (inputRoot?.AccessKeyHandler != null)
{
inputRoot.AccessKeyHandler.MainMenu = this;
}
diff --git a/src/Perspex.Controls/MenuItem.cs b/src/Perspex.Controls/MenuItem.cs
index 4d73240604..560b93495f 100644
--- a/src/Perspex.Controls/MenuItem.cs
+++ b/src/Perspex.Controls/MenuItem.cs
@@ -2,25 +2,21 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Windows.Input;
using Perspex.Controls.Mixins;
-using Perspex.Controls.Presenters;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Input;
using Perspex.Interactivity;
using Perspex.Threading;
-using Perspex.VisualTree;
namespace Perspex.Controls
{
///
/// A menu item control.
///
- public class MenuItem : SelectingItemsControl, ISelectable
+ public class MenuItem : HeaderedSelectingItemsControl, ISelectable
{
///
/// Defines the property.
@@ -40,12 +36,6 @@ namespace Perspex.Controls
public static readonly PerspexProperty CommandParameterProperty =
Button.CommandParameterProperty.AddOwner
/// The visual.
/// The value to set.
- public static void SetNameScope(Visual visual, INameScope value)
+ public static void SetNameScope(Control visual, INameScope value)
{
visual.SetValue(NameScopeProperty, value);
}
@@ -102,7 +102,7 @@ namespace Perspex
if (_inner.TryGetValue(name, out element))
{
_inner.Remove(name);
- Registered?.Invoke(this, new NameScopeEventArgs(name, element));
+ Unregistered?.Invoke(this, new NameScopeEventArgs(name, element));
}
}
}
diff --git a/src/Perspex.SceneGraph/NameScopeEventArgs.cs b/src/Perspex.Controls/NameScopeEventArgs.cs
similarity index 94%
rename from src/Perspex.SceneGraph/NameScopeEventArgs.cs
rename to src/Perspex.Controls/NameScopeEventArgs.cs
index 4d19082a24..254b13c4bd 100644
--- a/src/Perspex.SceneGraph/NameScopeEventArgs.cs
+++ b/src/Perspex.Controls/NameScopeEventArgs.cs
@@ -3,7 +3,7 @@
using System;
-namespace Perspex
+namespace Perspex.Controls
{
public class NameScopeEventArgs : EventArgs
{
diff --git a/src/Perspex.SceneGraph/NameScopeExtensions.cs b/src/Perspex.Controls/NameScopeExtensions.cs
similarity index 98%
rename from src/Perspex.SceneGraph/NameScopeExtensions.cs
rename to src/Perspex.Controls/NameScopeExtensions.cs
index 3e80f8e838..9053adebee 100644
--- a/src/Perspex.SceneGraph/NameScopeExtensions.cs
+++ b/src/Perspex.Controls/NameScopeExtensions.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
-namespace Perspex
+namespace Perspex.Controls
{
///
/// Extension methods for .
diff --git a/src/Perspex.Controls/Panel.cs b/src/Perspex.Controls/Panel.cs
index 9adda8bb16..edb110f22d 100644
--- a/src/Perspex.Controls/Panel.cs
+++ b/src/Perspex.Controls/Panel.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
-using Perspex.Collections;
using Perspex.Media;
using Perspex.Metadata;
@@ -18,7 +17,7 @@ namespace Perspex.Controls
/// Controls can be added to a by adding them to its
/// collection. All children are layed out to fill the panel.
///
- public class Panel : Control, IReparentingControl, IPanel
+ public class Panel : Control, IPanel
{
///
/// Defines the property.
@@ -68,7 +67,7 @@ namespace Perspex.Controls
{
Contract.Requires(value != null);
- ClearVisualChildren();
+ VisualChildren.Clear();
_children.Clear();
_children.AddRange(value);
}
@@ -83,58 +82,6 @@ namespace Perspex.Controls
set { SetValue(BackgroundProperty, value); }
}
- ///
- /// Requests that the visual children of the panel use another control as their logical
- /// parent.
- ///
- ///
- /// The logical parent for the visual children of the panel.
- ///
- ///
- /// The collection to modify.
- ///
- void IReparentingControl.ReparentLogicalChildren(ILogical logicalParent, IPerspexList children)
- {
- Contract.Requires(logicalParent != null);
- Contract.Requires(children != null);
-
- _childLogicalParent = logicalParent;
- RedirectLogicalChildren(children);
-
- foreach (var control in Children)
- {
- ((ISetLogicalParent)control).SetParent(null);
- ((ISetLogicalParent)control).SetParent((IControl)logicalParent);
- children.Add(control);
- }
- }
-
- ///
- /// Clears for the specified controls.
- ///
- /// The controls.
- private void ClearLogicalParent(IEnumerable controls)
- {
- foreach (var control in controls)
- {
- ((ISetLogicalParent)control).SetParent(null);
- }
- }
-
- ///
- /// Sets for the specified controls.
- ///
- /// The controls.
- private void SetLogicalParent(IEnumerable controls)
- {
- var parent = _childLogicalParent as Control;
-
- foreach (var control in controls)
- {
- ((ISetLogicalParent)control).SetParent(parent);
- }
- }
-
///
/// Called when the collection changes.
///
@@ -144,29 +91,35 @@ namespace Perspex.Controls
{
List controls;
- // TODO: Handle Replace.
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
controls = e.NewItems.OfType().ToList();
- SetLogicalParent(controls);
- AddVisualChildren(e.NewItems.OfType());
LogicalChildren.InsertRange(e.NewStartingIndex, controls);
+ VisualChildren.AddRange(e.NewItems.OfType());
break;
case NotifyCollectionChangedAction.Remove:
controls = e.OldItems.OfType().ToList();
- ClearLogicalParent(e.OldItems.OfType());
LogicalChildren.RemoveAll(controls);
- RemoveVisualChildren(e.OldItems.OfType());
+ VisualChildren.RemoveAll(e.OldItems.OfType());
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ for (var i = 0; i < e.OldItems.Count; ++i)
+ {
+ var index = i + e.OldStartingIndex;
+ var child = (IControl)e.NewItems[i];
+ LogicalChildren[index] = child;
+ VisualChildren[index] = child;
+ }
break;
case NotifyCollectionChangedAction.Reset:
controls = e.OldItems.OfType().ToList();
- ClearLogicalParent(controls);
LogicalChildren.Clear();
- ClearVisualChildren();
- AddVisualChildren(_children);
+ VisualChildren.Clear();
+ VisualChildren.AddRange(_children);
break;
}
diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj
index e02af26c3f..1c2d72190e 100644
--- a/src/Perspex.Controls/Perspex.Controls.csproj
+++ b/src/Perspex.Controls/Perspex.Controls.csproj
@@ -27,6 +27,7 @@
prompt
4
bin\Debug\Perspex.Controls.XML
+ CS1591
pdbonly
@@ -41,10 +42,29 @@
Properties\SharedAssemblyInfo.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -52,11 +72,10 @@
-
+
-
@@ -68,14 +87,11 @@
-
-
-
@@ -145,6 +161,8 @@
+
+
@@ -167,9 +185,14 @@
+
+
+ ..\..\packages\JetBrains.Annotations.9.2.0\lib\portable-net4+sl5+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\JetBrains.Annotations.PCL328.dll
+ True
+
..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll
diff --git a/src/Perspex.Controls/Platform/IPopupImpl.cs b/src/Perspex.Controls/Platform/IPopupImpl.cs
index 495b16409c..5cbdafb9f9 100644
--- a/src/Perspex.Controls/Platform/IPopupImpl.cs
+++ b/src/Perspex.Controls/Platform/IPopupImpl.cs
@@ -8,15 +8,6 @@ namespace Perspex.Platform
///
public interface IPopupImpl : ITopLevelImpl
{
- ///
- /// Sets the position of the popup.
- ///
- /// The position, in screen coordinates.
- void SetPosition(Point p);
- ///
- /// Hides the popup.
- ///
- void Hide();
}
}
diff --git a/src/Perspex.Controls/Platform/ITopLevelImpl.cs b/src/Perspex.Controls/Platform/ITopLevelImpl.cs
index f826ec5990..7c80b951a5 100644
--- a/src/Perspex.Controls/Platform/ITopLevelImpl.cs
+++ b/src/Perspex.Controls/Platform/ITopLevelImpl.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using Perspex.Controls;
using Perspex.Input;
using Perspex.Input.Raw;
@@ -88,5 +89,23 @@ namespace Perspex.Platform
/// Shows the toplevel.
///
void Show();
+
+ ///
+ /// Hides the window.
+ ///
+ void Hide();
+
+ ///
+ /// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
+ ///
+ void BeginMoveDrag();
+
+ ///
+ /// Starts resizing a window. This function is used if an application has window resizing controls.
+ /// Should be called from left mouse button press event handler
+ ///
+ void BeginResizeDrag(WindowEdge edge);
+
+ Point Position { get; set; }
}
}
diff --git a/src/Perspex.Controls/Platform/IWindowImpl.cs b/src/Perspex.Controls/Platform/IWindowImpl.cs
index f8bd6588cd..cb266a9153 100644
--- a/src/Perspex.Controls/Platform/IWindowImpl.cs
+++ b/src/Perspex.Controls/Platform/IWindowImpl.cs
@@ -30,8 +30,8 @@ namespace Perspex.Platform
IDisposable ShowDialog();
///
- /// Hides the window.
+ /// Enables of disables system window decorations (title bar, buttons, etc)
///
- void Hide();
+ void SetSystemDecorations(bool enabled);
}
}
diff --git a/src/Perspex.Controls/Platform/IWindowingPlatform.cs b/src/Perspex.Controls/Platform/IWindowingPlatform.cs
new file mode 100644
index 0000000000..5a12fbdeb2
--- /dev/null
+++ b/src/Perspex.Controls/Platform/IWindowingPlatform.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Perspex.Platform
+{
+ public interface IWindowingPlatform
+ {
+ IWindowImpl CreateWindow();
+ IWindowImpl CreateEmbeddableWindow();
+ IPopupImpl CreatePopup();
+ }
+}
diff --git a/src/Perspex.Controls/Platform/PlatformManager.cs b/src/Perspex.Controls/Platform/PlatformManager.cs
index ba239d1a46..6cec6cfadf 100644
--- a/src/Perspex.Controls/Platform/PlatformManager.cs
+++ b/src/Perspex.Controls/Platform/PlatformManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reactive.Disposables;
using System.Text;
using System.Threading.Tasks;
using Perspex.Input;
@@ -17,6 +18,9 @@ namespace Perspex.Controls.Platform
static IPlatformSettings GetSettings()
=> PerspexLocator.Current.GetService();
+ static bool s_designerMode;
+ private static double _designerScalingFactor = 1;
+
public static IRenderTarget CreateRenderTarget(ITopLevelImpl window)
{
return
@@ -24,6 +28,19 @@ namespace Perspex.Controls.Platform
PerspexLocator.Current.GetService().CreateRenderer(window.Handle), window);
}
+ public static IDisposable DesignerMode()
+ {
+ s_designerMode = true;
+ return Disposable.Create(() => s_designerMode = false);
+ }
+
+ public static void SetDesignerScalingFactor(double factor)
+ {
+ _designerScalingFactor = factor;
+ }
+
+ static double RenderScalingFactor => (GetSettings()?.RenderScalingFactor ?? 1)*_designerScalingFactor;
+ static double LayoutScalingFactor => (GetSettings()?.LayoutScalingFactor ?? 1) * _designerScalingFactor;
class RenderTargetDecorator : IRenderTarget
{
@@ -45,7 +62,7 @@ namespace Perspex.Controls.Platform
{
var cs = _window.ClientSize;
var ctx = _target.CreateDrawingContext();
- var factor = GetSettings()?.RenderScalingFactor ?? 1;
+ var factor = RenderScalingFactor;
if (factor != 1)
{
ctx.PushPostTransform(Matrix.CreateScale(factor, factor));
@@ -62,7 +79,7 @@ namespace Perspex.Controls.Platform
private readonly IPopupImpl _popup;
public ITopLevelImpl TopLevel => _tl;
- double ScalingFactor => GetSettings()?.LayoutScalingFactor ?? 1;
+ double ScalingFactor => LayoutScalingFactor;
public WindowDecorator(ITopLevelImpl tl)
{
@@ -138,11 +155,6 @@ namespace Perspex.Controls.Platform
set { _tl.Deactivated = value; }
}
- public void SetPosition(Point p)
- {
- _popup.SetPosition(p*ScalingFactor);
- }
-
public void Dispose() => _tl.Dispose();
public IPlatformHandle Handle => _tl.Handle;
@@ -157,20 +169,31 @@ namespace Perspex.Controls.Platform
public void SetTitle(string title) => _window.SetTitle(title);
public void Show() => _tl.Show();
+ public void BeginMoveDrag() => _tl.BeginMoveDrag();
+ public void BeginResizeDrag(WindowEdge edge) => _tl.BeginResizeDrag(edge);
+
+ public Point Position
+ {
+ get { return _tl.Position; }
+ set { _tl.Position = value; }
+ }
public IDisposable ShowDialog() => _window.ShowDialog();
public void Hide() => _popup.Hide();
+ public void SetSystemDecorations(bool enabled) => _window.SetSystemDecorations(enabled);
}
public static IWindowImpl CreateWindow()
{
- return new WindowDecorator(PerspexLocator.Current.GetService());
+ var platform = PerspexLocator.Current.GetService();
+ return
+ new WindowDecorator(s_designerMode ? platform.CreateEmbeddableWindow() : platform.CreateWindow());
}
public static IPopupImpl CreatePopup()
{
- return new WindowDecorator(PerspexLocator.Current.GetService());
+ return new WindowDecorator(PerspexLocator.Current.GetService().CreatePopup());
}
}
}
diff --git a/src/Perspex.Controls/Presenters/CarouselPresenter.cs b/src/Perspex.Controls/Presenters/CarouselPresenter.cs
index 7af2416b9a..6662210288 100644
--- a/src/Perspex.Controls/Presenters/CarouselPresenter.cs
+++ b/src/Perspex.Controls/Presenters/CarouselPresenter.cs
@@ -55,6 +55,8 @@ namespace Perspex.Controls.Presenters
private int _selectedIndex = -1;
private bool _createdPanel;
private IItemContainerGenerator _generator;
+ private Task _currentTransition;
+ private int _queuedTransitionIndex = -1;
///
/// Initializes static members of the class.
@@ -62,6 +64,7 @@ namespace Perspex.Controls.Presenters
static CarouselPresenter()
{
SelectedIndexProperty.Changed.AddClassHandler(x => x.SelectedIndexChanged);
+ TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged);
}
///
@@ -160,20 +163,13 @@ namespace Perspex.Controls.Presenters
///
private void CreatePanel()
{
- var logicalHost = this.FindReparentingHost();
-
- ClearVisualChildren();
Panel = ItemsPanel.Build();
Panel.SetValue(TemplatedParentProperty, TemplatedParent);
- AddVisualChild(Panel);
-
- if (logicalHost != null)
- {
- ((IReparentingControl)Panel).ReparentLogicalChildren(
- logicalHost,
- logicalHost.LogicalChildren);
- }
+ LogicalChildren.Clear();
+ VisualChildren.Clear();
+ LogicalChildren.Add(Panel);
+ VisualChildren.Add(Panel);
_createdPanel = true;
var task = MoveToPage(-1, SelectedIndex);
@@ -187,35 +183,45 @@ namespace Perspex.Controls.Presenters
/// A task tracking the animation.
private async Task MoveToPage(int fromIndex, int toIndex)
{
- var generator = ItemContainerGenerator;
- IControl from = null;
- IControl to = null;
-
- if (fromIndex != -1)
+ if (fromIndex != toIndex)
{
- from = generator.ContainerFromIndex(fromIndex);
- }
+ var generator = ItemContainerGenerator;
+ IControl from = null;
+ IControl to = null;
- if (toIndex != -1)
- {
- var item = Items.Cast().ElementAt(toIndex);
- to = generator.Materialize(toIndex, new[] { item }, MemberSelector).FirstOrDefault();
+ if (fromIndex != -1)
+ {
+ from = generator.ContainerFromIndex(fromIndex);
+ }
- if (to != null)
+ if (toIndex != -1)
{
- Panel.Children.Add(to);
+ var item = Items.Cast().ElementAt(toIndex);
+ to = generator.ContainerFromIndex(toIndex) ??
+ generator.Materialize(toIndex, new[] { item }, MemberSelector)
+ .FirstOrDefault()?.ContainerControl;
+
+ if (to != null)
+ {
+ Panel.Children.Add(to);
+ }
}
- }
- if (Transition != null && (from != null || to != null))
- {
- await Transition.Start((Visual)from, (Visual)to, fromIndex < toIndex);
- }
+ if (Transition != null && (from != null || to != null))
+ {
+ await Transition.Start((Visual)from, (Visual)to, fromIndex < toIndex);
+ }
+ else if (to != null)
+ {
+ to.IsVisible = true;
+ }
+
+ if (from != null)
+ {
+ Panel.Children.Remove(from);
+ generator.Dematerialize(fromIndex, 1);
+ }
- if (from != null)
- {
- Panel.Children.Remove(from);
- generator.Dematerialize(fromIndex, 1);
}
}
@@ -223,12 +229,43 @@ namespace Perspex.Controls.Presenters
/// Called when the property changes.
///
/// The event args.
- private void SelectedIndexChanged(PerspexPropertyChangedEventArgs e)
+ private async void SelectedIndexChanged(PerspexPropertyChangedEventArgs e)
{
if (Panel != null)
{
- var task = MoveToPage((int)e.OldValue, (int)e.NewValue);
+ if (_currentTransition == null)
+ {
+ int fromIndex = (int)e.OldValue;
+ int toIndex = (int)e.NewValue;
+
+ for (;;)
+ {
+ _currentTransition = MoveToPage(fromIndex, toIndex);
+ await _currentTransition;
+
+ if (_queuedTransitionIndex != -1)
+ {
+ fromIndex = toIndex;
+ toIndex = _queuedTransitionIndex;
+ _queuedTransitionIndex = -1;
+ }
+ else
+ {
+ _currentTransition = null;
+ break;
+ }
+ }
+ }
+ else
+ {
+ _queuedTransitionIndex = (int)e.NewValue;
+ }
}
}
+
+ private void TemplatedParentChanged(PerspexPropertyChangedEventArgs e)
+ {
+ (e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this);
+ }
}
}
diff --git a/src/Perspex.Controls/Presenters/ContentPresenter.cs b/src/Perspex.Controls/Presenters/ContentPresenter.cs
index b2b2e6ff9f..223dca8088 100644
--- a/src/Perspex.Controls/Presenters/ContentPresenter.cs
+++ b/src/Perspex.Controls/Presenters/ContentPresenter.cs
@@ -34,6 +34,7 @@ namespace Perspex.Controls.Presenters
static ContentPresenter()
{
ContentProperty.Changed.AddClassHandler(x => x.ContentChanged);
+ TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged);
}
///
@@ -57,12 +58,66 @@ namespace Perspex.Controls.Presenters
///
public override sealed void ApplyTemplate()
{
- if (!_createdChild)
+ if (!_createdChild && ((ILogical)this).IsAttachedToLogicalTree)
{
- CreateChild();
+ UpdateChild();
}
}
+ ///
+ /// Updates the control based on the control's .
+ ///
+ ///
+ /// Usually the control is created automatically when
+ /// is called; however for this to happen, the control needs to
+ /// be attached to a logical tree (if the control is not attached to the logical tree, it
+ /// is reasonable to expect that the DataTemplates needed for the child are not yet
+ /// available). This method forces the control's creation at any point,
+ /// and is particularly useful in unit tests.
+ ///
+ public void UpdateChild()
+ {
+ var old = Child;
+ var content = Content;
+ var result = this.MaterializeDataTemplate(content);
+
+ if (old != null)
+ {
+ VisualChildren.Remove(old);
+ }
+
+ if (result != null)
+ {
+ if (!(content is IControl))
+ {
+ result.DataContext = content;
+ }
+
+ Child = result;
+
+ if (result.Parent == null)
+ {
+ ((ISetLogicalParent)result).SetParent((ILogical)this.TemplatedParent ?? this);
+ }
+
+ VisualChildren.Add(result);
+ }
+ else
+ {
+ Child = null;
+ }
+
+ _createdChild = true;
+ }
+
+ ///
+ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToLogicalTree(e);
+ _createdChild = false;
+ InvalidateMeasure();
+ }
+
///
protected override Size MeasureCore(Size availableSize)
{
@@ -93,45 +148,9 @@ namespace Perspex.Controls.Presenters
InvalidateMeasure();
}
- ///
- /// Creates the control from the .
- ///
- private void CreateChild()
+ private void TemplatedParentChanged(PerspexPropertyChangedEventArgs e)
{
- var old = Child;
- var content = Content;
- var result = this.MaterializeDataTemplate(content);
- var logicalHost = this.FindReparentingHost();
- var logicalChildren = logicalHost?.LogicalChildren ?? LogicalChildren;
-
- if (old != null)
- {
- logicalChildren.Remove(old);
- ((ISetLogicalParent)old).SetParent(null);
- ClearVisualChildren();
- }
-
- Child = result;
-
- if (result != null)
- {
- if (!(content is IControl))
- {
- result.DataContext = content;
- }
-
- AddVisualChild(result);
-
- if (result.Parent == null)
- {
- ((ISetLogicalParent)result).SetParent((ILogical)logicalHost ?? this);
- }
-
- logicalChildren.Remove(old);
- logicalChildren.Add(result);
- }
-
- _createdChild = true;
+ (e.NewValue as IContentPresenterHost)?.RegisterContentPresenter(this);
}
}
}
diff --git a/src/Perspex.Controls/Presenters/IContentPresenterHost.cs b/src/Perspex.Controls/Presenters/IContentPresenterHost.cs
new file mode 100644
index 0000000000..137742a080
--- /dev/null
+++ b/src/Perspex.Controls/Presenters/IContentPresenterHost.cs
@@ -0,0 +1,27 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Perspex.Styling;
+
+namespace Perspex.Controls.Presenters
+{
+ ///
+ /// Represents a control which hosts a content presenter.
+ ///
+ ///
+ /// This interface is implemented by which usually contains a
+ /// and exposes it through its
+ /// property. ContentPresenters can be within
+ /// nested templates or in popups and so are not necessarily created immediately when the
+ /// parent control's template is instantiated so they register themselves using this
+ /// interface.
+ ///
+ public interface IContentPresenterHost : ITemplatedControl
+ {
+ ///
+ /// Registers an with a host control.
+ ///
+ /// The content presenter.
+ void RegisterContentPresenter(IContentPresenter presenter);
+ }
+}
diff --git a/src/Perspex.Controls/Presenters/IItemsPresenterHost.cs b/src/Perspex.Controls/Presenters/IItemsPresenterHost.cs
new file mode 100644
index 0000000000..47f50eb862
--- /dev/null
+++ b/src/Perspex.Controls/Presenters/IItemsPresenterHost.cs
@@ -0,0 +1,27 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Perspex.Styling;
+
+namespace Perspex.Controls.Presenters
+{
+ ///
+ /// Represents a control which hosts an items presenter.
+ ///
+ ///
+ /// This interface is implemented by which usually contains an
+ /// and exposes it through its
+ /// property. ItemsPresenters can be within
+ /// nested templates or in popups and so are not necessarily created immediately when the
+ /// parent control's template is instantiated so they register themselves using this
+ /// interface.
+ ///
+ public interface IItemsPresenterHost : ITemplatedControl
+ {
+ ///
+ /// Registers an with a host control.
+ ///
+ /// The items presenter.
+ void RegisterItemsPresenter(IItemsPresenter presenter);
+ }
+}
diff --git a/src/Perspex.Controls/Presenters/ItemsPresenter.cs b/src/Perspex.Controls/Presenters/ItemsPresenter.cs
index ecd74e7bdb..c2eb692b7a 100644
--- a/src/Perspex.Controls/Presenters/ItemsPresenter.cs
+++ b/src/Perspex.Controls/Presenters/ItemsPresenter.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.Linq;
using Perspex.Controls.Generators;
using Perspex.Controls.Templates;
using Perspex.Input;
@@ -48,6 +49,7 @@ namespace Perspex.Controls.Presenters
typeof(ItemsPresenter),
KeyboardNavigationMode.Once);
ItemsProperty.Changed.AddClassHandler(x => x.ItemsChanged);
+ TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged);
}
///
@@ -150,7 +152,6 @@ namespace Perspex.Controls.Presenters
///
private void CreatePanel()
{
- ClearVisualChildren();
Panel = ItemsPanel.Build();
Panel.SetValue(TemplatedParentProperty, TemplatedParent);
@@ -161,16 +162,10 @@ namespace Perspex.Controls.Presenters
KeyboardNavigationMode.Contained);
}
- AddVisualChild(Panel);
-
- var logicalHost = this.FindReparentingHost();
-
- if (logicalHost != null)
- {
- ((IReparentingControl)Panel).ReparentLogicalChildren(
- logicalHost,
- logicalHost.LogicalChildren);
- }
+ LogicalChildren.Clear();
+ VisualChildren.Clear();
+ LogicalChildren.Add(Panel);
+ VisualChildren.Add(Panel);
KeyboardNavigation.SetTabNavigation(
(InputElement)Panel,
@@ -187,7 +182,7 @@ namespace Perspex.Controls.Presenters
{
if (items != null)
{
- Panel.Children.AddRange(ItemContainerGenerator.Materialize(0, Items, MemberSelector));
+ AddContainers(ItemContainerGenerator.Materialize(0, Items, MemberSelector));
INotifyCollectionChanged incc = items as INotifyCollectionChanged;
@@ -238,30 +233,28 @@ namespace Perspex.Controls.Presenters
if (_createdPanel)
{
var generator = ItemContainerGenerator;
- IEnumerable containers;
// TODO: Handle Move and Replace etc.
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
- containers = generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector);
- Panel.Children.AddRange(containers);
+ AddContainers(generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector));
break;
case NotifyCollectionChangedAction.Remove:
- containers = generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
- Panel.Children.RemoveAll(containers);
+ RemoveContainers(generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count));
break;
case NotifyCollectionChangedAction.Replace:
- generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count);
- containers = generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector);
+ RemoveContainers(generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count));
+ var containers = generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector);
+ AddContainers(containers);
var i = e.NewStartingIndex;
foreach (var container in containers)
{
- Panel.Children[i++] = container;
+ Panel.Children[i++] = container.ContainerControl;
}
break;
@@ -269,13 +262,40 @@ namespace Perspex.Controls.Presenters
case NotifyCollectionChangedAction.Move:
// TODO: Implement Move in a more efficient manner.
case NotifyCollectionChangedAction.Reset:
- Panel.Children.RemoveAll(generator.Clear());
- Panel.Children.AddRange(generator.Materialize(0, Items, MemberSelector));
+ RemoveContainers(generator.Clear());
+ AddContainers(generator.Materialize(0, Items, MemberSelector));
break;
}
InvalidateMeasure();
}
}
+
+ private void TemplatedParentChanged(PerspexPropertyChangedEventArgs e)
+ {
+ (e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this);
+ }
+
+ private void AddContainers(IEnumerable items)
+ {
+ foreach (var i in items)
+ {
+ if (i.ContainerControl != null)
+ {
+ this.Panel.Children.Add(i.ContainerControl);
+ }
+ }
+ }
+
+ private void RemoveContainers(IEnumerable items)
+ {
+ foreach (var i in items)
+ {
+ if (i.ContainerControl != null)
+ {
+ this.Panel.Children.Remove(i.ContainerControl);
+ }
+ }
+ }
}
}
diff --git a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs
index f9e870ea1d..b7b225ae2b 100644
--- a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs
+++ b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs
@@ -3,74 +3,185 @@
using System;
using System.Linq;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using Perspex.Controls.Primitives;
using Perspex.Input;
using Perspex.Layout;
using Perspex.VisualTree;
namespace Perspex.Controls.Presenters
{
+ ///
+ /// Presents a scrolling view of content inside a .
+ ///
public class ScrollContentPresenter : ContentPresenter, IPresenter
{
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty ExtentProperty =
- ScrollViewer.ExtentProperty.AddOwner();
+ ScrollViewer.ExtentProperty.AddOwner(
+ o => o.Extent,
+ (o, v) => o.Extent = v);
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty OffsetProperty =
- ScrollViewer.OffsetProperty.AddOwner();
+ ScrollViewer.OffsetProperty.AddOwner(
+ o => o.Offset,
+ (o, v) => o.Offset = v);
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty ViewportProperty =
- ScrollViewer.ViewportProperty.AddOwner();
+ ScrollViewer.ViewportProperty.AddOwner(
+ o => o.Viewport,
+ (o, v) => o.Viewport = v);
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty CanScrollHorizontallyProperty =
PerspexProperty.Register("CanScrollHorizontally", true);
+ private Size _extent;
private Size _measuredExtent;
+ private Vector _offset;
+ private IDisposable _scrollableSubscription;
+ private Size _viewport;
+ ///
+ /// Initializes static members of the class.
+ ///
static ScrollContentPresenter()
{
ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true);
+ OffsetProperty.OverrideValidation(ValidateOffset);
AffectsArrange(OffsetProperty);
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
public ScrollContentPresenter()
{
AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested);
+
+ this.GetObservable(ChildProperty).Subscribe(ChildChanged);
}
+ ///
+ /// Gets the extent of the scrollable content.
+ ///
public Size Extent
{
- get { return GetValue(ExtentProperty); }
- private set { SetValue(ExtentProperty, value); }
+ get { return _extent; }
+ private set { SetAndRaise(ExtentProperty, ref _extent, value); }
}
+ ///
+ /// Gets or sets the current scroll offset.
+ ///
public Vector Offset
{
- get { return GetValue(OffsetProperty); }
- set { SetValue(OffsetProperty, value); }
+ get { return _offset; }
+ set { SetAndRaise(OffsetProperty, ref _offset, value); }
}
+ ///
+ /// Gets the size of the viewport on the scrollable content.
+ ///
public Size Viewport
{
- get { return GetValue(ViewportProperty); }
- private set { SetValue(ViewportProperty, value); }
+ get { return _viewport; }
+ private set { SetAndRaise(ViewportProperty, ref _viewport, value); }
}
+ ///
+ /// Gets a value indicating whether the content can be scrolled horizontally.
+ ///
public bool CanScrollHorizontally => GetValue(CanScrollHorizontallyProperty);
+ ///
+ /// Attempts to bring a portion of the target visual into view by scrolling the content.
+ ///
+ /// The target visual.
+ /// The portion of the target visual to bring into view.
+ /// True if the scroll offset was changed; otherwise false.
+ public bool BringDescendentIntoView(IVisual target, Rect targetRect)
+ {
+ if (Child == null)
+ {
+ return false;
+ }
+
+ var transform = target.TransformToVisual(Child);
+
+ if (transform == null)
+ {
+ return false;
+ }
+
+ var rect = targetRect * transform.Value;
+ var offset = Offset;
+ var result = false;
+
+ if (rect.Bottom > offset.Y + Viewport.Height)
+ {
+ offset = offset.WithY((rect.Bottom - Viewport.Height) + Child.Margin.Top);
+ result = true;
+ }
+
+ if (rect.Y < offset.Y)
+ {
+ offset = offset.WithY(rect.Y);
+ result = true;
+ }
+
+ if (rect.Right > offset.X + Viewport.Width)
+ {
+ offset = offset.WithX((rect.Right - Viewport.Width) + Child.Margin.Left);
+ result = true;
+ }
+
+ if (rect.X < offset.X)
+ {
+ offset = offset.WithX(rect.X);
+ result = true;
+ }
+
+ if (result)
+ {
+ Offset = offset;
+ }
+
+ return result;
+ }
+
+ ///
protected override Size MeasureOverride(Size availableSize)
{
- var content = Content as ILayoutable;
+ var child = Child;
- if (content != null)
+ if (child != null)
{
- var measureSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
+ var measureSize = availableSize;
- if (!CanScrollHorizontally)
+ if (_scrollableSubscription == null)
{
- measureSize = measureSize.WithWidth(availableSize.Width);
+ measureSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
+
+ if (!CanScrollHorizontally)
+ {
+ measureSize = measureSize.WithWidth(availableSize.Width);
+ }
}
- content.Measure(measureSize);
- var size = content.DesiredSize;
+ child.Measure(measureSize);
+ var size = child.DesiredSize;
_measuredExtent = size;
return size.Constrain(availableSize);
}
@@ -80,25 +191,32 @@ namespace Perspex.Controls.Presenters
}
}
+ ///
protected override Size ArrangeOverride(Size finalSize)
{
var child = this.GetVisualChildren().SingleOrDefault() as ILayoutable;
+ var offset = default(Vector);
- Viewport = finalSize;
- Extent = _measuredExtent;
+ if (_scrollableSubscription == null)
+ {
+ Viewport = finalSize;
+ Extent = _measuredExtent;
+ offset = Offset;
+ }
if (child != null)
{
var size = new Size(
Math.Max(finalSize.Width, child.DesiredSize.Width),
Math.Max(finalSize.Height, child.DesiredSize.Height));
- child.Arrange(new Rect((Point)(-Offset), size));
+ child.Arrange(new Rect((Point)(-offset), size));
return finalSize;
}
return new Size();
}
+ ///
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
if (Extent.Height > Viewport.Height)
@@ -113,35 +231,39 @@ namespace Perspex.Controls.Presenters
private void BringIntoViewRequested(object sender, RequestBringIntoViewEventArgs e)
{
- var transform = e.TargetObject.TransformToVisual(this.GetVisualChildren().Single());
- var rect = e.TargetRect * transform;
- var offset = Offset;
+ e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect);
+ }
- if (rect.Bottom > offset.Y + Viewport.Height)
- {
- offset = offset.WithY(rect.Bottom - Viewport.Height);
- e.Handled = true;
- }
+ private void ChildChanged(IControl child)
+ {
+ var scrollable = child as IScrollable;
- if (rect.Y < offset.Y)
- {
- offset = offset.WithY(rect.Y);
- e.Handled = true;
- }
+ _scrollableSubscription?.Dispose();
+ _scrollableSubscription = null;
- if (rect.Right > offset.X + Viewport.Width)
+ if (scrollable != null)
{
- offset = offset.WithX(rect.Right - Viewport.Width);
- e.Handled = true;
+ scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
+ _scrollableSubscription = new CompositeDisposable(
+ this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
+ Disposable.Create(() => scrollable.InvalidateScroll = null));
+ UpdateFromScrollable(scrollable);
}
+ }
- if (rect.X < offset.X)
- {
- offset = offset.WithX(rect.X);
- e.Handled = true;
- }
+ private void UpdateFromScrollable(IScrollable scrollable)
+ {
+ Viewport = scrollable.Viewport;
+ Extent = scrollable.Extent;
+ Offset = scrollable.Offset;
+ }
- Offset = offset;
+ private static Vector ValidateOffset(ScrollContentPresenter o, Vector value)
+ {
+ return ScrollViewer.CoerceOffset(
+ o.GetValue(ExtentProperty),
+ o.GetValue(ViewportProperty),
+ value);
}
}
}
diff --git a/src/Perspex.Controls/Presenters/TextPresenter.cs b/src/Perspex.Controls/Presenters/TextPresenter.cs
index 4b68d160e5..c923340bd3 100644
--- a/src/Perspex.Controls/Presenters/TextPresenter.cs
+++ b/src/Perspex.Controls/Presenters/TextPresenter.cs
@@ -33,15 +33,15 @@ namespace Perspex.Controls.Presenters
_caretTimer.Interval = TimeSpan.FromMilliseconds(500);
_caretTimer.Tick += CaretTimerTick;
- _canScrollHorizontally = GetObservable(TextWrappingProperty)
+ _canScrollHorizontally = this.GetObservable(TextWrappingProperty)
.Select(x => x == TextWrapping.NoWrap);
Observable.Merge(
- GetObservable(SelectionStartProperty),
- GetObservable(SelectionEndProperty))
+ this.GetObservable(SelectionStartProperty),
+ this.GetObservable(SelectionEndProperty))
.Subscribe(_ => InvalidateFormattedText());
- GetObservable(CaretIndexProperty)
+ this.GetObservable(CaretIndexProperty)
.Subscribe(CaretIndexChanged);
}
@@ -91,9 +91,7 @@ namespace Perspex.Controls.Presenters
base.Render(context);
if (selectionStart == selectionEnd)
- {
- var charPos = FormattedText.HitTestTextPosition(CaretIndex);
-
+ {
var backgroundColor = (((Control)TemplatedParent).GetValue(BackgroundProperty) as SolidColorBrush)?.Color;
var caretBrush = Brushes.Black;
@@ -108,6 +106,7 @@ namespace Perspex.Controls.Presenters
if (_caretBlink)
{
+ var charPos = FormattedText.HitTestTextPosition(CaretIndex);
var x = Math.Floor(charPos.X) + 0.5;
var y = Math.Floor(charPos.Y) + 0.5;
var b = Math.Ceiling(charPos.Bottom) - 0.5;
@@ -143,8 +142,24 @@ namespace Perspex.Controls.Presenters
_caretTimer.Start();
InvalidateVisual();
- var rect = FormattedText.HitTestTextPosition(caretIndex);
- this.BringIntoView(rect);
+ if (IsMeasureValid)
+ {
+ var rect = FormattedText.HitTestTextPosition(caretIndex);
+ this.BringIntoView(rect);
+ }
+ else
+ {
+ // The measure is currently invalid so there's no point trying to bring the
+ // current char into view until a measure has been carried out as the scroll
+ // viewer extents may not be up-to-date.
+ Dispatcher.UIThread.InvokeAsync(
+ () =>
+ {
+ var rect = FormattedText.HitTestTextPosition(caretIndex);
+ this.BringIntoView(rect);
+ },
+ DispatcherPriority.Normal);
+ }
}
}
diff --git a/src/Perspex.Controls/Primitives/AccessText.cs b/src/Perspex.Controls/Primitives/AccessText.cs
index 9ad2640534..38dab95803 100644
--- a/src/Perspex.Controls/Primitives/AccessText.cs
+++ b/src/Perspex.Controls/Primitives/AccessText.cs
@@ -37,7 +37,7 @@ namespace Perspex.Controls.Primitives
///
public AccessText()
{
- GetObservable(TextProperty).Subscribe(TextChanged);
+ this.GetObservable(TextProperty).Subscribe(TextChanged);
}
///
diff --git a/src/Perspex.Controls/Primitives/AdornerDecorator.cs b/src/Perspex.Controls/Primitives/AdornerDecorator.cs
index 6fa8ec81b3..218e7b8360 100644
--- a/src/Perspex.Controls/Primitives/AdornerDecorator.cs
+++ b/src/Perspex.Controls/Primitives/AdornerDecorator.cs
@@ -10,7 +10,7 @@ namespace Perspex.Controls.Primitives
AdornerLayer = new AdornerLayer();
((ISetLogicalParent)AdornerLayer).SetParent(this);
AdornerLayer.ZIndex = int.MaxValue;
- AddVisualChild(AdornerLayer);
+ VisualChildren.Add(AdornerLayer);
}
public AdornerLayer AdornerLayer
diff --git a/src/Perspex.Controls/Primitives/AdornerLayer.cs b/src/Perspex.Controls/Primitives/AdornerLayer.cs
index 67ef2e71da..138df92f4b 100644
--- a/src/Perspex.Controls/Primitives/AdornerLayer.cs
+++ b/src/Perspex.Controls/Primitives/AdornerLayer.cs
@@ -74,11 +74,7 @@ namespace Perspex.Controls.Primitives
var adorner = (Visual)e.Sender;
var adorned = (Visual)e.NewValue;
var layer = adorner.GetVisualParent();
-
- if (layer != null)
- {
- layer.UpdateAdornedElement(adorner, adorned);
- }
+ layer?.UpdateAdornedElement(adorner, adorned);
}
private void ChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
diff --git a/src/Perspex.Controls/Primitives/HeaderedItemsControl.cs b/src/Perspex.Controls/Primitives/HeaderedItemsControl.cs
index cf58646d4b..5f543cae17 100644
--- a/src/Perspex.Controls/Primitives/HeaderedItemsControl.cs
+++ b/src/Perspex.Controls/Primitives/HeaderedItemsControl.cs
@@ -1,17 +1,58 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
+using System.Linq;
+using Perspex.Controls.Mixins;
+using Perspex.Controls.Presenters;
+
namespace Perspex.Controls.Primitives
{
+ ///
+ /// Represents an with a related header.
+ ///
public class HeaderedItemsControl : ItemsControl
{
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty HeaderProperty =
HeaderedContentControl.HeaderProperty.AddOwner();
+ ///
+ /// Initializes static members of the class.
+ ///
+ static HeaderedItemsControl()
+ {
+ ContentControlMixin.Attach(
+ HeaderProperty,
+ x => x.LogicalChildren,
+ "PART_HeaderPresenter");
+ }
+
+ ///
+ /// Gets or sets the content of the control's header.
+ ///
public object Header
{
get { return GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
+
+ ///
+ /// Gets the header presenter from the control's template.
+ ///
+ public ContentPresenter HeaderPresenter
+ {
+ get;
+ private set;
+ }
+
+ ///
+ protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+ {
+ base.OnTemplateApplied(e);
+ HeaderPresenter = e.NameScope.Find("PART_HeaderPresenter");
+ }
}
}
diff --git a/src/Perspex.Controls/Primitives/HeaderedSelectingControl.cs b/src/Perspex.Controls/Primitives/HeaderedSelectingControl.cs
new file mode 100644
index 0000000000..9a6431c3e5
--- /dev/null
+++ b/src/Perspex.Controls/Primitives/HeaderedSelectingControl.cs
@@ -0,0 +1,58 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Linq;
+using Perspex.Controls.Mixins;
+using Perspex.Controls.Presenters;
+
+namespace Perspex.Controls.Primitives
+{
+ ///
+ /// Represents a with a related header.
+ ///
+ public class HeaderedSelectingItemsControl : SelectingItemsControl
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly PerspexProperty HeaderProperty =
+ HeaderedContentControl.HeaderProperty.AddOwner();
+
+ ///
+ /// Initializes static members of the class.
+ ///
+ static HeaderedSelectingItemsControl()
+ {
+ ContentControlMixin.Attach(
+ HeaderProperty,
+ x => x.LogicalChildren,
+ "PART_HeaderPresenter");
+ }
+
+ ///
+ /// Gets or sets the content of the control's header.
+ ///
+ public object Header
+ {
+ get { return GetValue(HeaderProperty); }
+ set { SetValue(HeaderProperty, value); }
+ }
+
+ ///
+ /// Gets the header presenter from the control's template.
+ ///
+ public ContentPresenter HeaderPresenter
+ {
+ get;
+ private set;
+ }
+
+ ///
+ protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+ {
+ base.OnTemplateApplied(e);
+ HeaderPresenter = e.NameScope.Find("PART_HeaderPresenter");
+ }
+ }
+}
diff --git a/src/Perspex.Controls/Primitives/IScrollInfo.cs b/src/Perspex.Controls/Primitives/IScrollInfo.cs
deleted file mode 100644
index 59dbc085e6..0000000000
--- a/src/Perspex.Controls/Primitives/IScrollInfo.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) The Perspex Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Perspex.Controls.Primitives
-{
- public interface IScrollInfoBase
- {
- ///
- /// ScrollOwner is the container that controls any scrollbars, headers, etc... that are dependant
- /// on this IScrollInfo's properties. Implementers of IScrollInfo should call InvalidateScrollInfo()
- /// on this object when properties change.
- ///
- ScrollViewer ScrollOwner { get; set; }
-
- Rect MakeVisible(Visual visual, Rect rectangle);
- }
-
- public interface IVerticalScrollInfo : IScrollInfoBase
- {
- ///
- /// VerticalOffset is the vertical offset into the scrolled content that represents the first unit visible.
- ///
- double VerticalOffset { get; set; }
-
- ///
- /// ExtentHeight contains the full vertical range of the scrolled content.
- ///
- double ExtentHeight { get; }
-
- ///
- /// ViewportHeight contains the currently visible vertical range of the scrolled content.
- ///
- double ViewportHeight { get; }
-
- ///
- /// This property indicates to the IScrollInfo whether or not it can scroll in the vertical given dimension.
- ///
- bool CanVerticallyScroll { get; set; }
-
- void LineDown();
-
- void LineUp();
-
- void MouseWheelDown();
-
- void MouseWheelUp();
-
- void PageDown();
-
- void PageUp();
- }
-
- public interface IHorizontalScrollInfo : IScrollInfoBase
- {
- ///
- /// ExtentWidth contains the full horizontal range of the scrolled content.
- ///
- double ExtentWidth { get; }
-
- ///
- /// ViewportWidth contains the currently visible horizontal range of the scrolled content.
- ///
- double ViewportWidth { get; }
-
- ///
- /// HorizontalOffset is the horizontal offset into the scrolled content that represents the first unit visible.
- ///
- double HorizontalOffset { get; set; }
-
- ///
- /// This property indicates to the IScrollInfo whether or not it can scroll in the horizontal given dimension.
- ///
- bool CanHorizontallyScroll { get; set; }
-
- void LineLeft();
-
- void LineRight();
-
- void MouseWheelLeft();
-
- void MouseWheelRight();
-
- void PageLeft();
-
- void PageRight();
- }
-
- public interface IScrollInfo : IHorizontalScrollInfo, IVerticalScrollInfo
- {
- }
-}
diff --git a/src/Perspex.Controls/Primitives/IScrollable.cs b/src/Perspex.Controls/Primitives/IScrollable.cs
new file mode 100644
index 0000000000..752c02685d
--- /dev/null
+++ b/src/Perspex.Controls/Primitives/IScrollable.cs
@@ -0,0 +1,44 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Perspex.Controls.Primitives
+{
+ ///
+ /// Interface implemented by controls that handle their own scrolling when placed inside a
+ /// .
+ ///
+ public interface IScrollable
+ {
+ ///
+ /// Gets or sets the scroll invalidation method.
+ ///
+ ///
+ ///
+ /// This method notifies the attached of a change in
+ /// the , or properties.
+ ///
+ ///
+ /// This property is set by the parent when the
+ /// is placed inside it.
+ ///
+ ///
+ Action InvalidateScroll { get; set; }
+
+ ///
+ /// Gets the extent of the scrollable content, in logical units
+ ///
+ Size Extent { get; }
+
+ ///
+ /// Gets or sets the current scroll offset, in logical units.
+ ///
+ Vector Offset { get; set; }
+
+ ///
+ /// Gets the size of the viewport, in logical units.
+ ///
+ Size Viewport { get; }
+ }
+}
diff --git a/src/Perspex.Controls/Primitives/Popup.cs b/src/Perspex.Controls/Primitives/Popup.cs
index 138c5b586f..ce409b4256 100644
--- a/src/Perspex.Controls/Primitives/Popup.cs
+++ b/src/Perspex.Controls/Primitives/Popup.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using Perspex.Input;
using Perspex.Interactivity;
using Perspex.Metadata;
using Perspex.Rendering;
@@ -24,7 +25,10 @@ namespace Perspex.Controls.Primitives
/// Defines the property.
///
public static readonly PerspexProperty IsOpenProperty =
- PerspexProperty.Register(nameof(IsOpen));
+ PerspexProperty.RegisterDirect(
+ nameof(IsOpen),
+ o => o.IsOpen,
+ (o, v) => o.IsOpen = v);
///
/// Defines the property.
@@ -44,14 +48,8 @@ namespace Perspex.Controls.Primitives
public static readonly PerspexProperty StaysOpenProperty =
PerspexProperty.Register(nameof(StaysOpen), true);
- ///
- /// The root of the popup.
- ///
+ private bool _isOpen;
private PopupRoot _popupRoot;
-
- ///
- /// The top level control of the Popup's visual tree.
- ///
private TopLevel _topLevel;
///
@@ -59,6 +57,7 @@ namespace Perspex.Controls.Primitives
///
static Popup()
{
+ IsHitTestVisibleProperty.OverrideDefaultValue(false);
ChildProperty.Changed.AddClassHandler(x => x.ChildChanged);
IsOpenProperty.Changed.AddClassHandler(x => x.IsOpenChanged);
}
@@ -106,8 +105,8 @@ namespace Perspex.Controls.Primitives
///
public bool IsOpen
{
- get { return GetValue(IsOpenProperty); }
- set { SetValue(IsOpenProperty, value); }
+ get { return _isOpen; }
+ set { SetAndRaise(IsOpenProperty, ref _isOpen, value); }
}
///
@@ -169,13 +168,12 @@ namespace Perspex.Controls.Primitives
((ISetLogicalParent)_popupRoot).SetParent(this);
}
- _popupRoot.SetPosition(GetPosition());
- _popupRoot.AddHandler(PointerPressedEvent, MaybeClose, RoutingStrategies.Bubble, true);
+ _popupRoot.Position = GetPosition();
if (_topLevel != null)
{
- _topLevel.Deactivated += MaybeClose;
- _topLevel.AddHandler(PointerPressedEvent, MaybeClose, RoutingStrategies.Tunnel);
+ _topLevel.Deactivated += TopLevelDeactivated;
+ _topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
}
PopupRootCreated?.Invoke(this, EventArgs.Empty);
@@ -192,9 +190,8 @@ namespace Perspex.Controls.Primitives
{
if (_popupRoot != null)
{
- _popupRoot.PointerPressed -= MaybeClose;
- _topLevel.RemoveHandler(PointerPressedEvent, MaybeClose);
- _topLevel.Deactivated -= MaybeClose;
+ _topLevel.RemoveHandler(PointerPressedEvent, PointerPressedOutside);
+ _topLevel.Deactivated -= TopLevelDeactivated;
_popupRoot.Hide();
}
@@ -250,10 +247,7 @@ namespace Perspex.Controls.Primitives
{
LogicalChildren.Clear();
- if (e.OldValue != null)
- {
- ((ISetLogicalParent)e.OldValue).SetParent(null);
- }
+ ((ISetLogicalParent)e.OldValue)?.SetParent(null);
if (e.NewValue != null)
{
@@ -269,10 +263,11 @@ namespace Perspex.Controls.Primitives
private Point GetPosition()
{
var target = PlacementTarget ?? this.GetVisualParent();
- Point point;
if (target != null)
{
+ Point point;
+
switch (PlacementMode)
{
case PlacementMode.Bottom:
@@ -293,13 +288,21 @@ namespace Perspex.Controls.Primitives
}
}
- ///
- /// Conditionally closes the popup in response to an event, based on the value of the
- /// property.
- ///
- /// The event sender.
- /// The event args.
- private void MaybeClose(object sender, EventArgs e)
+ private void PointerPressedOutside(object sender, PointerPressEventArgs e)
+ {
+ if (!StaysOpen)
+ {
+ var root = ((IVisual)e.Source).GetVisualRoot();
+
+ if (root != this.PopupRoot)
+ {
+ Close();
+ e.Handled = true;
+ }
+ }
+ }
+
+ private void TopLevelDeactivated(object sender, EventArgs e)
{
if (!StaysOpen)
{
diff --git a/src/Perspex.Controls/Primitives/PopupRoot.cs b/src/Perspex.Controls/Primitives/PopupRoot.cs
index 3b52f27cbd..5347ef9180 100644
--- a/src/Perspex.Controls/Primitives/PopupRoot.cs
+++ b/src/Perspex.Controls/Primitives/PopupRoot.cs
@@ -44,7 +44,6 @@ namespace Perspex.Controls.Primitives
public PopupRoot(IPerspexDependencyResolver dependencyResolver)
: base(PlatformManager.CreatePopup(), dependencyResolver)
{
- GetObservable(ParentProperty).Subscribe(x => InheritanceParent = (PerspexObject)x);
}
///
@@ -65,15 +64,6 @@ namespace Perspex.Controls.Primitives
///
IVisual IHostedVisualTreeRoot.Host => Parent;
- ///
- /// Sets the position of the popup in screen coordinates.
- ///
- /// The position.
- public void SetPosition(Point p)
- {
- PlatformImpl.SetPosition(p);
- }
-
///
/// Hides the popup.
///
@@ -94,9 +84,9 @@ namespace Perspex.Controls.Primitives
}
///
- protected override void OnTemplateApplied(INameScope nameScope)
+ protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
- base.OnTemplateApplied(nameScope);
+ base.OnTemplateApplied(e);
if (Parent.TemplatedParent != null)
{
@@ -106,13 +96,9 @@ namespace Perspex.Controls.Primitives
_presenterSubscription = null;
}
- var presenter = Presenter;
-
- if (presenter != null)
- {
- presenter.GetObservable(ContentPresenter.ChildProperty)
- .Subscribe(SetTemplatedParentAndApplyChildTemplates);
- }
+ Presenter?.ApplyTemplate();
+ Presenter?.GetObservable(ContentPresenter.ChildProperty)
+ .Subscribe(SetTemplatedParentAndApplyChildTemplates);
}
}
diff --git a/src/Perspex.Controls/Primitives/RangeBase.cs b/src/Perspex.Controls/Primitives/RangeBase.cs
index 430ffbd766..69a6db645c 100644
--- a/src/Perspex.Controls/Primitives/RangeBase.cs
+++ b/src/Perspex.Controls/Primitives/RangeBase.cs
@@ -15,34 +15,38 @@ namespace Perspex.Controls.Primitives
/// Defines the property.
///
public static readonly PerspexProperty MinimumProperty =
- PerspexProperty.Register(
+ PerspexProperty.RegisterDirect(
nameof(Minimum),
- validate: ValidateMinimum);
+ o => o.Minimum,
+ (o, v) => o.Minimum = v);
///
/// Defines the property.
///
public static readonly PerspexProperty MaximumProperty =
- PerspexProperty.Register(
+ PerspexProperty.RegisterDirect(
nameof(Maximum),
- defaultValue: 100.0,
- validate: ValidateMaximum);
+ o => o.Maximum,
+ (o, v) => o.Maximum = v);
///
/// Defines the property.
///
public static readonly PerspexProperty ValueProperty =
- PerspexProperty.Register(
+ PerspexProperty.RegisterDirect(
nameof(Value),
- validate: ValidateValue);
+ o => o.Value,
+ (o, v) => o.Value = v);
+
+ private double _minimum;
+ private double _maximum = 100.0;
+ private double _value;
///
/// Initializes a new instance of the class.
///
public RangeBase()
{
- AffectsValidation(MinimumProperty, MaximumProperty, ValueProperty);
- AffectsValidation(MaximumProperty, ValueProperty);
}
///
@@ -50,8 +54,18 @@ namespace Perspex.Controls.Primitives
///
public double Minimum
{
- get { return GetValue(MinimumProperty); }
- set { SetValue(MinimumProperty, value); }
+ get
+ {
+ return _minimum;
+ }
+
+ set
+ {
+ value = ValidateMinimum(value);
+ SetAndRaise(MinimumProperty, ref _minimum, value);
+ Maximum = ValidateMaximum(Maximum);
+ Value = ValidateValue(Value);
+ }
}
///
@@ -59,8 +73,17 @@ namespace Perspex.Controls.Primitives
///
public double Maximum
{
- get { return GetValue(MaximumProperty); }
- set { SetValue(MaximumProperty, value); }
+ get
+ {
+ return _maximum;
+ }
+
+ set
+ {
+ value = ValidateMaximum(value);
+ SetAndRaise(MaximumProperty, ref _maximum, value);
+ Value = ValidateValue(Value);
+ }
}
///
@@ -68,8 +91,16 @@ namespace Perspex.Controls.Primitives
///
public double Value
{
- get { return GetValue(ValueProperty); }
- set { SetValue(ValueProperty, value); }
+ get
+ {
+ return _value;
+ }
+
+ set
+ {
+ value = ValidateValue(value);
+ SetAndRaise(ValueProperty, ref _value, value);
+ }
}
///
@@ -88,10 +119,9 @@ namespace Perspex.Controls.Primitives
///
/// Validates the property.
///
- /// The RangeBase control.
/// The value.
/// The coerced value.
- private static double ValidateMinimum(RangeBase sender, double value)
+ private double ValidateMinimum(double value)
{
ValidateDouble(value, "Minimum");
return value;
@@ -100,25 +130,23 @@ namespace Perspex.Controls.Primitives
///
/// Validates/coerces the property.
///
- /// The RangeBase control.
/// The value.
/// The coerced value.
- private static double ValidateMaximum(RangeBase sender, double value)
+ private double ValidateMaximum(double value)
{
ValidateDouble(value, "Maximum");
- return Math.Max(value, sender.Minimum);
+ return Math.Max(value, Minimum);
}
///
/// Validates/coerces the property.
///
- /// The RangeBase control.
/// The value.
/// The coerced value.
- private static double ValidateValue(RangeBase sender, double value)
+ private double ValidateValue(double value)
{
ValidateDouble(value, "Value");
- return MathUtilities.Clamp(value, sender.Minimum, sender.Maximum);
+ return MathUtilities.Clamp(value, Minimum, Maximum);
}
}
}
diff --git a/src/Perspex.Controls/Primitives/ScrollBar.cs b/src/Perspex.Controls/Primitives/ScrollBar.cs
index adaad0c2d6..6cc9903531 100644
--- a/src/Perspex.Controls/Primitives/ScrollBar.cs
+++ b/src/Perspex.Controls/Primitives/ScrollBar.cs
@@ -4,6 +4,7 @@
using System;
using System.Reactive;
using System.Reactive.Linq;
+using Perspex.Data;
namespace Perspex.Controls.Primitives
{
@@ -36,10 +37,10 @@ namespace Perspex.Controls.Primitives
public ScrollBar()
{
var isVisible = Observable.Merge(
- GetObservable(MinimumProperty).Select(_ => Unit.Default),
- GetObservable(MaximumProperty).Select(_ => Unit.Default),
- GetObservable(ViewportSizeProperty).Select(_ => Unit.Default),
- GetObservable(VisibilityProperty).Select(_ => Unit.Default))
+ this.GetObservable(MinimumProperty).Select(_ => Unit.Default),
+ this.GetObservable(MaximumProperty).Select(_ => Unit.Default),
+ this.GetObservable(ViewportSizeProperty).Select(_ => Unit.Default),
+ this.GetObservable(VisibilityProperty).Select(_ => Unit.Default))
.Select(_ => CalculateIsVisible());
Bind(IsVisibleProperty, isVisible, BindingPriority.Style);
}
@@ -72,12 +73,6 @@ namespace Perspex.Controls.Primitives
set { SetValue(OrientationProperty, value); }
}
- ///
- protected override Size MeasureOverride(Size availableSize)
- {
- return base.MeasureOverride(availableSize);
- }
-
///
/// Calculates whether the scrollbar should be visible.
///
diff --git a/src/Perspex.Controls/Primitives/ScrollInfoAdapter.cs b/src/Perspex.Controls/Primitives/ScrollInfoAdapter.cs
deleted file mode 100644
index 8ea94f6fb6..0000000000
--- a/src/Perspex.Controls/Primitives/ScrollInfoAdapter.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright (c) The Perspex Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-namespace Perspex.Controls.Primitives
-{
- public class ScrollInfoAdapter : IScrollInfo
- {
- private readonly IScrollInfoBase _nfo;
- public ScrollInfoAdapter(IScrollInfoBase nfo)
- {
- _nfo = nfo;
- }
-
- public ScrollViewer ScrollOwner
- {
- get { return _nfo.ScrollOwner; }
- set { _nfo.ScrollOwner = value; }
- }
-
- public double ExtentWidth => (_nfo as IHorizontalScrollInfo)?.ExtentWidth ?? 0;
-
- public double ViewportWidth => (_nfo as IHorizontalScrollInfo)?.ViewportWidth ?? 0;
-
- public double ExtentHeight => (_nfo as IVerticalScrollInfo)?.ExtentHeight ?? 0;
-
- public double ViewportHeight => (_nfo as IVerticalScrollInfo)?.ViewportHeight ?? 0;
-
- private double _horizontalOffset;
- public double HorizontalOffset
- {
- get
- {
- return (_nfo as IHorizontalScrollInfo)?.HorizontalOffset ?? _horizontalOffset;
- }
-
- set
- {
- var info = (_nfo as IHorizontalScrollInfo);
- if (info == null)
- _horizontalOffset = value;
- else
- info.HorizontalOffset = value;
- }
- }
-
- private double _verticalOffset;
- public double VerticalOffset
- {
- get
- {
- return (_nfo as IVerticalScrollInfo)?.VerticalOffset ?? _verticalOffset;
- }
-
- set
- {
- var info = (_nfo as IVerticalScrollInfo);
- if (info == null)
- _verticalOffset = value;
- else
- info.VerticalOffset = value;
- }
- }
-
- public void LineLeft() => (_nfo as IHorizontalScrollInfo)?.LineLeft();
-
- public void LineRight() => (_nfo as IHorizontalScrollInfo)?.LineRight();
-
- public void MouseWheelLeft() => (_nfo as IHorizontalScrollInfo)?.MouseWheelLeft();
-
- public void MouseWheelRight() => (_nfo as IHorizontalScrollInfo)?.MouseWheelRight();
-
- public void PageLeft() => (_nfo as IHorizontalScrollInfo)?.PageLeft();
-
- public Rect MakeVisible(Visual visual, Rect rectangle) => _nfo.MakeVisible(visual, rectangle);
-
- public void PageRight() => (_nfo as IHorizontalScrollInfo)?.PageRight();
-
- public void LineDown() => (_nfo as IVerticalScrollInfo)?.LineDown();
-
- public void LineUp() => (_nfo as IVerticalScrollInfo)?.LineUp();
-
- public void MouseWheelDown() => (_nfo as IVerticalScrollInfo)?.MouseWheelDown();
-
- public void MouseWheelUp() => (_nfo as IVerticalScrollInfo)?.MouseWheelUp();
-
- public void PageDown() => (_nfo as IVerticalScrollInfo)?.PageDown();
-
- public void PageUp() => (_nfo as IVerticalScrollInfo)?.PageUp();
-
- private bool _canVerticallyScroll;
- public bool CanVerticallyScroll
- {
- get
- {
- return (_nfo as IVerticalScrollInfo)?.CanVerticallyScroll ?? _canVerticallyScroll;
- }
-
- set
- {
- var info = (_nfo as IVerticalScrollInfo);
- if (info == null)
- _canVerticallyScroll = value;
- else
- info.CanVerticallyScroll = value;
- }
- }
-
- private bool _canHorizontallyScroll;
- public bool CanHorizontallyScroll
- {
- get
- {
- return (_nfo as IHorizontalScrollInfo)?.CanHorizontallyScroll ?? _canHorizontallyScroll;
- }
-
- set
- {
- var info = (_nfo as IHorizontalScrollInfo);
- if (info == null)
- _canHorizontallyScroll = value;
- else
- info.CanHorizontallyScroll = value;
- }
- }
- }
-}
diff --git a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs
index 55ff57e6be..56de4789e5 100644
--- a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs
@@ -8,6 +8,7 @@ using System.Collections.Specialized;
using System.Linq;
using Perspex.Collections;
using Perspex.Controls.Generators;
+using Perspex.Data;
using Perspex.Input;
using Perspex.Interactivity;
using Perspex.Styling;
@@ -76,7 +77,19 @@ namespace Perspex.Controls.Primitives
/// has changed.
///
public static readonly RoutedEvent IsSelectedChangedEvent =
- RoutedEvent.Register("IsSelectedChanged", RoutingStrategies.Bubble);
+ RoutedEvent.Register(
+ "IsSelectedChanged",
+ RoutingStrategies.Bubble);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent SelectionChangedEvent =
+ RoutedEvent.Register(
+ "SelectionChanged",
+ RoutingStrategies.Bubble);
+
+ private static readonly IList Empty = new object[0];
private int _selectedIndex = -1;
private object _selectedItem;
@@ -98,7 +111,15 @@ namespace Perspex.Controls.Primitives
///
public SelectingItemsControl()
{
- ItemContainerGenerator.ContainersInitialized.Subscribe(ContainersInitialized);
+ }
+
+ ///
+ /// Occurs when the control's selection changes.
+ ///
+ public event EventHandler SelectionChanged
+ {
+ add { AddHandler(SelectionChangedEvent, value); }
+ remove { RemoveHandler(SelectionChangedEvent, value); }
}
///
@@ -225,8 +246,8 @@ namespace Perspex.Controls.Primitives
protected IControl GetContainerFromEventSource(IInteractive eventSource)
{
var item = ((IVisual)eventSource).GetSelfAndVisualAncestors()
- .OfType()
- .FirstOrDefault(x => x.LogicalParent == this);
+ .OfType()
+ .FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1);
return item as IControl;
}
@@ -287,7 +308,32 @@ namespace Perspex.Controls.Primitives
}
///
- protected override void OnDataContextFinishedChanging()
+ protected override void OnContainersMaterialized(ItemContainerEventArgs e)
+ {
+ base.OnContainersMaterialized(e);
+
+ var selectedIndex = SelectedIndex;
+ var selectedContainer = e.Containers
+ .FirstOrDefault(x => (x.ContainerControl as ISelectable)?.IsSelected == true);
+
+ if (selectedContainer != null)
+ {
+ SelectedIndex = selectedContainer.Index;
+ }
+ else if (selectedIndex >= e.StartingIndex &&
+ selectedIndex < e.StartingIndex + e.Containers.Count)
+ {
+ var container = e.Containers[selectedIndex - e.StartingIndex];
+
+ if (container.ContainerControl != null)
+ {
+ MarkContainerSelected(container.ContainerControl, true);
+ }
+ }
+ }
+
+ ///
+ protected override void OnDataContextChanged()
{
if (_clearSelectedItemsAfterDataContextChanged == SelectedItems)
{
@@ -317,7 +363,7 @@ namespace Perspex.Controls.Primitives
var mode = SelectionMode;
var toggle = toggleModifier || (mode & SelectionMode.Toggle) != 0;
var multi = (mode & SelectionMode.Multiple) != 0;
- var range = multi && SelectedIndex != -1 ? rangeModifier : false;
+ var range = multi && SelectedIndex != -1 && rangeModifier;
if (!toggle && !range)
{
@@ -379,7 +425,7 @@ namespace Perspex.Controls.Primitives
bool rangeModifier = false,
bool toggleModifier = false)
{
- var index = ItemContainerGenerator.IndexFromContainer(container);
+ var index = ItemContainerGenerator?.IndexFromContainer(container) ?? -1;
if (index != -1)
{
@@ -416,61 +462,6 @@ namespace Perspex.Controls.Primitives
return false;
}
- ///
- /// Gets the item at the specified index in a collection.
- ///
- /// The collection.
- /// The index.
- /// The index of the item or -1 if the item was not found.
- private static object ElementAt(IEnumerable items, int index)
- {
- var typedItems = items?.Cast();
-
- if (index != -1 && typedItems != null && index < typedItems.Count())
- {
- return typedItems.ElementAt(index) ?? null;
- }
- else
- {
- return null;
- }
- }
-
- ///
- /// Gets the index of an item in a collection.
- ///
- /// The collection.
- /// The item.
- /// The index of the item or -1 if the item was not found.
- private static int IndexOf(IEnumerable items, object item)
- {
- if (items != null && item != null)
- {
- var list = items as IList;
-
- if (list != null)
- {
- return list.IndexOf(item);
- }
- else
- {
- int index = 0;
-
- foreach (var i in items)
- {
- if (Equals(i, item))
- {
- return index;
- }
-
- ++index;
- }
- }
- }
-
- return -1;
- }
-
///
/// Gets a range of items from an IEnumerable.
///
@@ -523,27 +514,6 @@ namespace Perspex.Controls.Primitives
}
}
- ///
- /// Called when new containers are initialized by the .
- ///
- /// The containers.
- private void ContainersInitialized(ItemContainers containers)
- {
- var selectedIndex = SelectedIndex;
- var selectedContainer = containers.Items.OfType().FirstOrDefault(x => x.IsSelected);
-
- if (selectedContainer != null)
- {
- SelectedIndex = containers.Items.IndexOf((IControl)selectedContainer) + containers.StartingIndex;
- }
- else if (selectedIndex >= containers.StartingIndex &&
- selectedIndex < containers.StartingIndex + containers.Items.Count)
- {
- var container = containers.Items[selectedIndex - containers.StartingIndex];
- MarkContainerSelected(container, true);
- }
- }
-
///
/// Called when a container raises the .
///
@@ -588,30 +558,28 @@ namespace Perspex.Controls.Primitives
///
/// The container.
/// Whether the control is selected
- private void MarkContainerSelected(IControl container, bool selected)
+ /// The previous selection state.
+ private bool MarkContainerSelected(IControl container, bool selected)
{
try
{
var selectable = container as ISelectable;
- var styleable = container as IStyleable;
+ bool result;
_ignoreContainerSelectionChanged = true;
if (selectable != null)
{
+ result = selectable.IsSelected;
selectable.IsSelected = selected;
}
- else if (styleable != null)
+ else
{
- if (selected)
- {
- styleable.Classes.Add(":selected");
- }
- else
- {
- styleable.Classes.Remove(":selected");
- }
+ result = container.Classes.Contains(":selected");
+ ((IPseudoClasses)container.Classes).Set(":selected", selected);
}
+
+ return result;
}
finally
{
@@ -626,7 +594,7 @@ namespace Perspex.Controls.Primitives
/// Whether the item should be selected or deselected.
private void MarkItemSelected(int index, bool selected)
{
- var container = ItemContainerGenerator.ContainerFromIndex(index);
+ var container = ItemContainerGenerator?.ContainerFromIndex(index);
if (container != null)
{
@@ -656,10 +624,15 @@ namespace Perspex.Controls.Primitives
/// The event args.
private void SelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
+ var generator = ItemContainerGenerator;
+ IList added = null;
+ IList removed = null;
+
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
SelectedItemsAdded(e.NewItems.Cast().ToList());
+ added = e.NewItems;
break;
case NotifyCollectionChangedAction.Remove:
@@ -678,20 +651,36 @@ namespace Perspex.Controls.Primitives
}
}
+ removed = e.OldItems;
break;
case NotifyCollectionChangedAction.Reset:
- foreach (var item in ItemContainerGenerator.Containers)
+ if (generator != null)
{
- MarkContainerSelected(item, false);
+ removed = new List();
+
+ foreach (var item in generator.Containers)
+ {
+ if (item?.ContainerControl != null)
+ {
+ if (MarkContainerSelected(item.ContainerControl, false))
+ {
+ removed.Add(item.Item);
+ }
+ }
+ }
}
- if (!_syncingSelectedItems)
+ if (SelectedItems.Count > 0)
+ {
+ SelectedItemsAdded(SelectedItems);
+ added = SelectedItems;
+ }
+ else if (!_syncingSelectedItems)
{
SelectedIndex = -1;
}
- SelectedItemsAdded(SelectedItems);
break;
case NotifyCollectionChangedAction.Replace:
@@ -717,8 +706,19 @@ namespace Perspex.Controls.Primitives
RaisePropertyChanged(SelectedItemProperty, oldItem, item, BindingPriority.LocalValue);
}
+ added = e.OldItems;
+ removed = e.NewItems;
break;
}
+
+ if (added?.Count > 0 || removed?.Count > 0)
+ {
+ var changed = new SelectionChangedEventArgs(
+ SelectionChangedEvent,
+ added ?? Empty,
+ removed ?? Empty);
+ RaiseEvent(changed);
+ }
}
///
diff --git a/src/Perspex.Controls/Primitives/TabStrip.cs b/src/Perspex.Controls/Primitives/TabStrip.cs
index 5fc305c73a..1cfb4e0515 100644
--- a/src/Perspex.Controls/Primitives/TabStrip.cs
+++ b/src/Perspex.Controls/Primitives/TabStrip.cs
@@ -1,52 +1,26 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Linq;
-using System.Reactive.Linq;
using Perspex.Controls.Generators;
+using Perspex.Controls.Templates;
using Perspex.Input;
namespace Perspex.Controls.Primitives
{
public class TabStrip : SelectingItemsControl
{
- public static readonly PerspexProperty SelectedTabProperty =
- TabControl.SelectedTabProperty.AddOwner();
+ private static IMemberSelector s_MemberSelector = new FuncMemberSelector(SelectHeader);
static TabStrip()
{
+ MemberSelectorProperty.OverrideDefaultValue(s_MemberSelector);
SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected);
FocusableProperty.OverrideDefaultValue(typeof(TabStrip), false);
}
- public TabStrip()
- {
- GetObservable(SelectedItemProperty).Subscribe(x => SelectedTab = x as TabItem);
- GetObservable(SelectedTabProperty).Subscribe(x => SelectedItem = x as TabItem);
- }
-
- public TabItem SelectedTab
- {
- get { return GetValue(SelectedTabProperty); }
- set { SetValue(SelectedTabProperty, value); }
- }
-
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
- TabControl tabControl = TemplatedParent as TabControl;
- IItemContainerGenerator result;
-
- if (tabControl != null)
- {
- result = tabControl.ItemContainerGenerator;
- }
- else
- {
- result = new ItemContainerGenerator(this, TabItem.ContentProperty);
- }
-
- return result;
+ return new ItemContainerGenerator(this, ContentControl.ContentProperty);
}
///
@@ -70,5 +44,11 @@ namespace Perspex.Controls.Primitives
e.Handled = UpdateSelectionFromEventSource(e.Source);
}
}
+
+ private static object SelectHeader(object o)
+ {
+ var headered = o as IHeadered;
+ return (headered != null) ? (headered.Header ?? string.Empty) : o;
+ }
}
}
diff --git a/src/Perspex.Controls/Primitives/TabStripItem.cs b/src/Perspex.Controls/Primitives/TabStripItem.cs
new file mode 100644
index 0000000000..5e6787c167
--- /dev/null
+++ b/src/Perspex.Controls/Primitives/TabStripItem.cs
@@ -0,0 +1,12 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Perspex.Controls.Primitives
+{
+ ///
+ /// Represents a tab in a .
+ ///
+ public class TabStripItem : ListBoxItem
+ {
+ }
+}
diff --git a/src/Perspex.Controls/Primitives/TemplateAppliedEventArgs.cs b/src/Perspex.Controls/Primitives/TemplateAppliedEventArgs.cs
new file mode 100644
index 0000000000..e921f40785
--- /dev/null
+++ b/src/Perspex.Controls/Primitives/TemplateAppliedEventArgs.cs
@@ -0,0 +1,28 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Perspex.Interactivity;
+
+namespace Perspex.Controls.Primitives
+{
+ ///
+ /// Holds the details of the event.
+ ///
+ public class TemplateAppliedEventArgs : RoutedEventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The applied template's name scope.
+ public TemplateAppliedEventArgs(INameScope nameScope)
+ : base(TemplatedControl.TemplateAppliedEvent)
+ {
+ NameScope = nameScope;
+ }
+
+ ///
+ /// Gets the name scope of the applied template.
+ ///
+ public INameScope NameScope { get; }
+ }
+}
diff --git a/src/Perspex.Controls/Primitives/TemplatedControl.cs b/src/Perspex.Controls/Primitives/TemplatedControl.cs
index 170bfa6fa8..7e26758054 100644
--- a/src/Perspex.Controls/Primitives/TemplatedControl.cs
+++ b/src/Perspex.Controls/Primitives/TemplatedControl.cs
@@ -3,8 +3,11 @@
using System;
using System.Linq;
+using System.Reactive.Linq;
using Perspex.Controls.Presenters;
using Perspex.Controls.Templates;
+using Perspex.Data;
+using Perspex.Interactivity;
using Perspex.Media;
using Perspex.Styling;
using Perspex.VisualTree;
@@ -72,6 +75,14 @@ namespace Perspex.Controls.Primitives
public static readonly PerspexProperty TemplateProperty =
PerspexProperty.Register("Template");
+ ///
+ /// Defines the routed event.
+ ///
+ public static readonly RoutedEvent TemplateAppliedEvent =
+ RoutedEvent.Register(
+ "TemplateApplied",
+ RoutingStrategies.Direct);
+
private bool _templateApplied;
private readonly ILogger _templateLog;
@@ -81,12 +92,8 @@ namespace Perspex.Controls.Primitives
///
static TemplatedControl()
{
- TemplateProperty.Changed.Subscribe(e =>
- {
- var templatedControl = (TemplatedControl)e.Sender;
- templatedControl._templateApplied = false;
- templatedControl.InvalidateMeasure();
- });
+ ClipToBoundsProperty.OverrideDefaultValue(true);
+ TemplateProperty.Changed.AddClassHandler(x => x.OnTemplateChanged);
}
///
@@ -102,6 +109,15 @@ namespace Perspex.Controls.Primitives
});
}
+ ///
+ /// Raised when the control's template is applied.
+ ///
+ public event EventHandler TemplateApplied
+ {
+ add { AddHandler(TemplateAppliedEvent, value); }
+ remove { RemoveHandler(TemplateAppliedEvent, value); }
+ }
+
///
/// Gets or sets the brush used to draw the control's background.
///
@@ -188,7 +204,7 @@ namespace Perspex.Controls.Primitives
{
if (!_templateApplied)
{
- ClearVisualChildren();
+ VisualChildren.Clear();
if (Template != null)
{
@@ -196,62 +212,71 @@ namespace Perspex.Controls.Primitives
var child = Template.Build(this);
var nameScope = new NameScope();
- NameScope.SetNameScope((Visual)child, nameScope);
-
- // We need to call SetTemplatedParentAndApplyChildTemplates twice - once
- // before the controls are added to the visual tree so that the logical
- // tree can be set up before styling is applied.
+ NameScope.SetNameScope((Control)child, nameScope);
+ child.SetValue(TemplatedParentProperty, this);
+ RegisterNames(child, nameScope);
((ISetLogicalParent)child).SetParent(this);
- SetupTemplateControls(child, nameScope);
-
- // And again after the controls are added to the visual tree, and have their
- // styling and thus Template property set.
- AddVisualChild((Visual)child);
- SetupTemplateControls(child, nameScope);
+ VisualChildren.Add(child);
- OnTemplateApplied(nameScope);
+ OnTemplateApplied(new TemplateAppliedEventArgs(nameScope));
}
_templateApplied = true;
}
}
+ protected sealed override IndexerDescriptor CreateBindingDescriptor(IndexerDescriptor source)
+ {
+ var result = base.CreateBindingDescriptor(source);
+
+ // If the binding is a template binding, then complete when the Template changes.
+ if (source.Priority == BindingPriority.TemplatedParent)
+ {
+ var templateChanged = this.GetObservable(TemplateProperty).Skip(1);
+
+ result.SourceObservable = result.Source.GetObservable(result.Property)
+ .TakeUntil(templateChanged);
+ }
+
+ return result;
+ }
+
///
/// Called when the control's template is applied.
///
- /// The template name scope.
- protected virtual void OnTemplateApplied(INameScope nameScope)
+ /// The event args.
+ protected virtual void OnTemplateApplied(TemplateAppliedEventArgs e)
+ {
+ RaiseEvent(e);
+ }
+
+ ///
+ /// Called when the property changes.
+ ///
+ /// The event args.
+ protected virtual void OnTemplateChanged(PerspexPropertyChangedEventArgs e)
{
+ _templateApplied = false;
+ InvalidateMeasure();
}
///
- /// Sets the TemplatedParent property for a control created from the control template and
- /// applies the templates of nested templated controls. Also adds each control to its name
- /// scope if it has a name.
+ /// Registers each control with its name scope.
///
/// The control.
/// The name scope.
- private void SetupTemplateControls(IControl control, INameScope nameScope)
+ private void RegisterNames(IControl control, INameScope nameScope)
{
- // If control.TemplatedParent is null at this point, then the control is our templated
- // child so set its TemplatedParent and register it with its name scope.
- if (control.TemplatedParent == null)
+ if (control.Name != null)
{
- control.SetValue(TemplatedParentProperty, this);
-
- if (control.Name != null)
- {
- nameScope.Register(control.Name, control);
- }
+ nameScope.Register(control.Name, control);
}
- control.ApplyTemplate();
-
- if (!(control is IPresenter && control.TemplatedParent == this))
+ if (control.TemplatedParent == this)
{
foreach (IControl child in control.GetVisualChildren())
{
- SetupTemplateControls(child, nameScope);
+ RegisterNames(child, nameScope);
}
}
}
diff --git a/src/Perspex.Controls/Primitives/ToggleButton.cs b/src/Perspex.Controls/Primitives/ToggleButton.cs
index 601792ba1f..744af915c0 100644
--- a/src/Perspex.Controls/Primitives/ToggleButton.cs
+++ b/src/Perspex.Controls/Primitives/ToggleButton.cs
@@ -9,21 +9,22 @@ namespace Perspex.Controls.Primitives
public class ToggleButton : Button
{
public static readonly PerspexProperty IsCheckedProperty =
- PerspexProperty.Register("IsChecked");
+ PerspexProperty.RegisterDirect(
+ "IsChecked",
+ o => o.IsChecked,
+ (o,v) => o.IsChecked = v);
+
+ private bool _isChecked;
static ToggleButton()
{
PseudoClass(IsCheckedProperty, ":checked");
}
- public ToggleButton()
- {
- }
-
public bool IsChecked
{
- get { return GetValue(IsCheckedProperty); }
- set { SetValue(IsCheckedProperty, value); }
+ get { return _isChecked; }
+ set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); }
}
protected override void OnClick(RoutedEventArgs e)
diff --git a/src/Perspex.Controls/Primitives/Track.cs b/src/Perspex.Controls/Primitives/Track.cs
index ae9a8d1c4a..238b633050 100644
--- a/src/Perspex.Controls/Primitives/Track.cs
+++ b/src/Perspex.Controls/Primitives/Track.cs
@@ -11,13 +11,13 @@ namespace Perspex.Controls.Primitives
public class Track : Control
{
public static readonly PerspexProperty MinimumProperty =
- RangeBase.MinimumProperty.AddOwner
diff --git a/src/Perspex.Styling/Properties/AssemblyInfo.cs b/src/Perspex.Styling/Properties/AssemblyInfo.cs
index f06c17710e..21034a0753 100644
--- a/src/Perspex.Styling/Properties/AssemblyInfo.cs
+++ b/src/Perspex.Styling/Properties/AssemblyInfo.cs
@@ -2,7 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Reflection;
+using System.Runtime.CompilerServices;
using Perspex.Metadata;
[assembly: AssemblyTitle("Perspex.Styling")]
-[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Styling")]
\ No newline at end of file
+[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Styling")]
+[assembly: InternalsVisibleTo("Perspex.Styling.UnitTests")]
\ No newline at end of file
diff --git a/src/Perspex.Styling/Styling/ActivatedObservable.cs b/src/Perspex.Styling/Styling/ActivatedObservable.cs
new file mode 100644
index 0000000000..97c1b73106
--- /dev/null
+++ b/src/Perspex.Styling/Styling/ActivatedObservable.cs
@@ -0,0 +1,74 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Reactive;
+using System.Reactive.Linq;
+
+namespace Perspex.Styling
+{
+ ///
+ /// An observable which is switched on or off according to an activator observable.
+ ///
+ ///
+ /// An has two inputs: an activator observable a
+ /// observable which produces the activated value. When the activator
+ /// produces true, the will produce the current activated
+ /// value. When the activator produces false it will produce
+ /// .
+ ///
+ internal class ActivatedObservable : ObservableBase, IDescription
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The activator.
+ /// An observable that produces the activated value.
+ /// The binding description.
+ public ActivatedObservable(
+ IObservable activator,
+ IObservable source,
+ string description)
+ {
+ Activator = activator;
+ Description = description;
+ Source = source;
+ }
+
+ ///
+ /// Gets the activator observable.
+ ///
+ public IObservable Activator { get; }
+
+ ///
+ /// Gets a description of the binding.
+ ///
+ public string Description { get; }
+
+ ///
+ /// Gets an observable which produces the .
+ ///
+ public IObservable Source { get; }
+
+ ///
+ /// Notifies the provider that an observer is to receive notifications.
+ ///
+ /// The observer.
+ /// IDisposable object used to unsubscribe from the observable sequence.
+ protected override IDisposable SubscribeCore(IObserver observer)
+ {
+ Contract.Requires(observer != null);
+
+ var sourceCompleted = Source.TakeLast(1).Select(_ => Unit.Default);
+ var activatorCompleted = Activator.TakeLast(1).Select(_ => Unit.Default);
+ var completed = sourceCompleted.Merge(activatorCompleted);
+
+ return Activator
+ .CombineLatest(Source, (x, y) => new { Active = x, Value = y })
+ .Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue)
+ .DistinctUntilChanged()
+ .TakeUntil(completed)
+ .Subscribe(observer);
+ }
+ }
+}
diff --git a/src/Perspex.Styling/Styling/ActivatedSubject.cs b/src/Perspex.Styling/Styling/ActivatedSubject.cs
new file mode 100644
index 0000000000..3ccbfac6c0
--- /dev/null
+++ b/src/Perspex.Styling/Styling/ActivatedSubject.cs
@@ -0,0 +1,111 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+
+namespace Perspex.Styling
+{
+ ///
+ /// A subject which is switched on or off according to an activator observable.
+ ///
+ ///
+ /// An has two inputs: an activator observable and either an
+ /// or a observable which produces the
+ /// activated value. When the activator produces true, the will
+ /// produce the current activated value. When the activator produces false it will produce
+ /// .
+ ///
+ internal class ActivatedSubject : ActivatedObservable, ISubject, IDescription
+ {
+ private bool? _active;
+ private bool _completed;
+ private object _value;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The activator.
+ /// An observable that produces the activated value.
+ /// The binding description.
+ public ActivatedSubject(
+ IObservable activator,
+ ISubject source,
+ string description)
+ : base(activator, source, description)
+ {
+ Activator.Subscribe(ActivatorChanged, ActivatorError, ActivatorCompleted);
+ }
+
+ ///
+ /// Gets the underlying subject.
+ ///
+ public new ISubject Source
+ {
+ get { return (ISubject)base.Source; }
+ }
+
+ ///
+ /// Notifies all subscribed observers about the end of the sequence.
+ ///
+ public void OnCompleted()
+ {
+ if (_active.Value && !_completed)
+ {
+ Source.OnCompleted();
+ }
+ }
+
+ ///
+ /// Notifies all subscribed observers with the exception.
+ ///
+ /// The exception to send to all subscribed observers.
+ /// is null.
+ public void OnError(Exception error)
+ {
+ if (_active.Value && !_completed)
+ {
+ Source.OnError(error);
+ }
+ }
+
+ ///
+ /// Notifies all subscribed observers with the value.
+ ///
+ /// The value to send to all subscribed observers.
+ public void OnNext(object value)
+ {
+ _value = value;
+
+ if (_active.Value && !_completed)
+ {
+ Source.OnNext(value);
+ }
+ }
+
+ private void ActivatorChanged(bool active)
+ {
+ bool first = !_active.HasValue;
+
+ _active = active;
+
+ if (!first)
+ {
+ Source.OnNext(active ? _value : PerspexProperty.UnsetValue);
+ }
+ }
+
+ private void ActivatorCompleted()
+ {
+ _completed = true;
+ Source.OnCompleted();
+ }
+
+ private void ActivatorError(Exception e)
+ {
+ _completed = true;
+ Source.OnError(e);
+ }
+ }
+}
diff --git a/src/Perspex.Styling/Styling/ActivatedValue.cs b/src/Perspex.Styling/Styling/ActivatedValue.cs
new file mode 100644
index 0000000000..0c02598086
--- /dev/null
+++ b/src/Perspex.Styling/Styling/ActivatedValue.cs
@@ -0,0 +1,72 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Reactive;
+using System.Reactive.Linq;
+
+namespace Perspex.Styling
+{
+ ///
+ /// An value which is switched on or off according to an activator observable.
+ ///
+ ///
+ /// An has two inputs: an activator observable and an
+ /// . When the activator produces true, the
+ /// will produce the current value. When the activator
+ /// produces false it will produce .
+ ///
+ internal class ActivatedValue : ObservableBase, IDescription
+ {
+ ///
+ /// The activator.
+ ///
+ private readonly IObservable _activator;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The activator.
+ /// The activated value.
+ /// The binding description.
+ public ActivatedValue(
+ IObservable activator,
+ object value,
+ string description)
+ {
+ _activator = activator;
+ Value = value;
+ Description = description;
+ }
+
+ ///
+ /// Gets the activated value.
+ ///
+ public object Value
+ {
+ get;
+ }
+
+ ///
+ /// Gets a description of the binding.
+ ///
+ public string Description
+ {
+ get;
+ }
+
+ ///
+ /// Notifies the provider that an observer is to receive notifications.
+ ///
+ /// The observer.
+ /// IDisposable object used to unsubscribe from the observable sequence.
+ protected override IDisposable SubscribeCore(IObserver observer)
+ {
+ Contract.Requires(observer != null);
+
+ return _activator
+ .Select(active => active ? Value : PerspexProperty.UnsetValue)
+ .Subscribe(observer);
+ }
+ }
+}
diff --git a/src/Perspex.Styling/Styling/Classes.cs b/src/Perspex.Styling/Styling/Classes.cs
deleted file mode 100644
index 8cb5be11a6..0000000000
--- a/src/Perspex.Styling/Styling/Classes.cs
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (c) The Perspex Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Linq;
-using System.Reactive;
-using System.Reactive.Subjects;
-
-namespace Perspex.Styling
-{
- public class Classes : ICollection, INotifyCollectionChanged
- {
- private readonly List _inner;
-
- private readonly Subject _beforeChanged
- = new Subject();
-
- private readonly Subject _changed
- = new Subject();
-
- private readonly Subject _afterChanged
- = new Subject();
-
- public Classes()
- {
- _inner = new List();
- }
-
- public Classes(params string[] classes)
- {
- _inner = new List(classes);
- }
-
- public Classes(IEnumerable classes)
- {
- _inner = new List(classes);
- }
-
- public event NotifyCollectionChangedEventHandler CollectionChanged;
-
- public int Count => _inner.Count;
-
- public bool IsReadOnly => false;
-
- public IObservable BeforeChanged => _beforeChanged;
-
- public IObservable Changed => _changed;
-
- public IObservable AfterChanged => _afterChanged;
-
- public void Add(string item)
- {
- Add(Enumerable.Repeat(item, 1));
- }
-
- public void Add(params string[] items)
- {
- Add((IEnumerable)items);
- }
-
- public void Add(IEnumerable items)
- {
- items = items.Except(_inner);
-
- NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(
- NotifyCollectionChangedAction.Add,
- items);
-
- _beforeChanged.OnNext(e);
- _inner.AddRange(items);
- RaiseChanged(e);
- }
-
- public void Clear()
- {
- NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(
- NotifyCollectionChangedAction.Reset);
-
- _beforeChanged.OnNext(e);
- _inner.Clear();
- RaiseChanged(e);
- }
-
- public bool Contains(string item)
- {
- return _inner.Contains(item);
- }
-
- public void CopyTo(string[] array, int arrayIndex)
- {
- _inner.CopyTo(array, arrayIndex);
- }
-
- public IEnumerator GetEnumerator()
- {
- return _inner.GetEnumerator();
- }
-
- public override string ToString()
- {
- return string.Join(" ", this);
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return _inner.GetEnumerator();
- }
-
- public bool Remove(string item)
- {
- return Remove(Enumerable.Repeat(item, 1));
- }
-
- public bool Remove(params string[] items)
- {
- return Remove((IEnumerable)items);
- }
-
- public bool Remove(IEnumerable items)
- {
- items = items.Intersect(_inner);
-
- if (items.Any())
- {
- NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(
- NotifyCollectionChangedAction.Remove,
- items);
-
- _beforeChanged.OnNext(e);
-
- foreach (string item in items)
- {
- _inner.Remove(item);
- }
-
- RaiseChanged(e);
- return true;
- }
- else
- {
- return false;
- }
- }
-
- private void RaiseChanged(NotifyCollectionChangedEventArgs e)
- {
- if (CollectionChanged != null)
- {
- CollectionChanged(this, e);
- }
-
- _changed.OnNext(e);
- _afterChanged.OnNext(e);
- }
- }
-}
diff --git a/src/Perspex.Styling/Styling/IGlobalStyles.cs b/src/Perspex.Styling/Styling/IGlobalStyles.cs
index ef246f9b84..85cc9abaa7 100644
--- a/src/Perspex.Styling/Styling/IGlobalStyles.cs
+++ b/src/Perspex.Styling/Styling/IGlobalStyles.cs
@@ -3,8 +3,10 @@
namespace Perspex.Styling
{
- public interface IGlobalStyles
+ ///
+ /// Defines the style host that provides styles global to the application.
+ ///
+ public interface IGlobalStyles : IStyleRoot
{
- Styles Styles { get; }
}
}
diff --git a/src/Perspex.Styling/Styling/IStyleHost.cs b/src/Perspex.Styling/Styling/IStyleHost.cs
index cb0da117fb..9f434e1231 100644
--- a/src/Perspex.Styling/Styling/IStyleHost.cs
+++ b/src/Perspex.Styling/Styling/IStyleHost.cs
@@ -3,8 +3,20 @@
namespace Perspex.Styling
{
- public interface IStyleHost : IVisual
+ ///
+ /// Defines an element that has a collection.
+ ///
+ public interface IStyleHost
{
+ ///
+ /// Gets the styles for the element.
+ ///
Styles Styles { get; }
+
+ ///
+ /// Gets the parent style host element.
+ ///
+ IStyleHost StylingParent { get; }
+
}
}
diff --git a/src/Perspex.Styling/Styling/IStyleRoot.cs b/src/Perspex.Styling/Styling/IStyleRoot.cs
new file mode 100644
index 0000000000..9b3e1dcc70
--- /dev/null
+++ b/src/Perspex.Styling/Styling/IStyleRoot.cs
@@ -0,0 +1,12 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Perspex.Styling
+{
+ ///
+ /// Denotes the root in a tree.
+ ///
+ public interface IStyleRoot : IStyleHost
+ {
+ }
+}
diff --git a/src/Perspex.Styling/Styling/IStyleable.cs b/src/Perspex.Styling/Styling/IStyleable.cs
index c28e96f184..36b092ff1d 100644
--- a/src/Perspex.Styling/Styling/IStyleable.cs
+++ b/src/Perspex.Styling/Styling/IStyleable.cs
@@ -2,18 +2,25 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using Perspex.Collections;
+using System.Reactive;
namespace Perspex.Styling
{
///
/// Interface for styleable elements.
///
- public interface IStyleable : IObservablePropertyBag, INamed
+ public interface IStyleable : IPerspexObject, INamed
{
+ ///
+ /// Raised when the control's style should be removed.
+ ///
+ IObservable StyleDetach { get; }
+
///
/// Gets the list of classes for the control.
///
- Classes Classes { get; }
+ IPerspexReadOnlyList Classes { get; }
///
/// Gets the type by which the control is styled.
diff --git a/src/Perspex.Styling/Styling/ITemplatedControl.cs b/src/Perspex.Styling/Styling/ITemplatedControl.cs
index a31d834e5a..27e328613b 100644
--- a/src/Perspex.Styling/Styling/ITemplatedControl.cs
+++ b/src/Perspex.Styling/Styling/ITemplatedControl.cs
@@ -5,14 +5,7 @@ using System;
namespace Perspex.Styling
{
- public interface ITemplatedControl
+ public interface ITemplatedControl : IPerspexObject
{
- ///
- /// Gets an observable for a .
- ///
- ///
- /// The property to get the observable for.
- /// The observable.
- IObservable GetObservable(PerspexProperty property);
}
}
diff --git a/src/Perspex.Styling/Styling/ObservableSetter.cs b/src/Perspex.Styling/Styling/ObservableSetter.cs
deleted file mode 100644
index 74e9fb07c9..0000000000
--- a/src/Perspex.Styling/Styling/ObservableSetter.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (c) The Perspex Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-
-namespace Perspex.Styling
-{
- ///
- /// A setter for a whose source is an observable.
- ///
- ///
- /// A is used to set a value on a
- /// depending on a condition.
- ///
- public class ObservableSetter : ISetter
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The property to set.
- /// An observable which produces the value for the property.
- public ObservableSetter(PerspexProperty property, IObservable source)
- {
- Property = property;
- Source = source;
- }
-
- ///
- /// Gets or sets the property to set.
- ///
- public PerspexProperty Property
- {
- get;
- set;
- }
-
- ///
- /// Gets or sets an observable which produces the value for the property.
- ///
- public IObservable Source
- {
- get;
- set;
- }
-
- ///
- /// Applies the setter to the control.
- ///
- /// The style that is being applied.
- /// The control.
- /// An optional activator.
- public void Apply(IStyle style, IStyleable control, IObservable activator)
- {
- if (activator == null)
- {
- control.Bind(Property, Source, BindingPriority.Style);
- }
- else
- {
- var binding = new StyleBinding(activator, Source, style.ToString());
- control.Bind(Property, binding, BindingPriority.StyleTrigger);
- }
- }
- }
-}
diff --git a/src/Perspex.Styling/Styling/Selector.cs b/src/Perspex.Styling/Styling/Selector.cs
index 889b343cfb..b47a3e28fd 100644
--- a/src/Perspex.Styling/Styling/Selector.cs
+++ b/src/Perspex.Styling/Styling/Selector.cs
@@ -97,10 +97,7 @@ namespace Perspex.Styling
///
/// Gets the target type of the selector, if available.
///
- public Type TargetType
- {
- get { return _targetType ?? MovePrevious()?.TargetType; }
- }
+ public Type TargetType => _targetType ?? MovePrevious()?.TargetType;
///
/// Returns the previous selector if traversal is not stopped.
@@ -144,7 +141,7 @@ namespace Perspex.Styling
if (inputs.Count > 0)
{
- return new SelectorMatch(new StyleActivator(inputs));
+ return new SelectorMatch(StyleActivator.And(inputs));
}
else
{
diff --git a/src/Perspex.Styling/Styling/Selectors.cs b/src/Perspex.Styling/Styling/Selectors.cs
index d291bcd3f9..3c88cac0a6 100644
--- a/src/Perspex.Styling/Styling/Selectors.cs
+++ b/src/Perspex.Styling/Styling/Selectors.cs
@@ -3,6 +3,9 @@
using System;
using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Reactive;
using System.Reactive.Linq;
using System.Reflection;
@@ -61,7 +64,7 @@ namespace Perspex.Styling
{
Contract.Requires(previous != null);
- return new Selector(previous, x => MatchIs(x, type), type.Name, type);
+ return new Selector(previous, x => MatchIs(x, type), $":is({type.Name})", type);
}
///
@@ -131,7 +134,6 @@ namespace Perspex.Styling
///
/// Returns a selector which matches a control with the specified property value.
///
- /// The property type.
/// The previous selector.
/// The property.
/// The property value.
@@ -177,10 +179,16 @@ namespace Perspex.Styling
private static SelectorMatch MatchClass(IStyleable control, string name)
{
- return new SelectorMatch(
- Observable
- .Return(control.Classes.Contains(name))
- .Concat(control.Classes.Changed.Select(e => control.Classes.Contains(name))));
+ var observable = Observable.FromEventPattern<
+ NotifyCollectionChangedEventHandler,
+ NotifyCollectionChangedEventArgs>(
+ x => control.Classes.CollectionChanged += x,
+ x => control.Classes.CollectionChanged -= x)
+ .Select(_ => Unit.Default)
+ .StartWith(Unit.Default)
+ .Select(_ => control.Classes.Contains(name));
+
+ return new SelectorMatch(observable);
}
private static SelectorMatch MatchDescendent(IStyleable control, Selector previous)
@@ -210,9 +218,7 @@ namespace Perspex.Styling
}
}
- return new SelectorMatch(new StyleActivator(
- descendentMatches,
- ActivatorMode.Or));
+ return new SelectorMatch(StyleActivator.Or(descendentMatches));
}
private static SelectorMatch MatchIs(IStyleable control, Type type)
@@ -234,7 +240,7 @@ namespace Perspex.Styling
private static SelectorMatch MatchPropertyEquals(IStyleable x, PerspexProperty property, object value)
{
- if (!x.IsRegistered(property))
+ if (!PerspexPropertyRegistry.Instance.IsRegistered(x, property))
{
return SelectorMatch.False;
}
diff --git a/src/Perspex.Styling/Styling/Setter.cs b/src/Perspex.Styling/Styling/Setter.cs
index 88b7ac9cbc..675c21789a 100644
--- a/src/Perspex.Styling/Styling/Setter.cs
+++ b/src/Perspex.Styling/Styling/Setter.cs
@@ -2,7 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Reactive.Subjects;
+using Perspex.Data;
using Perspex.Metadata;
+using Perspex.Reactive;
namespace Perspex.Styling
{
@@ -46,6 +49,7 @@ namespace Perspex.Styling
/// Gets or sets the property value.
///
[Content]
+ [AssignBinding]
public object Value
{
get;
@@ -60,15 +64,62 @@ namespace Perspex.Styling
/// An optional activator.
public void Apply(IStyle style, IStyleable control, IObservable activator)
{
- if (activator == null)
+ Contract.Requires(control != null);
+
+ var description = style?.ToString();
+
+ if (Property == null)
{
- control.SetValue(Property, Value, BindingPriority.Style);
+ throw new InvalidOperationException("Setter.Property must be set.");
+ }
+
+ var binding = Value as IBinding;
+
+ if (binding != null)
+ {
+ if (activator == null)
+ {
+ control.Bind(Property, binding);
+ }
+ else
+ {
+ var subject = binding.CreateSubject(control, Property);
+ var activated = new ActivatedSubject(activator, subject, description);
+ Bind(control, Property, binding, activated);
+ }
}
else
{
- var binding = new StyleBinding(activator, Value, style.ToString());
- control.Bind(Property, binding, BindingPriority.StyleTrigger);
+ if (activator == null)
+ {
+ control.SetValue(Property, Value, BindingPriority.Style);
+ }
+ else
+ {
+ var activated = new ActivatedValue(activator, Value, description);
+ control.Bind(Property, activated, BindingPriority.StyleTrigger);
+ }
}
}
+
+ private void Bind(
+ IStyleable control,
+ PerspexProperty property,
+ IBinding binding,
+ ISubject subject)
+ {
+ var mode = binding.Mode;
+
+ if (mode == BindingMode.Default)
+ {
+ mode = property.DefaultBindingMode;
+ }
+
+ control.Bind(
+ property,
+ subject,
+ mode,
+ binding.Priority);
+ }
}
}
diff --git a/src/Perspex.Styling/Styling/Style.cs b/src/Perspex.Styling/Styling/Style.cs
index 61b05d1412..b1337b0233 100644
--- a/src/Perspex.Styling/Styling/Style.cs
+++ b/src/Perspex.Styling/Styling/Style.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Reactive.Linq;
using Perspex.Metadata;
@@ -13,6 +14,8 @@ namespace Perspex.Styling
///
public class Style : IStyle
{
+ private static readonly IObservable True = Observable.Never().StartWith(true);
+
///
/// Initializes a new instance of the class.
///
@@ -56,9 +59,12 @@ namespace Perspex.Styling
if (match.ImmediateResult != false)
{
+ var activator = (match.ObservableResult ?? True)
+ .TakeUntil(control.StyleDetach);
+
foreach (var setter in Setters)
{
- setter.Apply(this, control, match.ObservableResult);
+ setter.Apply(this, control, activator);
}
}
}
diff --git a/src/Perspex.Styling/Styling/StyleActivator.cs b/src/Perspex.Styling/Styling/StyleActivator.cs
index c6b92a4e39..dd2601e0bb 100644
--- a/src/Perspex.Styling/Styling/StyleActivator.cs
+++ b/src/Perspex.Styling/Styling/StyleActivator.cs
@@ -4,7 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Reactive.Disposables;
+using System.Reactive;
+using System.Reactive.Linq;
namespace Perspex.Styling
{
@@ -14,134 +15,20 @@ namespace Perspex.Styling
Or,
}
- public class StyleActivator : IObservable, IDisposable
+ public static class StyleActivator
{
- private readonly ActivatorMode _mode;
-
- private readonly bool[] _values;
-
- private readonly List _subscriptions = new List();
-
- private readonly List> _observers = new List>();
-
- public StyleActivator(
- IList> inputs,
- ActivatorMode mode = ActivatorMode.And)
+ public static IObservable And(IEnumerable> inputs)
{
- int i = 0;
-
- _mode = mode;
- _values = new bool[inputs.Count];
-
- foreach (IObservable input in inputs)
- {
- int capturedIndex = i;
-
- IDisposable subscription = input.Subscribe(
- x => Update(capturedIndex, x),
- x => Finish(capturedIndex),
- () => Finish(capturedIndex));
- _subscriptions.Add(subscription);
- ++i;
- }
- }
-
- public bool CurrentValue
- {
- get;
- private set;
- }
-
- public bool HasCompleted
- {
- get;
- private set;
- }
-
- public void Dispose()
- {
- foreach (IObserver observer in _observers)
- {
- observer.OnCompleted();
- }
-
- foreach (IDisposable subscription in _subscriptions)
- {
- subscription.Dispose();
- }
- }
-
- public IDisposable Subscribe(IObserver observer)
- {
- Contract.Requires(observer != null);
-
- observer.OnNext(CurrentValue);
-
- if (HasCompleted)
- {
- observer.OnCompleted();
- return Disposable.Empty;
- }
- else
- {
- _observers.Add(observer);
- return Disposable.Create(() => _observers.Remove(observer));
- }
- }
-
- private void Update(int index, bool value)
- {
- _values[index] = value;
-
- bool current;
-
- switch (_mode)
- {
- case ActivatorMode.And:
- current = _values.All(x => x);
- break;
- case ActivatorMode.Or:
- current = _values.Any(x => x);
- break;
- default:
- throw new InvalidOperationException("Invalid Activator mode.");
- }
-
- if (current != CurrentValue)
- {
- Push(current);
- CurrentValue = current;
- }
- }
-
- private void Finish(int i)
- {
- // We can unsubscribe from everything if the completed observable:
- // - Is the only subscription.
- // - Has finished on 'false' and we're in And mode
- // - Has finished on 'true' and we're in Or mode
- var value = _values[i];
- var unsubscribe =
- (_values.Length == 1) ||
- (_mode == ActivatorMode.And ? !value : value);
-
- if (unsubscribe)
- {
- foreach (IDisposable subscription in _subscriptions)
- {
- subscription.Dispose();
- }
-
- HasCompleted = true;
- }
+ return inputs.CombineLatest()
+ .Select(values => values.All(x => x))
+ .DistinctUntilChanged();
}
- private void Push(bool value)
+ public static IObservable Or(IEnumerable> inputs)
{
- foreach (IObserver observer in _observers)
- {
- observer.OnNext(value);
- }
+ return inputs.CombineLatest()
+ .Select(values => values.Any(x => x))
+ .DistinctUntilChanged();
}
}
}
diff --git a/src/Perspex.Styling/Styling/StyleBinding.cs b/src/Perspex.Styling/Styling/StyleBinding.cs
deleted file mode 100644
index c0235161db..0000000000
--- a/src/Perspex.Styling/Styling/StyleBinding.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) The Perspex Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Reactive;
-using System.Reactive.Linq;
-
-namespace Perspex.Styling
-{
- ///
- /// Provides an observable for a style.
- ///
- ///
- /// A has two inputs: an activator observable and either an
- /// or a observable which produces the
- /// activated value. When the activator produces true, the will
- /// produce the current activated value. When the activator produces false it will produce
- /// .
- ///
- internal class StyleBinding : ObservableBase, IDescription
- {
- ///
- /// The activator.
- ///
- private readonly IObservable _activator;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The activator.
- /// The activated value.
- /// The binding description.
- public StyleBinding(
- IObservable activator,
- object activatedValue,
- string description)
- {
- _activator = activator;
- ActivatedValue = activatedValue;
- Description = description;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The activator.
- /// An observable that produces the activated value.
- /// The binding description.
- public StyleBinding(
- IObservable activator,
- IObservable source,
- string description)
- {
- _activator = activator;
- Description = description;
- Source = source;
- }
-
- ///
- /// Gets the activated value.
- ///
- public object ActivatedValue
- {
- get; }
-
- ///
- /// Gets a description of the binding.
- ///
- public string Description
- {
- get;
- }
-
- ///
- /// Gets an observable which produces the .
- ///
- public IObservable Source
- {
- get;
- }
-
- ///
- /// Notifies the provider that an observer is to receive notifications.
- ///
- /// The observer.
- /// IDisposable object used to unsubscribe from the observable sequence.
- protected override IDisposable SubscribeCore(IObserver observer)
- {
- Contract.Requires(observer != null);
-
- if (Source == null)
- {
- return _activator.Subscribe(
- active => observer.OnNext(active ? ActivatedValue : PerspexProperty.UnsetValue),
- observer.OnError,
- observer.OnCompleted);
- }
- else
- {
- return Observable
- .CombineLatest(_activator, Source, (x, y) => new { Active = x, Value = y })
- .Subscribe(x => observer.OnNext(x.Active ? x.Value : PerspexProperty.UnsetValue));
- }
- }
- }
-}
diff --git a/src/Perspex.Styling/Styling/Styler.cs b/src/Perspex.Styling/Styling/Styler.cs
index d9780de6e6..400af18252 100644
--- a/src/Perspex.Styling/Styling/Styler.cs
+++ b/src/Perspex.Styling/Styling/Styler.cs
@@ -2,8 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Linq;
-using Perspex.VisualTree;
namespace Perspex.Styling
{
@@ -11,50 +9,27 @@ namespace Perspex.Styling
{
public void ApplyStyles(IStyleable control)
{
- IVisual visual = control as IVisual;
- IStyleHost styleContainer = visual
- .GetSelfAndVisualAncestors()
- .OfType()
- .FirstOrDefault();
- IGlobalStyles global = PerspexLocator.Current.GetService();
+ var styleHost = control as IStyleHost;
- if (global != null)
+ if (styleHost != null)
{
- global.Styles.Attach(control, null);
- }
-
- if (styleContainer != null)
- {
- ApplyStyles(control, styleContainer);
+ ApplyStyles(control, styleHost);
}
}
- private void ApplyStyles(IStyleable control, IStyleHost container)
+ private void ApplyStyles(IStyleable control, IStyleHost styleHost)
{
Contract.Requires(control != null);
- Contract.Requires(container != null);
+ Contract.Requires(styleHost != null);
- IVisual visual = container as IVisual;
+ var parentContainer = styleHost.StylingParent;
- if (visual != null)
+ if (parentContainer != null)
{
- IStyleHost parentContainer = visual
- .GetVisualAncestors()
- .OfType()
- .FirstOrDefault();
-
- if (parentContainer != null)
- {
- ApplyStyles(control, parentContainer);
- }
+ ApplyStyles(control, parentContainer);
}
- container.Styles.Attach(control, container);
- }
-
- private IStyleHost GetParentContainer(IStyleHost container)
- {
- return container.GetVisualAncestors().OfType().FirstOrDefault();
+ styleHost.Styles.Attach(control, styleHost);
}
}
}
diff --git a/src/Perspex.Themes.Default/Button.paml b/src/Perspex.Themes.Default/Button.paml
index 256ac94df9..7cfd1e7f35 100644
--- a/src/Perspex.Themes.Default/Button.paml
+++ b/src/Perspex.Themes.Default/Button.paml
@@ -13,7 +13,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Perspex.Themes.Default/DropDown.paml b/src/Perspex.Themes.Default/DropDown.paml
index ac136e7bbe..d1646ac21c 100644
--- a/src/Perspex.Themes.Default/DropDown.paml
+++ b/src/Perspex.Themes.Default/DropDown.paml
@@ -2,8 +2,7 @@
+
+
+
\ No newline at end of file
diff --git a/src/Perspex.Themes.Default/Expander.paml b/src/Perspex.Themes.Default/Expander.paml
new file mode 100644
index 0000000000..158f6514fd
--- /dev/null
+++ b/src/Perspex.Themes.Default/Expander.paml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Perspex.Themes.Default/GridSplitter.paml b/src/Perspex.Themes.Default/GridSplitter.paml
index ee0757a3f5..6c5f936ec2 100644
--- a/src/Perspex.Themes.Default/GridSplitter.paml
+++ b/src/Perspex.Themes.Default/GridSplitter.paml
@@ -1,8 +1,51 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Perspex.Themes.Default/MenuItem.paml b/src/Perspex.Themes.Default/MenuItem.paml
index 68d4602a92..b9ba42eac5 100644
--- a/src/Perspex.Themes.Default/MenuItem.paml
+++ b/src/Perspex.Themes.Default/MenuItem.paml
@@ -24,7 +24,8 @@
IsVisible="False"
Margin="3"
VerticalAlignment="Center"/>
-
@@ -44,22 +45,22 @@
PlacementMode="Right"
StaysOpen="True"
IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}">
-
+
-
@@ -78,7 +79,8 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
-
@@ -89,22 +91,22 @@
-
+
-
diff --git a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj
index cd60cda586..aff93ac443 100644
--- a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj
+++ b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj
@@ -140,6 +140,13 @@
Designer
+
+ Designer
+
+
+
+ Designer
+
Designer
@@ -156,7 +163,7 @@
Designer
-
+
Designer
diff --git a/src/Perspex.Themes.Default/ScrollViewer.paml b/src/Perspex.Themes.Default/ScrollViewer.paml
index a459b11536..becdaab70e 100644
--- a/src/Perspex.Themes.Default/ScrollViewer.paml
+++ b/src/Perspex.Themes.Default/ScrollViewer.paml
@@ -5,6 +5,7 @@
diff --git a/src/Perspex.Themes.Default/TabControl.paml b/src/Perspex.Themes.Default/TabControl.paml
index d5d7ba3c9f..e02c385127 100644
--- a/src/Perspex.Themes.Default/TabControl.paml
+++ b/src/Perspex.Themes.Default/TabControl.paml
@@ -1,15 +1,50 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Perspex.Themes.Default/TabItem.paml b/src/Perspex.Themes.Default/TabItem.paml
deleted file mode 100644
index 3781d32de7..0000000000
--- a/src/Perspex.Themes.Default/TabItem.paml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/src/Perspex.Themes.Default/TabStrip.paml b/src/Perspex.Themes.Default/TabStrip.paml
index a9df7c0ca7..fbc8aefd8d 100644
--- a/src/Perspex.Themes.Default/TabStrip.paml
+++ b/src/Perspex.Themes.Default/TabStrip.paml
@@ -2,7 +2,9 @@
-
\ No newline at end of file
diff --git a/src/Perspex.Themes.Default/TabStripItem.paml b/src/Perspex.Themes.Default/TabStripItem.paml
new file mode 100644
index 0000000000..38ad8d139d
--- /dev/null
+++ b/src/Perspex.Themes.Default/TabStripItem.paml
@@ -0,0 +1,20 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Perspex.Themes.Default/ToggleButton.paml b/src/Perspex.Themes.Default/ToggleButton.paml
index cbb5341450..0311294db9 100644
--- a/src/Perspex.Themes.Default/ToggleButton.paml
+++ b/src/Perspex.Themes.Default/ToggleButton.paml
@@ -13,7 +13,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
-
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Perspex.Themes.Default/TreeViewItem.paml b/src/Perspex.Themes.Default/TreeViewItem.paml
index d7063eeba8..de5b43f092 100644
--- a/src/Perspex.Themes.Default/TreeViewItem.paml
+++ b/src/Perspex.Themes.Default/TreeViewItem.paml
@@ -12,7 +12,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Grid.Column="1">
-
diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs
index 407579b55f..5b747ae666 100644
--- a/src/Shared/PlatformSupport/AssetLoader.cs
+++ b/src/Shared/PlatformSupport/AssetLoader.cs
@@ -15,26 +15,102 @@ namespace Perspex.Shared.PlatformSupport
///
public class AssetLoader : IAssetLoader
{
- private static readonly Dictionary AssemblyNameCache
- = new Dictionary();
+ class AssemblyDescriptor
+ {
+ public AssemblyDescriptor(Assembly assembly)
+ {
+ Assembly = assembly;
+
+ if (assembly != null)
+ {
+ Resources = assembly.GetManifestResourceNames()
+ .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
+ Name = assembly.GetName().Name;
+ }
+ }
+
+ public Assembly Assembly { get; }
+ public Dictionary Resources { get; }
+ public string Name { get; }
+ }
+
+
+ private static readonly Dictionary AssemblyNameCache
+ = new Dictionary();
- private Assembly _defaultAssembly;
+ private readonly AssemblyDescriptor _defaultAssembly;
public AssetLoader(Assembly assembly = null)
{
- _defaultAssembly = assembly;
+ if (assembly == null)
+ assembly = Assembly.GetEntryAssembly();
+ if (assembly != null)
+ _defaultAssembly = new AssemblyDescriptor(assembly);
}
+
- static Assembly GetAssembly(string name)
+
+ AssemblyDescriptor GetAssembly(string name)
{
- Assembly rv;
+ if (name == null)
+ return _defaultAssembly;
+ AssemblyDescriptor rv;
if (!AssemblyNameCache.TryGetValue(name, out rv))
AssemblyNameCache[name] = rv =
- AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == name)
- ?? Assembly.Load(name);
+ new AssemblyDescriptor(AppDomain.CurrentDomain.GetAssemblies()
+ .FirstOrDefault(a => a.GetName().Name == name)
+ ?? Assembly.Load(name));
return rv;
}
+ interface IAssetDescriptor
+ {
+ Stream GetStream();
+ }
+
+
+ class AssemblyResourceDescriptor : IAssetDescriptor
+ {
+ private readonly Assembly _asm;
+ private readonly string _name;
+
+ public AssemblyResourceDescriptor(Assembly asm, string name)
+ {
+ _asm = asm;
+ _name = name;
+ }
+
+ public Stream GetStream()
+ {
+ return _asm.GetManifestResourceStream(_name);
+ }
+ }
+
+
+ IAssetDescriptor GetAsset(Uri uri)
+ {
+ if (!uri.IsAbsoluteUri || uri.Scheme == "resm")
+ {
+ var qs = uri.Query.TrimStart('?')
+ .Split(new[] {'&'}, StringSplitOptions.RemoveEmptyEntries)
+ .Select(p => p.Split('='))
+ .ToDictionary(p => p[0], p => p[1]);
+ //TODO: Replace _defaultAssembly by current one (need support from OmniXAML)
+ var asm = _defaultAssembly;
+ if (qs.ContainsKey("assembly"))
+ asm = GetAssembly(qs["assembly"]);
+
+ if (asm == null && _defaultAssembly == null)
+ throw new ArgumentException(
+ "No defaultAssembly, entry assembly or explicit assembly specified, don't know where to look up for the resource, try specifiyng assembly explicitly");
+
+ IAssetDescriptor rv;
+ asm.Resources.TryGetValue(uri.AbsolutePath, out rv);
+ return rv;
+ }
+ throw new ArgumentException($"Invalid uri, see https://github.com/Perspex/Perspex/issues/282#issuecomment-166982104", nameof(uri));
+ }
+
///
/// Checks if an asset with the specified URI exists.
///
@@ -42,11 +118,7 @@ namespace Perspex.Shared.PlatformSupport
/// True if the asset could be found; otherwise false.
public bool Exists(Uri uri)
{
- var parts = uri.AbsolutePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
- var asm = parts.Length == 1 ? (_defaultAssembly ?? Assembly.GetEntryAssembly()) : GetAssembly(parts[0]);
- var typeName = parts[parts.Length == 1 ? 0 : 1];
- var rv = asm.GetManifestResourceStream(typeName);
- return rv != null;
+ return GetAsset(uri) != null;
}
///
@@ -59,18 +131,10 @@ namespace Perspex.Shared.PlatformSupport
///
public Stream Open(Uri uri)
{
- var parts = uri.AbsolutePath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
- var asm = parts.Length == 1 ? (_defaultAssembly ?? Assembly.GetEntryAssembly()) : GetAssembly(parts[0]);
- var typeName = parts[parts.Length == 1 ? 0 : 1];
- var rv = asm.GetManifestResourceStream(typeName);
- if (rv == null)
- {
-#if DEBUG
- var names = asm.GetManifestResourceNames().ToList();
-#endif
+ var asset = GetAsset(uri);
+ if (asset == null)
throw new FileNotFoundException($"The resource {uri} could not be found.");
- }
- return rv;
+ return asset.GetStream();
}
}
}
diff --git a/src/Shared/RenderHelpers/ArcToHelper.cs b/src/Shared/RenderHelpers/ArcToHelper.cs
index b7db56a4d8..ccf21b9199 100644
--- a/src/Shared/RenderHelpers/ArcToHelper.cs
+++ b/src/Shared/RenderHelpers/ArcToHelper.cs
@@ -948,11 +948,11 @@ namespace Perspex.RenderHelpers
else if (degree == 2)
{
double k = (yBDot * (xB - xA) - xBDot * (yB - yA)) / (xADot * yBDot - yADot * xBDot);
- path.QuadTo(new Point(xA + k * xADot, yA + k * yADot), new Point(xB, yB));
+ path.QuadraticBezierTo(new Point(xA + k * xADot, yA + k * yADot), new Point(xB, yB));
}
else
{
- path.BezierTo(
+ path.CubicBezierTo(
new Point(xA + alpha * xADot, yA + alpha * yADot),
new Point(xB - alpha * xBDot, yB - alpha * yBDot),
new Point(xB, yB)
diff --git a/src/Shared/RenderHelpers/QuadBezierHelper.cs b/src/Shared/RenderHelpers/QuadBezierHelper.cs
index 8c6c9f3d63..33b5042de7 100644
--- a/src/Shared/RenderHelpers/QuadBezierHelper.cs
+++ b/src/Shared/RenderHelpers/QuadBezierHelper.cs
@@ -4,10 +4,10 @@ namespace Perspex.RenderHelpers
{
static class QuadBezierHelper
{
- public static void QuadTo(IStreamGeometryContextImpl context, Point current, Point controlPoint, Point endPoint)
+ public static void QuadraticBezierTo(IStreamGeometryContextImpl context, Point current, Point controlPoint, Point endPoint)
{
//(s, (s + 2c)/ 3, (e + 2c)/ 3, e)
- context.BezierTo((current + 2*controlPoint)/3, (endPoint + 2*controlPoint)/3, endPoint);
+ context.CubicBezierTo((current + 2*controlPoint)/3, (endPoint + 2*controlPoint)/3, endPoint);
}
}
}
diff --git a/src/Shared/RenderHelpers/TileBrushImplHelper.cs b/src/Shared/RenderHelpers/TileBrushImplHelper.cs
index e0d21a3d7b..663bc7ca9b 100644
--- a/src/Shared/RenderHelpers/TileBrushImplHelper.cs
+++ b/src/Shared/RenderHelpers/TileBrushImplHelper.cs
@@ -23,8 +23,8 @@ namespace Perspex.RenderHelpers
private readonly Size _imageSize;
private readonly VisualBrush _visualBrush;
private readonly ImageBrush _imageBrush;
- private Matrix _transform;
- private Rect _drawRect;
+ private readonly Matrix _transform;
+ private readonly Rect _drawRect;
public bool IsValid { get; }
@@ -47,14 +47,17 @@ namespace Perspex.RenderHelpers
return;
var layoutable = visual as ILayoutable;
- if (layoutable?.IsArrangeValid == false)
+ if (layoutable != null)
{
- layoutable.Measure(Size.Infinity);
- layoutable.Arrange(new Rect(layoutable.DesiredSize));
+ if (layoutable.IsArrangeValid == false)
+ {
+ layoutable.Measure(Size.Infinity);
+ layoutable.Arrange(new Rect(layoutable.DesiredSize));
+ }
+
+ _imageSize = layoutable.Bounds.Size;
+ IsValid = true;
}
- //I have no idea why are we using layoutable after `as` cast, but it was in original VisualBrush code by @grokys
- _imageSize = layoutable.Bounds.Size;
- IsValid = true;
}
else
return;
@@ -105,8 +108,10 @@ namespace Perspex.RenderHelpers
}
else if (_visualBrush != null)
{
- ctx.FillRectangle(Brushes.Black, new Rect(new Point(0, 0), IntermediateSize));
- ctx.Render(_visualBrush.Visual);
+ using (ctx.PushPostTransform(Matrix.CreateTranslation(-_visualBrush.Visual.Bounds.Position)))
+ {
+ ctx.Render(_visualBrush.Visual);
+ }
}
}
}
diff --git a/src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj b/src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj
index 8a27aec08c..614b0210bb 100644
--- a/src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj
+++ b/src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj
@@ -16,7 +16,7 @@
Resources\Resource.Designer.cs
Off
True
- v5.1
+ v6.0
Properties\AndroidManifest.xml
diff --git a/src/Skia/Perspex.Skia.Android/Perspex.Skia.Android.csproj b/src/Skia/Perspex.Skia.Android/Perspex.Skia.Android.csproj
index 2146b7a9e9..2916c4059e 100644
--- a/src/Skia/Perspex.Skia.Android/Perspex.Skia.Android.csproj
+++ b/src/Skia/Perspex.Skia.Android/Perspex.Skia.Android.csproj
@@ -15,7 +15,7 @@
Resources\Resource.Designer.cs
Off
True
- v5.1
+ v6.0
true
diff --git a/src/Skia/Perspex.Skia.iOS.TestApp/MainView.cs b/src/Skia/Perspex.Skia.iOS.TestApp/MainView.cs
index fe818d9d84..ff8f6995c9 100644
--- a/src/Skia/Perspex.Skia.iOS.TestApp/MainView.cs
+++ b/src/Skia/Perspex.Skia.iOS.TestApp/MainView.cs
@@ -1,7 +1,9 @@
using System;
+using System.Diagnostics;
using System.Drawing;
using CoreAnimation;
using CoreGraphics;
+using CoreMedia;
using Foundation;
using Perspex.Media;
using Perspex.Platform;
@@ -10,54 +12,40 @@ using UIKit;
namespace Perspex.Skia.iOS.TestApp
{
[Register("MainView")]
- public class MainView : UIView
+ public class MainView : SkiaView
{
- CAEAGLLayer _layer = new CAEAGLLayer();
private IRenderTarget _target;
-
+ FormattedText _text;
public MainView()
- {
- Initialize();
- }
-
- public MainView(RectangleF bounds) : base(bounds)
- {
- Initialize();
- }
-
-
- void Initialize()
{
AutoresizingMask = UIViewAutoresizing.All;
- Layer.AddSublayer(_layer);
-
SkiaPlatform.Initialize();
_target = PerspexLocator.Current.GetService()
- .CreateRenderer(new PlatformHandle(_layer.DangerousRetain().Handle, "Layer"));
+ .CreateRenderer(PerspexPlatformHandle);
+ UpdateText(0);
}
-
double _radians = 0;
- public override void TouchesMoved(NSSet touches, UIEvent evt)
+
+ void UpdateText(int fps)
{
- foreach (var touch in touches)
- {
- var loc = ((UITouch) touch).LocationInView(this);
- _radians = (loc.X + loc.Y)/100;
- }
- SetNeedsDisplay();
- base.TouchesMoved(touches, evt);
+ _text?.Dispose();
+ _text = new FormattedText("FPS: " + fps, "Arial", 15, FontStyle.Normal, TextAlignment.Left,
+ FontWeight.Normal);
}
- public override void Draw(CGRect rect)
+ double _lastFps;
+ int _frames;
+ Stopwatch St = Stopwatch.StartNew();
+ protected override void Draw()
{
- _layer.Bounds = new CGRect(0, 0, Bounds.Width, Bounds.Height);
+ _radians += 0.02;
var scale = UIScreen.MainScreen.Scale;
int width = (int) (Bounds.Width*scale), height = (int) (Bounds.Height*scale);
using (var ctx = _target.CreateDrawingContext())
{
ctx.FillRectangle(Brushes.Green, new Rect(0, 0, width, height));
-
+ ctx.DrawText(Brushes.Red, new Point(50, 50), _text);
var rc = new Rect(0, 0, width/3, height/3);
using (ctx.PushPostTransform(
Perspex.Matrix.CreateTranslation(-width/6, -width/6)*
@@ -74,7 +62,16 @@ namespace Perspex.Skia.iOS.TestApp
}, rc, 5);
}
}
-
+ _frames++;
+ var now = St.Elapsed.TotalSeconds;
+ var elapsed = now - _lastFps;
+ if (elapsed > 1)
+ {
+ UpdateText((int) (_frames/elapsed));
+ _frames = 0;
+ _lastFps = now;
+ }
+ DrawOnNextFrame();
}
}
}
\ No newline at end of file
diff --git a/src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj b/src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj
index 1d2fd23ae4..a583ceb5b2 100644
--- a/src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj
+++ b/src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj
@@ -37,6 +37,7 @@
+
diff --git a/src/Skia/Perspex.Skia.iOS/SkiaView.cs b/src/Skia/Perspex.Skia.iOS/SkiaView.cs
new file mode 100644
index 0000000000..7d4efea4aa
--- /dev/null
+++ b/src/Skia/Perspex.Skia.iOS/SkiaView.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using CoreAnimation;
+using CoreGraphics;
+using Foundation;
+using GLKit;
+using ObjCRuntime;
+using OpenGLES;
+using Perspex.Platform;
+using UIKit;
+
+namespace Perspex.Skia.iOS
+{
+ public abstract class SkiaView : GLKView
+ {
+ [DllImport("__Internal")]
+ static extern IntPtr GetPerspexEAGLContext();
+
+ bool _drawQueued;
+ CADisplayLink _link;
+ static EAGLContext GetContext()
+ {
+ //Ensure initialization
+ MethodTable.Instance.SetOption((MethodTable.Option)0x10009999, IntPtr.Zero);
+ var ctx = GetPerspexEAGLContext();
+ var rv = Runtime.GetNSObject(ctx);
+ rv.DangerousRetain();
+ return rv;
+ }
+
+
+ protected SkiaView(Action registerFrame) : base(UIScreen.MainScreen.ApplicationFrame, GetContext())
+ {
+ registerFrame(OnFrame);
+ }
+
+ protected SkiaView() : base(UIScreen.MainScreen.ApplicationFrame, GetContext())
+ {
+ (_link = CADisplayLink.Create(() => OnFrame())).AddToRunLoop(NSRunLoop.Main, NSRunLoop.NSDefaultRunLoopMode);
+ }
+
+ protected void OnFrame()
+ {
+ if (_drawQueued)
+ {
+ _drawQueued = false;
+ Display();
+ }
+ }
+
+ protected void DrawOnNextFrame()
+ {
+ _drawQueued = true;
+ }
+
+ protected IPlatformHandle PerspexPlatformHandle { get; }
+ = new PlatformHandle(IntPtr.Zero, "Null (iOS-specific)");
+
+
+ protected abstract void Draw();
+
+ public override void Draw(CGRect rect)
+ {
+ Draw();
+ }
+ }
+}
diff --git a/src/Skia/Perspex.Skia/BitmapImpl.cs b/src/Skia/Perspex.Skia/BitmapImpl.cs
index 75c2ae5213..83d89feac0 100644
--- a/src/Skia/Perspex.Skia/BitmapImpl.cs
+++ b/src/Skia/Perspex.Skia/BitmapImpl.cs
@@ -10,9 +10,6 @@ namespace Perspex.Skia
{
class BitmapImpl : PerspexHandleHolder, IRenderTargetBitmapImpl
{
- private int width;
- private int height;
-
public void Save(string fileName)
{
var ext = Path.GetExtension(fileName)?.ToLower();
diff --git a/src/Skia/Perspex.Skia/DrawingContextImpl.cs b/src/Skia/Perspex.Skia/DrawingContextImpl.cs
index eec1e44e10..405f021515 100644
--- a/src/Skia/Perspex.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Perspex.Skia/DrawingContextImpl.cs
@@ -46,7 +46,7 @@ namespace Perspex.Skia
using (var stroke = pen?.Brush != null ? CreateBrush(pen, size) : null)
{
MethodTable.Instance.DrawGeometry(Handle, impl.Path.Handle, fill != null ? fill.Brush : null,
- stroke != null ? stroke.Brush : null);
+ stroke != null ? stroke.Brush : null, impl.FillRule == FillRule.EvenOdd);
}
Transform = oldTransform;
}
@@ -187,7 +187,7 @@ namespace Perspex.Skia
public void PopOpacity() => _settings->Opacity = _opacityStack.Pop();
private Matrix _currentTransform = Matrix.Identity;
- private float[] _fmatrix = new float[6];
+ private readonly float[] _fmatrix = new float[6];
public Matrix Transform
{
get { return _currentTransform; }
diff --git a/src/Skia/Perspex.Skia/FormattedTextImpl.cs b/src/Skia/Perspex.Skia/FormattedTextImpl.cs
index 211da7bbc1..808935e99e 100644
--- a/src/Skia/Perspex.Skia/FormattedTextImpl.cs
+++ b/src/Skia/Perspex.Skia/FormattedTextImpl.cs
@@ -24,9 +24,9 @@ namespace Perspex.Skia
return new FormattedTextImpl(handle, pShared, text);
}
}
-
- List _lines = new List();
- List _rects = new List();
+
+ readonly List _lines = new List();
+ readonly List _rects = new List();
Size _size;
public IEnumerable GetLines()
@@ -114,6 +114,8 @@ namespace Perspex.Skia
get { return _constraint; }
set
{
+ if(_constraint == value)
+ return;
_constraint = value;
_shared->WidthConstraint = (_constraint.Width != double.PositiveInfinity)
diff --git a/src/Skia/Perspex.Skia/MethodTable.cs b/src/Skia/Perspex.Skia/MethodTable.cs
index cee36bbded..ba21588333 100644
--- a/src/Skia/Perspex.Skia/MethodTable.cs
+++ b/src/Skia/Perspex.Skia/MethodTable.cs
@@ -67,7 +67,7 @@ namespace Perspex.Skia
public _DisposePath DisposePath;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- public delegate void _DrawGeometry(IntPtr ctx, IntPtr path, void* fill, void* stroke);
+ public delegate void _DrawGeometry(IntPtr ctx, IntPtr path, void* fill, void* stroke, bool useEvenOdd);
public _DrawGeometry DrawGeometry;
diff --git a/src/Skia/Perspex.Skia/NativeBrush.cs b/src/Skia/Perspex.Skia/NativeBrush.cs
index 885425d768..9d667f0c51 100644
--- a/src/Skia/Perspex.Skia/NativeBrush.cs
+++ b/src/Skia/Perspex.Skia/NativeBrush.cs
@@ -69,7 +69,7 @@ namespace Perspex.Skia
private readonly NativeBrushPool _pool;
public NativeBrush* Brush;
- List _disposables = new List();
+ readonly List _disposables = new List();
public NativeBrushContainer(NativeBrushPool pool)
{
@@ -96,7 +96,7 @@ namespace Perspex.Skia
class NativeBrushPool
{
public static NativeBrushPool Instance { get; } = new NativeBrushPool();
- Stack _pool = new Stack();
+ readonly Stack _pool = new Stack();
public void Return(NativeBrushContainer c)
{
diff --git a/src/Skia/Perspex.Skia/PerspexHandleHolder.cs b/src/Skia/Perspex.Skia/PerspexHandleHolder.cs
index 20b6ca929f..d8f52e8f61 100644
--- a/src/Skia/Perspex.Skia/PerspexHandleHolder.cs
+++ b/src/Skia/Perspex.Skia/PerspexHandleHolder.cs
@@ -4,7 +4,7 @@ namespace Perspex.Skia
{
abstract class PerspexHandleHolder : IDisposable
{
- private IntPtr _handle;
+ private readonly IntPtr _handle;
public IntPtr Handle
{
diff --git a/src/Skia/Perspex.Skia/StreamGeometryImpl.cs b/src/Skia/Perspex.Skia/StreamGeometryImpl.cs
index 0a516c139e..3c62e65e7d 100644
--- a/src/Skia/Perspex.Skia/StreamGeometryImpl.cs
+++ b/src/Skia/Perspex.Skia/StreamGeometryImpl.cs
@@ -99,7 +99,7 @@ namespace Perspex.Skia
_currentPoint = startPoint;
}
- public void BezierTo(Point point1, Point point2, Point point3)
+ public void CubicBezierTo(Point point1, Point point2, Point point3)
{
_elements.Add(new SkiaGeometryElement
{
@@ -111,7 +111,7 @@ namespace Perspex.Skia
_currentPoint = point3;
}
- public void QuadTo(Point control, Point endPoint)
+ public void QuadraticBezierTo(Point control, Point endPoint)
{
_elements.Add(new SkiaGeometryElement
{
@@ -140,6 +140,13 @@ namespace Perspex.Skia
Flag = isClosed
});
}
+
+ public void SetFillRule(FillRule fillRule)
+ {
+ _geometryImpl.FillRule = fillRule;
+ }
}
+
+ public FillRule FillRule { get; set; }
}
}
diff --git a/src/Skia/getnatives.sh b/src/Skia/getnatives.sh
new file mode 100755
index 0000000000..7ceace9db3
--- /dev/null
+++ b/src/Skia/getnatives.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+rm -rf native
+mkdir -p native
+cd native
+wget `cat ../native.url` -O native.zip
+unzip native.zip
+
diff --git a/src/Skia/native.url b/src/Skia/native.url
index 697513ae7a..737e1d3d06 100644
--- a/src/Skia/native.url
+++ b/src/Skia/native.url
@@ -1 +1 @@
-https://dl.dropboxusercontent.com/u/18301199/Perspex.Skia/abi-0002.zip
+https://dl.dropboxusercontent.com/u/18301199/Perspex.Skia/abi-0003.zip
diff --git a/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs b/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs
index 649b5d48ee..31fe2beeeb 100644
--- a/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs
+++ b/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs
@@ -5,16 +5,24 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
+using System.Windows.Media;
using JetBrains.Annotations;
namespace Perspex.Designer.AppHost
{
public class HostedAppModel : INotifyPropertyChanged
{
+ private readonly PerspexAppHost _host;
private IntPtr _nativeWindowHandle;
private string _error;
private string _errorDetails;
+ internal HostedAppModel(PerspexAppHost host)
+ {
+ _host = host;
+ Background = Settings.Background;
+ }
+
public IntPtr NativeWindowHandle
{
get { return _nativeWindowHandle; }
@@ -48,12 +56,39 @@ namespace Perspex.Designer.AppHost
}
}
+ public string Background
+ {
+ get { return _background; }
+ set
+ {
+ if (value == _background) return;
+ _background = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public IReadOnlyList AvailableScalingFactors => new List() {1, 2, 4, 8};
+
+ public double CurrentScalingFactor
+ {
+ get { return _currentScalingFactor; }
+ set
+ {
+ _currentScalingFactor = value;
+ _host.Api.SetScalingFactor(value);
+ }
+ }
+
public void SetError(string error, string details = null)
{
Error = error;
ErrorDetails = details;
}
+ double _currentScalingFactor = 1;
+ private string _color;
+ private string _background;
+
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
diff --git a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs
index a26808a507..c3ca657baf 100644
--- a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs
+++ b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs
@@ -12,23 +12,24 @@ using Perspex.Designer.Comm;
using Perspex.Designer.InProcDesigner;
using Perspex.Designer.Metadata;
using Timer = System.Windows.Forms.Timer;
+using Perspex.DesignerSupport;
namespace Perspex.Designer.AppHost
{
class PerspexAppHost
{
private string _appDir;
- private CommChannel _comm;
+ private readonly CommChannel _comm;
private string _lastXaml;
private string _currentXaml;
- private Func _xamlReader;
private bool _initSuccess;
- private HostedAppModel _appModel = new HostedAppModel();
+ private readonly HostedAppModel _appModel;
private Control _window;
public PerspexAppHost(CommChannel channel)
{
_comm = channel;
+ _appModel = new HostedAppModel(this);
}
public void Start()
@@ -92,8 +93,6 @@ namespace Perspex.Designer.AppHost
PerspexDesignerMetadata BuildMetadata(List asms, Type xmlNsAttr)
{
-
-
var rv = new PerspexDesignerMetadata()
{
@@ -177,7 +176,6 @@ namespace Perspex.Designer.AppHost
private void DoInit(string targetExe, StringBuilder logger)
{
- MessageBox.Show("WAT");
_appDir = Path.GetFullPath(Path.GetDirectoryName(targetExe));
Directory.SetCurrentDirectory(_appDir);
Action log = s =>
@@ -201,34 +199,10 @@ namespace Perspex.Designer.AppHost
log("Looking up Perspex types");
BuildMetadataAndSendMessageAsync(asms);
- var syncContext = LookupType("Perspex.Threading.PerspexSynchronizationContext");
- syncContext.GetProperty("AutoInstall", BindingFlags.Public | BindingFlags.Static).SetValue(null, false);
-
- var app = Activator.CreateInstance(LookupType("Perspex.Application"));
- app.GetType()
- .GetMethod("RegisterServices", BindingFlags.NonPublic | BindingFlags.Instance)
- .Invoke(app, null);
-
- LookupStaticMethod("Perspex.Direct2D1.Direct2D1Platform", "Initialize").Invoke(null, null);
- LookupStaticMethod("Perspex.Win32.Win32Platform", "InitializeEmbedded").Invoke(null, null);
-
- app.GetType().GetProperty("Styles").GetSetMethod(true)
- .Invoke(app, new[] {Activator.CreateInstance(LookupType("Perspex.Themes.Default.DefaultTheme"))});
-
-
- dynamic dispatcher =
- LookupType("Perspex.Threading.Dispatcher")
- .GetProperty("UIThread", BindingFlags.Static | BindingFlags.Public)
- .GetValue(null);
-
-
-
- var xamlFactory = Activator.CreateInstance(LookupType("Perspex.Markup.Xaml.Context.PerspexParserFactory"));
-
- dynamic xamlLoader =
- LookupType("OmniXaml.XamlLoader", "OmniXaml.XamlXmlLoader").GetConstructors().First().Invoke(new object[] {xamlFactory});
-
- _xamlReader = (stream) => xamlLoader.Load(stream);
+ log("Initializing built-in designer");
+ var dic = new Dictionary();
+ Api = new DesignerApi(dic) {OnResize = OnResize, OnWindowCreated = OnWindowCreated};
+ LookupStaticMethod("Perspex.DesignerSupport.DesignerAssist", "Init").Invoke(null, new object[] {dic});
_window = new Control
{
@@ -243,15 +217,19 @@ namespace Perspex.Designer.AppHost
};
_window.CreateControl();
- new Timer {Interval = 10, Enabled = true}.Tick += delegate
- {
- dispatcher.RunJobs();
- };
new Timer {Interval = 200, Enabled = true}.Tick += delegate { XamlUpdater(); };
_comm.SendMessage(new WindowCreatedMessage(_window.Handle));
_initSuccess = true;
}
+ private void OnWindowCreated(IntPtr hWnd)
+ {
+ _appModel.NativeWindowHandle = hWnd;
+ }
+
+
+ public DesignerApi Api { get; set; }
+
bool ValidateXml(string xml)
{
@@ -270,6 +248,11 @@ namespace Perspex.Designer.AppHost
return true;
}
+ private void OnResize()
+ {
+
+ }
+
void XamlUpdater()
{
if (!_initSuccess)
@@ -283,27 +266,10 @@ namespace Perspex.Designer.AppHost
_appModel.SetError("Invalid markup");
return;
}
-
-
try
{
- const string windowType = "Perspex.Controls.Window";
-
- var root = _xamlReader(new MemoryStream(Encoding.UTF8.GetBytes(_currentXaml)));
- dynamic window = root;
- if (root.GetType().FullName != windowType)
- {
- window = Activator.CreateInstance(LookupType(windowType));
- window.Content = root;
- }
+ Api.UpdateXaml(_currentXaml);
- var w = ((object) (window.PlatformImpl)).Prop("Handle");
- if (!(w is IntPtr))
- w = w.Prop("Handle");
-
- var hWnd = (IntPtr) w;
- _appModel.NativeWindowHandle = hWnd;
- window.Show();
_appModel.SetError(null);
}
catch (Exception e)
@@ -311,6 +277,8 @@ namespace Perspex.Designer.AppHost
_appModel.SetError("XAML load error", e.ToString());
}
}
+
+
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
diff --git a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs
index 549e8a3e79..e598e4cb6e 100644
--- a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs
+++ b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs
@@ -11,20 +11,39 @@ namespace Perspex.Designer.AppHost
{
class WindowHost : UserControl
{
- public WindowHost()
+ private readonly bool _supportScroll;
+
+ public WindowHost(bool supportScroll)
{
- AutoScroll = true;
- VerticalScroll.Enabled = true;
- HorizontalScroll.Enabled = true;
+ _supportScroll = supportScroll;
+ if (_supportScroll)
+ {
+ AutoScroll = true;
+ VerticalScroll.Enabled = true;
+ HorizontalScroll.Enabled = true;
+ }
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
- Text = "ScrollableArea";
Controls.Add(_windowHost);
_windowHost.Anchor = AnchorStyles.None;
- _timer.Tick += delegate { FixWindow(); };
+ if (!supportScroll)
+ _windowHost.Visible = false;
+ _timer.Tick += delegate
+ {
+ ReloadSettings();
+ FixWindow();
+ };
}
- private Control _windowHost = new Control() {Text = "WindowWrapper"};
- private Timer _timer = new Timer {Enabled = true, Interval = 50};
+ private void ReloadSettings()
+ {
+ var bkg = Settings.Background;
+ var color = System.Drawing.ColorTranslator.FromHtml(bkg);
+ if (BackColor != color)
+ BackColor = color;
+ }
+
+ private readonly Control _windowHost = new Control() {Text = "WindowWrapper"};
+ private readonly Timer _timer = new Timer {Enabled = true, Interval = 50};
private IntPtr _hWnd;
private int _desiredWidth;
private int _desiredHeight;
@@ -43,6 +62,12 @@ namespace Perspex.Designer.AppHost
base.WndProc(ref m);
}
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ using (var b = new SolidBrush(BackColor))
+ e.Graphics.FillRectangle(b, e.ClipRectangle);
+ }
+
void FixPosition()
{
var newScrollSize = new Size(_desiredWidth, _desiredHeight);
@@ -75,7 +100,7 @@ namespace Perspex.Designer.AppHost
_hWnd = hWnd;
if (_hWnd != IntPtr.Zero)
{
- WinApi.SetParent(hWnd, _windowHost.Handle);
+ WinApi.SetParent(hWnd, _supportScroll ? _windowHost.Handle : Handle);
FixWindow();
}
}
@@ -84,25 +109,34 @@ namespace Perspex.Designer.AppHost
{
if (_hWnd != IntPtr.Zero)
{
- WinApi.RECT rc;
- WinApi.GetWindowRect(_hWnd, out rc);
- _desiredWidth = rc.Right - rc.Left;
- _desiredHeight = rc.Bottom - rc.Top;
- var pt = _windowHost.PointToClient(new Point(rc.Left, rc.Top));
+ if (_supportScroll)
+ {
+ WinApi.RECT rc;
+ WinApi.GetWindowRect(_hWnd, out rc);
+ _desiredWidth = rc.Right - rc.Left;
+ _desiredHeight = rc.Bottom - rc.Top;
+ var pt = _windowHost.PointToClient(new Point(rc.Left, rc.Top));
- if (!(pt.Y == 0 && pt.X == 0 && _desiredWidth == _windowHost.Width && _desiredHeight == _windowHost.Height))
+ if (
+ !(pt.Y == 0 && pt.X == 0 && _desiredWidth == _windowHost.Width &&
+ _desiredHeight == _windowHost.Height))
+ {
+ _windowHost.Width = _desiredWidth;
+ _windowHost.Height = _desiredHeight;
+ WinApi.MoveWindow(_hWnd, 0, 0, _desiredWidth, _desiredHeight, true);
+ }
+ FixPosition();
+ }
+ else
{
- _windowHost.Width = _desiredWidth;
- _windowHost.Height = _desiredHeight;
- WinApi.MoveWindow(_hWnd, 0, 0, _desiredWidth, _desiredHeight, true);
+ WinApi.MoveWindow(_hWnd, 0, 0, Width, Height, true);
}
- FixPosition();
}
}
protected override void OnResize(EventArgs e)
{
- FixPosition();
+ FixWindow();
base.OnResize(e);
}
}
diff --git a/src/Windows/Perspex.Designer/Comm/CommChannel.cs b/src/Windows/Perspex.Designer/Comm/CommChannel.cs
index 4e7bc641c9..41d613d4d6 100644
--- a/src/Windows/Perspex.Designer/Comm/CommChannel.cs
+++ b/src/Windows/Perspex.Designer/Comm/CommChannel.cs
@@ -13,13 +13,13 @@ using System.Windows.Threading;
namespace Perspex.Designer.Comm
{
- internal class CommChannel : IDisposable
+ class CommChannel : IDisposable
{
private readonly BinaryReader _input;
private readonly BinaryWriter _output;
- private SynchronizationContext _dispatcher;
- TaskCompletionSource _terminating = new TaskCompletionSource();
- private BlockingCollection _outputQueue = new BlockingCollection();
+ private readonly SynchronizationContext _dispatcher;
+ readonly TaskCompletionSource _terminating = new TaskCompletionSource();
+ private readonly BlockingCollection _outputQueue = new BlockingCollection();
public event Action OnMessage;
public event Action Disposed;
public event Action Exception;
diff --git a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml
index 38e09688cb..721cb83e04 100644
--- a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml
+++ b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml
@@ -4,17 +4,31 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:appHost="clr-namespace:Perspex.Designer.AppHost"
+ xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{d:DesignInstance appHost:HostedAppModel}">
-
-
-
-
+
+
+
+
+ 00%
+
+
+
+
+
+
+
+
+
+
(View details)
-
-
-
+
+
+
+
diff --git a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs
index 801b088730..c936b2faa8 100644
--- a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs
+++ b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs
@@ -1,18 +1,21 @@
using System;
using System.Collections.Generic;
+using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
-using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
+using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Perspex.Designer.AppHost;
+using TextBox = System.Windows.Controls.TextBox;
+using UserControl = System.Windows.Controls.UserControl;
namespace Perspex.Designer.InProcDesigner
{
@@ -30,7 +33,7 @@ namespace Perspex.Designer.InProcDesigner
InitializeComponent();
DataContext = _appModel;
_appModel.PropertyChanged += ModelPropertyChanged;
- WindowHostControl.Child = _host = new WindowHost();
+ WindowHostControl.Child = _host = new WindowHost(true);
HandleVisibility();
HandleWindow();
@@ -80,5 +83,16 @@ namespace Perspex.Designer.InProcDesigner
};
wnd.ShowDialog();
}
+
+ private void ColorPicker_OnClick(object sender, MouseButtonEventArgs e)
+ {
+ var dlg = new ColorDialog() {Color = ColorTranslator.FromHtml(Settings.Background)};
+ if (dlg.ShowDialog(_host) == DialogResult.OK)
+ {
+ var color = ColorTranslator.ToHtml(dlg.Color);
+ _appModel.Background = color;
+ Settings.Background = color;
+ }
+ }
}
}
diff --git a/src/Windows/Perspex.Designer/Perspex.Designer.csproj b/src/Windows/Perspex.Designer/Perspex.Designer.csproj
index f5dd8bd8ac..42131b06fd 100644
--- a/src/Windows/Perspex.Designer/Perspex.Designer.csproj
+++ b/src/Windows/Perspex.Designer/Perspex.Designer.csproj
@@ -67,6 +67,9 @@
+
+ AppHost\DesignerApi.cs
+
App.xaml
@@ -94,6 +97,7 @@
+
diff --git a/src/Windows/Perspex.Designer/PerspexDesigner.xaml b/src/Windows/Perspex.Designer/PerspexDesigner.xaml
index 673b4ca52c..96679be499 100644
--- a/src/Windows/Perspex.Designer/PerspexDesigner.xaml
+++ b/src/Windows/Perspex.Designer/PerspexDesigner.xaml
@@ -6,12 +6,12 @@
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
-
+
-
+
diff --git a/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs b/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs
index 2f944d7251..1b394a0f7d 100644
--- a/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs
+++ b/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs
@@ -73,41 +73,21 @@ namespace Perspex.Designer
{
if (e.PropertyName == nameof(ProcessHost.WindowHandle))
{
- if (NativeContainer.Content != null)
+ if (NativeContainer.Child != null)
{
- var wndHost = ((HwndHost) NativeContainer.Content);
- NativeContainer.Content = null;
- wndHost?.Dispose();
+ var child = NativeContainer.Child;
+ NativeContainer.Child = null;
+ child.Dispose();
}
- if (_host.WindowHandle != IntPtr.Zero)
- {
- var host = new NativeWindowHost(_host.WindowHandle);
- NativeContainer.Content = host;
- }
- }
- }
+ NativeContainer.Child = new WindowHost(false);
+ var wndHost = ((WindowHost) NativeContainer.Child);
+ wndHost.SetWindow(_host.WindowHandle);
- class NativeWindowHost :HwndHost
- {
- private readonly IntPtr _hWnd;
- public NativeWindowHost(IntPtr hWnd)
- {
- _hWnd = hWnd;
- }
-
- protected override HandleRef BuildWindowCore(HandleRef hwndParent)
- {
- WinApi.SetParent(_hWnd, hwndParent.Handle);
- return new HandleRef(this, _hWnd);
- }
-
- protected override void DestroyWindowCore(HandleRef hwnd)
- {
- WinApi.SendMessage(hwnd.Handle, WinApi.WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
}
+
public void KillProcess()
{
_host.Kill();
diff --git a/src/Windows/Perspex.Designer/Settings.cs b/src/Windows/Perspex.Designer/Settings.cs
new file mode 100644
index 0000000000..534dd2d5f1
--- /dev/null
+++ b/src/Windows/Perspex.Designer/Settings.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Win32;
+
+namespace Perspex.Designer
+{
+ static class Settings
+ {
+ static readonly string Root = @"HKEY_CURRENT_USER\Software\PerspexUI\Designer";
+ public static string Background
+ {
+ get { return Registry.GetValue(Root, "Background", "#f3f3f3")?.ToString() ?? "#f3f3f3"; }
+ set { Registry.SetValue(Root, "Background", value); }
+ }
+ }
+}
diff --git a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs
index fc4418f682..0876a2a8a3 100644
--- a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs
+++ b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs
@@ -9,6 +9,7 @@ using Perspex.Media;
using SharpDX;
using SharpDX.Direct2D1;
using IBitmap = Perspex.Media.Imaging.IBitmap;
+using SharpDX.Mathematics.Interop;
namespace Perspex.Direct2D1.Media
{
@@ -218,11 +219,11 @@ namespace Perspex.Direct2D1.Media
_renderTarget.FillRoundedRectangle(
new RoundedRectangle
{
- Rect = new RectangleF(
+ Rect = new RawRectangleF(
(float)rect.X,
(float)rect.Y,
- (float)rect.Width,
- (float)rect.Height),
+ (float)rect.Right,
+ (float)rect.Bottom),
RadiusX = cornerRadius,
RadiusY = cornerRadius
},
@@ -247,7 +248,7 @@ namespace Perspex.Direct2D1.Media
_renderTarget.PopAxisAlignedClip();
}
- Stack _layers = new Stack();
+ readonly Stack _layers = new Stack();
private readonly Stack _layerPool = new Stack();
///
/// Pushes an opacity value.
@@ -260,8 +261,8 @@ namespace Perspex.Direct2D1.Media
{
var parameters = new LayerParameters
{
- ContentBounds = RectangleF.Infinite,
- MaskTransform = Matrix3x2.Identity,
+ ContentBounds = PrimitiveExtensions.RectangleInfinite,
+ MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
Opacity = (float) opacity,
};
@@ -295,8 +296,8 @@ namespace Perspex.Direct2D1.Media
var solidColorBrush = brush as Perspex.Media.SolidColorBrush;
var linearGradientBrush = brush as Perspex.Media.LinearGradientBrush;
var radialGradientBrush = brush as Perspex.Media.RadialGradientBrush;
- var imageBrush = brush as ImageBrush;
- var visualBrush = brush as VisualBrush;
+ var imageBrush = brush as Perspex.Media.ImageBrush;
+ var visualBrush = brush as Perspex.Media.VisualBrush;
if (solidColorBrush != null)
{
diff --git a/src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs
index 6ace607107..22f8b94491 100644
--- a/src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs
+++ b/src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs
@@ -53,9 +53,7 @@ namespace Perspex.Direct2D1.Media
}
}
- public DWrite.TextLayout TextLayout
- {
- get; }
+ public DWrite.TextLayout TextLayout { get; }
public void Dispose()
{
@@ -70,8 +68,8 @@ namespace Perspex.Direct2D1.Media
public TextHitTestResult HitTestPoint(Point point)
{
- SharpDX.Bool isTrailingHit;
- SharpDX.Bool isInside;
+ SharpDX.Mathematics.Interop.RawBool isTrailingHit;
+ SharpDX.Mathematics.Interop.RawBool isInside;
var result = TextLayout.HitTestPoint(
(float)point.X,
diff --git a/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs
index b67378990c..caf58044d6 100644
--- a/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs
+++ b/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs
@@ -1,11 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Perspex.Direct2D1.Media
{
@@ -16,17 +12,39 @@ namespace Perspex.Direct2D1.Media
SharpDX.Direct2D1.RenderTarget target,
Size destinationSize)
{
- var gradientStops = brush.GradientStops.Select(s => new SharpDX.Direct2D1.GradientStop { Color = s.Color.ToDirect2D(), Position = (float)s.Offset }).ToArray();
+ if (brush.GradientStops.Count == 0)
+ {
+ return;
+ }
- Point startPoint = brush.StartPoint.ToPixels(destinationSize);
- Point endPoint = brush.EndPoint.ToPixels(destinationSize);
+ var gradientStops = brush.GradientStops.Select(s => new SharpDX.Direct2D1.GradientStop
+ {
+ Color = s.Color.ToDirect2D(),
+ Position = (float)s.Offset
+ }).ToArray();
- PlatformBrush = new SharpDX.Direct2D1.LinearGradientBrush(
+ var startPoint = brush.StartPoint.ToPixels(destinationSize);
+ var endPoint = brush.EndPoint.ToPixels(destinationSize);
+
+ using (var stops = new SharpDX.Direct2D1.GradientStopCollection(
target,
- new SharpDX.Direct2D1.LinearGradientBrushProperties { StartPoint = startPoint.ToSharpDX(), EndPoint = endPoint.ToSharpDX() },
- new SharpDX.Direct2D1.BrushProperties { Opacity = (float)brush.Opacity, Transform = target.Transform },
- new SharpDX.Direct2D1.GradientStopCollection(target, gradientStops, brush.SpreadMethod.ToDirect2D())
- );
+ gradientStops,
+ brush.SpreadMethod.ToDirect2D()))
+ {
+ PlatformBrush = new SharpDX.Direct2D1.LinearGradientBrush(
+ target,
+ new SharpDX.Direct2D1.LinearGradientBrushProperties
+ {
+ StartPoint = startPoint.ToSharpDX(),
+ EndPoint = endPoint.ToSharpDX()
+ },
+ new SharpDX.Direct2D1.BrushProperties
+ {
+ Opacity = (float)brush.Opacity,
+ Transform = PrimitiveExtensions.Matrix3x2Identity,
+ },
+ stops);
+ }
}
}
}
diff --git a/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs b/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs
index 297dcdb581..806883c172 100644
--- a/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs
+++ b/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs
@@ -5,6 +5,7 @@ using System;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.DirectWrite;
+using SharpDX.Mathematics.Interop;
namespace Perspex.Direct2D1.Media
{
@@ -34,6 +35,7 @@ namespace Perspex.Direct2D1.Media
public void Dispose()
{
+ Shadow?.Dispose();
}
public Result DrawGlyphRun(
@@ -53,7 +55,7 @@ namespace Perspex.Direct2D1.Media
_context.CreateBrush(wrapper.Brush, new Size()).PlatformBrush;
_renderTarget.DrawGlyphRun(
- new Vector2(baselineOriginX, baselineOriginY),
+ new RawVector2 { X = baselineOriginX, Y = baselineOriginY },
glyphRun,
brush,
measuringMode);
@@ -81,7 +83,7 @@ namespace Perspex.Direct2D1.Media
throw new NotImplementedException();
}
- public Matrix3x2 GetCurrentTransform(object clientDrawingContext)
+ public RawMatrix3x2 GetCurrentTransform(object clientDrawingContext)
{
return _renderTarget.Transform;
}
diff --git a/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs
index f0b21c7ae7..80cc87821a 100644
--- a/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs
+++ b/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs
@@ -1,11 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Perspex.Direct2D1.Media
{
@@ -16,20 +12,45 @@ namespace Perspex.Direct2D1.Media
SharpDX.Direct2D1.RenderTarget target,
Size destinationSize)
{
- var gradientStops = brush.GradientStops.Select(s => new SharpDX.Direct2D1.GradientStop { Color = s.Color.ToDirect2D(), Position = (float)s.Offset }).ToArray();
+ if (brush.GradientStops.Count == 0)
+ {
+ return;
+ }
- Point centerPoint = brush.Center.ToPixels(destinationSize);
- Point GradientOriginOffset = brush.GradientOrigin.ToPixels(destinationSize);
+ var gradientStops = brush.GradientStops.Select(s => new SharpDX.Direct2D1.GradientStop
+ {
+ Color = s.Color.ToDirect2D(),
+ Position = (float)s.Offset
+ }).ToArray();
+
+ var centerPoint = brush.Center.ToPixels(destinationSize);
+ var GradientOriginOffset = brush.GradientOrigin.ToPixels(destinationSize);
+
// Note: Direct2D supports RadiusX and RadiusY but Cairo backend supports only Radius property
- double radiusX = brush.Radius;
- double radiusY = brush.Radius;
+ var radiusX = brush.Radius;
+ var radiusY = brush.Radius;
- PlatformBrush = new SharpDX.Direct2D1.RadialGradientBrush(
+ using (var stops = new SharpDX.Direct2D1.GradientStopCollection(
target,
- new SharpDX.Direct2D1.RadialGradientBrushProperties { Center = centerPoint.ToSharpDX(), GradientOriginOffset = GradientOriginOffset.ToSharpDX(), RadiusX = (float)radiusX, RadiusY = (float)radiusY },
- new SharpDX.Direct2D1.BrushProperties { Opacity = (float)brush.Opacity, Transform = target.Transform },
- new SharpDX.Direct2D1.GradientStopCollection(target, gradientStops, brush.SpreadMethod.ToDirect2D())
- );
+ gradientStops,
+ brush.SpreadMethod.ToDirect2D()))
+ {
+ PlatformBrush = new SharpDX.Direct2D1.RadialGradientBrush(
+ target,
+ new SharpDX.Direct2D1.RadialGradientBrushProperties
+ {
+ Center = centerPoint.ToSharpDX(),
+ GradientOriginOffset = GradientOriginOffset.ToSharpDX(),
+ RadiusX = (float)radiusX,
+ RadiusY = (float)radiusY
+ },
+ new SharpDX.Direct2D1.BrushProperties
+ {
+ Opacity = (float)brush.Opacity,
+ Transform = PrimitiveExtensions.Matrix3x2Identity,
+ },
+ stops);
+ }
}
}
}
diff --git a/src/Windows/Perspex.Direct2D1/Media/SolidColorBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/SolidColorBrushImpl.cs
index ce48231891..82a7db516d 100644
--- a/src/Windows/Perspex.Direct2D1/Media/SolidColorBrushImpl.cs
+++ b/src/Windows/Perspex.Direct2D1/Media/SolidColorBrushImpl.cs
@@ -9,7 +9,7 @@ namespace Perspex.Direct2D1.Media
{
PlatformBrush = new SharpDX.Direct2D1.SolidColorBrush(
target,
- brush?.Color.ToDirect2D() ?? new SharpDX.Color4(),
+ brush?.Color.ToDirect2D() ?? new SharpDX.Mathematics.Interop.RawColor4(),
new SharpDX.Direct2D1.BrushProperties
{
Opacity = brush != null ? (float)brush.Opacity : 1.0f,
diff --git a/src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs b/src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs
index bf72373423..8dc3c6231d 100644
--- a/src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs
+++ b/src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs
@@ -1,8 +1,10 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using Perspex.Media;
using Perspex.Platform;
using SharpDX.Direct2D1;
+using SweepDirection = SharpDX.Direct2D1.SweepDirection;
namespace Perspex.Direct2D1.Media
{
@@ -37,7 +39,7 @@ namespace Perspex.Direct2D1.Media
_sink.BeginFigure(startPoint.ToSharpDX(), isFilled ? FigureBegin.Filled : FigureBegin.Hollow);
}
- public void BezierTo(Point point1, Point point2, Point point3)
+ public void CubicBezierTo(Point point1, Point point2, Point point3)
{
_sink.AddBezier(new BezierSegment
{
@@ -47,7 +49,7 @@ namespace Perspex.Direct2D1.Media
});
}
- public void QuadTo(Point control, Point dest)
+ public void QuadraticBezierTo(Point control, Point dest)
{
_sink.AddQuadraticBezier(new QuadraticBezierSegment
{
@@ -66,6 +68,11 @@ namespace Perspex.Direct2D1.Media
_sink.EndFigure(isClosed ? FigureEnd.Closed : FigureEnd.Open);
}
+ public void SetFillRule(FillRule fillRule)
+ {
+ _sink.SetFillMode(fillRule == FillRule.EvenOdd ? FillMode.Alternate : FillMode.Winding);
+ }
+
public void Dispose()
{
_sink.Close();
diff --git a/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs
index 0f0d929b9e..ab803e473e 100644
--- a/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs
+++ b/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs
@@ -25,7 +25,10 @@ namespace Perspex.Direct2D1.Media
using (var intermediate = new BitmapRenderTarget(target, CompatibleRenderTargetOptions.None, helper.IntermediateSize.ToSharpDX()))
{
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext())
+ {
+ intermediate.Clear(null);
helper.DrawIntermediate(ctx);
+ }
PlatformBrush = new BitmapBrush(
target,
@@ -36,20 +39,21 @@ namespace Perspex.Direct2D1.Media
}
- protected static BrushProperties GetBrushProperties(TileBrush brush, Rect destinationRect)
+ private static BrushProperties GetBrushProperties(TileBrush brush, Rect destinationRect)
{
+ var tileTransform =
+ brush.TileMode != TileMode.None ?
+ Matrix.CreateTranslation(destinationRect.X, destinationRect.Y) :
+ Matrix.Identity;
+
return new BrushProperties
{
Opacity = (float)brush.Opacity,
- Transform = brush.TileMode != TileMode.None ?
- SharpDX.Matrix3x2.Translation(
- (float)destinationRect.X,
- (float)destinationRect.Y) :
- SharpDX.Matrix3x2.Identity,
+ Transform = tileTransform.ToDirect2D(),
};
}
- protected static BitmapBrushProperties GetBitmapBrushProperties(TileBrush brush)
+ private static BitmapBrushProperties GetBitmapBrushProperties(TileBrush brush)
{
var tileMode = brush.TileMode;
@@ -60,12 +64,12 @@ namespace Perspex.Direct2D1.Media
};
}
- protected static ExtendMode GetExtendModeX(TileMode tileMode)
+ private static ExtendMode GetExtendModeX(TileMode tileMode)
{
return (tileMode & TileMode.FlipX) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap;
}
- protected static ExtendMode GetExtendModeY(TileMode tileMode)
+ private static ExtendMode GetExtendModeY(TileMode tileMode)
{
return (tileMode & TileMode.FlipY) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap;
}
diff --git a/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj b/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj
index 38e9b9ed8e..491badd112 100644
--- a/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj
+++ b/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj
@@ -23,6 +23,7 @@
prompt
4
bin\Debug\Perspex.Direct2D1.XML
+ CS1591
pdbonly
@@ -34,17 +35,17 @@
bin\Release\Perspex.Direct2D1.XML
-
- False
- $(SharpDXPackageBinDir)\SharpDX.dll
+
+ ..\..\..\packages\SharpDX.3.0.0\lib\net45\SharpDX.dll
+ True
-
- False
- $(SharpDXPackageBinDir)\SharpDX.Direct2D1.dll
+
+ ..\..\..\packages\SharpDX.Direct2D1.3.0.0\lib\net45\SharpDX.Direct2D1.dll
+ True
-
- False
- $(SharpDXPackageBinDir)\SharpDX.DXGI.dll
+
+ ..\..\..\packages\SharpDX.DXGI.3.0.0\lib\net45\SharpDX.DXGI.dll
+ True
@@ -116,13 +117,6 @@
-
-
-
- This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
+
\ No newline at end of file
diff --git a/tests/Perspex.LeakTests/Properties/AssemblyInfo.cs b/tests/Perspex.LeakTests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..cb943198bc
--- /dev/null
+++ b/tests/Perspex.LeakTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Perspex.LeakTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Perspex.LeakTests")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("e1aa3dbf-9056-4530-9376-18119a7a3ffe")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/tests/Perspex.LeakTests/Readme.txt b/tests/Perspex.LeakTests/Readme.txt
new file mode 100644
index 0000000000..2a6087b5b3
--- /dev/null
+++ b/tests/Perspex.LeakTests/Readme.txt
@@ -0,0 +1,8 @@
+Memory Leak Tests
+-----------------
+
+These tests use JetBrains' dotMemory Unit. When run in a normal test runner, they will always pass.
+
+To run the tests, you need to have dotMemory/ReSharper and install the XUnit plugin. You should
+then be able to run the tests using Resharper -> Unit Tests -> Run all tests from solution under
+dotMemory Unit.
\ No newline at end of file
diff --git a/tests/Perspex.LeakTests/TestApp.cs b/tests/Perspex.LeakTests/TestApp.cs
new file mode 100644
index 0000000000..8c08c57fc3
--- /dev/null
+++ b/tests/Perspex.LeakTests/TestApp.cs
@@ -0,0 +1,45 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Moq;
+using Perspex.Controls.UnitTests;
+using Perspex.Platform;
+using Perspex.Shared.PlatformSupport;
+using Perspex.Themes.Default;
+using Ploeh.AutoFixture;
+using Ploeh.AutoFixture.AutoMoq;
+
+namespace Perspex.LeakTests
+{
+ internal class TestApp : Application
+ {
+ private TestApp()
+ {
+ RegisterServices();
+
+ var fixture = new Fixture().Customize(new AutoMoqCustomization());
+ var windowImpl = new Mock();
+ var renderInterface = fixture.Create();
+ var threadingInterface = Mock.Of(x =>
+ x.CurrentThreadIsLoopThread == true);
+
+ PerspexLocator.CurrentMutable
+ .Bind().ToConstant(new AssetLoader())
+ .Bind().ToConstant(new PclPlatformWrapper())
+ .Bind().ToConstant(renderInterface)
+ .Bind().ToConstant(threadingInterface)
+ .Bind().ToConstant(new Mock().Object)
+ .Bind().ToConstant(new WindowingPlatformMock(() => windowImpl.Object));
+
+ Styles = new DefaultTheme();
+ }
+
+ public static void Initialize()
+ {
+ if (Current == null)
+ {
+ new TestApp();
+ }
+ }
+ }
+}
diff --git a/tests/Perspex.LeakTests/app.config b/tests/Perspex.LeakTests/app.config
new file mode 100644
index 0000000000..4ecf794192
--- /dev/null
+++ b/tests/Perspex.LeakTests/app.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Perspex.LeakTests/packages.config b/tests/Perspex.LeakTests/packages.config
new file mode 100644
index 0000000000..35e293bdbc
--- /dev/null
+++ b/tests/Perspex.LeakTests/packages.config
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Perspex.Markup.UnitTests/ControlLocatorTests.cs b/tests/Perspex.Markup.UnitTests/ControlLocatorTests.cs
index 14286479c2..19dbe5200b 100644
--- a/tests/Perspex.Markup.UnitTests/ControlLocatorTests.cs
+++ b/tests/Perspex.Markup.UnitTests/ControlLocatorTests.cs
@@ -71,6 +71,36 @@ namespace Perspex.Markup.UnitTests
Assert.Equal(0, root.NameScopeUnregisteredSubscribers);
}
+ [Fact]
+ public void Track_By_Name_Should_Track_Removal_And_Readd()
+ {
+ StackPanel panel;
+ TextBlock target;
+ TextBlock relativeTo;
+
+ var root = new TestRoot
+ {
+ Child = panel = new StackPanel
+ {
+ Children = new Controls.Controls
+ {
+ (target = new TextBlock { Name = "target" }),
+ (relativeTo = new TextBlock { Name = "start" }),
+ }
+ }
+ };
+
+ var locator = ControlLocator.Track(relativeTo, "target");
+ var result = new List();
+ locator.Subscribe(x => result.Add(x));
+
+ var other = new TextBlock { Name = "target" };
+ panel.Children.Remove(target);
+ panel.Children.Add(other);
+
+ Assert.Equal(new[] { target, null, other }, result);
+ }
+
[Fact]
public void Track_By_Name_Should_Find_Control_When_Tree_Changed()
{
diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs
new file mode 100644
index 0000000000..cfbc4bc29f
--- /dev/null
+++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs
@@ -0,0 +1,89 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reactive;
+using System.Reactive.Subjects;
+using Microsoft.Reactive.Testing;
+using Perspex.Markup.Data;
+using Xunit;
+
+namespace Perspex.Markup.UnitTests.Data
+{
+ public class ExpressionObserverTests_Lifetime
+ {
+ [Fact(Skip = "Not working yet")]
+ public void Should_Complete_When_Source_Observable_Completes()
+ {
+ var source = new BehaviorSubject(1);
+ var target = new ExpressionObserver(source, "Foo");
+ var completed = false;
+
+ target.Subscribe(_ => { }, () => completed = true);
+ source.OnCompleted();
+
+ Assert.True(completed);
+ }
+
+ [Fact]
+ public void Should_Unsubscribe_From_Source_Observable()
+ {
+ var scheduler = new TestScheduler();
+ var source = scheduler.CreateColdObservable(
+ OnNext(1, new { Foo = "foo" }));
+ var target = new ExpressionObserver(source, "Foo");
+ var result = new List();
+
+ using (target.Subscribe(x => result.Add(x)))
+ using (target.Subscribe(_ => { }))
+ {
+ scheduler.Start();
+ }
+
+ Assert.Equal(new[] { PerspexProperty.UnsetValue, "foo" }, result);
+ Assert.Equal(1, source.Subscriptions.Count);
+ Assert.NotEqual(Subscription.Infinite, source.Subscriptions[0].Unsubscribe);
+ }
+
+ [Fact]
+ public void Should_Unsubscribe_From_Update_Observable()
+ {
+ var scheduler = new TestScheduler();
+ var update = scheduler.CreateColdObservable();
+ var target = new ExpressionObserver(() => new { Foo = "foo" }, "Foo", update);
+ var result = new List();
+
+ using (target.Subscribe(x => result.Add(x)))
+ using (target.Subscribe(_ => { }))
+ {
+ scheduler.Start();
+ }
+
+ Assert.Equal(new[] { "foo" }, result);
+ Assert.Equal(1, update.Subscriptions.Count);
+ Assert.NotEqual(Subscription.Infinite, update.Subscriptions[0].Unsubscribe);
+ }
+
+ [Fact]
+ public void Should_Set_Node_Target_To_Null_On_Unsubscribe()
+ {
+ var target = new ExpressionObserver(new { Foo = "foo" }, "Foo");
+ var result = new List();
+
+ using (target.Subscribe(x => result.Add(x)))
+ using (target.Subscribe(_ => { }))
+ {
+ Assert.NotNull(target.Node.Target);
+ }
+
+ Assert.Equal(new[] { "foo" }, result);
+ Assert.Null(target.Node.Target);
+ }
+
+ private Recorded> OnNext(long time, object value)
+ {
+ return new Recorded>(time, Notification.CreateOnNext(value));
+ }
+ }
+}
diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs
index fd1583106c..fe9336ebe6 100644
--- a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs
+++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Reactive;
using System.Reactive.Linq;
+using System.Reactive.Subjects;
using Microsoft.Reactive.Testing;
using Perspex.Markup.Data;
using Xunit;
@@ -193,13 +194,14 @@ namespace Perspex.Markup.UnitTests.Data
public void Empty_Expression_Should_Track_Root()
{
var data = new Class1 { Foo = "foo" };
- var target = new ExpressionObserver(() => data.Foo, "");
+ var update = new Subject();
+ var target = new ExpressionObserver(() => data.Foo, "", update);
var result = new List();
target.Subscribe(x => result.Add(x));
data.Foo = "bar";
- target.UpdateRoot();
+ update.OnNext(Unit.Default);
Assert.Equal(new[] { "foo", "bar" }, result);
}
@@ -286,14 +288,15 @@ namespace Perspex.Markup.UnitTests.Data
var first = new Class1 { Foo = "foo" };
var second = new Class1 { Foo = "bar" };
var root = first;
- var target = new ExpressionObserver(() => root, "Foo");
+ var update = new Subject();
+ var target = new ExpressionObserver(() => root, "Foo", update);
var result = new List();
var sub = target.Subscribe(x => result.Add(x));
root = second;
- target.UpdateRoot();
+ update.OnNext(Unit.Default);
root = null;
- target.UpdateRoot();
+ update.OnNext(Unit.Default);
Assert.Equal(new[] { "foo", "bar", PerspexProperty.UnsetValue }, result);
@@ -355,7 +358,7 @@ namespace Perspex.Markup.UnitTests.Data
{
}
- public Recorded> OnNext(long time, object value)
+ private Recorded> OnNext(long time, object value)
{
return new Recorded>(time, Notification.CreateOnNext(value));
}
diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs
index c38085ef5f..2f11bf1551 100644
--- a/tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs
+++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs
@@ -1,7 +1,11 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
+using System.ComponentModel;
+using System.Globalization;
using System.Reactive.Linq;
+using Moq;
using Perspex.Markup.Data;
using Xunit;
@@ -33,6 +37,8 @@ namespace Perspex.Markup.UnitTests.Data
[Fact]
public async void Should_Convert_Get_String_To_Double()
{
+ CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
+
var data = new Class1 { StringValue = "5.6" };
var target = new ExpressionSubject(new ExpressionObserver(data, "StringValue"), typeof(double));
var result = await target.Take(1);
@@ -63,6 +69,8 @@ namespace Perspex.Markup.UnitTests.Data
[Fact]
public void Should_Convert_Set_String_To_Double()
{
+ CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
+
var data = new Class1 { StringValue = "5.6" };
var target = new ExpressionSubject(new ExpressionObserver(data, "StringValue"), typeof(double));
@@ -74,6 +82,8 @@ namespace Perspex.Markup.UnitTests.Data
[Fact]
public async void Should_Convert_Get_Double_To_String()
{
+ CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
+
var data = new Class1 { DoubleValue = 5.6 };
var target = new ExpressionSubject(new ExpressionObserver(data, "DoubleValue"), typeof(string));
var result = await target.Take(1);
@@ -84,6 +94,8 @@ namespace Perspex.Markup.UnitTests.Data
[Fact]
public void Should_Convert_Set_Double_To_String()
{
+ CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
+
var data = new Class1 { DoubleValue = 5.6 };
var target = new ExpressionSubject(new ExpressionObserver(data, "DoubleValue"), typeof(string));
@@ -125,11 +137,50 @@ namespace Perspex.Markup.UnitTests.Data
Assert.Equal(0, data.DoubleValue);
}
- private class Class1
+ [Fact]
+ public void Should_Pass_ConverterParameter_To_Convert()
{
+ var data = new Class1 { DoubleValue = 5.6 };
+ var converter = new Mock();
+ var target = new ExpressionSubject(
+ new ExpressionObserver(data, "DoubleValue"),
+ typeof(string),
+ converter.Object,
+ "foo");
+
+ target.Subscribe(_ => { });
+
+ converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.CurrentUICulture));
+ }
+
+ [Fact]
+ public void Should_Pass_ConverterParameter_To_ConvertBack()
+ {
+ var data = new Class1 { DoubleValue = 5.6 };
+ var converter = new Mock();
+ var target = new ExpressionSubject(
+ new ExpressionObserver(data, "DoubleValue"),
+ typeof(string),
+ converter.Object,
+ "foo");
+
+ target.OnNext("bar");
+
+ converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.CurrentUICulture));
+ }
+
+ private class Class1 : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
public string StringValue { get; set; }
public double DoubleValue { get; set; }
+
+ public void RaisePropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
}
}
}
diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj
index 1bc9e09112..161e79f6f6 100644
--- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj
+++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj
@@ -39,6 +39,10 @@
True
+
+ ..\..\packages\Moq.4.2.1507.0118\lib\net40\Moq.dll
+ True
+
@@ -80,6 +84,7 @@
+
diff --git a/tests/Perspex.Markup.UnitTests/TestRoot.cs b/tests/Perspex.Markup.UnitTests/TestRoot.cs
index fa63095210..26ac0ed0da 100644
--- a/tests/Perspex.Markup.UnitTests/TestRoot.cs
+++ b/tests/Perspex.Markup.UnitTests/TestRoot.cs
@@ -5,12 +5,13 @@ using System;
using Perspex.Controls;
using Perspex.Platform;
using Perspex.Rendering;
+using Perspex.Styling;
namespace Perspex.Markup.UnitTests
{
- public class TestRoot : Decorator, IRenderRoot, INameScope
+ public class TestRoot : Decorator, IRenderRoot, INameScope, IStyleRoot
{
- private NameScope _nameScope = new NameScope();
+ private readonly NameScope _nameScope = new NameScope();
event EventHandler INameScope.Registered
{
diff --git a/tests/Perspex.Markup.UnitTests/UnitTestSynchronizationContext.cs b/tests/Perspex.Markup.UnitTests/UnitTestSynchronizationContext.cs
index 1a1eaf4ecf..20dfec9168 100644
--- a/tests/Perspex.Markup.UnitTests/UnitTestSynchronizationContext.cs
+++ b/tests/Perspex.Markup.UnitTests/UnitTestSynchronizationContext.cs
@@ -45,8 +45,8 @@ namespace Perspex.Markup.UnitTests
public class Scope : IDisposable
{
- private SynchronizationContext _old;
- private UnitTestSynchronizationContext _new;
+ private readonly SynchronizationContext _old;
+ private readonly UnitTestSynchronizationContext _new;
public Scope(SynchronizationContext old, UnitTestSynchronizationContext n)
{
diff --git a/tests/Perspex.Markup.UnitTests/packages.config b/tests/Perspex.Markup.UnitTests/packages.config
index 2a659c426f..4e0bc991f5 100644
--- a/tests/Perspex.Markup.UnitTests/packages.config
+++ b/tests/Perspex.Markup.UnitTests/packages.config
@@ -1,5 +1,6 @@
+
diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs
index 9b6230df17..270b6d3d9b 100644
--- a/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs
+++ b/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs
@@ -2,11 +2,14 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Reactive;
using Moq;
using OmniXaml;
using OmniXaml.ObjectAssembler.Commands;
using OmniXaml.TypeConversion;
using OmniXaml.Typing;
+using Perspex.Collections;
+using Perspex.Controls;
using Perspex.Markup.Xaml.Converters;
using Perspex.Styling;
using Xunit;
@@ -74,7 +77,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Converters
public static readonly PerspexProperty FooProperty =
PerspexProperty.Register("Foo");
- public Classes Classes
+ public IPerspexReadOnlyList Classes
{
get { throw new NotImplementedException(); }
}
@@ -93,6 +96,8 @@ namespace Perspex.Markup.Xaml.UnitTests.Converters
{
get { throw new NotImplementedException(); }
}
+
+ IObservable IStyleable.StyleDetach { get; }
}
private class AttachedOwner
diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs
index 9184b485b7..64efaa07de 100644
--- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs
+++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs
@@ -1,13 +1,12 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
using Moq;
using Perspex.Controls;
+using Perspex.Data;
using Perspex.Markup.Data;
using Perspex.Markup.Xaml.Data;
+using ReactiveUI;
using Xunit;
namespace Perspex.Markup.Xaml.UnitTests.Data
@@ -17,101 +16,101 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
[Fact]
public void OneWay_Binding_Should_Be_Set_Up()
{
- var target = CreateTarget();
+ var source = new Source { Foo = "foo" };
+ var target = new TextBlock { DataContext = source };
var binding = new Binding
{
Path = "Foo",
Mode = BindingMode.OneWay,
};
- binding.Bind(target.Object, TextBox.TextProperty);
+ target.Bind(TextBox.TextProperty, binding);
- target.Verify(x => x.Bind(
- TextBox.TextProperty,
- It.IsAny>(),
- BindingPriority.LocalValue));
+ Assert.Equal("foo", target.Text);
+ source.Foo = "bar";
+ Assert.Equal("bar", target.Text);
+ target.Text = "baz";
+ Assert.Equal("bar", source.Foo);
}
[Fact]
public void TwoWay_Binding_Should_Be_Set_Up()
{
- var target = CreateTarget();
+ var source = new Source { Foo = "foo" };
+ var target = new TextBlock { DataContext = source };
var binding = new Binding
{
Path = "Foo",
Mode = BindingMode.TwoWay,
};
- binding.Bind(target.Object, TextBox.TextProperty);
+ target.Bind(TextBox.TextProperty, binding);
- target.Verify(x => x.BindTwoWay(
- TextBox.TextProperty,
- It.IsAny>(),
- BindingPriority.LocalValue));
+ Assert.Equal("foo", target.Text);
+ source.Foo = "bar";
+ Assert.Equal("bar", target.Text);
+ target.Text = "baz";
+ Assert.Equal("baz", source.Foo);
}
[Fact]
public void OneTime_Binding_Should_Be_Set_Up()
{
- var dataContext = new BehaviorSubject(null);
- var expression = new BehaviorSubject(null);
- var target = CreateTarget(dataContext: dataContext);
+ var source = new Source { Foo = "foo" };
+ var target = new TextBlock { DataContext = source };
var binding = new Binding
{
Path = "Foo",
Mode = BindingMode.OneTime,
};
- binding.Bind(target.Object, TextBox.TextProperty, expression);
+ target.Bind(TextBox.TextProperty, binding);
- target.Verify(x => x.SetValue(
- (PerspexProperty)TextBox.TextProperty,
- null,
- BindingPriority.LocalValue));
- target.ResetCalls();
-
- expression.OnNext("foo");
- dataContext.OnNext(1);
-
- target.Verify(x => x.SetValue(
- (PerspexProperty)TextBox.TextProperty,
- "foo",
- BindingPriority.LocalValue));
+ Assert.Equal("foo", target.Text);
+ source.Foo = "bar";
+ Assert.Equal("foo", target.Text);
+ target.Text = "baz";
+ Assert.Equal("bar", source.Foo);
}
[Fact]
public void OneWayToSource_Binding_Should_Be_Set_Up()
{
- var textObservable = new Mock>();
- var expression = new Mock>();
- var target = CreateTarget(text: textObservable.Object);
+ var source = new Source { Foo = "foo" };
+ var target = new TextBlock { DataContext = source, Text = "bar" };
var binding = new Binding
{
Path = "Foo",
Mode = BindingMode.OneWayToSource,
};
- binding.Bind(target.Object, TextBox.TextProperty, expression.Object);
+ target.Bind(TextBox.TextProperty, binding);
- textObservable.Verify(x => x.Subscribe(expression.Object));
+ Assert.Equal("bar", source.Foo);
+ target.Text = "baz";
+ Assert.Equal("baz", source.Foo);
+ source.Foo = "quz";
+ Assert.Equal("baz", target.Text);
}
[Fact]
public void Default_BindingMode_Should_Be_Used()
{
- var target = CreateTarget(null);
+ // Default for TextBox.Text is two-way.
+ var source = new Source { Foo = "foo" };
+ var target = new TextBlock { DataContext = source };
var binding = new Binding
{
Path = "Foo",
};
- binding.Bind(target.Object, TextBox.TextProperty);
+ target.Bind(TextBox.TextProperty, binding);
- // Default for TextBox.Text is two-way.
- target.Verify(x => x.BindTwoWay(
- TextBox.TextProperty,
- It.IsAny>(),
- BindingPriority.LocalValue));
+ Assert.Equal("foo", target.Text);
+ source.Foo = "bar";
+ Assert.Equal("bar", target.Text);
+ target.Text = "baz";
+ Assert.Equal("baz", source.Foo);
}
[Fact]
@@ -130,7 +129,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Header",
};
- binding.Bind(parent.Child, Control.DataContextProperty);
+ parent.Child.Bind(Control.DataContextProperty, binding);
Assert.Equal("Foo", parent.Child.DataContext);
@@ -139,16 +138,38 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Assert.Equal("Bar", parent.Child.DataContext);
}
+ [Fact]
+ public void DataContext_Binding_Should_Track_Parent()
+ {
+ var parent = new Decorator
+ {
+ DataContext = new { Foo = "foo" },
+ };
+
+ var child = new Control();
+
+ var binding = new Binding
+ {
+ Path = "Foo",
+ };
+
+ child.Bind(Control.DataContextProperty, binding);
+
+ Assert.Null(child.DataContext);
+ parent.Child = child;
+ Assert.Equal("foo", child.DataContext);
+ }
+
[Fact]
public void Should_Use_DefaultValueConverter_When_No_Converter_Specified()
{
- var target = CreateTarget(null);
+ var target = new TextBlock(); ;
var binding = new Binding
{
Path = "Foo",
};
- var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
+ var result = binding.CreateSubject(target, TextBox.TextProperty);
Assert.IsType(((ExpressionSubject)result).Converter);
}
@@ -156,7 +177,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
[Fact]
public void Should_Use_Supplied_Converter()
{
- var target = CreateTarget(null);
+ var target = new TextBlock();
var converter = new Mock();
var binding = new Binding
{
@@ -164,11 +185,28 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Foo",
};
- var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
+ var result = binding.CreateSubject(target, TextBox.TextProperty);
Assert.Same(converter.Object, ((ExpressionSubject)result).Converter);
}
+ [Fact]
+ public void Should_Pass_ConverterParameter_To_Supplied_Converter()
+ {
+ var target = new TextBlock();
+ var converter = new Mock();
+ var binding = new Binding
+ {
+ Converter = converter.Object,
+ ConverterParameter = "foo",
+ Path = "Bar",
+ };
+
+ var result = binding.CreateSubject(target, TextBox.TextProperty);
+
+ Assert.Same("foo", ((ExpressionSubject)result).ConverterParameter);
+ }
+
///
/// Tests a problem discovered with ListBox with selection.
///
@@ -200,8 +238,8 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
};
// Bind Foo and Bar to the VM.
- fooBinding.Bind(target, OldDataContextTest.FooProperty);
- barBinding.Bind(target, OldDataContextTest.BarProperty);
+ target.Bind(OldDataContextTest.FooProperty, fooBinding);
+ target.Bind(OldDataContextTest.BarProperty, barBinding);
target.DataContext = vm;
// Make sure the control's Foo and Bar properties are read from the VM
@@ -222,24 +260,15 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Assert.Equal(2, vm.Bar);
}
- private Mock CreateTarget(object dataContext)
+ public class Source : ReactiveObject
{
- return CreateTarget(dataContext: Observable.Never().StartWith(dataContext));
- }
+ private string _foo;
- private Mock CreateTarget(
- IObservable dataContext = null,
- IObservable text = null)
- {
- var result = new Mock();
-
- dataContext = dataContext ?? Observable.Never().StartWith((object)null);
- text = text ?? Observable.Never().StartWith((string)null);
-
- result.Setup(x => x.GetObservable(Control.DataContextProperty)).Returns(dataContext);
- result.Setup(x => x.GetObservable((PerspexProperty)Control.DataContextProperty)).Returns(dataContext);
- result.Setup(x => x.GetObservable((PerspexProperty)TextBox.TextProperty)).Returns(text);
- return result;
+ public string Foo
+ {
+ get { return _foo; }
+ set { this.RaiseAndSetIfChanged(ref _foo, value); }
+ }
}
private class OldDataContextViewModel
@@ -258,7 +287,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
public OldDataContextTest()
{
- Bind(BarProperty, GetObservable(FooProperty));
+ Bind(BarProperty, this.GetObservable(FooProperty));
}
}
}
diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs
index bde7297d47..d1b929b4a9 100644
--- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs
+++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs
@@ -10,7 +10,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
public class BindingTests_ElementName
{
[Fact]
- public void Should_Bind_To_Element()
+ public void Should_Bind_To_Element_Path()
{
TextBlock target;
var root = new TestRoot
@@ -38,13 +38,48 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Text",
};
- binding.Bind(target, TextBlock.TextProperty);
+ target.Bind(TextBox.TextProperty, binding);
Assert.Equal("foo", target.Text);
}
[Fact]
- public void Should_Bind_To_Later_Added_Element()
+ public void Should_Bind_To_Element()
+ {
+ TextBlock source;
+ ContentControl target;
+
+ var root = new TestRoot
+ {
+ Child = new StackPanel
+ {
+ Children = new Controls.Controls
+ {
+ (source = new TextBlock
+ {
+ Name = "source",
+ Text = "foo",
+ }),
+ (target = new ContentControl
+ {
+ Name = "target",
+ })
+ }
+ }
+ };
+
+ var binding = new Binding
+ {
+ ElementName = "source",
+ };
+
+ target.Bind(ContentControl.ContentProperty, binding);
+
+ Assert.Same(source, target.Content);
+ }
+
+ [Fact]
+ public void Should_Bind_To_Later_Added_Element_Path()
{
TextBlock target;
StackPanel stackPanel;
@@ -69,7 +104,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Text",
};
- binding.Bind(target, TextBlock.TextProperty);
+ target.Bind(TextBox.TextProperty, binding);
stackPanel.Children.Add(new TextBlock
{
@@ -79,5 +114,43 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Assert.Equal("foo", target.Text);
}
+
+ [Fact]
+ public void Should_Bind_To_Later_Added_Element()
+ {
+ ContentControl target;
+ StackPanel stackPanel;
+
+ var root = new TestRoot
+ {
+ Child = stackPanel = new StackPanel
+ {
+ Children = new Controls.Controls
+ {
+ (target = new ContentControl
+ {
+ Name = "target",
+ }),
+ }
+ }
+ };
+
+ var binding = new Binding
+ {
+ ElementName = "source",
+ };
+
+ target.Bind(ContentControl.ContentProperty, binding);
+
+ var source = new TextBlock
+ {
+ Name = "source",
+ Text = "foo",
+ };
+
+ stackPanel.Children.Add(source);
+
+ Assert.Same(source, target.Content);
+ }
}
}
diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
index af0b492e49..b8ddd9a280 100644
--- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
+++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
@@ -6,6 +6,7 @@ using System.Reactive.Linq;
using System.Reactive.Subjects;
using Moq;
using Perspex.Controls;
+using Perspex.Data;
using Perspex.Markup.Xaml.Data;
using Perspex.Styling;
using Xunit;
@@ -26,7 +27,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Foo",
};
- binding.Bind(target.Object, TextBox.TextProperty);
+ target.Object.Bind(TextBox.TextProperty, binding);
target.Verify(x => x.Bind(
TextBox.TextProperty,
@@ -46,48 +47,28 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Foo",
};
- binding.Bind(target.Object, TextBox.TextProperty);
+ target.Object.Bind(TextBox.TextProperty, binding);
- target.Verify(x => x.BindTwoWay(
+ target.Verify(x => x.Bind(
TextBox.TextProperty,
It.IsAny>(),
BindingPriority.TemplatedParent));
}
- [Fact]
- public void OneWayToSource_Binding_Should_Be_Set_Up()
- {
- var textObservable = new Mock>();
- var expression = new Mock>();
- var target = CreateTarget(text: textObservable.Object);
- var binding = new Binding
- {
- Path = "Foo",
- Mode = BindingMode.OneWayToSource,
- };
-
- binding.Bind(target.Object, TextBox.TextProperty, expression.Object);
-
- textObservable.Verify(x => x.Subscribe(expression.Object));
- }
-
- private Mock CreateTarget(ITemplatedControl templatedParent)
+ private Mock CreateTarget(ITemplatedControl templatedParent)
{
- return CreateTarget(templatedParent: Observable.Never().StartWith(templatedParent));
+ return CreateTarget(templatedParent: templatedParent);
}
- private Mock CreateTarget(
- IObservable templatedParent = null,
- IObservable text = null)
+ private Mock CreateTarget(
+ ITemplatedControl templatedParent = null,
+ string text = null)
{
- var result = new Mock();
-
- templatedParent = templatedParent ?? Observable.Never().StartWith((ITemplatedControl)null);
- text = text ?? Observable.Never().StartWith((string)null);
+ var result = new Mock();
- result.Setup(x => x.GetObservable(Control.TemplatedParentProperty)).Returns(templatedParent);
- result.Setup(x => x.GetObservable((PerspexProperty)Control.TemplatedParentProperty)).Returns(templatedParent);
- result.Setup(x => x.GetObservable((PerspexProperty)TextBox.TextProperty)).Returns(text);
+ result.Setup(x => x.GetValue(Control.TemplatedParentProperty)).Returns(templatedParent);
+ result.Setup(x => x.GetValue((PerspexProperty)Control.TemplatedParentProperty)).Returns(templatedParent);
+ result.Setup(x => x.GetValue((PerspexProperty)TextBox.TextProperty)).Returns(text);
return result;
}
}
diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs
index 23db6d4c39..908bb16e37 100644
--- a/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs
+++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reactive.Linq;
-using System.Reactive.Subjects;
using Moq;
using Perspex.Controls;
using Perspex.Markup.Xaml.Data;
@@ -31,12 +30,10 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
}
};
- var target = new Mock();
+ var target = new Mock();
target.Setup(x => x.GetValue(Control.DataContextProperty)).Returns(source);
- target.Setup(x => x.GetObservable(Control.DataContextProperty)).Returns(
- Observable.Never().StartWith(source));
- var subject = binding.CreateSubject(target.Object, typeof(string));
+ var subject = binding.CreateSubject(target.Object, null);
var result = await subject.Take(1);
Assert.Equal("1,2,3", result);
diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj
index 3883b7594e..8a79e9eb28 100644
--- a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj
+++ b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj
@@ -43,9 +43,6 @@
4
-
- ..\..\packages\Octokit.0.14.0\lib\net45\Octokit.dll
-
..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll
True
@@ -104,7 +101,9 @@
+
+
diff --git a/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs
new file mode 100644
index 0000000000..a496f26fc5
--- /dev/null
+++ b/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs
@@ -0,0 +1,120 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Linq;
+using System.Reactive.Linq;
+using Moq;
+using Perspex.Controls;
+using Perspex.Controls.Primitives;
+using Perspex.Data;
+using Perspex.Markup.Xaml.Data;
+using Perspex.Platform;
+using Perspex.Styling;
+using Xunit;
+
+namespace Perspex.Markup.Xaml.UnitTests
+{
+ public class StyleTests
+ {
+ [Fact]
+ public void Binding_Should_Be_Assigned_To_Setter_Value_Instead_Of_Bound()
+ {
+ using (PerspexLocator.EnterScope())
+ {
+ PerspexLocator.CurrentMutable
+ .Bind()
+ .ToConstant(Mock.Of());
+
+ var xaml = "";
+ var loader = new PerspexXamlLoader();
+ var style = (Style)loader.Load(xaml);
+ var setter = (Setter)(style.Setters.First());
+
+ Assert.IsType(setter.Value);
+ }
+ }
+
+ [Fact]
+ public void Setter_With_TwoWay_Binding_Should_Update_Source()
+ {
+ using (PerspexLocator.EnterScope())
+ {
+ PerspexLocator.CurrentMutable
+ .Bind()
+ .ToConstant(Mock.Of(x =>
+ x.CurrentThreadIsLoopThread == true));
+
+ var data = new Data
+ {
+ Foo = "foo",
+ };
+
+ var control = new TextBox
+ {
+ DataContext = data,
+ };
+
+ var setter = new Setter
+ {
+ Property = TextBox.TextProperty,
+ Value = new Binding
+ {
+ Path = "Foo",
+ Mode = BindingMode.TwoWay
+ }
+ };
+
+ setter.Apply(null, control, null);
+ Assert.Equal("foo", control.Text);
+
+ control.Text = "bar";
+ Assert.Equal("bar", data.Foo);
+ }
+ }
+
+ [Fact]
+ public void Setter_With_TwoWay_Binding_And_Activator_Should_Update_Source()
+ {
+ using (PerspexLocator.EnterScope())
+ {
+ PerspexLocator.CurrentMutable
+ .Bind()
+ .ToConstant(Mock.Of(x =>
+ x.CurrentThreadIsLoopThread == true));
+
+ var data = new Data
+ {
+ Foo = "foo",
+ };
+
+ var control = new TextBox
+ {
+ DataContext = data,
+ };
+
+ var setter = new Setter
+ {
+ Property = TextBox.TextProperty,
+ Value = new Binding
+ {
+ Path = "Foo",
+ Mode = BindingMode.TwoWay
+ }
+ };
+
+ var activator = Observable.Never().StartWith(true);
+
+ setter.Apply(null, control, activator);
+ Assert.Equal("foo", control.Text);
+
+ control.Text = "bar";
+ Assert.Equal("bar", data.Foo);
+ }
+ }
+
+ private class Data
+ {
+ public string Foo { get; set; }
+ }
+ }
+}
diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Templates/TreeDataTemplateTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Templates/TreeDataTemplateTests.cs
new file mode 100644
index 0000000000..5bf4cea9eb
--- /dev/null
+++ b/tests/Perspex.Markup.Xaml.UnitTests/Templates/TreeDataTemplateTests.cs
@@ -0,0 +1,34 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Linq;
+using Moq;
+using Perspex.Controls.Templates;
+using Perspex.Markup.Xaml.Data;
+using Perspex.Markup.Xaml.Templates;
+using Perspex.Platform;
+using Xunit;
+
+namespace Perspex.Markup.Xaml.UnitTests
+{
+ public class TreeDataTemplateTests
+ {
+ [Fact]
+ public void Binding_Should_Be_Assigned_To_ItemsSource_Instead_Of_Bound()
+ {
+ using (PerspexLocator.EnterScope())
+ {
+ PerspexLocator.CurrentMutable
+ .Bind()
+ .ToConstant(Mock.Of());
+
+ var xaml = "";
+ var loader = new PerspexXamlLoader();
+ var templates = (DataTemplates)loader.Load(xaml);
+ var template = (TreeDataTemplate)(templates.First());
+
+ Assert.IsType(template.ItemsSource);
+ }
+ }
+ }
+}
diff --git a/tests/Perspex.Markup.Xaml.UnitTests/TestRoot.cs b/tests/Perspex.Markup.Xaml.UnitTests/TestRoot.cs
index b9db948265..e172e2ccb7 100644
--- a/tests/Perspex.Markup.Xaml.UnitTests/TestRoot.cs
+++ b/tests/Perspex.Markup.Xaml.UnitTests/TestRoot.cs
@@ -5,10 +5,11 @@ using System;
using Perspex.Controls;
using Perspex.Platform;
using Perspex.Rendering;
+using Perspex.Styling;
namespace Perspex.Markup.Xaml.UnitTests
{
- public class TestRoot : Decorator, IRenderRoot, INameScope
+ public class TestRoot : Decorator, IRenderRoot, INameScope, IStyleRoot
{
private readonly NameScope _nameScope = new NameScope();
diff --git a/tests/Perspex.RenderTests/Shapes/PathTests.cs b/tests/Perspex.RenderTests/Shapes/PathTests.cs
index 54cdf7e1cf..68b5956234 100644
Binary files a/tests/Perspex.RenderTests/Shapes/PathTests.cs and b/tests/Perspex.RenderTests/Shapes/PathTests.cs differ
diff --git a/tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj b/tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj
index f5cfe531c9..d1a6119151 100644
--- a/tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj
+++ b/tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj
@@ -40,6 +40,10 @@
4
+
+ ..\..\packages\Moq.4.2.1507.0118\lib\net40\Moq.dll
+ True
+
..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll
@@ -72,7 +76,6 @@
-
@@ -81,6 +84,7 @@
+
@@ -160,4 +164,4 @@
-->
-
+
\ No newline at end of file
diff --git a/tests/Perspex.SceneGraph.UnitTests/RenderTests_Culling.cs b/tests/Perspex.SceneGraph.UnitTests/RenderTests_Culling.cs
new file mode 100644
index 0000000000..6eba2de185
--- /dev/null
+++ b/tests/Perspex.SceneGraph.UnitTests/RenderTests_Culling.cs
@@ -0,0 +1,183 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Moq;
+using Perspex.Controls;
+using Perspex.Media;
+using Perspex.Rendering;
+using Xunit;
+
+namespace Perspex.SceneGraph.UnitTests
+{
+ public class RenderTests_Culling
+ {
+ [Fact]
+ public void In_Bounds_Control_Should_Be_Rendered()
+ {
+ TestControl target;
+ var container = new Canvas
+ {
+ Width = 100,
+ Height = 100,
+ ClipToBounds = true,
+ Children = new Controls.Controls
+ {
+ (target = new TestControl
+ {
+ Width = 10,
+ Height = 10,
+ [Canvas.LeftProperty] = 98,
+ [Canvas.TopProperty] = 98,
+ })
+ }
+ };
+
+ Render(container);
+
+ Assert.True(target.Rendered);
+ }
+
+ [Fact]
+ public void Out_Of_Bounds_Control_Should_Not_Be_Rendered()
+ {
+ TestControl target;
+ var container = new Canvas
+ {
+ Width = 100,
+ Height = 100,
+ ClipToBounds = true,
+ Children = new Controls.Controls
+ {
+ (target = new TestControl
+ {
+ Width = 10,
+ Height = 10,
+ ClipToBounds = true,
+ [Canvas.LeftProperty] = 110,
+ [Canvas.TopProperty] = 110,
+ })
+ }
+ };
+
+ Render(container);
+
+ Assert.False(target.Rendered);
+ }
+
+ [Fact]
+ public void Out_Of_Bounds_Child_Control_Should_Not_Be_Rendered()
+ {
+ TestControl target;
+ var container = new Canvas
+ {
+ Width = 100,
+ Height = 100,
+ ClipToBounds = true,
+ Children = new Controls.Controls
+ {
+ new Canvas
+ {
+ Width = 100,
+ Height = 100,
+ [Canvas.LeftProperty] = 50,
+ [Canvas.TopProperty] = 50,
+ Children = new Controls.Controls
+ {
+ (target = new TestControl
+ {
+ Width = 10,
+ Height = 10,
+ ClipToBounds = true,
+ [Canvas.LeftProperty] = 50,
+ [Canvas.TopProperty] = 50,
+ })
+ }
+ }
+ }
+ };
+
+ Render(container);
+
+ Assert.False(target.Rendered);
+ }
+
+ [Fact]
+ public void RenderTransform_Should_Be_Respected()
+ {
+ TestControl target;
+ var container = new Canvas
+ {
+ Width = 100,
+ Height = 100,
+ ClipToBounds = true,
+ Children = new Controls.Controls
+ {
+ (target = new TestControl
+ {
+ Width = 10,
+ Height = 10,
+ [Canvas.LeftProperty] = 110,
+ [Canvas.TopProperty] = 110,
+ RenderTransform = new TranslateTransform(-100, -100),
+ })
+ }
+ };
+
+ Render(container);
+
+ Assert.True(target.Rendered);
+ }
+
+ [Fact]
+ public void Negative_Margin_Should_Be_Respected()
+ {
+ TestControl target;
+ var container = new Canvas
+ {
+ Width = 100,
+ Height = 100,
+ ClipToBounds = true,
+ Children = new Controls.Controls
+ {
+ new Border
+ {
+ Margin = new Thickness(100, 100, 0, 0),
+ Child = target = new TestControl
+ {
+ Width = 10,
+ Height = 10,
+ Margin = new Thickness(-100, -100, 0, 0),
+ }
+ }
+ }
+ };
+
+ Render(container);
+
+ Assert.True(target.Rendered);
+ }
+
+ private void Render(IControl control)
+ {
+ var ctx = CreateDrawingContext();
+ control.Measure(Size.Infinity);
+ control.Arrange(new Rect(control.DesiredSize));
+ ctx.Render(control);
+ }
+
+ private DrawingContext CreateDrawingContext()
+ {
+ return new DrawingContext(Mock.Of());
+ }
+
+ private class TestControl : Control
+ {
+ public bool Rendered { get; private set; }
+
+ public override void Render(DrawingContext context)
+ {
+ Rendered = true;
+ }
+ }
+ }
+}
diff --git a/tests/Perspex.SceneGraph.UnitTests/TestRoot.cs b/tests/Perspex.SceneGraph.UnitTests/TestRoot.cs
index 62bc782b78..99a1fd0d4a 100644
--- a/tests/Perspex.SceneGraph.UnitTests/TestRoot.cs
+++ b/tests/Perspex.SceneGraph.UnitTests/TestRoot.cs
@@ -7,22 +7,8 @@ using Perspex.Rendering;
namespace Perspex.SceneGraph.UnitTests
{
- public class TestRoot : TestVisual, IRenderRoot, INameScope
+ public class TestRoot : TestVisual, IRenderRoot
{
- private NameScope _nameScope = new NameScope();
-
- event EventHandler INameScope.Registered
- {
- add { _nameScope.Registered += value; }
- remove { _nameScope.Registered -= value; }
- }
-
- public event EventHandler Unregistered
- {
- add { _nameScope.Unregistered += value; }
- remove { _nameScope.Unregistered -= value; }
- }
-
public IRenderTarget RenderTarget
{
get { throw new NotImplementedException(); }
@@ -37,20 +23,5 @@ namespace Perspex.SceneGraph.UnitTests
{
throw new NotImplementedException();
}
-
- public void Register(string name, object element)
- {
- _nameScope.Register(name, element);
- }
-
- public object Find(string name)
- {
- return _nameScope.Find(name);
- }
-
- public void Unregister(string name)
- {
- _nameScope.Unregister(name);
- }
}
}
diff --git a/tests/Perspex.SceneGraph.UnitTests/TestVisual.cs b/tests/Perspex.SceneGraph.UnitTests/TestVisual.cs
index 88f47d7876..21a4427fbc 100644
--- a/tests/Perspex.SceneGraph.UnitTests/TestVisual.cs
+++ b/tests/Perspex.SceneGraph.UnitTests/TestVisual.cs
@@ -20,8 +20,6 @@ namespace Perspex.SceneGraph.UnitTests
public class TestVisual : Visual
{
- public new PerspexObject InheritanceParent => base.InheritanceParent;
-
public IVisual Child
{
get
@@ -33,34 +31,34 @@ namespace Perspex.SceneGraph.UnitTests
{
if (Child != null)
{
- RemoveVisualChild(Child);
+ VisualChildren.Remove(Child);
}
if (value != null)
{
- AddVisualChild(value);
+ VisualChildren.Add(value);
}
}
}
public void AddChild(Visual v)
{
- AddVisualChild(v);
+ VisualChildren.Add(v);
}
public void AddChildren(IEnumerable v)
{
- AddVisualChildren(v);
+ VisualChildren.AddRange(v);
}
public void RemoveChild(Visual v)
{
- RemoveVisualChild(v);
+ VisualChildren.Remove(v);
}
public void ClearChildren()
{
- ClearVisualChildren();
+ VisualChildren.Clear();
}
}
}
diff --git a/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs b/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs
index 7756130c20..00d8c01d0d 100644
--- a/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs
+++ b/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs
@@ -23,17 +23,6 @@ namespace Perspex.SceneGraph.UnitTests
Assert.Equal(target, child.GetVisualParent());
}
- [Fact]
- public void Added_Child_Should_Have_InheritanceParent_Set()
- {
- var target = new TestVisual();
- var child = new TestVisual();
-
- target.AddChild(child);
-
- Assert.Equal(target, child.InheritanceParent);
- }
-
[Fact]
public void Added_Child_Should_Notify_VisualParent_Changed()
{
@@ -60,18 +49,6 @@ namespace Perspex.SceneGraph.UnitTests
Assert.Null(child.GetVisualParent());
}
- [Fact]
- public void Removed_Child_Should_Have_InheritanceParent_Cleared()
- {
- var target = new TestVisual();
- var child = new TestVisual();
-
- target.AddChild(child);
- target.RemoveChild(child);
-
- Assert.Null(child.InheritanceParent);
- }
-
[Fact]
public void Clearing_Children_Should_Clear_VisualParent()
{
@@ -121,55 +98,5 @@ namespace Perspex.SceneGraph.UnitTests
Assert.True(called1);
Assert.True(called2);
}
-
- [Fact]
- public void Controls_Should_Add_Themselves_To_Root_NameScope()
- {
- var child2 = new TestVisual { Name = "bar" };
- var child1 = new TestVisual { Name = "foo", Child = child2 };
- var root = new TestRoot { Child = child1 };
-
- Assert.Same(child1, root.Find("foo"));
- Assert.Same(child2, root.Find("bar"));
- }
-
- [Fact]
- public void Controls_Should_Add_Themselves_To_NameScopes_In_Attached_Property()
- {
- var child2 = new TestVisual { Name = "bar", [NameScope.NameScopeProperty] = new NameScope() };
- var child1 = new TestVisual { Name = "foo", Child = child2};
- var root = new TestRoot { Child = child1 };
-
- Assert.Same(child1, root.Find("foo"));
- Assert.Null(root.Find("bar"));
- Assert.Same(child2, NameScope.GetNameScope(child2).Find("bar"));
- }
-
- [Fact]
- public void Controls_Should_Remove_Themselves_From_Root_NameScope()
- {
- var child2 = new TestVisual { Name = "bar" };
- var child1 = new TestVisual { Name = "foo", Child = child2 };
- var root = new TestRoot { Child = child1 };
-
- root.Child = null;
-
- Assert.Null(root.Find("foo"));
- Assert.Null(root.Find("bar"));
- }
-
- [Fact]
- public void Controls_Should_Remove_Themselves_From_NameScopes_In_Attached_Property()
- {
- var child2 = new TestVisual { Name = "bar" };
- var child1 = new TestVisual { Name = "foo", Child = child2,[NameScope.NameScopeProperty] = new NameScope() };
- var root = new TestRoot { Child = child1 };
-
- root.Child = null;
-
- Assert.Null(root.Find("foo"));
- Assert.Null(root.Find("bar"));
- Assert.Null(NameScope.GetNameScope(child1).Find("bar"));
- }
}
}
diff --git a/tests/Perspex.SceneGraph.UnitTests/packages.config b/tests/Perspex.SceneGraph.UnitTests/packages.config
index 8cbc4ec26f..3cf2b984d5 100644
--- a/tests/Perspex.SceneGraph.UnitTests/packages.config
+++ b/tests/Perspex.SceneGraph.UnitTests/packages.config
@@ -1,5 +1,6 @@
+
diff --git a/tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs
new file mode 100644
index 0000000000..e1beb992c2
--- /dev/null
+++ b/tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs
@@ -0,0 +1,70 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using Xunit;
+
+namespace Perspex.Styling.UnitTests
+{
+ public class ActivatedObservableTests
+ {
+ [Fact]
+ public void Should_Produce_Correct_Values()
+ {
+ var activator = new BehaviorSubject(false);
+ var source = new BehaviorSubject(1);
+ var target = new ActivatedObservable(activator, source, string.Empty);
+ var result = new List();
+
+ target.Subscribe(x => result.Add(x));
+
+ activator.OnNext(true);
+ source.OnNext(2);
+ activator.OnNext(false);
+ source.OnNext(3);
+ activator.OnNext(true);
+
+ Assert.Equal(
+ new[]
+ {
+ PerspexProperty.UnsetValue,
+ 1,
+ 2,
+ PerspexProperty.UnsetValue,
+ 3,
+ },
+ result);
+ }
+
+ [Fact]
+ public void Should_Complete_When_Source_Completes()
+ {
+ var activator = new BehaviorSubject(false);
+ var source = new BehaviorSubject(1);
+ var target = new ActivatedObservable(activator, source, string.Empty);
+ var completed = false;
+
+ target.Subscribe(_ => { }, () => completed = true);
+ source.OnCompleted();
+
+ Assert.True(completed);
+ }
+
+ [Fact]
+ public void Should_Complete_When_Activator_Completes()
+ {
+ var activator = new BehaviorSubject(false);
+ var source = new BehaviorSubject(1);
+ var target = new ActivatedObservable(activator, source, string.Empty);
+ var completed = false;
+
+ target.Subscribe(_ => { }, () => completed = true);
+ activator.OnCompleted();
+
+ Assert.True(completed);
+ }
+ }
+}
diff --git a/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs
new file mode 100644
index 0000000000..58b7d9fd46
--- /dev/null
+++ b/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs
@@ -0,0 +1,98 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Reactive.Disposables;
+using System.Reactive.Subjects;
+using Xunit;
+
+namespace Perspex.Styling.UnitTests
+{
+ public class ActivatedSubjectTests
+ {
+ [Fact]
+ public void Should_Set_Values()
+ {
+ var activator = new BehaviorSubject(false);
+ var source = new TestSubject();
+ var target = new ActivatedSubject(activator, source, string.Empty);
+
+ target.OnNext("bar");
+ Assert.Equal(PerspexProperty.UnsetValue, source.Value);
+ activator.OnNext(true);
+ target.OnNext("baz");
+ Assert.Equal("baz", source.Value);
+ activator.OnNext(false);
+ Assert.Equal(PerspexProperty.UnsetValue, source.Value);
+ target.OnNext("bax");
+ activator.OnNext(true);
+ Assert.Equal("bax", source.Value);
+ }
+
+ [Fact]
+ public void Should_Invoke_OnCompleted_On_Activator_Completed()
+ {
+ var activator = new BehaviorSubject(false);
+ var source = new TestSubject();
+ var target = new ActivatedSubject(activator, source, string.Empty);
+
+ activator.OnCompleted();
+
+ Assert.True(source.Completed);
+ }
+
+ [Fact]
+ public void Should_Invoke_OnError_On_Activator_Error()
+ {
+ var activator = new BehaviorSubject(false);
+ var source = new TestSubject();
+ var target = new ActivatedSubject(activator, source, string.Empty);
+
+ activator.OnError(new Exception());
+
+ Assert.NotNull(source.Error);
+ }
+
+ private class Class1 : PerspexObject
+ {
+ public static readonly PerspexProperty FooProperty =
+ PerspexProperty.Register("Foo", "foodefault");
+
+ public string Foo
+ {
+ get { return GetValue(FooProperty); }
+ set { SetValue(FooProperty, value); }
+ }
+ }
+
+ private class TestSubject : ISubject
+ {
+ private IObserver _observer;
+
+ public bool Completed { get; set; }
+ public Exception Error { get; set; }
+ public object Value { get; set; } = PerspexProperty.UnsetValue;
+
+ public void OnCompleted()
+ {
+ Completed = true;
+ }
+
+ public void OnError(Exception error)
+ {
+ Error = error;
+ }
+
+ public void OnNext(object value)
+ {
+ Value = value;
+ }
+
+ public IDisposable Subscribe(IObserver observer)
+ {
+ _observer = observer;
+ return Disposable.Empty;
+ }
+ }
+ }
+}
diff --git a/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs
new file mode 100644
index 0000000000..4921aeaa7b
--- /dev/null
+++ b/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs
@@ -0,0 +1,42 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using Xunit;
+
+namespace Perspex.Styling.UnitTests
+{
+ public class ActivatedValueTests
+ {
+ [Fact]
+ public void Should_Produce_Correct_Values()
+ {
+ var activator = new BehaviorSubject(false);
+ var target = new ActivatedValue(activator, 1, string.Empty);
+ var result = new List();
+
+ target.Subscribe(x => result.Add(x));
+
+ activator.OnNext(true);
+ activator.OnNext(false);
+
+ Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result);
+ }
+
+ [Fact]
+ public void Should_Complete_When_Activator_Completes()
+ {
+ var activator = new BehaviorSubject(false);
+ var target = new ActivatedValue(activator, 1, string.Empty);
+ var completed = false;
+
+ target.Subscribe(_ => { }, () => completed = true);
+ activator.OnCompleted();
+
+ Assert.True(completed);
+ }
+ }
+}
diff --git a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj
index 4099319b5a..af640ebdf4 100644
--- a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj
+++ b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj
@@ -40,6 +40,11 @@
4
+
+ ..\..\packages\Rx-Testing.2.2.5\lib\net45\Microsoft.Reactive.Testing.dll
+ True
+
+
..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll
@@ -75,6 +80,8 @@
+
+
@@ -85,10 +92,13 @@
+
+
+
diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs
index 0df2e061a3..427a806376 100644
--- a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs
+++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs
@@ -2,12 +2,13 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Collections.Generic;
using System.Linq;
+using System.Reactive;
using System.Reactive.Linq;
-using System.Reactive.Subjects;
-using System.Threading.Tasks;
using Perspex.Collections;
-using Perspex.Styling;
+using Perspex.Controls;
+using Perspex.Data;
using Xunit;
namespace Perspex.Styling.UnitTests
@@ -43,7 +44,7 @@ namespace Perspex.Styling.UnitTests
}
[Fact]
- public async Task Child_Matches_Control_When_It_Is_Child_OfType_And_Class()
+ public async void Child_Matches_Control_When_It_Is_Child_OfType_And_Class()
{
var parent = new TestLogical1();
var child = new TestLogical2();
@@ -52,6 +53,7 @@ namespace Perspex.Styling.UnitTests
var selector = new Selector().OfType().Class("foo").Child().OfType();
var activator = selector.Match(child).ObservableResult;
+ var result = new List();
Assert.False(await activator.Take(1));
parent.Classes.Add("foo");
@@ -76,10 +78,14 @@ namespace Perspex.Styling.UnitTests
Classes = new Classes();
}
+ public event EventHandler PropertyChanged;
+
public Classes Classes { get; }
public string Name { get; set; }
+ public bool IsAttachedToLogicalTree { get; }
+
public IPerspexReadOnlyList LogicalChildren { get; set; }
public ILogical LogicalParent { get; set; }
@@ -88,40 +94,31 @@ namespace Perspex.Styling.UnitTests
public ITemplatedControl TemplatedParent { get; }
- public IPropertyBag InheritanceParent
- {
- get
- {
- throw new NotImplementedException();
- }
- }
+ IObservable IStyleable.StyleDetach { get; }
- public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority)
- {
- throw new NotImplementedException();
- }
+ IPerspexReadOnlyList IStyleable.Classes => Classes;
- public void SetValue(PerspexProperty property, object value, BindingPriority priority)
+ public object GetValue(PerspexProperty property)
{
throw new NotImplementedException();
}
- public IObservable GetObservable(PerspexProperty property)
+ public T GetValue(PerspexProperty property)
{
throw new NotImplementedException();
}
- public bool IsRegistered(PerspexProperty property)
+ public void SetValue(PerspexProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}
- public void ClearValue(PerspexProperty property)
+ public void SetValue(PerspexProperty property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
- public object GetValue(PerspexProperty property)
+ public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority)
{
throw new NotImplementedException();
}
@@ -135,31 +132,6 @@ namespace Perspex.Styling.UnitTests
{
throw new NotImplementedException();
}
-
- public IObservable GetObservable(PerspexProperty property)
- {
- throw new NotImplementedException();
- }
-
- public T GetValue(PerspexProperty property)
- {
- throw new NotImplementedException();
- }
-
- public void SetValue(PerspexProperty property, T value, BindingPriority priority = BindingPriority.LocalValue)
- {
- throw new NotImplementedException();
- }
-
- public IDisposable BindTwoWay(PerspexProperty property, PerspexObject source, PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue)
- {
- throw new NotImplementedException();
- }
-
- public IDisposable BindTwoWay(PerspexProperty property, ISubject source, BindingPriority priority = BindingPriority.LocalValue)
- {
- throw new NotImplementedException();
- }
}
public class TestLogical1 : TestLogical
diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs
index 9d98bd9a12..3232279e5e 100644
--- a/tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs
+++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Moq;
+using Perspex.Controls;
using Perspex.Styling;
using Xunit;
diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs
index 7b5f78e6bb..fab2f15a97 100644
--- a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs
+++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs
@@ -3,11 +3,12 @@
using System;
using System.Linq;
+using System.Reactive;
using System.Reactive.Linq;
-using System.Reactive.Subjects;
using System.Threading.Tasks;
using Perspex.Collections;
-using Perspex.Styling;
+using Perspex.Controls;
+using Perspex.Data;
using Xunit;
namespace Perspex.Styling.UnitTests
@@ -108,10 +109,14 @@ namespace Perspex.Styling.UnitTests
Classes = new Classes();
}
+ public event EventHandler PropertyChanged;
+
public Classes Classes { get; }
public string Name { get; set; }
+ public bool IsAttachedToLogicalTree { get; }
+
public IPerspexReadOnlyList LogicalChildren { get; set; }
public ILogical LogicalParent { get; set; }
@@ -120,40 +125,31 @@ namespace Perspex.Styling.UnitTests
public ITemplatedControl TemplatedParent { get; }
- public IPropertyBag InheritanceParent
- {
- get
- {
- throw new NotImplementedException();
- }
- }
+ IPerspexReadOnlyList IStyleable.Classes => Classes;
- public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority)
- {
- throw new NotImplementedException();
- }
+ IObservable IStyleable.StyleDetach { get; }
- public void SetValue(PerspexProperty property, object value, BindingPriority priority)
+ public object GetValue(PerspexProperty property)
{
throw new NotImplementedException();
}
- public IObservable GetObservable(PerspexProperty property)
+ public T GetValue(PerspexProperty property)
{
throw new NotImplementedException();
}
- public bool IsRegistered(PerspexProperty property)
+ public void SetValue(PerspexProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}
- public void ClearValue(PerspexProperty property)
+ public void SetValue(PerspexProperty property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
- public object GetValue(PerspexProperty property)
+ public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority)
{
throw new NotImplementedException();
}
@@ -167,31 +163,6 @@ namespace Perspex.Styling.UnitTests
{
throw new NotImplementedException();
}
-
- public IObservable GetObservable(PerspexProperty property)
- {
- throw new NotImplementedException();
- }
-
- public T GetValue(PerspexProperty property)
- {
- throw new NotImplementedException();
- }
-
- public void SetValue(PerspexProperty property, T value, BindingPriority priority = BindingPriority.LocalValue)
- {
- throw new NotImplementedException();
- }
-
- public IDisposable BindTwoWay(PerspexProperty property, PerspexObject source, PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue)
- {
- throw new NotImplementedException();
- }
-
- public IDisposable BindTwoWay(PerspexProperty property, ISubject source, BindingPriority priority = BindingPriority.LocalValue)
- {
- throw new NotImplementedException();
- }
}
public class TestLogical1 : TestLogical
diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs
index 0f14820f59..89cd535338 100644
--- a/tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs
+++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs
@@ -44,7 +44,7 @@ namespace Perspex.Styling.UnitTests
activator.Subscribe(x => values.Add(x));
Assert.Equal(new[] { false }, values);
- control.Classes.Add("foo", "bar");
+ control.Classes.AddRange(new[] { "foo", "bar" });
Assert.Equal(new[] { false, true }, values);
control.Classes.Remove("foo");
Assert.Equal(new[] { false, true, false }, values);
diff --git a/tests/Perspex.Styling.UnitTests/SetterTests.cs b/tests/Perspex.Styling.UnitTests/SetterTests.cs
new file mode 100644
index 0000000000..c42426e97b
--- /dev/null
+++ b/tests/Perspex.Styling.UnitTests/SetterTests.cs
@@ -0,0 +1,28 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Reactive.Subjects;
+using Moq;
+using Perspex.Controls;
+using Perspex.Data;
+using Xunit;
+
+namespace Perspex.Styling.UnitTests
+{
+ public class SetterTests
+ {
+ [Fact]
+ public void Setter_Should_Apply_Binding_To_Property()
+ {
+ var control = new TextBlock();
+ var subject = new BehaviorSubject("foo");
+ var binding = Mock.Of(x => x.CreateSubject(control, TextBlock.TextProperty) == subject);
+ var style = Mock.Of();
+ var setter = new Setter(TextBlock.TextProperty, binding);
+
+ setter.Apply(style, control, null);
+
+ Assert.Equal("foo", control.Text);
+ }
+ }
+}
diff --git a/tests/Perspex.Styling.UnitTests/StyleActivatorTests.cs b/tests/Perspex.Styling.UnitTests/StyleActivatorTests.cs
index 2172bc39f7..afd573dd9d 100644
--- a/tests/Perspex.Styling.UnitTests/StyleActivatorTests.cs
+++ b/tests/Perspex.Styling.UnitTests/StyleActivatorTests.cs
@@ -2,85 +2,65 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Collections.Generic;
+using System.Reactive;
using System.Reactive.Linq;
+using Microsoft.Reactive.Testing;
using Xunit;
namespace Perspex.Styling.UnitTests
{
- public class StyleActivatorTests
+ public class StyleActivatorTests : ReactiveTest
{
[Fact]
- public void Activator_And_Should_Follow_Single_Input()
+ public void Activator_Should_Subscribe_To_Inputs_On_First_Subscription()
{
- var inputs = new[] { new TestSubject(false) };
- var target = new StyleActivator(inputs, ActivatorMode.And);
- var result = new TestObserver();
-
- target.Subscribe(result);
- Assert.False(result.GetValue());
- inputs[0].OnNext(true);
- Assert.True(result.GetValue());
- inputs[0].OnNext(false);
- Assert.False(result.GetValue());
- inputs[0].OnNext(true);
- Assert.True(result.GetValue());
+ var scheduler = new TestScheduler();
+ var source = scheduler.CreateColdObservable();
+ var target = StyleActivator.And(new[] { source });
- Assert.Equal(1, inputs[0].SubscriberCount);
+ Assert.Equal(0, source.Subscriptions.Count);
+ target.Subscribe(_ => { });
+ Assert.Equal(1, source.Subscriptions.Count);
}
[Fact]
- public void Activator_And_Should_AND_Multiple_Inputs()
+ public void Activator_Should_Unsubscribe_From_Inputs_After_Last_Subscriber_Completes()
{
- var inputs = new[]
- {
- new TestSubject(false),
- new TestSubject(false),
- new TestSubject(true),
- };
- var target = new StyleActivator(inputs, ActivatorMode.And);
- var result = new TestObserver();
+ var scheduler = new TestScheduler();
+ var source = scheduler.CreateColdObservable();
+ var target = StyleActivator.And(new[] { source });
- target.Subscribe(result);
- Assert.False(result.GetValue());
- inputs[0].OnNext(true);
- inputs[1].OnNext(true);
- Assert.True(result.GetValue());
- inputs[0].OnNext(false);
- Assert.False(result.GetValue());
+ var dispose = target.Subscribe(_ => { });
+ Assert.Equal(1, source.Subscriptions.Count);
+ Assert.Equal(Subscription.Infinite, source.Subscriptions[0].Unsubscribe);
- Assert.Equal(1, inputs[0].SubscriberCount);
- Assert.Equal(1, inputs[1].SubscriberCount);
- Assert.Equal(1, inputs[2].SubscriberCount);
+ dispose.Dispose();
+ Assert.Equal(1, source.Subscriptions.Count);
+ Assert.Equal(0, source.Subscriptions[0].Unsubscribe);
}
[Fact]
- public void Activator_And_Should_Unsubscribe_All_When_Input_Completes_On_False()
+ public void Activator_And_Should_Follow_Single_Input()
{
- var inputs = new[]
- {
- new TestSubject(false),
- new TestSubject(false),
- new TestSubject(true),
- };
- var target = new StyleActivator(inputs, ActivatorMode.And);
+ var inputs = new[] { new TestSubject(false) };
+ var target = StyleActivator.And(inputs);
var result = new TestObserver();
target.Subscribe(result);
Assert.False(result.GetValue());
inputs[0].OnNext(true);
- inputs[1].OnNext(true);
Assert.True(result.GetValue());
inputs[0].OnNext(false);
Assert.False(result.GetValue());
- inputs[0].OnCompleted();
+ inputs[0].OnNext(true);
+ Assert.True(result.GetValue());
- Assert.Equal(0, inputs[0].SubscriberCount);
- Assert.Equal(0, inputs[1].SubscriberCount);
- Assert.Equal(0, inputs[2].SubscriberCount);
+ Assert.Equal(1, inputs[0].SubscriberCount);
}
[Fact]
- public void Activator_And_Should_Not_Unsubscribe_All_When_Input_Completes_On_True()
+ public void Activator_And_Should_AND_Multiple_Inputs()
{
var inputs = new[]
{
@@ -88,13 +68,16 @@ namespace Perspex.Styling.UnitTests
new TestSubject(false),
new TestSubject(true),
};
- var target = new StyleActivator(inputs, ActivatorMode.And);
+ var target = StyleActivator.And(inputs);
var result = new TestObserver();
target.Subscribe(result);
Assert.False(result.GetValue());
inputs[0].OnNext(true);
- inputs[0].OnCompleted();
+ inputs[1].OnNext(true);
+ Assert.True(result.GetValue());
+ inputs[0].OnNext(false);
+ Assert.False(result.GetValue());
Assert.Equal(1, inputs[0].SubscriberCount);
Assert.Equal(1, inputs[1].SubscriberCount);
@@ -105,7 +88,7 @@ namespace Perspex.Styling.UnitTests
public void Activator_Or_Should_Follow_Single_Input()
{
var inputs = new[] { new TestSubject(false) };
- var target = new StyleActivator(inputs, ActivatorMode.Or);
+ var target = StyleActivator.Or(inputs);
var result = new TestObserver();
target.Subscribe(result);
@@ -129,7 +112,7 @@ namespace Perspex.Styling.UnitTests
new TestSubject(false),
new TestSubject(true),
};
- var target = new StyleActivator(inputs, ActivatorMode.Or);
+ var target = StyleActivator.Or(inputs);
var result = new TestObserver();
target.Subscribe(result);
@@ -144,31 +127,6 @@ namespace Perspex.Styling.UnitTests
Assert.Equal(1, inputs[2].SubscriberCount);
}
- [Fact]
- public void Activator_Or_Should_Unsubscribe_All_When_Input_Completes_On_True()
- {
- var inputs = new[]
- {
- new TestSubject(false),
- new TestSubject(false),
- new TestSubject(true),
- };
- var target = new StyleActivator(inputs, ActivatorMode.Or);
- var result = new TestObserver();
-
- target.Subscribe(result);
- Assert.True(result.GetValue());
- inputs[2].OnNext(false);
- Assert.False(result.GetValue());
- inputs[0].OnNext(true);
- Assert.True(result.GetValue());
- inputs[0].OnCompleted();
-
- Assert.Equal(0, inputs[0].SubscriberCount);
- Assert.Equal(0, inputs[1].SubscriberCount);
- Assert.Equal(0, inputs[2].SubscriberCount);
- }
-
[Fact]
public void Activator_Or_Should_Not_Unsubscribe_All_When_Input_Completes_On_False()
{
@@ -178,7 +136,7 @@ namespace Perspex.Styling.UnitTests
new TestSubject(false),
new TestSubject(true),
};
- var target = new StyleActivator(inputs, ActivatorMode.Or);
+ var target = StyleActivator.Or(inputs);
var result = new TestObserver();
target.Subscribe(result);
@@ -189,7 +147,7 @@ namespace Perspex.Styling.UnitTests
Assert.Equal(1, inputs[0].SubscriberCount);
Assert.Equal(1, inputs[1].SubscriberCount);
- Assert.Equal(1, inputs[2].SubscriberCount);
+ Assert.Equal(0, inputs[2].SubscriberCount);
}
[Fact]
@@ -200,12 +158,25 @@ namespace Perspex.Styling.UnitTests
Observable.Return(false),
};
- var target = new StyleActivator(inputs, ActivatorMode.Or);
+ var target = StyleActivator.Or(inputs);
var completed = false;
target.Subscribe(_ => { }, () => completed = true);
Assert.True(completed);
}
+
+ private Recorded>[] OnNextValues(params bool[] values)
+ {
+ var result = new List>>();
+ var time = 1;
+
+ foreach (var value in values)
+ {
+ result.Add(new Recorded>(time, Notification.CreateOnNext(value)));
+ }
+
+ return result.ToArray();
+ }
}
}
diff --git a/tests/Perspex.Styling.UnitTests/StyleTests.cs b/tests/Perspex.Styling.UnitTests/StyleTests.cs
index cda975b597..7066d2fe28 100644
--- a/tests/Perspex.Styling.UnitTests/StyleTests.cs
+++ b/tests/Perspex.Styling.UnitTests/StyleTests.cs
@@ -142,49 +142,28 @@ namespace Perspex.Styling.UnitTests
}
[Fact]
- public void Style_With_ObservableSetter_Should_Update_Value()
+ public void Style_Should_Detach_When_Removed_From_Logical_Tree()
{
- var source = new BehaviorSubject("Foo");
+ Border border;
- Style style = new Style(x => x.OfType())
+ var style = new Style(x => x.OfType())
{
Setters = new[]
{
- new ObservableSetter(Class1.FooProperty, source),
- },
+ new Setter(Border.BorderThicknessProperty, 4),
+ }
};
- var target = new Class1();
-
- style.Attach(target, null);
-
- Assert.Equal("Foo", target.Foo);
- }
-
- [Fact]
- public void Style_With_ObservableSetter_Should_Update_And_Restore_Value()
- {
- var source = new BehaviorSubject("Foo");
-
- Style style = new Style(x => x.OfType().Class("foo"))
+ var root = new TestRoot
{
- Setters = new[]
- {
- new ObservableSetter(Class1.FooProperty, source),
- },
+ Child = border = new Border(),
};
- var target = new Class1();
-
- style.Attach(target, null);
+ style.Attach(border, null);
- Assert.Equal("foodefault", target.Foo);
- target.Classes.Add("foo");
- Assert.Equal("Foo", target.Foo);
- source.OnNext("Bar");
- Assert.Equal("Bar", target.Foo);
- target.Classes.Remove("foo");
- Assert.Equal("foodefault", target.Foo);
+ Assert.Equal(4, border.BorderThickness);
+ root.Child = null;
+ Assert.Equal(0, border.BorderThickness);
}
private class Class1 : Control
diff --git a/tests/Perspex.Styling.UnitTests/TestControlBase.cs b/tests/Perspex.Styling.UnitTests/TestControlBase.cs
index ad47f1eb27..3a0f0e5434 100644
--- a/tests/Perspex.Styling.UnitTests/TestControlBase.cs
+++ b/tests/Perspex.Styling.UnitTests/TestControlBase.cs
@@ -2,7 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Reactive.Subjects;
+using System.Reactive;
+using Perspex.Collections;
+using Perspex.Controls;
+using Perspex.Data;
namespace Perspex.Styling.UnitTests
{
@@ -14,6 +17,8 @@ namespace Perspex.Styling.UnitTests
SubscribeCheckObservable = new TestObservable();
}
+ public event EventHandler PropertyChanged;
+
public string Name { get; set; }
public virtual Classes Classes { get; set; }
@@ -28,75 +33,41 @@ namespace Perspex.Styling.UnitTests
set;
}
- public IPropertyBag InheritanceParent
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority)
- {
- throw new NotImplementedException();
- }
-
- public void SetValue(PerspexProperty property, object value, BindingPriority priority)
- {
- throw new NotImplementedException();
- }
-
- public IObservable GetObservable(PerspexProperty property)
- {
- throw new NotImplementedException();
- }
-
- public bool IsRegistered(PerspexProperty property)
- {
- throw new NotImplementedException();
- }
+ IPerspexReadOnlyList IStyleable.Classes => Classes;
- public void ClearValue(PerspexProperty property)
- {
- throw new NotImplementedException();
- }
+ IObservable IStyleable.StyleDetach { get; }
public object GetValue(PerspexProperty property)
{
throw new NotImplementedException();
}
- public bool IsSet(PerspexProperty property)
- {
- throw new NotImplementedException();
- }
-
- public IDisposable Bind(PerspexProperty property, IObservable