Browse Source

Merge branch 'master' into skiasharp

pull/506/head
Jason Jarvis 10 years ago
parent
commit
71cf73cc72
  1. 86
      Perspex.sln
  2. 2
      docs/docfx.json
  3. 54
      docs/spec/logging.md
  4. 10
      docs/spec/styles.md
  5. 4
      docs/spec/toc.yml
  6. 13
      docs/template/partials/footer.tmpl.partial
  7. 176
      docs/tutorial/from-wpf.md
  8. 6
      nuget/build-version.ps1
  9. 6
      samples/BindingTest/App.config
  10. 20
      samples/BindingTest/App.xaml.cs
  11. 15
      samples/BindingTest/BindingTest.csproj
  12. 10
      samples/ControlCatalog/App.config
  13. 9
      samples/ControlCatalog/App.xaml.cs
  14. BIN
      samples/ControlCatalog/Assets/github_icon.png
  15. 76
      samples/ControlCatalog/ControlCatalog.csproj
  16. 7
      samples/ControlCatalog/MainWindow.xaml
  17. 32
      samples/ControlCatalog/Pages/ExpanderPage.xaml
  18. 18
      samples/ControlCatalog/Pages/ExpanderPage.xaml.cs
  19. 39
      samples/ControlCatalog/Pages/ImagePage.xaml
  20. 18
      samples/ControlCatalog/Pages/ImagePage.xaml.cs
  21. 26
      samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml
  22. 18
      samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml.cs
  23. 34
      samples/ControlCatalog/Pages/MenuPage.xaml
  24. 18
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  25. 15
      samples/ControlCatalog/Pages/RadioButtonPage.xaml
  26. 18
      samples/ControlCatalog/Pages/RadioButtonPage.xaml.cs
  27. 30
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  28. 18
      samples/ControlCatalog/Pages/TextBoxPage.xaml.cs
  29. 22
      samples/ControlCatalog/Pages/ToolTipPage.xaml
  30. 18
      samples/ControlCatalog/Pages/ToolTipPage.xaml.cs
  31. 4
      samples/ControlCatalog/SideBar.xaml
  32. 12
      samples/TestApplication/App.config
  33. 8
      samples/TestApplication/TestApplication.csproj
  34. 4
      samples/TestApplicationShared/GalleryStyle.cs
  35. 70
      samples/TestApplicationShared/MainWindow.cs
  36. 19
      samples/XamlTestApplication/App.cs
  37. 12
      samples/XamlTestApplication/XamlTestApplication.csproj
  38. 16
      samples/XamlTestApplicationPcl/TestScrollable.cs
  39. 1
      samples/XamlTestApplicationPcl/XamlTestApp.cs
  40. 8
      samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj
  41. 4
      src/Android/Perspex.Android/Perspex.Android.csproj
  42. 4
      src/Android/Perspex.AndroidTestApplication/Perspex.AndroidTestApplication.csproj
  43. 8
      src/Gtk/Perspex.Cairo/Media/DrawingContext.cs
  44. 2
      src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs
  45. 16
      src/Gtk/Perspex.Cairo/Perspex.Cairo.csproj
  46. 4
      src/Gtk/Perspex.Gtk/Perspex.Gtk.csproj
  47. 4
      src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeFeatureProvider.cs
  48. 20
      src/Markup/Perspex.Markup.Xaml/Context/PropertyAccessor.cs
  49. 33
      src/Markup/Perspex.Markup.Xaml/Converters/SolidColorBrushTypeConverter.cs
  50. 10
      src/Markup/Perspex.Markup.Xaml/Data/DelayedBinding.cs
  51. 4
      src/Markup/Perspex.Markup.Xaml/Data/StyleResourceBinding.cs
  52. 5
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  53. 4
      src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs
  54. 1
      src/Markup/Perspex.Markup.Xaml/packages.config
  55. 51
      src/Markup/Perspex.Markup/Data/Parsers/ExpressionParser.cs
  56. 18
      src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs
  57. 7
      src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs
  58. 13
      src/Markup/Perspex.Markup/DefaultValueConverter.cs
  59. 2
      src/Perspex.Base/Collections/PerspexList.cs
  60. 26
      src/Perspex.Base/Logging/ILogSink.cs
  61. 36
      src/Perspex.Base/Logging/LogArea.cs
  62. 41
      src/Perspex.Base/Logging/LogEventLevel.cs
  63. 139
      src/Perspex.Base/Logging/Logger.cs
  64. 9
      src/Perspex.Base/Perspex.Base.csproj
  65. 72
      src/Perspex.Base/PerspexObject.cs
  66. 13
      src/Perspex.Base/PerspexObjectExtensions.cs
  67. 1
      src/Perspex.Base/PriorityLevel.cs
  68. 30
      src/Perspex.Base/PriorityValue.cs
  69. 41
      src/Perspex.Base/Reactive/ObservableEx.cs
  70. 2
      src/Perspex.Base/Utilities/WeakSubscriptionManager.cs
  71. 1
      src/Perspex.Base/packages.config
  72. 0
      src/Perspex.Controls/Application.cs
  73. 12
      src/Perspex.Controls/Border.cs
  74. 35
      src/Perspex.Controls/Canvas.cs
  75. 184
      src/Perspex.Controls/Control.cs
  76. 10
      src/Perspex.Controls/Design.cs
  77. 42
      src/Perspex.Controls/Expander.cs
  78. 9
      src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  79. 14
      src/Perspex.Controls/IControl.cs
  80. 389
      src/Perspex.Controls/LayoutTransformControl.cs
  81. 22
      src/Perspex.Controls/MenuItem.cs
  82. 13
      src/Perspex.Controls/Mixins/ContentControlMixin.cs
  83. 6
      src/Perspex.Controls/Panel.cs
  84. 7
      src/Perspex.Controls/Perspex.Controls.csproj
  85. 171
      src/Perspex.Controls/Platform/PlatformManager.cs
  86. 8
      src/Perspex.Controls/Presenters/ContentPresenter.cs
  87. 25
      src/Perspex.Controls/Presenters/ScrollContentPresenter.cs
  88. 9
      src/Perspex.Controls/Presenters/TextPresenter.cs
  89. 10
      src/Perspex.Controls/Primitives/IScrollable.cs
  90. 10
      src/Perspex.Controls/Primitives/Popup.cs
  91. 11
      src/Perspex.Controls/Primitives/PopupRoot.cs
  92. 32
      src/Perspex.Controls/Primitives/TemplatedControl.cs
  93. 2
      src/Perspex.Controls/Properties/AssemblyInfo.cs
  94. 238
      src/Perspex.Controls/ScrollViewer.cs
  95. 12
      src/Perspex.Controls/Shapes/Shape.cs
  96. 5
      src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs
  97. 9
      src/Perspex.Controls/Templates/ITreeDataTemplate.cs
  98. 16
      src/Perspex.Controls/TextBlock.cs
  99. 76
      src/Perspex.Controls/TextBox.cs
  100. 5
      src/Perspex.Controls/ToolTip.cs

86
Perspex.sln

@ -25,8 +25,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Styling", "src\Pers
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Themes.Default", "src\Perspex.Themes.Default\Perspex.Themes.Default.csproj", "{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Application", "src\Perspex.Application\Perspex.Application.csproj", "{799A7BB5-3C2C-48B6-85A7-406A12C420DA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Diagnostics", "src\Perspex.Diagnostics\Perspex.Diagnostics.csproj", "{7062AE20-5DCC-4442-9645-8195BDECE63E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Animation", "src\Perspex.Animation\Perspex.Animation.csproj", "{D211E587-D8BC-45B9-95A4-F297C8FA5200}"
@ -99,6 +97,9 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup.UnitTests", "tests\Perspex.Markup.UnitTests\Perspex.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BindingTest", "samples\BindingTest\BindingTest.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}"
ProjectSection(ProjectDependencies) = postProject
{B61B66A3-B82D-4875-8001-89D3394FE0C9} = {B61B66A3-B82D-4875-8001-89D3394FE0C9}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlTestApplicationPcl", "samples\XamlTestApplicationPcl\XamlTestApplicationPcl.csproj", "{EA113F1A-D8D7-4142-9948-353270E7EBAE}"
EndProject
@ -142,6 +143,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.UnitTests", "tests\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Benchmarks", "tests\Perspex.Benchmarks\Perspex.Benchmarks.csproj", "{410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Logging.Serilog", "src\Perspex.Logging.Serilog\Perspex.Logging.Serilog.csproj", "{B61B66A3-B82D-4875-8001-89D3394FE0C9}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.DesignerSupport", "src\Perspex.DesignerSupport\Perspex.DesignerSupport.csproj", "{799A7BB5-3C2C-48B6-85A7-406A12C420DA}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4
@ -150,20 +154,20 @@ Global
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
src\Shared\PlatformSupport\PlatformSupport.projitems*{db070a10-bf39-4752-8456-86e9d5928478}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4
src\Skia\Perspex.Skia\Perspex.Skia.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4
samples\TestApplicationShared\TestApplicationShared.projitems*{78345174-5b52-4a14-b9fd-d5f2428137f0}*SharedItemsImports = 13
src\Shared\PlatformSupport\PlatformSupport.projitems*{54f237d5-a70a-4752-9656-0c70b1a7b047}*SharedItemsImports = 4
samples\TestApplicationShared\TestApplicationShared.projitems*{ff69b927-c545-49ae-8e16-3d14d621aa12}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\PlatformSupport\PlatformSupport.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*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\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*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
@ -422,30 +426,6 @@ Global
{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}.Release|iPhone.Build.0 = Release|Any CPU
{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.AppStore|Any CPU.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.AppStore|iPhone.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|iPhone.Build.0 = Debug|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|Any CPU.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|iPhone.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|iPhone.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{7062AE20-5DCC-4442-9645-8195BDECE63E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{7062AE20-5DCC-4442-9645-8195BDECE63E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{7062AE20-5DCC-4442-9645-8195BDECE63E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
@ -1321,6 +1301,54 @@ Global
{410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhone.Build.0 = Release|Any CPU
{410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.AppStore|Any CPU.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.AppStore|iPhone.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Debug|iPhone.Build.0 = Debug|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Release|Any CPU.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Release|iPhone.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Release|iPhone.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.AppStore|Any CPU.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.AppStore|iPhone.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|iPhone.Build.0 = Debug|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|Any CPU.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|iPhone.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|iPhone.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

2
docs/docfx.json

@ -50,6 +50,6 @@
"_appTitle": "Perspex Website"
},
"dest": "_site",
"template": "default"
"template": [ "default", "template"]
}
}

54
docs/spec/logging.md

@ -0,0 +1,54 @@
# Perspex Logging
Perspex uses [Serilog](https://github.com/serilog/serilog) for logging via
the Perspex.Logging.Serilog assembly.
The following method should be present in your App.xaml.cs file:
```C#
private void InitializeLogging()
{
#if DEBUG
SerilogLogger.Initialize(new LoggerConfiguration()
.MinimumLevel.Warning()
.WriteTo.Trace(outputTemplate: "{Area}: {Message}")
.CreateLogger());
#endif
}
```
By default, this logging setup will write log messages with a severity of
`Warning` or higher to `System.Diagnostics.Trace`. See the [Serilog
documentation](https://github.com/serilog/serilog/wiki/Configuration-Basics)
for more information on the options here.
## Areas
Each Perspex log message has an "Area" that can be used to filter the log to
include only the type of events that you are interested in. These are currently:
- Property
- Binding
- Visual
- Layout
- Control
To limit the log output to a specific area you can add a filter; for example
to enable verbose logging but only about layout:
```C#
SerilogLogger.Initialize(new LoggerConfiguration()
.Filter.ByIncludingOnly(Matching.WithProperty("Area", LogArea.Layout))
.MinimumLevel.Verbose()
.WriteTo.Trace(outputTemplate: "{Area}: {Message}")
.CreateLogger());
```
## Removing Serilog
If you don't want a dependency on Serilog in your application, simply remove
the reference to Perspex.Logging.Serilog and the code that initializes it. If
you do however still want some kinda of logging, there are two steps:
- Implement `Perspex.Logging.ILogSink`
- Assign your implementation to `Logger.Sink`

10
docs/spec/styles.md

@ -49,6 +49,16 @@ As in CSS, controls can be given *style classes* which can be used in selectors:
<Button>I will not.</Button>
</StackPanel>
Each control can be given 0 or more style classes. This is different to WPF
where only a single style can be applied to a control: in Perspex any number
of separate styles can be applied to a control. If more than one style affects
a particular property, the style closest to the control will take precedence.
Style classes can also be manipulated in code using the `Classes` collection:
control.Classes.Add("blue");
control.Classes.Remove("red");
## Pseudoclasses
Also as in CSS, controls can have pseudoclasses; these are classes that are

4
docs/spec/toc.yml

@ -5,4 +5,6 @@
- name: Defining Properties
href: defining-properties.md
- name: Working with Properties
href: working-with-properties.md
href: working-with-properties.md
- name: Logging
href: logging.md

13
docs/template/partials/footer.tmpl.partial

@ -0,0 +1,13 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<footer>
<div class="grad-bottom"></div>
<div class="footer">
<div class="container">
<span class="pull-right">
<a href="#top">Back to top</a>
</span>
<span>Copyright © 2016 The Perspex Project<br>Generated by <strong>DocFX</strong></span>
</div>
</div>
</footer>

176
docs/tutorial/from-wpf.md

@ -0,0 +1,176 @@
# Perspex for WPF Developers
Perspex is in general very similar to WPF, but you will find differences. Here
are the most common:
## Styling
The most obvious difference from other XAML frameworks is that Perspex uses a
[CSS-like styling system](../spec/styles.md). Styles aren't stored in a
`Resources` collection as in WPF, they are stored in a separate `Styles`
collection:
<UserControl>
<UserControl.Styles>
<!-- Make TextBlocks with the h1 style class have a font size of 24 points -->
<Style Selector="TextBlock.h1">
<Setter Property="FontSize" Value="24"/>
</Style>
</UserControl.Styles>
<TextBlock Classes="h1">Header</TextBlock>
<UserControl>
## DataTemplates
As styles aren't stored in `Resources`, neither are `DataTemplates` ([in fact
there is no `Resources` collection](#resources)). Instead, `DataTemplates` are
placed in a `DataTemplates` collection on each control (and on `Application`):
<UserControl xmlns:viewmodels="clr-namespace:MyApp.ViewModels;assembly=MyApp">
<UserControl.DataTemplates>
<DataTemplate DataType="viewmodels:FooViewModel">
<Border Background="Red" CornerRadius="8">
<TextBox Text="{Binding Name}"/>
</Border>
</DataTemplate>
</UserControl.Styles>
<!-- Assuming that DataContext.Foo is an object of type
MyApp.ViewModels.FooViewModel then a red border with a corner
radius of 8 containing a TextBox will be displayed here -->
<ContentControl Content="{Binding Foo}"/>
<UserControl>
`ItemsControl`s don't currently have an `ItemTemplate` property: instead just
place the template for your items into the control's `DataTemplates`, e.g.
<ListBox Items="ItemsSource">
<ListBox.DataTemplates>
<DataTemplate>
<TextBlock Text="{Binding Caption}"/>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
Data templates in Perspex can also target interfaces and derived classes (which
cannot be done in WPF) and so the order of `DataTemplate`s can be important:
`DataTemplate`s within the same collection are evaluated in declaration order
so you need to place them from most-specific to least-specific as you would in
code.
## HierachicalDataTemplate
WPF's `HierarchicalDataTemplate` is called `TreeDataTemplate` in Perspex (as the
former is difficult to type!). The two are almost entirely equivalent except
that the `ItemTemplate` property is not present in Perspex.
## UIElement, FrameworkElement and Control
WPF's `UIElement` and `FrameworkElement` are non-templated control base classes,
which roughly equate to the Perspex `Control` class. WPF's `Control` class on
the other hand is a templated control - Perspex's equivalent of this is
`TemplatedControl`.
So to recap:
- `UIElement`: `Control`
- `FrameworkElement`: `Control`
- `Control`: `TemplatedControl`
## DependencyProperty
The Perspex equivalent of `DependencyProperty` is `StyledProperty`, however
Perspex [has a richer property system than WPF](../spec/defining-properties.md),
and includes `DirectProperty` for turning standard CLR properties into Perspex
properties. The common base class of `StyledProperty` and `DirectProperty`
is `PerspexProperty`.
# Resources
There is no `Resources` collection on controls in Perspex, however `Style`s
do have a `Resources` collection for style-related resources. These can be
referred to using the `{StyleResource}` markup extension both inside and outside
styles.
For non-style-related resources, we suggest defining them in code and referring
to them in markup using the `{Static}` markup extension. There are [various
reasons](http://www.codemag.com/article/1501091) for this, but briefly:
- Resources have to be parsed
- The tree has to be traversed to find them
- XAML doesn't handle immutable objects
- XAML syntax can be long-winded compared to C#
## Grid
Column and row definitions can be specified in Perspex using strings, avoiding
the clunky syntax in WPF:
<Grid ColumnDefinitions="Auto,*,32" RowDefinitions="*,Auto">
A common use of `Grid` in WPF is to stack two controls on top of each other.
For this purpose in Perspex you can just use a `Panel` which is more lightweight
than `Grid`.
We don't yet support `SharedSizeScope` in `Grid`.
## ItemsControl
In WPF, `ItemsControl` and derived classes such as `ListBox` have two separate
items properties: `Items` and `ItemsSource`. Perspex however just has a single
one: `Items`.
## Tunnelling Events
Perspex has tunnelling events (unlike UWP!) but they're not exposed via
separate `Preview` CLR event handlers. To subscribe to a tunnelling event you
must call `AddHandler` with `RoutingStrategies.Tunnel`:
```
target.AddHandler(InputElement.KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel);
void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
// Handler code
}
```
## Class Handlers
In WPF, class handlers for events can be added by calling
[EventManager.RegisterClassHandler](https://msdn.microsoft.com/en-us/library/ms597875.aspx).
An example of registering a class handler in WPF might be:
static MyControl()
{
EventManager.RegisterClassHandler(typeof(MyControl), MyEvent, HandleMyEvent));
}
private static void HandleMyEvent(object sender, RoutedEventArgs e)
{
}
The equivalent of this in Perspex would be:
static MyControl()
{
MyEvent.AddClassHandler<MyControl>(x => x.HandleMyEvent);
}
private void HandleMyEvent(object sender, RoutedEventArgs e)
{
}
Notice that in WPF you have to add the class handler as a static method, whereas
in Perspex the class handler is not static: the notification is automatically
directed to the correct instance.
## PropertyChangedCallback
Listening to changes on DependencyProperties in WPF can be complex. When you
register a `DependencyProperty` you can supply a static `PropertyChangedCallback`
but if you want to listen to changes from elsewhere [things can get complicated
and error-prone](http://stackoverflow.com/questions/23682232).
In Perspex, there is no `PropertyChangedCallback` at the time of registration,
instead a class listener is [added to the control's static constructor in much
the same way that event class listeners are added](../spec/working-with-properties.md#subscribing-to-a-property-on-any-object).

6
nuget/build-version.ps1

@ -27,12 +27,12 @@ mkdir $ios
Copy-Item ..\src\Perspex.Animation\bin\Release\Perspex.Animation.dll $lib
Copy-Item ..\src\Perspex.Animation\bin\Release\Perspex.Animation.xml $lib
Copy-Item ..\src\Perspex.Application\bin\Release\Perspex.Application.dll $lib
Copy-Item ..\src\Perspex.Application\bin\Release\Perspex.Application.xml $lib
Copy-Item ..\src\Perspex.Base\bin\Release\Perspex.Base.dll $lib
Copy-Item ..\src\Perspex.Base\bin\Release\Perspex.Base.xml $lib
Copy-Item ..\src\Perspex.Controls\bin\Release\Perspex.Controls.dll $lib
Copy-Item ..\src\Perspex.Controls\bin\Release\Perspex.Controls.xml $lib
Copy-Item ..\src\Perspex.DesignerSupport\bin\Release\Perspex.DesignerSupport.dll $lib
Copy-Item ..\src\Perspex.DesignerSupport\bin\Release\Perspex.DesignerSupport.xml $lib
Copy-Item ..\src\Perspex.Diagnostics\bin\Release\\Perspex.Diagnostics.dll $lib
Copy-Item ..\src\Perspex.Diagnostics\bin\Release\\Perspex.Diagnostics.xml $lib
Copy-Item ..\src\Perspex.Input\bin\Release\Perspex.Input.dll $lib
@ -41,6 +41,8 @@ Copy-Item ..\src\Perspex.Interactivity\bin\Release\Perspex.Interactivity.dll $li
Copy-Item ..\src\Perspex.Interactivity\bin\Release\Perspex.Interactivity.xml $lib
Copy-Item ..\src\Perspex.Layout\bin\Release\Perspex.Layout.dll $lib
Copy-Item ..\src\Perspex.Layout\bin\Release\Perspex.Layout.xml $lib
Copy-Item ..\src\Perspex.Logging.Serilog\bin\Release\Perspex.Logging.Serilog.dll $lib
Copy-Item ..\src\Perspex.Logging.Serilog\bin\Release\Perspex.Logging.Serilog.xml $lib
Copy-Item ..\src\Perspex.SceneGraph\bin\Release\Perspex.SceneGraph.dll $lib
Copy-Item ..\src\Perspex.SceneGraph\bin\Release\Perspex.SceneGraph.xml $lib
Copy-Item ..\src\Perspex.Styling\bin\Release\Perspex.Styling.dll $lib

6
samples/BindingTest/App.config

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup>
</configuration>
</configuration>

20
samples/BindingTest/App.xaml.cs

@ -2,9 +2,9 @@
using Perspex;
using Perspex.Controls;
using Perspex.Diagnostics;
using Perspex.Logging.Serilog;
using Perspex.Markup.Xaml;
using Serilog;
using Serilog.Filters;
namespace BindingTest
{
@ -15,13 +15,7 @@ namespace BindingTest
RegisterServices();
InitializeSubsystems((int)Environment.OSVersion.Platform);
InitializeComponent();
Log.Logger = new LoggerConfiguration()
.Filter.ByIncludingOnly(Matching.WithProperty("Area", "Property"))
.Filter.ByIncludingOnly(Matching.WithProperty("Property", "Text"))
.MinimumLevel.Verbose()
.WriteTo.Trace(outputTemplate: "[{Id:X8}] [{SourceContext}] {Message}")
.CreateLogger();
InitializeLogging();
}
public static void AttachDevTools(Window window)
@ -41,5 +35,15 @@ namespace BindingTest
{
PerspexXamlLoader.Load(this);
}
private void InitializeLogging()
{
#if DEBUG
SerilogLogger.Initialize(new LoggerConfiguration()
.MinimumLevel.Warning()
.WriteTo.Trace(outputTemplate: "{Area}: {Message}")
.CreateLogger());
#endif
}
}
}

15
samples/BindingTest/BindingTest.csproj

@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BindingTest</RootNamespace>
<AssemblyName>BindingTest</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -111,10 +112,6 @@
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Application\Perspex.Application.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.Application</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
@ -123,6 +120,10 @@
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.DesignerSupport\Perspex.DesignerSupport.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.DesignerSupport</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Diagnostics\Perspex.Diagnostics.csproj">
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Perspex.Diagnostics</Name>
@ -139,6 +140,10 @@
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Logging.Serilog\Perspex.Logging.Serilog.csproj">
<Project>{b61b66a3-b82d-4875-8001-89d3394fe0c9}</Project>
<Name>Perspex.Logging.Serilog</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.ReactiveUI\Perspex.ReactiveUI.csproj">
<Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
<Name>Perspex.ReactiveUI</Name>

10
samples/ControlCatalog/App.config

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
</configuration>

9
samples/ControlCatalog/App.xaml.cs

@ -4,6 +4,7 @@ using Perspex;
using Perspex.Controls;
using Perspex.Diagnostics;
using Perspex.Markup.Xaml;
using Perspex.Logging.Serilog;
using Serilog;
namespace ControlCatalog
@ -41,10 +42,10 @@ namespace ControlCatalog
private void InitializeLogging()
{
#if DEBUG
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Error()
.WriteTo.Trace(outputTemplate: "{Message}")
.CreateLogger();
SerilogLogger.Initialize(new LoggerConfiguration()
.MinimumLevel.Warning()
.WriteTo.Trace(outputTemplate: "{Area}: {Message}")
.CreateLogger());
#endif
}

BIN
samples/ControlCatalog/Assets/github_icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

76
samples/ControlCatalog/ControlCatalog.csproj

@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ControlCatalog</RootNamespace>
<AssemblyName>ControlCatalog</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -60,6 +61,27 @@
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ImagePage.xaml.cs">
<DependentUpon>ImagePage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ExpanderPage.xaml.cs">
<DependentUpon>ExpanderPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\RadioButtonPage.xaml.cs">
<DependentUpon>RadioButtonPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\MenuPage.xaml.cs">
<DependentUpon>MenuPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ToolTipPage.xaml.cs">
<DependentUpon>ToolTipPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\TextBoxPage.xaml.cs">
<DependentUpon>TextBoxPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\LayoutTransformControlPage.xaml.cs">
<DependentUpon>LayoutTransformControlPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\CheckBoxPage.xaml.cs">
<DependentUpon>CheckBoxPage.xaml</DependentUpon>
</Compile>
@ -136,10 +158,6 @@
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Application\Perspex.Application.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.Application</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
@ -148,6 +166,10 @@
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.DesignerSupport\Perspex.DesignerSupport.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.DesignerSupport</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Diagnostics\Perspex.Diagnostics.csproj">
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Perspex.Diagnostics</Name>
@ -168,6 +190,10 @@
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Logging.Serilog\Perspex.Logging.Serilog.csproj">
<Project>{b61b66a3-b82d-4875-8001-89d3394fe0c9}</Project>
<Name>Perspex.Logging.Serilog</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.ReactiveUI\Perspex.ReactiveUI.csproj">
<Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
<Name>Perspex.ReactiveUI</Name>
@ -200,6 +226,46 @@
<EmbeddedResource Include="Assets\delicate-arch-896885_640.jpg" />
<EmbeddedResource Include="Assets\maple-leaf-888807_640.jpg" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\LayoutTransformControlPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\TextBoxPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\ToolTipPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\MenuPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\github_icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\RadioButtonPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\ExpanderPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\ImagePage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

7
samples/ControlCatalog/MainWindow.xaml

@ -11,6 +11,13 @@
<TabItem Header="Carousel"><pages:CarouselPage/></TabItem>
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="DropDown"><pages:DropDownPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem>
<TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
</TabControl>
</Window>

32
samples/ControlCatalog/Pages/ExpanderPage.xaml

@ -0,0 +1,32 @@
<UserControl xmlns="https://github.com/perspex">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">Expander</TextBlock>
<TextBlock Classes="h2">Expands to show nested content</TextBlock>
<StackPanel Orientation="Vertical"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<Expander Header="Expand Up" ExpandDirection="Up">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander Header="Expand Down" ExpandDirection="Down">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander Header="Expand Left" ExpandDirection="Left">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander Header="Expand Right" ExpandDirection="Right">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
</StackPanel>
</StackPanel>
</UserControl>

18
samples/ControlCatalog/Pages/ExpanderPage.xaml.cs

@ -0,0 +1,18 @@
using Perspex.Controls;
using Perspex.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class ExpanderPage : UserControl
{
public ExpanderPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
PerspexXamlLoader.Load(this);
}
}
}

39
samples/ControlCatalog/Pages/ImagePage.xaml

@ -0,0 +1,39 @@
<UserControl xmlns="https://github.com/perspex">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">Image</TextBlock>
<TextBlock Classes="h2">Displays an image</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<StackPanel Orientation="Vertical">
<TextBlock>No Stretch</TextBlock>
<Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="None"/>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>Fill</TextBlock>
<Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="Fill"/>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>Uniform</TextBlock>
<Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="Uniform"/>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>UniformToFill</TextBlock>
<Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="UniformToFill"/>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

18
samples/ControlCatalog/Pages/ImagePage.xaml.cs

@ -0,0 +1,18 @@
using Perspex.Controls;
using Perspex.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class ImagePage : UserControl
{
public ImagePage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
PerspexXamlLoader.Load(this);
}
}
}

26
samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml

@ -0,0 +1,26 @@
<UserControl xmlns="https://github.com/perspex"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto" Margin="16" DockPanel.Dock="Top">
<TextBlock Grid.Column="0" Grid.Row="0">Rotation</TextBlock>
<Slider Name="rotation" Maximum="360" Grid.Column="1" Grid.Row="0"/>
</Grid>
<Grid ColumnDefinitions="24,Auto,24"
RowDefinitions="24,Auto,24"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Border Background="{StyleResource ThemeAccentBrush}" Grid.Column="1" Grid.Row="0"/>
<Border Background="{StyleResource ThemeAccentBrush}" Grid.Column="0" Grid.Row="1"/>
<Border Background="{StyleResource ThemeAccentBrush}" Grid.Column="2" Grid.Row="1"/>
<Border Background="{StyleResource ThemeAccentBrush}" Grid.Column="1" Grid.Row="2"/>
<LayoutTransformControl Name="layoutTransform" Grid.Column="1" Grid.Row="1">
<LayoutTransformControl.LayoutTransform>
<RotateTransform Angle="{Binding #rotation.Value}"/>
</LayoutTransformControl.LayoutTransform>
<TextBlock>Layout Transform</TextBlock>
</LayoutTransformControl>
</Grid>
</DockPanel>
</UserControl>

18
samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml.cs

@ -0,0 +1,18 @@
using Perspex.Controls;
using Perspex.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class LayoutTransformControlPage : UserControl
{
public LayoutTransformControlPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
PerspexXamlLoader.Load(this);
}
}
}

34
samples/ControlCatalog/Pages/MenuPage.xaml

@ -0,0 +1,34 @@
<UserControl xmlns="https://github.com/perspex">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">Menu</TextBlock>
<TextBlock Classes="h2">A window menu</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<Menu>
<MenuItem Header="_First">
<MenuItem Header="Standard _Menu Item"/>
<MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/>
</MenuItem>
<MenuItem Header="Menu Item with _Icon">
<MenuItem.Icon>
<Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Menu Item with _Checkbox">
<MenuItem.Icon>
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="_Second">
<MenuItem Header="Second _Menu Item"/>
</MenuItem>
</Menu>
</StackPanel>
</StackPanel>
</UserControl>

18
samples/ControlCatalog/Pages/MenuPage.xaml.cs

@ -0,0 +1,18 @@
using Perspex.Controls;
using Perspex.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class MenuPage : UserControl
{
public MenuPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
PerspexXamlLoader.Load(this);
}
}
}

15
samples/ControlCatalog/Pages/RadioButtonPage.xaml

@ -0,0 +1,15 @@
<UserControl xmlns="https://github.com/perspex">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">RadioButton</TextBlock>
<TextBlock Classes="h2">Allows the selection of a single option of many</TextBlock>
<StackPanel Orientation="Vertical"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<RadioButton IsChecked="True">Option 1</RadioButton>
<RadioButton>Option 2</RadioButton>
<RadioButton IsEnabled="False">Disabled</RadioButton>
</StackPanel>
</StackPanel>
</UserControl>

18
samples/ControlCatalog/Pages/RadioButtonPage.xaml.cs

@ -0,0 +1,18 @@
using Perspex.Controls;
using Perspex.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class RadioButtonPage : UserControl
{
public RadioButtonPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
PerspexXamlLoader.Load(this);
}
}
}

30
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -0,0 +1,30 @@
<UserControl xmlns="https://github.com/perspex">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">TextBox</TextBlock>
<TextBlock Classes="h2">A control into which the user can input text</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<StackPanel Orientation="Vertical" Gap="8">
<TextBox Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit." Width="200" />
<TextBox Width="200" Watermark="Watermark" />
<TextBox Width="200"
Watermark="Floating Watermark"
UseFloatingWatermark="True"
Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBox Width="200" Text="Left aligned text" TextAlignment="Left" />
<TextBox Width="200" Text="Center aligned text" TextAlignment="Center" />
<TextBox Width="200" Text="Right aligned text" TextAlignment="Right" />
</StackPanel>
<StackPanel Orientation="Vertical" Gap="8">
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Width="200" Height="125"
Text="Multiline TextBox with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
<TextBox AcceptsReturn="True" Width="200" Height="125"
Text="Multiline TextBox with no TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

18
samples/ControlCatalog/Pages/TextBoxPage.xaml.cs

@ -0,0 +1,18 @@
using Perspex.Controls;
using Perspex.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class TextBoxPage : UserControl
{
public TextBoxPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
PerspexXamlLoader.Load(this);
}
}
}

22
samples/ControlCatalog/Pages/ToolTipPage.xaml

@ -0,0 +1,22 @@
<UserControl xmlns="https://github.com/perspex">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">ToolTip</TextBlock>
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<Border Background="{StyleResource ThemeAccentBrush}"
Padding="48,48,48,48">
<ToolTip.Tip>
<StackPanel>
<TextBlock Classes="h1">ToolTip</TextBlock>
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock>
</StackPanel>
</ToolTip.Tip>
<TextBlock>Hover Here</TextBlock>
</Border>
</StackPanel>
</StackPanel>
</UserControl>

18
samples/ControlCatalog/Pages/ToolTipPage.xaml.cs

@ -0,0 +1,18 @@
using Perspex.Controls;
using Perspex.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class ToolTipPage : UserControl
{
public ToolTipPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
PerspexXamlLoader.Load(this);
}
}
}

4
samples/ControlCatalog/SideBar.xaml

@ -3,7 +3,7 @@
<Setter Property="Template">
<ControlTemplate>
<DockPanel>
<Border MinWidth="190" Background="{StyleResource ThemeAccentBrush}" DockPanel.Dock="Left">
<ScrollViewer MinWidth="190" Background="{StyleResource ThemeAccentBrush}" DockPanel.Dock="Left">
<TabStrip Name="PART_TabStrip"
MemberSelector="{Static TabControl.HeaderSelector}"
Items="{TemplateBinding Items}"
@ -14,7 +14,7 @@
</ItemsPanelTemplate>
</TabStrip.ItemsPanel>
</TabStrip>
</Border>
</ScrollViewer>
<Carousel Name="PART_Content"
Margin="8 0 0 0"
MemberSelector="{Static TabControl.ContentSelector}"

12
samples/TestApplication/App.config

@ -18,16 +18,16 @@
<bindingRedirect oldVersion="0.0.0.0-2.2.5.0" newVersion="2.2.5.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SharpDX.Direct2D1" publicKeyToken="b4dcf0f35e5521f1" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0"/>
<assemblyIdentity name="SharpDX.Direct2D1" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SharpDX" publicKeyToken="b4dcf0f35e5521f1" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0"/>
<assemblyIdentity name="SharpDX" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SharpDX.DXGI" publicKeyToken="b4dcf0f35e5521f1" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0"/>
<assemblyIdentity name="SharpDX.DXGI" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral" />

8
samples/TestApplication/TestApplication.csproj

@ -98,10 +98,6 @@
<Project>{D211E587-D8BC-45B9-95A4-F297C8FA5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Application\Perspex.Application.csproj">
<Project>{799A7BB5-3C2C-48B6-85A7-406A12C420DA}</Project>
<Name>Perspex.Application</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Base\Perspex.Base.csproj">
<Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
<Name>Perspex.Base</Name>
@ -110,6 +106,10 @@
<Project>{D2221C82-4A25-4583-9B43-D791E3F6820C}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.DesignerSupport\Perspex.DesignerSupport.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.DesignerSupport</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Diagnostics\Perspex.Diagnostics.csproj">
<Project>{7062AE20-5DCC-4442-9645-8195BDECE63E}</Project>
<Name>Perspex.Diagnostics</Name>

4
samples/TestApplicationShared/GalleryStyle.cs

@ -49,7 +49,7 @@ namespace TestApplication
Setters = new[]
{
new Setter(TemplatedControl.ForegroundProperty, Brushes.White),
new Setter(TemplatedControl.BackgroundProperty, new SolidColorBrush(Colors.White) { Opacity = 0.1 }),
new Setter(TemplatedControl.BackgroundProperty, new SolidColorBrush(Colors.White, 0.1)),
},
},
});
@ -91,7 +91,7 @@ namespace TestApplication
new Border
{
Width = 190,
Background = SolidColorBrush.Parse("#1976D2"),
Background = Brush.Parse("#1976D2"),
Child = new ScrollViewer
{
Content = new TabStrip

70
samples/TestApplicationShared/MainWindow.cs

@ -131,13 +131,13 @@ namespace TestApplication
Text = "Button",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "A button control",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new Button
@ -157,13 +157,13 @@ namespace TestApplication
Text = "ToggleButton",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "A toggle button control",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new ToggleButton
@ -207,13 +207,13 @@ namespace TestApplication
Text = "TextBlock",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "A control for displaying text.",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new TextBlock
@ -245,18 +245,18 @@ namespace TestApplication
Text = "HtmlLabel",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "A label capable of displaying HTML content",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new HtmlLabel
{
Background = SolidColorBrush.Parse("#CCCCCC"),
Background = Brush.Parse("#CCCCCC"),
Padding = new Thickness(5),
Text = @"<p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href=""#"">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p>
<h2>Header Level 2</h2>
@ -300,13 +300,13 @@ namespace TestApplication
Text = "TextBox",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "A text box control",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
@ -320,13 +320,13 @@ namespace TestApplication
Text = "CheckBox",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "A check box control",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new CheckBox { IsChecked = true, Margin = new Thickness(0, 0, 0, 5), Content = "Checked" },
@ -337,13 +337,13 @@ namespace TestApplication
Text = "RadioButton",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "A radio button control",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new RadioButton { IsChecked = true, Content = "Option 1" },
@ -398,13 +398,13 @@ namespace TestApplication
Text = "ListBox",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "A list box control.",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new ListBox
@ -420,13 +420,13 @@ namespace TestApplication
Text = "TreeView",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "A tree view control.",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new TreeView
@ -514,13 +514,13 @@ namespace TestApplication
Text = "Carousel",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "An items control that displays its items as pages that fill the controls.",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new StackPanel
@ -573,13 +573,13 @@ namespace TestApplication
Text = "Grid",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "Lays out child controls according to a grid.",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new Grid
@ -601,21 +601,21 @@ namespace TestApplication
new Rectangle
{
Fill = SolidColorBrush.Parse("#FF5722"),
Fill = Brush.Parse("#FF5722"),
[Grid.ColumnSpanProperty] = 2,
Height = 200,
Margin = new Thickness(2.5)
},
new Rectangle
{
Fill = SolidColorBrush.Parse("#FF5722"),
Fill = Brush.Parse("#FF5722"),
[Grid.RowProperty] = 1,
Height = 100,
Margin = new Thickness(2.5)
},
new Rectangle
{
Fill = SolidColorBrush.Parse("#FF5722"),
Fill = Brush.Parse("#FF5722"),
[Grid.RowProperty] = 1,
[Grid.ColumnProperty] = 1,
Height = 100,
@ -629,13 +629,13 @@ namespace TestApplication
Text = "StackPanel",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "A panel which lays out its children horizontally or vertically.",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new StackPanel
@ -647,17 +647,17 @@ namespace TestApplication
{
new Rectangle
{
Fill = SolidColorBrush.Parse("#FFC107"),
Fill = Brush.Parse("#FFC107"),
Height = 50,
},
new Rectangle
{
Fill = SolidColorBrush.Parse("#FFC107"),
Fill = Brush.Parse("#FFC107"),
Height = 50,
},
new Rectangle
{
Fill = SolidColorBrush.Parse("#FFC107"),
Fill = Brush.Parse("#FFC107"),
Height = 50,
},
}
@ -668,13 +668,13 @@ namespace TestApplication
Text = "Canvas",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "A panel which lays out its children by explicit coordinates.",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new Canvas
@ -762,13 +762,13 @@ namespace TestApplication
Text = "Animations",
FontWeight = FontWeight.Medium,
FontSize = 20,
Foreground = SolidColorBrush.Parse("#212121"),
Foreground = Brush.Parse("#212121"),
},
new TextBlock
{
Text = "A few animations showcased below",
FontSize = 13,
Foreground = SolidColorBrush.Parse("#727272"),
Foreground = Brush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
(button1 = new Button

19
samples/XamlTestApplication/App.cs

@ -2,16 +2,31 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex;
using Perspex.Themes.Default;
using Perspex.Logging.Serilog;
using Serilog;
namespace XamlTestApplication
{
public class App : XamlTestApp
{
public App()
{
InitializeLogging();
}
protected override void RegisterPlatform()
{
InitializeSubsystems((int)Environment.OSVersion.Platform);
}
private void InitializeLogging()
{
#if DEBUG
SerilogLogger.Initialize(new LoggerConfiguration()
.MinimumLevel.Warning()
.WriteTo.Trace(outputTemplate: "{Area}: {Message}")
.CreateLogger());
#endif
}
}
}

12
samples/XamlTestApplication/XamlTestApplication.csproj

@ -95,10 +95,6 @@
<Project>{D211E587-D8BC-45B9-95A4-F297C8FA5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Application\Perspex.Application.csproj">
<Project>{799A7BB5-3C2C-48B6-85A7-406A12C420DA}</Project>
<Name>Perspex.Application</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Base\Perspex.Base.csproj">
<Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
<Name>Perspex.Base</Name>
@ -107,6 +103,10 @@
<Project>{D2221C82-4A25-4583-9B43-D791E3F6820C}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.DesignerSupport\Perspex.DesignerSupport.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.DesignerSupport</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Diagnostics\Perspex.Diagnostics.csproj">
<Project>{7062AE20-5DCC-4442-9645-8195BDECE63E}</Project>
<Name>Perspex.Diagnostics</Name>
@ -127,6 +127,10 @@
<Project>{42472427-4774-4C81-8AFF-9F27B8E31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Logging.Serilog\Perspex.Logging.Serilog.csproj">
<Project>{b61b66a3-b82d-4875-8001-89d3394fe0c9}</Project>
<Name>Perspex.Logging.Serilog</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.ReactiveUI\Perspex.ReactiveUI.csproj">
<Project>{6417B24E-49C2-4985-8DB2-3AB9D898EC91}</Project>
<Name>Perspex.ReactiveUI</Name>

16
samples/XamlTestApplicationPcl/TestScrollable.cs

@ -37,6 +37,22 @@ namespace XamlTestApplication
get { return _viewport; }
}
public Size ScrollSize
{
get
{
return new Size(double.PositiveInfinity, 1);
}
}
public Size PageScrollSize
{
get
{
return new Size(double.PositiveInfinity, Bounds.Height);
}
}
protected override Size MeasureOverride(Size availableSize)
{
using (var line = new FormattedText(

1
samples/XamlTestApplicationPcl/XamlTestApp.cs

@ -1,6 +1,5 @@
using Perspex;
using Perspex.Markup.Xaml;
using Perspex.Themes.Default;
namespace XamlTestApplication
{

8
samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj

@ -69,10 +69,6 @@
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Application\Perspex.Application.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.Application</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
@ -81,6 +77,10 @@
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.DesignerSupport\Perspex.DesignerSupport.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.DesignerSupport</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Diagnostics\Perspex.Diagnostics.csproj">
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Perspex.Diagnostics</Name>

4
src/Android/Perspex.Android/Perspex.Android.csproj

@ -99,10 +99,6 @@
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Application\Perspex.Application.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.Application</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>

4
src/Android/Perspex.AndroidTestApplication/Perspex.AndroidTestApplication.csproj

@ -139,10 +139,6 @@
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.Application\Perspex.Application.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.Application</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>

8
src/Gtk/Perspex.Cairo/Media/DrawingContext.cs

@ -135,7 +135,7 @@ namespace Perspex.Cairo.Media
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
public void DrawGeometry(Brush brush, Pen pen, Geometry geometry)
public void DrawGeometry(IBrush brush, Pen pen, Geometry geometry)
{
var impl = geometry.PlatformImpl as StreamGeometryImpl;
@ -190,7 +190,7 @@ namespace Perspex.Cairo.Media
/// <param name="foreground">The foreground brush.</param>
/// <param name="origin">The upper-left corner of the text.</param>
/// <param name="text">The text.</param>
public void DrawText(Brush foreground, Point origin, FormattedText text)
public void DrawText(IBrush foreground, Point origin, FormattedText text)
{
var layout = ((FormattedTextImpl)text.PlatformImpl).Layout;
_context.MoveTo(origin.X, origin.Y);
@ -206,7 +206,7 @@ namespace Perspex.Cairo.Media
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="rect">The rectangle bounds.</param>
public void FillRectangle(Brush brush, Rect rect, float cornerRadius)
public void FillRectangle(IBrush brush, Rect rect, float cornerRadius)
{
using (var b = SetBrush(brush, rect.Size))
{
@ -271,7 +271,7 @@ namespace Perspex.Cairo.Media
private double opacityOverride = 1.0f;
private IDisposable SetBrush(Brush brush, Size destinationSize)
private IDisposable SetBrush(IBrush brush, Size destinationSize)
{
_context.Save ();

2
src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs

@ -133,7 +133,7 @@ namespace Perspex.Cairo.Media
return new Size(width, height);
}
public void SetForegroundBrush(Brush brush, int startIndex, int count)
public void SetForegroundBrush(IBrush brush, int startIndex, int count)
{
var scb = brush as SolidColorBrush;
if (scb != null)

16
src/Gtk/Perspex.Cairo/Perspex.Cairo.csproj

@ -80,6 +80,18 @@
<Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
<Name>Perspex.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Controls\Perspex.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Input\Perspex.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Perspex.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Interactivity\Perspex.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Perspex.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Layout\Perspex.Layout.csproj">
<Project>{42472427-4774-4C81-8AFF-9F27B8E31721}</Project>
<Name>Perspex.Layout</Name>
@ -88,6 +100,10 @@
<Project>{EB582467-6ABB-43A1-B052-E981BA910E3A}</Project>
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Styling\Perspex.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />

4
src/Gtk/Perspex.Gtk/Perspex.Gtk.csproj

@ -63,10 +63,6 @@
<Project>{D211E587-D8BC-45B9-95A4-F297C8FA5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Application\Perspex.Application.csproj">
<Project>{799A7BB5-3C2C-48B6-85A7-406A12C420DA}</Project>
<Name>Perspex.Application</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Base\Perspex.Base.csproj">
<Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
<Name>Perspex.Base</Name>

4
src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeFeatureProvider.cs

@ -150,7 +150,7 @@ namespace Perspex.Markup.Xaml.Context
RegisterTypeConverter(typeof(Type), new TypeTypeConverter());
RegisterTypeConverter(typeof(IBitmap), new BitmapTypeConverter());
RegisterTypeConverter(typeof(Brush), new BrushTypeConverter());
RegisterTypeConverter(typeof(IBrush), new BrushTypeConverter());
RegisterTypeConverter(typeof(Color), new ColorTypeConverter());
RegisterTypeConverter(typeof(Classes), new ClassesTypeConverter());
RegisterTypeConverter(typeof(ColumnDefinitions), new ColumnDefinitionsTypeConverter());
@ -166,7 +166,7 @@ namespace Perspex.Markup.Xaml.Context
RegisterTypeConverter(typeof(RelativeRect), new RelativeRectTypeConverter());
RegisterTypeConverter(typeof(RowDefinitions), new RowDefinitionsTypeConverter());
RegisterTypeConverter(typeof(Selector), new SelectorTypeConverter());
RegisterTypeConverter(typeof(SolidColorBrush), new SolidColorBrushTypeConverter());
RegisterTypeConverter(typeof(SolidColorBrush), new BrushTypeConverter());
RegisterTypeConverter(typeof(Thickness), new ThicknessTypeConverter());
RegisterTypeConverter(typeof(TimeSpan), new TimeSpanTypeConverter());
RegisterTypeConverter(typeof(Uri), new UriTypeConverter());

20
src/Markup/Perspex.Markup.Xaml/Context/PropertyAccessor.cs

@ -54,6 +54,8 @@ namespace Perspex.Markup.Xaml.Context
private static PerspexProperty FindPerspexProperty(object instance, MutableMember member)
{
var registry = PerspexPropertyRegistry.Instance;
var attached = member as PerspexAttachableXamlMember;
var target = instance as PerspexObject;
if (target == null)
@ -61,12 +63,20 @@ namespace Perspex.Markup.Xaml.Context
return null;
}
var attached = member as PerspexAttachableXamlMember;
var propertyName = attached == null ?
member.Name :
member.DeclaringType.Name + "." + member.Name;
if (attached == null)
{
return registry.FindRegistered(target, member.Name);
}
else
{
var ownerType = attached.DeclaringType.UnderlyingType;
return PerspexPropertyRegistry.Instance.FindRegistered(target, propertyName);
RuntimeHelpers.RunClassConstructor(ownerType.TypeHandle);
return registry.GetRegistered(target)
.Where(x => x.OwnerType == ownerType && x.Name == attached.Name)
.FirstOrDefault();
}
}
private static void SetBinding(

33
src/Markup/Perspex.Markup.Xaml/Converters/SolidColorBrushTypeConverter.cs

@ -1,33 +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.Globalization;
using OmniXaml.TypeConversion;
using Perspex.Media;
namespace Perspex.Markup.Xaml.Converters
{
public class SolidColorBrushTypeConverter : ITypeConverter
{
public bool CanConvertFrom(IValueContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public bool CanConvertTo(IValueContext context, Type destinationType)
{
return false;
}
public object ConvertFrom(IValueContext context, CultureInfo culture, object value)
{
return Brush.Parse((string)value);
}
public object ConvertTo(IValueContext context, CultureInfo culture, object value, Type destinationType)
{
throw new NotImplementedException();
}
}
}

10
src/Markup/Perspex.Markup.Xaml/Data/DelayedBinding.cs

@ -14,7 +14,7 @@ namespace Perspex.Markup.Xaml.Data
/// </summary>
/// <remarks>
/// The XAML engine applies its bindings in a delayed manner where bindings are only applied
/// when a control is added to the visual tree. This was done because applying bindings as soon
/// when a control has finished initializing. This was done because applying bindings as soon
/// as controls are created means that long-form bindings (i.e. bindings that don't use the
/// `{Binding}` markup extension) don't work as the binding is applied to the property before
/// the binding properties are set, and looking at WPF it uses a similar mechanism for bindings
@ -33,7 +33,7 @@ namespace Perspex.Markup.Xaml.Data
/// <param name="binding">The binding.</param>
public static void Add(IControl target, PerspexProperty property, IBinding binding)
{
if (target.IsAttachedToVisualTree)
if (target.IsInitialized)
{
target.Bind(property, binding);
}
@ -47,7 +47,7 @@ namespace Perspex.Markup.Xaml.Data
_entries.Add(target, bindings);
// TODO: Make this a weak event listener.
target.AttachedToVisualTree += ApplyBindings;
target.Initialized += ApplyBindings;
}
bindings.Add(new Entry(binding, property));
@ -73,11 +73,11 @@ namespace Perspex.Markup.Xaml.Data
}
}
private static void ApplyBindings(object sender, VisualTreeAttachmentEventArgs e)
private static void ApplyBindings(object sender, EventArgs e)
{
var target = (IControl)sender;
ApplyBindings(target);
target.AttachedToVisualTree -= ApplyBindings;
target.Initialized -= ApplyBindings;
}
private class Entry

4
src/Markup/Perspex.Markup.Xaml/Data/StyleResourceBinding.cs

@ -39,6 +39,10 @@ namespace Perspex.Markup.Xaml.Data
PerspexProperty targetProperty,
object anchor = null)
{
if (Name == "Red")
{
}
var host = (target as IControl) ?? (anchor as IControl);
var style = anchor as IStyle;
var resource = PerspexProperty.UnsetValue;

5
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@ -56,7 +56,6 @@
<Compile Include="Converters\BitmapTypeConverter.cs" />
<Compile Include="Converters\BrushTypeConverter.cs" />
<Compile Include="Converters\ClassesTypeConverter.cs" />
<Compile Include="Converters\SolidColorBrushTypeConverter.cs" />
<Compile Include="Converters\ColorTypeConverter.cs" />
<Compile Include="Converters\ColumnDefinitionsTypeConverter.cs" />
<Compile Include="Converters\CursorTypeConverter.cs" />
@ -317,10 +316,6 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Reference Include="Serilog, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Serilog.1.5.14\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+MonoTouch10\Serilog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Sprache, Version=2.0.0.50, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Sprache.2.0.0.50\lib\portable-net4+netcore45+win8+wp8+sl5+MonoAndroid+Xamarin.iOS10+MonoTouch\Sprache.dll</HintPath>
<Private>True</Private>

4
src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -36,12 +36,12 @@ namespace Perspex.Markup.Xaml.Templates
}
}
public IEnumerable ItemsSelector(object item)
public InstancedBinding ItemsSelector(object item)
{
if (ItemsSource != null)
{
var obs = new ExpressionObserver(item, ItemsSource.Path);
return obs.Take(1).Wait() as IEnumerable;
return new InstancedBinding(obs);
}
return null;

1
src/Markup/Perspex.Markup.Xaml/packages.config

@ -5,6 +5,5 @@
<package id="Rx-Linq" version="2.2.5" targetFramework="portable46-net451+win81" />
<package id="Rx-Main" version="2.2.5" targetFramework="portable46-net451+win81" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable46-net451+win81" />
<package id="Serilog" version="1.5.14" targetFramework="portable45-net45+win8" />
<package id="Sprache" version="2.0.0.50" targetFramework="portable45-net45+win8" />
</packages>

51
src/Markup/Perspex.Markup/Data/Parsers/ExpressionParser.cs

@ -29,6 +29,10 @@ namespace Perspex.Markup.Data.Parsers
case State.BeforeMember:
state = ParseBeforeMember(r, nodes);
break;
case State.AttachedProperty:
state = ParseAttachedProperty(r, nodes);
break;
}
}
@ -52,6 +56,10 @@ namespace Perspex.Markup.Data.Parsers
nodes.Add(new LogicalNotNode());
return State.Start;
}
else if (ParseOpenBrace(r))
{
return State.AttachedProperty;
}
else
{
var identifier = IdentifierParser.Parse(r);
@ -93,15 +101,42 @@ namespace Perspex.Markup.Data.Parsers
private static State ParseBeforeMember(Reader r, IList<ExpressionNode> nodes)
{
var identifier = IdentifierParser.Parse(r);
if (ParseOpenBrace(r))
{
return State.AttachedProperty;
}
else
{
var identifier = IdentifierParser.Parse(r);
if (identifier != null)
{
nodes.Add(new PropertyAccessorNode(identifier));
return State.AfterMember;
}
return State.End;
}
}
private static State ParseAttachedProperty(Reader r, List<ExpressionNode> nodes)
{
var owner = IdentifierParser.Parse(r);
if (identifier != null)
if (r.End || !r.TakeIf('.'))
{
nodes.Add(new PropertyAccessorNode(identifier));
return State.AfterMember;
throw new ExpressionParseException(r.Position, "Invalid attached property name.");
}
return State.End;
var name = IdentifierParser.Parse(r);
if (r.End || !r.TakeIf(')'))
{
throw new ExpressionParseException(r.Position, "Expected ')'.");
}
nodes.Add(new PropertyAccessorNode(owner + '.' + name));
return State.AfterMember;
}
private static bool ParseNot(Reader r)
@ -114,11 +149,17 @@ namespace Perspex.Markup.Data.Parsers
return !r.End && r.TakeIf('.');
}
private static bool ParseOpenBrace(Reader r)
{
return !r.End && r.TakeIf('(');
}
private enum State
{
Start,
AfterMember,
BeforeMember,
AttachedProperty,
End,
}
}

18
src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs

@ -7,6 +7,7 @@ using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using Perspex.Data;
using Perspex.Logging;
using Perspex.Utilities;
namespace Perspex.Markup.Data.Plugins
@ -57,6 +58,13 @@ namespace Perspex.Markup.Data.Plugins
}
else
{
Logger.Error(
LogArea.Binding,
this,
"Could not find CLR property {Property} on {Source}",
propertyName,
instance);
return null;
}
}
@ -88,6 +96,16 @@ namespace Perspex.Markup.Data.Plugins
nameof(inpc.PropertyChanged),
this);
}
else
{
Logger.Warning(
LogArea.Binding,
this,
"Bound to property {Property} on {Source} which does not implement INotifyPropertyChanged",
property.Name,
reference.Target,
reference.Target.GetType());
}
}
public Type PropertyType => _property.PropertyType;

7
src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs

@ -4,6 +4,7 @@
using System;
using System.Reactive.Linq;
using Perspex.Data;
using Perspex.Logging;
namespace Perspex.Markup.Data.Plugins
{
@ -53,6 +54,12 @@ namespace Perspex.Markup.Data.Plugins
}
else
{
Logger.Error(
LogArea.Binding,
this,
"Could not find PerspexProperty {Property} on {Source}",
propertyName,
instance);
return null;
}
}

13
src/Markup/Perspex.Markup/DefaultValueConverter.cs

@ -5,6 +5,7 @@ using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Perspex.Logging;
using Perspex.Utilities;
namespace Perspex.Markup
@ -38,10 +39,18 @@ namespace Perspex.Markup
{
return result;
}
else
if (value != null)
{
return PerspexProperty.UnsetValue;
Logger.Error(
LogArea.Binding,
this,
"Could not convert {Value} to {Type}",
value,
targetType);
}
return PerspexProperty.UnsetValue;
}
/// <summary>

2
src/Perspex.Base/Collections/PerspexList.cs

@ -175,8 +175,6 @@ namespace Perspex.Collections
set { this[index] = (T)value; }
}
public int SubscriberCount => _collectionChanged?.GetInvocationList().Length ?? 0;
/// <summary>
/// Adds an item to the collection.
/// </summary>

26
src/Perspex.Base/Logging/ILogSink.cs

@ -0,0 +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.
namespace Perspex.Logging
{
/// <summary>
/// Defines a sink for Perspex logging messages.
/// </summary>
public interface ILogSink
{
/// <summary>
/// Logs a new event.
/// </summary>
/// <param name="level">The log event level.</param>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
void Log(
LogEventLevel level,
string area,
object source,
string messageTemplate,
params object[] propertyValues);
}
}

36
src/Perspex.Base/Logging/LogArea.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.Logging
{
/// <summary>
/// Specifies the area in which a log event occurred.
/// </summary>
public static class LogArea
{
/// <summary>
/// The log event comes from the property system.
/// </summary>
public const string Property = "Property";
/// <summary>
/// The log event comes from the binding system.
/// </summary>
public const string Binding = "Binding";
/// <summary>
/// The log event comes from the visual system.
/// </summary>
public const string Visual = "Visual";
/// <summary>
/// The log event comes from the layout system.
/// </summary>
public const string Layout = "Layout";
/// <summary>
/// The log event comes from the control system.
/// </summary>
public const string Control = "Control";
}
}

41
src/Perspex.Base/Logging/LogEventLevel.cs

@ -0,0 +1,41 @@
// 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.Logging
{
/// <summary>
/// Specifies the meaning and relative importance of a log event.
/// </summary>
public enum LogEventLevel
{
/// <summary>
/// Anything and everything you might want to know about a running block of code.
/// </summary>
Verbose,
/// <summary>
/// Internal system events that aren't necessarily observable from the outside.
/// </summary>
Debug,
/// <summary>
/// The lifeblood of operational intelligence - things happen.
/// </summary>
Information,
/// <summary>
/// Service is degraded or endangered.
/// </summary>
Warning,
/// <summary>
/// Functionality is unavailable, invariants are broken or data is lost.
/// </summary>
Error,
/// <summary>
/// If you have a pager, it goes off when one of these occurs.
/// </summary>
Fatal
}
}

139
src/Perspex.Base/Logging/Logger.cs

@ -0,0 +1,139 @@
// 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.Runtime.CompilerServices;
namespace Perspex.Logging
{
/// <summary>
/// Logs perspex messages.
/// </summary>
public static class Logger
{
/// <summary>
/// Gets or sets the application-defined sink that recieves the messages.
/// </summary>
public static ILogSink Sink { get; set; }
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="level">The log event level.</param>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Log(
LogEventLevel level,
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{
Sink?.Log(level, area, source, messageTemplate, propertyValues);
}
/// <summary>
/// Logs an event with the <see cref="LogEventLevel.Verbose"/> level.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Verbose(
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{
Log(LogEventLevel.Verbose, area, source, messageTemplate, propertyValues);
}
/// <summary>
/// Logs an event with the <see cref="LogEventLevel.Debug"/> level.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Debug(
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{
Log(LogEventLevel.Debug, area, source, messageTemplate, propertyValues);
}
/// <summary>
/// Logs an event with the <see cref="LogEventLevel.Information"/> level.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Information(
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{
Log(LogEventLevel.Information, area, source, messageTemplate, propertyValues);
}
/// <summary>
/// Logs an event with the <see cref="LogEventLevel.Warning"/> level.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Warning(
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{
Log(LogEventLevel.Warning, area, source, messageTemplate, propertyValues);
}
/// <summary>
/// Logs an event with the <see cref="LogEventLevel.Error"/> level.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Error(
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{
Log(LogEventLevel.Error, area, source, messageTemplate, propertyValues);
}
/// <summary>
/// Logs an event with the <see cref="LogEventLevel.Fatal"/> level.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Fatal(
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{
Log(LogEventLevel.Fatal, area, source, messageTemplate, propertyValues);
}
}
}

9
src/Perspex.Base/Perspex.Base.csproj

@ -61,6 +61,10 @@
<Compile Include="IDirectPropertyMetadata.cs" />
<Compile Include="IStyledPropertyMetadata.cs" />
<Compile Include="ISupportInitialize.cs" />
<Compile Include="Logging\ILogSink.cs" />
<Compile Include="Logging\LogArea.cs" />
<Compile Include="Logging\LogEventLevel.cs" />
<Compile Include="Logging\Logger.cs" />
<Compile Include="Metadata\DependsOnAttribute.cs" />
<Compile Include="Metadata\ContentAttribute.cs" />
<Compile Include="PerspexDisposable.cs" />
@ -69,6 +73,7 @@
<Compile Include="Metadata\XmlnsDefinitionAttribute.cs" />
<Compile Include="PerspexObjectExtensions.cs" />
<Compile Include="PropertyMetadata.cs" />
<Compile Include="Reactive\ObservableEx.cs" />
<Compile Include="Reactive\WeakPropertyChangedObservable.cs" />
<Compile Include="StyledProperty.cs" />
<Compile Include="StyledPropertyBase.cs" />
@ -117,10 +122,6 @@
<HintPath>..\..\packages\JetBrains.Annotations.10.0.0\lib\portable-net4+sl5+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\JetBrains.Annotations.PCL328.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Serilog, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
<HintPath>..\..\packages\Serilog.1.5.14\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+MonoTouch10\Serilog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Core">
<HintPath>..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll</HintPath>
</Reference>

72
src/Perspex.Base/PerspexObject.cs

@ -7,13 +7,11 @@ using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Perspex.Data;
using Perspex.Diagnostics;
using Perspex.Logging;
using Perspex.Threading;
using Perspex.Utilities;
using Serilog;
using Serilog.Core.Enrichers;
namespace Perspex
{
@ -25,6 +23,17 @@ namespace Perspex
/// </remarks>
public class PerspexObject : IPerspexObject, IPerspexObjectDebug, INotifyPropertyChanged
{
/// <summary>
/// Maintains a list of direct property binding subscriptions so that the binding source
/// doesn't get collected.
/// </summary>
/// <remarks>
/// If/when we provide a ClearBindings() method, then this collection will be need to be
/// moved to an instance field and indexed by property, but until that point a static
/// collection will suffice.
/// </remarks>
private static List<IDisposable> s_directBindings = new List<IDisposable>();
/// <summary>
/// The parent object that inherited values are inherited from.
/// </summary>
@ -46,23 +55,11 @@ namespace Perspex
/// </summary>
private EventHandler<PerspexPropertyChangedEventArgs> _propertyChanged;
/// <summary>
/// A serilog logger for logging property events.
/// </summary>
private readonly ILogger _propertyLog;
/// <summary>
/// Initializes a new instance of the <see cref="PerspexObject"/> class.
/// </summary>
public PerspexObject()
{
_propertyLog = Log.ForContext(new[]
{
new PropertyEnricher("Area", "Property"),
new PropertyEnricher("SourceContext", GetType()),
new PropertyEnricher("Id", GetHashCode()),
});
foreach (var property in PerspexPropertyRegistry.Instance.GetRegistered(this))
{
object value = property.IsDirect ?
@ -396,14 +393,27 @@ namespace Perspex
throw new ArgumentException($"The property {property.Name} is readonly.");
}
_propertyLog.Verbose(
"Bound {Property} to {Binding} with priority LocalValue",
property,
Logger.Verbose(
LogArea.Property,
this,
"Bound {Property} to {Binding} with priority LocalValue",
property,
GetDescription(source));
return source
IDisposable subscription = null;
subscription = source
.Select(x => TypeUtilities.CastOrDefault(x, property.PropertyType))
.Do(_ => { }, () => s_directBindings.Remove(subscription))
.Subscribe(x => SetValue(property, x));
s_directBindings.Add(subscription);
return Disposable.Create(() =>
{
subscription.Dispose();
s_directBindings.Remove(subscription);
});
}
else
{
@ -420,7 +430,9 @@ namespace Perspex
_values.Add(property, v);
}
_propertyLog.Verbose(
Logger.Verbose(
LogArea.Property,
this,
"Bound {Property} to {Binding} with priority {Priority}",
property,
GetDescription(source),
@ -515,7 +527,7 @@ namespace Perspex
PerspexProperty property,
object oldValue,
object newValue,
BindingPriority priority)
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
@ -591,10 +603,10 @@ namespace Perspex
}
PriorityValue result = new PriorityValue(
this,
property.Name,
property.PropertyType,
validate2,
_propertyLog);
validate2);
result.Changed.Subscribe(x =>
{
@ -609,11 +621,13 @@ namespace Perspex
{
RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)result.ValuePriority);
_propertyLog.Verbose(
Logger.Verbose(
LogArea.Property,
this,
"{Property} changed from {$Old} to {$Value} with priority {Priority}",
property,
oldValue,
newValue,
property,
oldValue,
newValue,
(BindingPriority)result.ValuePriority);
}
});
@ -706,7 +720,9 @@ namespace Perspex
/// <param name="priority">The priority.</param>
private void LogPropertySet(PerspexProperty property, object value, BindingPriority priority)
{
_propertyLog.Verbose(
Logger.Verbose(
LogArea.Property,
this,
"Set {Property} to {$Value} with priority {Priority}",
property,
value,

13
src/Perspex.Base/PerspexObjectExtensions.cs

@ -21,7 +21,10 @@ namespace Perspex
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <returns>An observable.</returns>
/// <returns>
/// An observable which fires immediately with the current value of the property on the
/// object and subsequently each time the property value changes.
/// </returns>
public static IObservable<object> GetObservable(this IPerspexObject o, PerspexProperty property)
{
Contract.Requires<ArgumentNullException>(o != null);
@ -56,7 +59,10 @@ namespace Perspex
/// <param name="o">The object.</param>
/// <typeparam name="T">The property type.</typeparam>
/// <param name="property">The property.</param>
/// <returns>An observable.</returns>
/// <returns>
/// An observable which fires immediately with the current value of the property on the
/// object and subsequently each time the property value changes.
/// </returns>
public static IObservable<T> GetObservable<T>(this IPerspexObject o, PerspexProperty<T> property)
{
Contract.Requires<ArgumentNullException>(o != null);
@ -73,7 +79,8 @@ namespace Perspex
/// <param name="property">The property.</param>
/// <returns>
/// An observable which when subscribed pushes the old and new values of the property each
/// time it is changed.
/// time it is changed. Note that the observable returned from this method does not fire
/// with the current value of the property immediately.
/// </returns>
public static IObservable<Tuple<T, T>> GetObservableWithHistory<T>(
this IPerspexObject o,

1
src/Perspex.Base/PriorityLevel.cs

@ -140,6 +140,7 @@ namespace Perspex
return Disposable.Create(() =>
{
Bindings.Remove(node);
entry.Dispose();
if (entry.Index >= ActiveBindingIndex)
{

30
src/Perspex.Base/PriorityValue.cs

@ -6,8 +6,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
using System.Text;
using Perspex.Logging;
using Perspex.Utilities;
using Serilog;
namespace Perspex
{
@ -25,6 +25,11 @@ namespace Perspex
/// </remarks>
internal class PriorityValue
{
/// <summary>
/// The owner of the object.
/// </summary>
private readonly PerspexObject _owner;
/// <summary>
/// The name of the property.
/// </summary>
@ -55,30 +60,25 @@ namespace Perspex
/// </summary>
private readonly Func<object, object> _validate;
/// <summary>
/// An optional logger.
/// </summary>
private ILogger _logger;
/// <summary>
/// Initializes a new instance of the <see cref="PriorityValue"/> class.
/// </summary>
/// <param name="owner">The owner of the object.</param>
/// <param name="name">The name of the property.</param>
/// <param name="valueType">The value type.</param>
/// <param name="validate">An optional validation function.</param>
/// <param name="logger">An optional logger</param>
public PriorityValue(
PerspexObject owner,
string name,
Type valueType,
Func<object, object> validate = null,
ILogger logger = null)
Func<object, object> validate = null)
{
_owner = owner;
_name = name;
_valueType = valueType;
_value = PerspexProperty.UnsetValue;
ValuePriority = int.MaxValue;
_validate = validate;
_logger = logger;
}
/// <summary>
@ -239,12 +239,14 @@ namespace Perspex
_value = castValue;
_changed.OnNext(Tuple.Create(old, _value));
}
else if (_logger != null)
else
{
_logger.Error(
Logger.Error(
LogArea.Property,
_owner,
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
_name,
_valueType,
_name,
_valueType,
value,
value.GetType());
}

41
src/Perspex.Base/Reactive/ObservableEx.cs

@ -0,0 +1,41 @@
// 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;
namespace Perspex.Reactive
{
/// <summary>
/// Provides common observable methods not found in standard Rx framework.
/// </summary>
public static class ObservableEx
{
/// <summary>
/// Returns an observable that fires once with the specified value and never completes.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="value">The value.</param>
/// <returns>The observable.</returns>
public static IObservable<T> SingleValue<T>(T value)
{
return new SingleValueImpl<T>(value);
}
private class SingleValueImpl<T> : IObservable<T>
{
private T _value;
public SingleValueImpl(T value)
{
_value = value;
}
public IDisposable Subscribe(IObserver<T> observer)
{
observer.OnNext(_value);
return Disposable.Empty;
}
}
}
}

2
src/Perspex.Base/Utilities/WeakSubscriptionManager.cs

@ -114,7 +114,7 @@ namespace Perspex.Utilities
void Destroy()
{
_info.RemoveEventHandler(_target, _delegate);
_info.RemoveMethod.Invoke(_target, new[] { _delegate });
_sdic.Remove(_eventName);
}

1
src/Perspex.Base/packages.config

@ -6,5 +6,4 @@
<package id="Rx-Linq" version="2.2.5" targetFramework="portable-net45+win8" />
<package id="Rx-Main" version="2.2.5" targetFramework="portable-net45+win8" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable-net45+win8" />
<package id="Serilog" version="1.5.14" targetFramework="portable45-net45+win8" />
</packages>

0
src/Perspex.Application/Application.cs → src/Perspex.Controls/Application.cs

12
src/Perspex.Controls/Border.cs

@ -13,14 +13,14 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Background"/> property.
/// </summary>
public static readonly StyledProperty<Brush> BackgroundProperty =
PerspexProperty.Register<Border, Brush>(nameof(Background));
public static readonly StyledProperty<IBrush> BackgroundProperty =
PerspexProperty.Register<Border, IBrush>(nameof(Background));
/// <summary>
/// Defines the <see cref="BorderBrush"/> property.
/// </summary>
public static readonly StyledProperty<Brush> BorderBrushProperty =
PerspexProperty.Register<Border, Brush>(nameof(BorderBrush));
public static readonly StyledProperty<IBrush> BorderBrushProperty =
PerspexProperty.Register<Border, IBrush>(nameof(BorderBrush));
/// <summary>
/// Defines the <see cref="BorderThickness"/> property.
@ -45,7 +45,7 @@ namespace Perspex.Controls
/// <summary>
/// Gets or sets a brush with which to paint the background.
/// </summary>
public Brush Background
public IBrush Background
{
get { return GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
@ -54,7 +54,7 @@ namespace Perspex.Controls
/// <summary>
/// Gets or sets a brush with which to paint the border.
/// </summary>
public Brush BorderBrush
public IBrush BorderBrush
{
get { return GetValue(BorderBrushProperty); }
set { SetValue(BorderBrushProperty, value); }

35
src/Perspex.Controls/Canvas.cs

@ -3,6 +3,7 @@
using System;
using Perspex.Input;
using Perspex.Layout;
namespace Perspex.Controls
{
@ -21,32 +22,32 @@ namespace Perspex.Controls
/// Defines the Left attached property.
/// </summary>
public static readonly AttachedProperty<double> LeftProperty =
PerspexProperty.RegisterAttached<StackPanel, Control, double>("Left");
PerspexProperty.RegisterAttached<Canvas, Control, double>("Left");
/// <summary>
/// Defines the Top attached property.
/// </summary>
public static readonly AttachedProperty<double> TopProperty =
PerspexProperty.RegisterAttached<StackPanel, Control, double>("Top");
PerspexProperty.RegisterAttached<Canvas, Control, double>("Top");
/// <summary>
/// Defines the Right attached property.
/// </summary>
public static readonly AttachedProperty<double> RightProperty =
PerspexProperty.RegisterAttached<StackPanel, Control, double>("Right");
PerspexProperty.RegisterAttached<Canvas, Control, double>("Right");
/// <summary>
/// Defines the Bottom attached property.
/// </summary>
public static readonly AttachedProperty<double> BottomProperty =
PerspexProperty.RegisterAttached<StackPanel, Control, double>("Bottom");
PerspexProperty.RegisterAttached<Canvas, Control, double>("Bottom");
/// <summary>
/// Initializes static members of the <see cref="Canvas"/> class.
/// </summary>
static Canvas()
{
AffectsArrange(LeftProperty, TopProperty, RightProperty, BottomProperty);
AffectsCanvasArrange(LeftProperty, TopProperty, RightProperty, BottomProperty);
}
/// <summary>
@ -204,5 +205,29 @@ namespace Perspex.Controls
return finalSize;
}
/// <summary>
/// Marks a property on a child as affecting the canvas' arrangement.
/// </summary>
/// <param name="properties">The properties.</param>
private static void AffectsCanvasArrange(params PerspexProperty[] properties)
{
foreach (var property in properties)
{
property.Changed.Subscribe(AffectsCanvasArrangeInvalidate);
}
}
/// <summary>
/// Calls <see cref="Layoutable.InvalidateArrange"/> on the parent of the control whose
/// property changed, if that parent is a canvas.
/// </summary>
/// <param name="e">The event args.</param>
private static void AffectsCanvasArrangeInvalidate(PerspexPropertyChangedEventArgs e)
{
var control = e.Sender as IControl;
var canvas = control?.Parent as Canvas;
canvas?.InvalidateArrange();
}
}
}

184
src/Perspex.Controls/Control.cs

@ -15,6 +15,7 @@ using Perspex.Data;
using Perspex.Diagnostics;
using Perspex.Input;
using Perspex.Interactivity;
using Perspex.Logging;
using Perspex.LogicalTree;
using Perspex.Styling;
@ -96,7 +97,7 @@ namespace Perspex.Controls
private INameScope _nameScope;
private Styles _styles;
private bool _styled;
private Subject<Unit> _styleDetach = new Subject<Unit>();
private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
/// <summary>
/// Initializes static members of the <see cref="Control"/> class.
@ -136,6 +137,19 @@ namespace Perspex.Controls
/// </remarks>
public event EventHandler DataContextChanged;
/// <summary>
/// Occurs when the control has finished initialization.
/// </summary>
/// <remarks>
/// The Initialized event indicates that all property values on the control have been set.
/// When loading the control from markup, it occurs when
/// <see cref="ISupportInitialize.EndInit"/> is called *and* the control
/// is attached to a rooted logical tree. When the control is created by code and
/// <see cref="ISupportInitialize"/> is not used, it is called when the control is attached
/// to the visual tree.
/// </remarks>
public event EventHandler Initialized;
/// <summary>
/// Gets or sets the name of the control.
/// </summary>
@ -232,6 +246,15 @@ namespace Perspex.Controls
set { _dataTemplates = value; }
}
/// <summary>
/// Gets a value that indicates whether the element has finished initialization.
/// </summary>
/// <remarks>
/// For more information about when IsInitialized is set, see the <see cref="Initialized"/>
/// event.
/// </remarks>
public bool IsInitialized { get; private set; }
/// <summary>
/// Gets or sets the styles for the control.
/// </summary>
@ -308,7 +331,7 @@ namespace Perspex.Controls
Type IStyleable.StyleKey => GetType();
/// <inheritdoc/>
IObservable<Unit> IStyleable.StyleDetach => _styleDetach;
IObservable<IStyleable> IStyleable.StyleDetach => _styleDetach;
/// <inheritdoc/>
IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
@ -316,7 +339,7 @@ namespace Perspex.Controls
/// <inheritdoc/>
void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
this.OnDetachedFromLogicalTree(e);
this.OnDetachedFromLogicalTreeCore(e);
}
/// <inheritdoc/>
@ -333,11 +356,20 @@ namespace Perspex.Controls
throw new InvalidOperationException("BeginInit was not called.");
}
if (--_initCount == 0 && _isAttachedToLogicalTree && !_styled)
if (--_initCount == 0 && _isAttachedToLogicalTree)
{
RegisterWithNameScope();
ApplyStyling();
_styled = true;
if (!_styled)
{
RegisterWithNameScope();
ApplyStyling();
_styled = true;
}
if (!IsInitialized)
{
IsInitialized = true;
Initialized?.Invoke(this, EventArgs.Empty);
}
}
}
@ -402,7 +434,7 @@ namespace Perspex.Controls
}
var e = new LogicalTreeAttachmentEventArgs(oldRoot);
OnDetachedFromLogicalTree(e);
OnDetachedFromLogicalTreeCore(e);
}
InheritanceParent = parent as PerspexObject;
@ -418,7 +450,7 @@ namespace Perspex.Controls
}
var e = new LogicalTreeAttachmentEventArgs(newRoot);
OnAttachedToLogicalTree(e);
OnAttachedToLogicalTreeCore(e);
}
RaisePropertyChanged(ParentProperty, old, _parent, BindingPriority.LocalValue);
@ -481,77 +513,41 @@ namespace Perspex.Controls
}
/// <summary>
/// Called when the control is added to a logical tree.
/// Called when the control is added to a rooted logical tree.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// 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.
/// </remarks>
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)
{
_isAttachedToLogicalTree = true;
if (_initCount == 0)
{
RegisterWithNameScope();
ApplyStyling();
_styled = true;
}
AttachedToLogicalTree?.Invoke(this, e);
}
foreach (var child in LogicalChildren.OfType<Control>())
{
child.OnAttachedToLogicalTree(e);
}
AttachedToLogicalTree?.Invoke(this, e);
}
/// <summary>
/// Called when the control is removed from a logical tree.
/// Called when the control is removed from a rooted logical tree.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// 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.
/// </remarks>
protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_isAttachedToLogicalTree)
{
if (Name != null)
{
_nameScope?.Unregister(Name);
}
_isAttachedToLogicalTree = false;
_styleDetach.OnNext(Unit.Default);
this.TemplatedParent = null;
DetachedFromLogicalTree?.Invoke(this, e);
DetachedFromLogicalTree?.Invoke(this, e);
}
foreach (var child in LogicalChildren.OfType<Control>())
{
child.OnDetachedFromLogicalTree(e);
}
/// <inheritdoc/>
protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTreeCore(e);
if (((INotifyCollectionChangedDebug)_classes).GetCollectionChangedSubscribers()?.Length > 0)
{
// TODO: This should be output using a standard logging mechanism.
System.Diagnostics.Debug.WriteLine(
$"{this.GetType().Name} detached from logical tree but still has class listeners");
}
if (!IsInitialized)
{
IsInitialized = true;
Initialized?.Invoke(this, EventArgs.Empty);
}
}
/// <inheritdoc/>
protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTreeCore(e);
}
/// <inheritdoc/>
protected override void OnGotFocus(GotFocusEventArgs e)
{
@ -674,6 +670,66 @@ namespace Perspex.Controls
}
}
private void OnAttachedToLogicalTreeCore(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)
{
_isAttachedToLogicalTree = true;
if (_initCount == 0)
{
RegisterWithNameScope();
ApplyStyling();
_styled = true;
}
OnAttachedToLogicalTree(e);
}
foreach (var child in LogicalChildren.OfType<Control>())
{
child.OnAttachedToLogicalTreeCore(e);
}
}
private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{
if (_isAttachedToLogicalTree)
{
if (Name != null)
{
_nameScope?.Unregister(Name);
}
_isAttachedToLogicalTree = false;
_styleDetach.OnNext(this);
this.TemplatedParent = null;
OnDetachedFromLogicalTree(e);
foreach (var child in LogicalChildren.OfType<Control>())
{
child.OnDetachedFromLogicalTreeCore(e);
}
#if DEBUG
if (((INotifyCollectionChangedDebug)_classes).GetCollectionChangedSubscribers()?.Length > 0)
{
Logger.Warning(
LogArea.Control,
this,
"{Type} detached from logical tree but still has class listeners",
this.GetType());
}
#endif
}
}
private void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)

10
src/Perspex.Application/Designer/Design.cs → src/Perspex.Controls/Design.cs

@ -1,11 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Perspex.Controls;
namespace Perspex

namespace Perspex.Controls
{
public static class Design
{

42
src/Perspex.Controls/Expander.cs

@ -14,14 +14,24 @@ namespace Perspex.Controls
public class Expander : HeaderedContentControl
{
public static readonly StyledProperty<bool> IsExpandedProperty =
PerspexProperty.Register<Expander, bool>(nameof(IsExpanded), true);
public static readonly DirectProperty<Expander, IPageTransition> ContentTransitionProperty =
PerspexProperty.RegisterDirect<Expander, IPageTransition>(
nameof(ContentTransition),
o => o.ContentTransition,
(o, v) => o.ContentTransition = v);
public static readonly StyledProperty<ExpandDirection> ExpandDirectionProperty =
PerspexProperty.Register<Expander, ExpandDirection>(nameof(ExpandDirection), ExpandDirection.Down);
public static readonly DirectProperty<Expander, ExpandDirection> ExpandDirectionProperty =
PerspexProperty.RegisterDirect<Expander, ExpandDirection>(
nameof(ExpandDirection),
o => o.ExpandDirection,
(o, v) => o.ExpandDirection = v,
ExpandDirection.Down);
public static readonly StyledProperty<IPageTransition> ContentTransitionProperty =
PerspexProperty.Register<Expander, IPageTransition>(nameof(ContentTransition));
public static readonly DirectProperty<Expander, bool> IsExpandedProperty =
PerspexProperty.RegisterDirect<Expander, bool>(
nameof(IsExpanded),
o => o.IsExpanded,
(o, v) => o.IsExpanded = v);
static Expander()
{
@ -35,22 +45,22 @@ namespace Perspex.Controls
IsExpandedProperty.Changed.AddClassHandler<Expander>(x => x.OnIsExpandedChanged);
}
public bool IsExpanded
public IPageTransition ContentTransition
{
get { return GetValue(IsExpandedProperty); }
set { SetValue(IsExpandedProperty, value); }
get { return _contentTransition; }
set { SetAndRaise(ContentTransitionProperty, ref _contentTransition, value); }
}
public ExpandDirection ExpandDirection
{
get { return GetValue(ExpandDirectionProperty); }
set { SetValue(ExpandDirectionProperty, value); }
get { return _expandDirection; }
set { SetAndRaise(ExpandDirectionProperty, ref _expandDirection, value); }
}
public IPageTransition ContentTransition
public bool IsExpanded
{
get { return GetValue(ContentTransitionProperty); }
set { SetValue(ContentTransitionProperty, value); }
get { return _isExpanded; }
set { SetAndRaise(IsExpandedProperty, ref _isExpanded, value); }
}
protected virtual void OnIsExpandedChanged(PerspexPropertyChangedEventArgs e)
@ -71,5 +81,9 @@ namespace Perspex.Controls
}
}
}
private IPageTransition _contentTransition;
private ExpandDirection _expandDirection;
private bool _isExpanded;
}
}

9
src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Perspex.Controls.Templates;
using Perspex.Data;
namespace Perspex.Controls.Generators
{
@ -76,7 +77,13 @@ namespace Perspex.Controls.Generators
var result = new T();
result.SetValue(ContentProperty, template.Build(item));
result.SetValue(ItemsProperty, template.ItemsSelector(item));
var itemsSelector = template.ItemsSelector(item);
if (itemsSelector != null)
{
BindingOperations.Apply(result, ItemsProperty, itemsSelector, null);
}
if (!(item is IControl))
{

14
src/Perspex.Controls/IControl.cs

@ -17,14 +17,9 @@ namespace Perspex.Controls
public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IStyleable, IStyleHost
{
/// <summary>
/// Raised when the control is attached to a rooted logical tree.
/// Occurs when the control has finished initialization.
/// </summary>
event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
/// <summary>
/// Raised when the control is detached from a rooted logical tree.
/// </summary>
event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;
event EventHandler Initialized;
/// <summary>
/// Gets or sets the control's styling classes.
@ -41,6 +36,11 @@ namespace Perspex.Controls
/// </summary>
DataTemplates DataTemplates { get; }
/// <summary>
/// Gets a value that indicates whether the element has finished initialization.
/// </summary>
bool IsInitialized { get; }
/// <summary>
/// Gets the control's logical parent.
/// </summary>

389
src/Perspex.Controls/LayoutTransformControl.cs

@ -0,0 +1,389 @@
// 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.
//
// Idea got from and adapted to work in perspex
// http://silverlight.codeplex.com/SourceControl/changeset/view/74775#Release/Silverlight4/Source/Controls.Layout.Toolkit/LayoutTransformer/LayoutTransformer.cs
//
using Perspex.Controls.Primitives;
using Perspex.Media;
using Perspex.VisualTree;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Linq;
namespace Perspex.Controls
{
public class LayoutTransformControl : ContentControl
{
public static readonly PerspexProperty<Transform> LayoutTransformProperty =
PerspexProperty.Register<LayoutTransformControl, Transform>(nameof(LayoutTransform));
static LayoutTransformControl()
{
LayoutTransformProperty.Changed
.AddClassHandler<LayoutTransformControl>(x => x.OnLayoutTransformChanged);
}
public Transform LayoutTransform
{
get { return GetValue(LayoutTransformProperty); }
set { SetValue(LayoutTransformProperty, value); }
}
public Control TransformRoot => _transformRoot ??
(_transformRoot = this.GetVisualChildren().OfType<Control>().FirstOrDefault());
/// <summary>
/// Provides the behavior for the "Arrange" pass of layout.
/// </summary>
/// <param name="finalSize">The final area within the parent that this element should use to arrange itself and its children.</param>
/// <returns>The actual size used.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
if (TransformRoot == null || LayoutTransform == null)
{
return base.ArrangeOverride(finalSize);
}
// Determine the largest available size after the transformation
Size finalSizeTransformed = ComputeLargestTransformedSize(finalSize);
if (IsSizeSmaller(finalSizeTransformed, TransformRoot.DesiredSize))
{
// Some elements do not like being given less space than they asked for (ex: TextBlock)
// Bump the working size up to do the right thing by them
finalSizeTransformed = TransformRoot.DesiredSize;
}
// Transform the working size to find its width/height
Rect transformedRect = new Rect(0, 0, finalSizeTransformed.Width, finalSizeTransformed.Height).TransformToAABB(_transformation);
// Create the Arrange rect to center the transformed content
Rect finalRect = new Rect(
-transformedRect.X + ((finalSize.Width - transformedRect.Width) / 2),
-transformedRect.Y + ((finalSize.Height - transformedRect.Height) / 2),
finalSizeTransformed.Width,
finalSizeTransformed.Height);
// Perform an Arrange on TransformRoot (containing Child)
Size arrangedsize;
TransformRoot.Arrange(finalRect);
arrangedsize = TransformRoot.Bounds.Size;
// This is the first opportunity under Silverlight to find out the Child's true DesiredSize
if (IsSizeSmaller(finalSizeTransformed, arrangedsize) && (Size.Empty == _childActualSize))
{
//// Unfortunately, all the work so far is invalid because the wrong DesiredSize was used
//// Make a note of the actual DesiredSize
//_childActualSize = arrangedsize;
//// Force a new measure/arrange pass
//InvalidateMeasure();
}
else
{
// Clear the "need to measure/arrange again" flag
_childActualSize = Size.Empty;
}
// Return result to perform the transformation
return finalSize;
}
/// <summary>
/// Provides the behavior for the "Measure" pass of layout.
/// </summary>
/// <param name="availableSize">The available size that this element can give to child elements.</param>
/// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
protected override Size MeasureOverride(Size availableSize)
{
if (TransformRoot == null || LayoutTransform == null)
{
return base.MeasureOverride(availableSize);
}
Size measureSize;
if (_childActualSize == Size.Empty)
{
// Determine the largest size after the transformation
measureSize = ComputeLargestTransformedSize(availableSize);
}
else
{
// Previous measure/arrange pass determined that Child.DesiredSize was larger than believed
measureSize = _childActualSize;
}
// Perform a measure on the TransformRoot (containing Child)
TransformRoot.Measure(measureSize);
var desiredSize = TransformRoot.DesiredSize;
// Transform DesiredSize to find its width/height
Rect transformedDesiredRect = new Rect(0, 0, desiredSize.Width, desiredSize.Height).TransformToAABB(_transformation);
Size transformedDesiredSize = new Size(transformedDesiredRect.Width, transformedDesiredRect.Height);
// Return result to allocate enough space for the transformation
return transformedDesiredSize;
}
/// <summary>
/// Builds the visual tree for the LayoutTransformerControl when a new
/// template is applied.
/// </summary>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
_matrixTransform = new MatrixTransform();
if (null != TransformRoot)
{
TransformRoot.RenderTransform = _matrixTransform;
TransformRoot.TransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute);
}
ApplyLayoutTransform();
}
/// <summary>
/// Acceptable difference between two doubles.
/// </summary>
private const double AcceptableDelta = 0.0001;
/// <summary>
/// Number of decimals to round the Matrix to.
/// </summary>
private const int DecimalsAfterRound = 4;
/// <summary>
/// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method).
/// </summary>
private Size _childActualSize = Size.Empty;
/// <summary>
/// RenderTransform/MatrixTransform applied to TransformRoot.
/// </summary>
private MatrixTransform _matrixTransform;
/// <summary>
/// Transformation matrix corresponding to _matrixTransform.
/// </summary>
private Matrix _transformation;
private IDisposable _transformChangedEvent = null;
private Control _transformRoot;
/// <summary>
/// Returns true if Size a is smaller than Size b in either dimension.
/// </summary>
/// <param name="a">Second Size.</param>
/// <param name="b">First Size.</param>
/// <returns>True if Size a is smaller than Size b in either dimension.</returns>
private static bool IsSizeSmaller(Size a, Size b)
{
return (a.Width + AcceptableDelta < b.Width) || (a.Height + AcceptableDelta < b.Height);
}
/// <summary>
/// Rounds the non-offset elements of a Matrix to avoid issues due to floating point imprecision.
/// </summary>
/// <param name="matrix">Matrix to round.</param>
/// <param name="decimals">Number of decimal places to round to.</param>
/// <returns>Rounded Matrix.</returns>
private static Matrix RoundMatrix(Matrix matrix, int decimals)
{
return new Matrix(
Math.Round(matrix.M11, decimals),
Math.Round(matrix.M12, decimals),
Math.Round(matrix.M21, decimals),
Math.Round(matrix.M22, decimals),
matrix.M31,
matrix.M32);
}
/// <summary>
/// Applies the layout transform on the LayoutTransformerControl content.
/// </summary>
/// <remarks>
/// Only used in advanced scenarios (like animating the LayoutTransform).
/// Should be used to notify the LayoutTransformer control that some aspect
/// of its Transform property has changed.
/// </remarks>
private void ApplyLayoutTransform()
{
if (LayoutTransform == null) return;
// Get the transform matrix and apply it
_transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound);
if (null != _matrixTransform)
{
_matrixTransform.Matrix = _transformation;
}
// New transform means re-layout is necessary
InvalidateMeasure();
}
/// <summary>
/// Compute the largest usable size (greatest area) after applying the transformation to the specified bounds.
/// </summary>
/// <param name="arrangeBounds">Arrange bounds.</param>
/// <returns>Largest Size possible.</returns>
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Closely corresponds to WPF's FrameworkElement.FindMaximalAreaLocalSpaceRect.")]
private Size ComputeLargestTransformedSize(Size arrangeBounds)
{
// Computed largest transformed size
Size computedSize = Size.Empty;
// Detect infinite bounds and constrain the scenario
bool infiniteWidth = double.IsInfinity(arrangeBounds.Width);
if (infiniteWidth)
{
// arrangeBounds.Width = arrangeBounds.Height;
arrangeBounds = arrangeBounds.WithWidth(arrangeBounds.Height);
}
bool infiniteHeight = double.IsInfinity(arrangeBounds.Height);
if (infiniteHeight)
{
//arrangeBounds.Height = arrangeBounds.Width;
arrangeBounds = arrangeBounds.WithHeight(arrangeBounds.Width);
}
// Capture the matrix parameters
double a = _transformation.M11;
double b = _transformation.M12;
double c = _transformation.M21;
double d = _transformation.M22;
// Compute maximum possible transformed width/height based on starting width/height
// These constraints define two lines in the positive x/y quadrant
double maxWidthFromWidth = Math.Abs(arrangeBounds.Width / a);
double maxHeightFromWidth = Math.Abs(arrangeBounds.Width / c);
double maxWidthFromHeight = Math.Abs(arrangeBounds.Height / b);
double maxHeightFromHeight = Math.Abs(arrangeBounds.Height / d);
// The transformed width/height that maximize the area under each segment is its midpoint
// At most one of the two midpoints will satisfy both constraints
double idealWidthFromWidth = maxWidthFromWidth / 2;
double idealHeightFromWidth = maxHeightFromWidth / 2;
double idealWidthFromHeight = maxWidthFromHeight / 2;
double idealHeightFromHeight = maxHeightFromHeight / 2;
// Compute slope of both constraint lines
double slopeFromWidth = -(maxHeightFromWidth / maxWidthFromWidth);
double slopeFromHeight = -(maxHeightFromHeight / maxWidthFromHeight);
if ((0 == arrangeBounds.Width) || (0 == arrangeBounds.Height))
{
// Check for empty bounds
computedSize = new Size(arrangeBounds.Width, arrangeBounds.Height);
}
else if (infiniteWidth && infiniteHeight)
{
// Check for completely unbound scenario
computedSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
}
else if (!_transformation.HasInverse)
{
// Check for singular matrix
computedSize = new Size(0, 0);
}
else if ((0 == b) || (0 == c))
{
// Check for 0/180 degree special cases
double maxHeight = (infiniteHeight ? double.PositiveInfinity : maxHeightFromHeight);
double maxWidth = (infiniteWidth ? double.PositiveInfinity : maxWidthFromWidth);
if ((0 == b) && (0 == c))
{
// No constraints
computedSize = new Size(maxWidth, maxHeight);
}
else if (0 == b)
{
// Constrained by width
double computedHeight = Math.Min(idealHeightFromWidth, maxHeight);
computedSize = new Size(
maxWidth - Math.Abs((c * computedHeight) / a),
computedHeight);
}
else if (0 == c)
{
// Constrained by height
double computedWidth = Math.Min(idealWidthFromHeight, maxWidth);
computedSize = new Size(
computedWidth,
maxHeight - Math.Abs((b * computedWidth) / d));
}
}
else if ((0 == a) || (0 == d))
{
// Check for 90/270 degree special cases
double maxWidth = (infiniteHeight ? double.PositiveInfinity : maxWidthFromHeight);
double maxHeight = (infiniteWidth ? double.PositiveInfinity : maxHeightFromWidth);
if ((0 == a) && (0 == d))
{
// No constraints
computedSize = new Size(maxWidth, maxHeight);
}
else if (0 == a)
{
// Constrained by width
double computedHeight = Math.Min(idealHeightFromHeight, maxHeight);
computedSize = new Size(
maxWidth - Math.Abs((d * computedHeight) / b),
computedHeight);
}
else if (0 == d)
{
// Constrained by height
double computedWidth = Math.Min(idealWidthFromWidth, maxWidth);
computedSize = new Size(
computedWidth,
maxHeight - Math.Abs((a * computedWidth) / c));
}
}
else if (idealHeightFromWidth <= ((slopeFromHeight * idealWidthFromWidth) + maxHeightFromHeight))
{
// Check the width midpoint for viability (by being below the height constraint line)
computedSize = new Size(idealWidthFromWidth, idealHeightFromWidth);
}
else if (idealHeightFromHeight <= ((slopeFromWidth * idealWidthFromHeight) + maxHeightFromWidth))
{
// Check the height midpoint for viability (by being below the width constraint line)
computedSize = new Size(idealWidthFromHeight, idealHeightFromHeight);
}
else
{
// Neither midpoint is viable; use the intersection of the two constraint lines instead
// Compute width by setting heights equal (m1*x+c1=m2*x+c2)
double computedWidth = (maxHeightFromHeight - maxHeightFromWidth) / (slopeFromWidth - slopeFromHeight);
// Compute height from width constraint line (y=m*x+c; using height would give same result)
computedSize = new Size(
computedWidth,
(slopeFromWidth * computedWidth) + maxHeightFromWidth);
}
// Return result
return computedSize;
}
private void OnLayoutTransformChanged(PerspexPropertyChangedEventArgs e)
{
var newTransform = e.NewValue as Transform;
if (_transformChangedEvent != null)
{
_transformChangedEvent.Dispose();
_transformChangedEvent = null;
}
if (newTransform != null)
{
_transformChangedEvent = Observable.FromEventPattern<EventHandler, EventArgs>(
v => newTransform.Changed += v, v => newTransform.Changed -= v)
.Subscribe(onNext: v => ApplyLayoutTransform());
}
ApplyLayoutTransform();
}
}
}

22
src/Perspex.Controls/MenuItem.cs

@ -9,6 +9,7 @@ using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Input;
using Perspex.Interactivity;
using Perspex.LogicalTree;
using Perspex.Threading;
namespace Perspex.Controls
@ -92,6 +93,7 @@ namespace Perspex.Controls
{
SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
FocusableProperty.OverrideDefaultValue<MenuItem>(true);
IconProperty.Changed.AddClassHandler<MenuItem>(x => x.IconChanged);
ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
ClickEvent.AddClassHandler<MenuItem>(x => x.OnClick);
SubmenuOpenedEvent.AddClassHandler<MenuItem>(x => x.OnSubmenuOpened);
@ -391,6 +393,26 @@ namespace Perspex.Controls
}
}
/// <summary>
/// Called when the <see cref="Icon"/> property changes.
/// </summary>
/// <param name="e">The property change event.</param>
private void IconChanged(PerspexPropertyChangedEventArgs e)
{
var oldValue = e.OldValue as ILogical;
var newValue = e.NewValue as ILogical;
if (oldValue != null)
{
LogicalChildren.Remove(oldValue);
}
if (newValue != null)
{
LogicalChildren.Add(newValue);
}
}
/// <summary>
/// Called when the <see cref="IsSubMenuOpen"/> property changes.
/// </summary>

13
src/Perspex.Controls/Mixins/ContentControlMixin.cs

@ -64,12 +64,19 @@ namespace Perspex.Controls.Mixins
var logicalChildren = logicalChildrenSelector(sender);
var subscription = presenter
.GetObservable(ContentPresenter.ChildProperty)
.GetObservableWithHistory(ContentPresenter.ChildProperty)
.Subscribe(child => UpdateLogicalChild(
sender,
logicalChildren,
logicalChildren.FirstOrDefault(),
child));
child.Item1,
child.Item2));
UpdateLogicalChild(
sender,
logicalChildren,
logicalChildren.FirstOrDefault(),
presenter.GetValue(ContentPresenter.ChildProperty));
subscriptions.Value.Add(sender, subscription);
}
}

6
src/Perspex.Controls/Panel.cs

@ -22,7 +22,7 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Background"/> property.
/// </summary>
public static readonly StyledProperty<Brush> BackgroundProperty =
public static readonly StyledProperty<IBrush> BackgroundProperty =
Border.BackgroundProperty.AddOwner<Panel>();
private readonly Controls _children = new Controls();
@ -73,7 +73,7 @@ namespace Perspex.Controls
/// <summary>
/// Gets or Sets Panel background brush.
/// </summary>
public Brush Background
public IBrush Background
{
get { return GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
@ -129,7 +129,7 @@ namespace Perspex.Controls
/// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context)
{
Brush background = Background;
var background = Background;
if (background != null)
{
var renderSize = Bounds.Size;

7
src/Perspex.Controls/Perspex.Controls.csproj

@ -43,8 +43,10 @@
<Compile Include="..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Application.cs" />
<Compile Include="Classes.cs" />
<Compile Include="ContextMenu.cs" />
<Compile Include="Design.cs" />
<Compile Include="DockPanel.cs" />
<Compile Include="Expander.cs" />
<Compile Include="Generators\ItemContainer.cs" />
@ -54,6 +56,7 @@
<Compile Include="INameScope.cs" />
<Compile Include="IPseudoClasses.cs" />
<Compile Include="DropDownItem.cs" />
<Compile Include="LayoutTransformControl.cs" />
<Compile Include="Mixins\ContentControlMixin.cs" />
<Compile Include="NameScope.cs" />
<Compile Include="NameScopeEventArgs.cs" />
@ -197,10 +200,6 @@
<HintPath>..\..\packages\JetBrains.Annotations.10.0.0\lib\portable-net4+sl5+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\JetBrains.Annotations.PCL328.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Serilog, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
<HintPath>..\..\packages\Serilog.1.5.14\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+MonoTouch10\Serilog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Core">
<HintPath>..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll</HintPath>
</Reference>

171
src/Perspex.Controls/Platform/PlatformManager.cs

@ -1,20 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Text;
using System.Threading.Tasks;
using Perspex.Input;
using Perspex.Input.Raw;
using Perspex.Media;
using Perspex.Media.Imaging;
using Perspex.Platform;
namespace Perspex.Controls.Platform
{
public static partial class PlatformManager
{
static IPlatformSettings GetSettings()
=> PerspexLocator.Current.GetService<IPlatformSettings>();
@ -23,9 +15,9 @@ namespace Perspex.Controls.Platform
public static IRenderTarget CreateRenderTarget(ITopLevelImpl window)
{
return
new RenderTargetDecorator(
PerspexLocator.Current.GetService<IPlatformRenderInterface>().CreateRenderer(window.Handle), window);
return PerspexLocator.Current
.GetService<IPlatformRenderInterface>()
.CreateRenderer(window.Handle);
}
public static IDisposable DesignerMode()
@ -39,158 +31,6 @@ namespace Perspex.Controls.Platform
_designerScalingFactor = factor;
}
class RenderTargetDecorator : IRenderTarget
{
private readonly IRenderTarget _target;
private readonly ITopLevelImpl _window;
public RenderTargetDecorator(IRenderTarget target, ITopLevelImpl window)
{
_target = target;
var dec = window as WindowDecorator;
if (dec != null)
window = dec.TopLevel;
_window = window;
}
public void Dispose() => _target.Dispose();
public DrawingContext CreateDrawingContext()
{
var cs = _window.ClientSize;
var ctx = _target.CreateDrawingContext();
var factor = _window.Scaling;
if (factor != 1)
{
ctx.PushPostTransform(Matrix.CreateScale(factor, factor));
ctx.PushTransformContainer();
}
return ctx;
}
}
class WindowDecorator : IPopupImpl, IWindowImpl
{
private readonly ITopLevelImpl _tl;
private readonly IWindowImpl _window;
private readonly IPopupImpl _popup;
public ITopLevelImpl TopLevel => _tl;
public WindowDecorator(ITopLevelImpl tl)
{
_tl = tl;
_window = tl as IWindowImpl;
_popup = tl as IPopupImpl;
_tl.Input = OnInput;
_tl.Paint = OnPaint;
_tl.Resized = OnResized;
}
private void OnResized(Size size)
{
Resized?.Invoke(size/Scaling);
}
private void OnPaint(Rect rc)
{
var f = Scaling;
Paint?.Invoke(new Rect(rc.X/f, rc.Y/f, rc.Width/f, rc.Height/f));
}
private void OnInput(RawInputEventArgs obj)
{
var mouseEvent = obj as RawMouseEventArgs;
if (mouseEvent != null)
mouseEvent.Position /= Scaling;
//TODO: Transform event coordinates
Input?.Invoke(obj);
}
public Point PointToClient(Point point)
{
return _tl.PointToClient(point / Scaling) * Scaling;
}
public Point PointToScreen(Point point)
{
return _tl.PointToScreen(point * Scaling) / Scaling;
}
public void Invalidate(Rect rc)
{
var f = Scaling;
_tl.Invalidate(new Rect(rc.X*f, rc.Y*f, (rc.Width + 1)*f, (rc.Height + 1)*f));
}
public Size ClientSize
{
get { return _tl.ClientSize/Scaling; }
set { _tl.ClientSize = value*Scaling; }
}
public Size MaxClientSize => _window.MaxClientSize/Scaling;
public double Scaling => _tl.Scaling;
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action Activated
{
get { return _tl.Activated; }
set { _tl.Activated = value; }
}
public Action Closed
{
get { return _tl.Closed; }
set { _tl.Closed = value; }
}
public Action Deactivated
{
get { return _tl.Deactivated; }
set { _tl.Deactivated = value; }
}
public WindowState WindowState
{
get { return _window.WindowState; }
set { _window.WindowState = value; }
}
public Action<double> ScalingChanged
{
get { return _tl.ScalingChanged; }
set { _tl.ScalingChanged = value; }
}
public void Dispose() => _tl.Dispose();
public IPlatformHandle Handle => _tl.Handle;
public void Activate() => _tl.Activate();
public void SetInputRoot(IInputRoot inputRoot) => _tl.SetInputRoot(inputRoot);
public void SetCursor(IPlatformHandle cursor) => _tl.SetCursor(cursor);
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()
{
var platform = PerspexLocator.Current.GetService<IWindowingPlatform>();
@ -200,13 +40,12 @@ namespace Perspex.Controls.Platform
throw new Exception("Could not CreateWindow(): IWindowingPlatform is not registered.");
}
var window = s_designerMode ? platform.CreateEmbeddableWindow() : platform.CreateWindow();
return new WindowDecorator(window);
return s_designerMode ? platform.CreateEmbeddableWindow() : platform.CreateWindow();
}
public static IPopupImpl CreatePopup()
{
return new WindowDecorator(PerspexLocator.Current.GetService<IWindowingPlatform>().CreatePopup());
return PerspexLocator.Current.GetService<IWindowingPlatform>().CreatePopup();
}
}
}

8
src/Perspex.Controls/Presenters/ContentPresenter.cs

@ -18,13 +18,13 @@ namespace Perspex.Controls.Presenters
/// <summary>
/// Defines the <see cref="Background"/> property.
/// </summary>
public static readonly StyledProperty<Brush> BackgroundProperty =
public static readonly StyledProperty<IBrush> BackgroundProperty =
Border.BackgroundProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="BorderBrush"/> property.
/// </summary>
public static readonly PerspexProperty<Brush> BorderBrushProperty =
public static readonly PerspexProperty<IBrush> BorderBrushProperty =
Border.BorderBrushProperty.AddOwner<ContentPresenter>();
/// <summary>
@ -86,7 +86,7 @@ namespace Perspex.Controls.Presenters
/// <summary>
/// Gets or sets a brush with which to paint the background.
/// </summary>
public Brush Background
public IBrush Background
{
get { return GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
@ -95,7 +95,7 @@ namespace Perspex.Controls.Presenters
/// <summary>
/// Gets or sets a brush with which to paint the border.
/// </summary>
public Brush BorderBrush
public IBrush BorderBrush
{
get { return GetValue(BorderBrushProperty); }
set { SetValue(BorderBrushProperty, value); }

25
src/Perspex.Controls/Presenters/ScrollContentPresenter.cs

@ -45,7 +45,7 @@ namespace Perspex.Controls.Presenters
/// Defines the <see cref="CanScrollHorizontally"/> property.
/// </summary>
public static readonly StyledProperty<bool> CanScrollHorizontallyProperty =
PerspexProperty.Register<ScrollContentPresenter, bool>("CanScrollHorizontally", true);
ScrollViewer.CanScrollHorizontallyProperty.AddOwner<ScrollContentPresenter>();
private Size _extent;
private Size _measuredExtent;
@ -220,11 +220,24 @@ namespace Perspex.Controls.Presenters
{
if (Extent.Height > Viewport.Height)
{
var y = Offset.Y + (-e.Delta.Y * 50);
y = Math.Max(y, 0);
y = Math.Min(y, Extent.Height - Viewport.Height);
Offset = new Vector(Offset.X, y);
e.Handled = true;
var scrollable = Child as IScrollable;
if (scrollable != null)
{
var y = Offset.Y + (-e.Delta.Y * scrollable.ScrollSize.Height);
y = Math.Max(y, 0);
y = Math.Min(y, Extent.Height - Viewport.Height);
Offset = new Vector(Offset.X, y);
e.Handled = true;
}
else
{
var y = Offset.Y + (-e.Delta.Y * 50);
y = Math.Max(y, 0);
y = Math.Min(y, Extent.Height - Viewport.Height);
Offset = new Vector(Offset.X, y);
e.Handled = true;
}
}
}

9
src/Perspex.Controls/Presenters/TextPresenter.cs

@ -2,7 +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 System.Reactive.Linq;
using Perspex.Media;
using Perspex.Styling;
@ -24,8 +23,7 @@ namespace Perspex.Controls.Presenters
private readonly DispatcherTimer _caretTimer;
private bool _caretBlink;
private IObservable<bool> _canScrollHorizontally;
private Brush _highlightBrush;
private IBrush _highlightBrush;
static TextPresenter()
{
@ -38,9 +36,6 @@ namespace Perspex.Controls.Presenters
_caretTimer.Interval = TimeSpan.FromMilliseconds(500);
_caretTimer.Tick += CaretTimerTick;
_canScrollHorizontally = this.GetObservable(TextWrappingProperty)
.Select(x => x == TextWrapping.NoWrap);
Observable.Merge(
this.GetObservable(SelectionStartProperty),
this.GetObservable(SelectionEndProperty))
@ -87,7 +82,7 @@ namespace Perspex.Controls.Presenters
if (_highlightBrush == null)
{
_highlightBrush = (Brush)this.FindStyleResource("HighlightBrush");
_highlightBrush = (IBrush)this.FindStyleResource("HighlightBrush");
}
foreach (var rect in rects)

10
src/Perspex.Controls/Primitives/IScrollable.cs

@ -40,5 +40,15 @@ namespace Perspex.Controls.Primitives
/// Gets the size of the viewport, in logical units.
/// </summary>
Size Viewport { get; }
/// <summary>
/// Gets the size to scroll by, in logical units.
/// </summary>
Size ScrollSize { get; }
/// <summary>
/// Gets the size to page by, in logical units.
/// </summary>
Size PageScrollSize { get; }
}
}

10
src/Perspex.Controls/Primitives/Popup.cs

@ -276,12 +276,18 @@ namespace Perspex.Controls.Primitives
protected virtual Point GetPosition()
{
var zero = default(Point);
var mode = PlacementMode;
var target = PlacementTarget ?? this.GetVisualParent<Control>();
switch (PlacementMode)
if (target?.GetVisualRoot() == null)
{
mode = PlacementMode.Pointer;
}
switch (mode)
{
case PlacementMode.Pointer:
return MouseDevice.Instance.Position;
return MouseDevice.Instance?.Position ?? default(Point);
case PlacementMode.Bottom:
return target?.PointToScreen(new Point(0, target.Bounds.Height)) ?? zero;

11
src/Perspex.Controls/Primitives/PopupRoot.cs

@ -84,6 +84,7 @@ namespace Perspex.Controls.Primitives
/// </summary>
public void Show()
{
EnsureInitialized();
PlatformImpl.Show();
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
IsVisible = true;
@ -108,6 +109,16 @@ namespace Perspex.Controls.Primitives
}
}
private void EnsureInitialized()
{
if (!this.IsInitialized)
{
var init = (ISupportInitialize)this;
init.BeginInit();
init.EndInit();
}
}
private void SetTemplatedParentAndApplyChildTemplates(IControl control)
{
var templatedParent = Parent.TemplatedParent;

32
src/Perspex.Controls/Primitives/TemplatedControl.cs

@ -7,12 +7,11 @@ using System.Reactive.Linq;
using Perspex.Controls.Templates;
using Perspex.Data;
using Perspex.Interactivity;
using Perspex.Logging;
using Perspex.LogicalTree;
using Perspex.Media;
using Perspex.Styling;
using Perspex.VisualTree;
using Serilog;
using Serilog.Core.Enrichers;
namespace Perspex.Controls.Primitives
{
@ -24,13 +23,13 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Defines the <see cref="Background"/> property.
/// </summary>
public static readonly StyledProperty<Brush> BackgroundProperty =
public static readonly StyledProperty<IBrush> BackgroundProperty =
Border.BackgroundProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="BorderBrush"/> property.
/// </summary>
public static readonly StyledProperty<Brush> BorderBrushProperty =
public static readonly StyledProperty<IBrush> BorderBrushProperty =
Border.BorderBrushProperty.AddOwner<TemplatedControl>();
/// <summary>
@ -66,7 +65,7 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Defines the <see cref="Foreground"/> property.
/// </summary>
public static readonly StyledProperty<Brush> ForegroundProperty =
public static readonly StyledProperty<IBrush> ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner<TemplatedControl>();
/// <summary>
@ -97,8 +96,6 @@ namespace Perspex.Controls.Primitives
private bool _templateApplied;
private readonly ILogger _templateLog;
/// <summary>
/// Initializes static members of the <see cref="TemplatedControl"/> class.
/// </summary>
@ -108,19 +105,6 @@ namespace Perspex.Controls.Primitives
TemplateProperty.Changed.AddClassHandler<TemplatedControl>(x => x.OnTemplateChanged);
}
/// <summary>
/// Initializes a new instance of the <see cref="TemplatedControl"/> class.
/// </summary>
public TemplatedControl()
{
_templateLog = Log.ForContext(new[]
{
new PropertyEnricher("Area", "Template"),
new PropertyEnricher("SourceContext", GetType()),
new PropertyEnricher("Id", GetHashCode()),
});
}
/// <summary>
/// Raised when the control's template is applied.
/// </summary>
@ -133,7 +117,7 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Gets or sets the brush used to draw the control's background.
/// </summary>
public Brush Background
public IBrush Background
{
get { return GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
@ -142,7 +126,7 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Gets or sets the brush used to draw the control's border.
/// </summary>
public Brush BorderBrush
public IBrush BorderBrush
{
get { return GetValue(BorderBrushProperty); }
set { SetValue(BorderBrushProperty, value); }
@ -196,7 +180,7 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Gets or sets the brush used to draw the control's text and other foreground elements.
/// </summary>
public Brush Foreground
public IBrush Foreground
{
get { return GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
@ -264,7 +248,7 @@ namespace Perspex.Controls.Primitives
if (Template != null)
{
_templateLog.Verbose("Creating control template");
Logger.Verbose(LogArea.Control, this, "Creating control template");
var child = Template.Build(this);
var nameScope = new NameScope();

2
src/Perspex.Controls/Properties/AssemblyInfo.cs

@ -7,7 +7,9 @@ using Perspex.Metadata;
[assembly: AssemblyTitle("Perspex.Controls")]
[assembly: InternalsVisibleTo("Perspex.Controls.UnitTests")]
[assembly: InternalsVisibleTo("Perspex.DesignerSupport")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Presenters")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Primitives")]

238
src/Perspex.Controls/ScrollViewer.cs

@ -2,9 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Perspex.Controls.Presenters;
using Perspex.Controls.Primitives;
@ -19,8 +17,8 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="CanScrollHorizontally"/> property.
/// </summary>
public static readonly AttachedProperty<bool> CanScrollHorizontallyProperty =
PerspexProperty.RegisterAttached<ScrollViewer, Control, bool>(nameof(CanScrollHorizontally), true);
public static readonly StyledProperty<bool> CanScrollHorizontallyProperty =
PerspexProperty.Register<ScrollViewer, bool>(nameof(CanScrollHorizontally), true);
/// <summary>
/// Defines the <see cref="Extent"/> property.
@ -51,37 +49,44 @@ namespace Perspex.Controls
/// Defines the HorizontalScrollBarMaximum property.
/// </summary>
/// <remarks>
/// There is no C# accessor for this property as it is intended to be bound to by a
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly StyledProperty<double> HorizontalScrollBarMaximumProperty =
PerspexProperty.Register<ScrollViewer, double>("HorizontalScrollBarMaximum");
public static readonly DirectProperty<ScrollViewer, double> HorizontalScrollBarMaximumProperty =
PerspexProperty.RegisterDirect<ScrollViewer, double>(
nameof(HorizontalScrollBarMaximum),
o => o.HorizontalScrollBarMaximum);
/// <summary>
/// Defines the HorizontalScrollBarValue property.
/// </summary>
/// <remarks>
/// There is no C# accessor for this property as it is intended to be bound to by a
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly StyledProperty<double> HorizontalScrollBarValueProperty =
PerspexProperty.Register<ScrollViewer, double>("HorizontalScrollBarValue");
public static readonly DirectProperty<ScrollViewer, double> HorizontalScrollBarValueProperty =
PerspexProperty.RegisterDirect<ScrollViewer, double>(
nameof(HorizontalScrollBarValue),
o => o.HorizontalScrollBarValue,
(o, v) => o.HorizontalScrollBarValue = v);
/// <summary>
/// Defines the HorizontalScrollBarViewportSize property.
/// </summary>
/// <remarks>
/// There is no C# accessor for this property as it is intended to be bound to by a
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly StyledProperty<double> HorizontalScrollBarViewportSizeProperty =
PerspexProperty.Register<ScrollViewer, double>("HorizontalScrollBarViewportSize");
public static readonly DirectProperty<ScrollViewer, double> HorizontalScrollBarViewportSizeProperty =
PerspexProperty.RegisterDirect<ScrollViewer, double>(
nameof(HorizontalScrollBarViewportSize),
o => o.HorizontalScrollBarViewportSize);
/// <summary>
/// Defines the <see cref="HorizontalScrollBarVisibility"/> property.
/// </summary>
/// <remarks>
/// There is no C# accessor for this property as it is intended to be bound to by a
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly AttachedProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty =
@ -93,31 +98,38 @@ namespace Perspex.Controls
/// Defines the VerticalScrollBarMaximum property.
/// </summary>
/// <remarks>
/// There is no C# accessor for this property as it is intended to be bound to by a
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly StyledProperty<double> VerticalScrollBarMaximumProperty =
PerspexProperty.Register<ScrollViewer, double>("VerticalScrollBarMaximum");
public static readonly DirectProperty<ScrollViewer, double> VerticalScrollBarMaximumProperty =
PerspexProperty.RegisterDirect<ScrollViewer, double>(
nameof(VerticalScrollBarMaximum),
o => o.VerticalScrollBarMaximum);
/// <summary>
/// Defines the VerticalScrollBarValue property.
/// </summary>
/// <remarks>
/// There is no C# accessor for this property as it is intended to be bound to by a
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly StyledProperty<double> VerticalScrollBarValueProperty =
PerspexProperty.Register<ScrollViewer, double>("VerticalScrollBarValue");
public static readonly DirectProperty<ScrollViewer, double> VerticalScrollBarValueProperty =
PerspexProperty.RegisterDirect<ScrollViewer, double>(
nameof(VerticalScrollBarValue),
o => o.VerticalScrollBarValue,
(o, v) => o.VerticalScrollBarValue = v);
/// <summary>
/// Defines the VerticalScrollBarViewportSize property.
/// </summary>
/// <remarks>
/// There is no C# accessor for this property as it is intended to be bound to by a
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly StyledProperty<double> VerticalScrollBarViewportSizeProperty =
PerspexProperty.Register<ScrollViewer, double>("VerticalScrollBarViewportSize");
public static readonly DirectProperty<ScrollViewer, double> VerticalScrollBarViewportSizeProperty =
PerspexProperty.RegisterDirect<ScrollViewer, double>(
nameof(VerticalScrollBarViewportSize),
o => o.VerticalScrollBarViewportSize);
/// <summary>
/// Defines the <see cref="VerticalScrollBarVisibility"/> property.
@ -149,34 +161,6 @@ namespace Perspex.Controls
this.GetObservable(ExtentProperty),
this.GetObservable(ViewportProperty))
.Select(x => new { Extent = x[0], Viewport = x[1] });
Bind(
VerticalScrollBarViewportSizeProperty,
extentAndViewport.Select(x => Max((x.Viewport.Height / x.Extent.Height) * (x.Extent.Height - x.Viewport.Height), 0)));
Bind(
HorizontalScrollBarViewportSizeProperty,
extentAndViewport.Select(x => Max((x.Viewport.Width / x.Extent.Width) * (x.Extent.Width - x.Viewport.Width), 0)));
Bind(
HorizontalScrollBarMaximumProperty,
extentAndViewport.Select(x => Max(x.Extent.Width - x.Viewport.Width, 0)));
Bind(
VerticalScrollBarMaximumProperty,
extentAndViewport.Select(x => Max(x.Extent.Height - x.Viewport.Height, 0)));
this.GetObservable(OffsetProperty).Subscribe(x =>
{
SetValue(HorizontalScrollBarValueProperty, x.X);
SetValue(VerticalScrollBarValueProperty, x.Y);
});
var scrollBarOffset = Observable.CombineLatest(
this.GetObservable(HorizontalScrollBarValueProperty),
this.GetObservable(VerticalScrollBarValueProperty))
.Select(x => new Vector(x[0], x[1]))
.Subscribe(x => Offset = x);
}
/// <summary>
@ -184,8 +168,18 @@ namespace Perspex.Controls
/// </summary>
public Size Extent
{
get { return _extent; }
private set { SetAndRaise(ExtentProperty, ref _extent, value); }
get
{
return _extent;
}
private set
{
if (SetAndRaise(ExtentProperty, ref _extent, value))
{
CalculatedPropertiesChanged();
}
}
}
/// <summary>
@ -201,7 +195,11 @@ namespace Perspex.Controls
set
{
value = ValidateOffset(this, value);
SetAndRaise(OffsetProperty, ref _offset, value);
if (SetAndRaise(OffsetProperty, ref _offset, value))
{
CalculatedPropertiesChanged();
}
}
}
@ -210,8 +208,18 @@ namespace Perspex.Controls
/// </summary>
public Size Viewport
{
get { return _viewport; }
private set { SetAndRaise(ViewportProperty, ref _viewport, value); }
get
{
return _viewport;
}
private set
{
if (SetAndRaise(ViewportProperty, ref _viewport, value))
{
CalculatedPropertiesChanged();
}
}
}
/// <summary>
@ -241,6 +249,112 @@ namespace Perspex.Controls
set { SetValue(VerticalScrollBarVisibilityProperty, value); }
}
/// <summary>
/// Gets the maximum horizontal scrollbar value.
/// </summary>
protected double HorizontalScrollBarMaximum
{
get { return Max(_extent.Width - _viewport.Width, 0); }
}
/// <summary>
/// Gets or sets the horizontal scrollbar value.
/// </summary>
protected double HorizontalScrollBarValue
{
get { return _offset.X; }
set
{
if (_offset.X != value)
{
var old = Offset.X;
Offset = Offset.WithX(value);
RaisePropertyChanged(HorizontalScrollBarValueProperty, old, value);
}
}
}
/// <summary>
/// Gets the size of the horizontal scrollbar viewport.
/// </summary>
protected double HorizontalScrollBarViewportSize
{
get { return Max((_viewport.Width / _extent.Width) * (_extent.Width - _viewport.Width), 0); }
}
/// <summary>
/// Gets the maximum vertical scrollbar value.
/// </summary>
protected double VerticalScrollBarMaximum
{
get { return Max(_extent.Height - _viewport.Height, 0); }
}
/// <summary>
/// Gets or sets the vertical scrollbar value.
/// </summary>
protected double VerticalScrollBarValue
{
get { return _offset.Y; }
set
{
if (_offset.Y != value)
{
var old = Offset.Y;
Offset = Offset.WithY(value);
RaisePropertyChanged(VerticalScrollBarValueProperty, old, value);
}
}
}
/// <summary>
/// Gets the size of the vertical scrollbar viewport.
/// </summary>
protected double VerticalScrollBarViewportSize
{
get { return Max((_viewport.Height / _extent.Height) * (_extent.Height - _viewport.Height), 0); }
}
/// <summary>
/// Gets the value of the HorizontalScrollBarVisibility attached property.
/// </summary>
/// <param name="control">The control to read the value from.</param>
/// <returns>The value of the property.</returns>
public ScrollBarVisibility GetHorizontalScrollBarVisibility(Control control)
{
return control.GetValue(HorizontalScrollBarVisibilityProperty);
}
/// <summary>
/// Gets the value of the HorizontalScrollBarVisibility attached property.
/// </summary>
/// <param name="control">The control to set the value on.</param>
/// <param name="value">The value of the property.</param>
public void SetHorizontalScrollBarVisibility(Control control, ScrollBarVisibility value)
{
control.SetValue(HorizontalScrollBarVisibilityProperty, value);
}
/// <summary>
/// Gets the value of the VerticalScrollBarVisibility attached property.
/// </summary>
/// <param name="control">The control to read the value from.</param>
/// <returns>The value of the property.</returns>
public ScrollBarVisibility GetVerticalScrollBarVisibility(Control control)
{
return control.GetValue(VerticalScrollBarVisibilityProperty);
}
/// <summary>
/// Gets the value of the VerticalScrollBarVisibility attached property.
/// </summary>
/// <param name="control">The control to set the value on.</param>
/// <param name="value">The value of the property.</param>
public void SetVerticalScrollBarVisibility(Control control, ScrollBarVisibility value)
{
control.SetValue(VerticalScrollBarVisibilityProperty, value);
}
internal static Vector CoerceOffset(Size extent, Size viewport, Vector offset)
{
var maxX = Math.Max(extent.Width - viewport.Width, 0);
@ -274,5 +388,17 @@ namespace Perspex.Controls
return value;
}
}
private void CalculatedPropertiesChanged()
{
// Pass old values of 0 here because we don't have the old values at this point,
// and it shouldn't matter as only the template uses these properies.
RaisePropertyChanged(HorizontalScrollBarMaximumProperty, 0, HorizontalScrollBarMaximum);
RaisePropertyChanged(HorizontalScrollBarValueProperty, 0, HorizontalScrollBarValue);
RaisePropertyChanged(HorizontalScrollBarViewportSizeProperty, 0, HorizontalScrollBarViewportSize);
RaisePropertyChanged(VerticalScrollBarMaximumProperty, 0, VerticalScrollBarMaximum);
RaisePropertyChanged(VerticalScrollBarValueProperty, 0, VerticalScrollBarValue);
RaisePropertyChanged(VerticalScrollBarViewportSizeProperty, 0, VerticalScrollBarViewportSize);
}
}
}

12
src/Perspex.Controls/Shapes/Shape.cs

@ -10,14 +10,14 @@ namespace Perspex.Controls.Shapes
{
public abstract class Shape : Control
{
public static readonly StyledProperty<Brush> FillProperty =
PerspexProperty.Register<Shape, Brush>("Fill");
public static readonly StyledProperty<IBrush> FillProperty =
PerspexProperty.Register<Shape, IBrush>("Fill");
public static readonly StyledProperty<Stretch> StretchProperty =
PerspexProperty.Register<Shape, Stretch>("Stretch");
public static readonly StyledProperty<Brush> StrokeProperty =
PerspexProperty.Register<Shape, Brush>("Stroke");
public static readonly StyledProperty<IBrush> StrokeProperty =
PerspexProperty.Register<Shape, IBrush>("Stroke");
public static readonly StyledProperty<PerspexList<double>> StrokeDashArrayProperty =
PerspexProperty.Register<Shape, PerspexList<double>>("StrokeDashArray");
@ -48,7 +48,7 @@ namespace Perspex.Controls.Shapes
}
}
public Brush Fill
public IBrush Fill
{
get { return GetValue(FillProperty); }
set { SetValue(FillProperty, value); }
@ -77,7 +77,7 @@ namespace Perspex.Controls.Shapes
set { SetValue(StretchProperty, value); }
}
public Brush Stroke
public IBrush Stroke
{
get { return GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }

5
src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs

@ -4,6 +4,7 @@
using System;
using System.Collections;
using System.Reflection;
using Perspex.Data;
namespace Perspex.Controls.Templates
{
@ -59,9 +60,9 @@ namespace Perspex.Controls.Templates
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The child items, or null if no child items.</returns>
public IEnumerable ItemsSelector(object item)
public InstancedBinding ItemsSelector(object item)
{
return this?._itemsSelector(item);
return new InstancedBinding(this?._itemsSelector(item));
}
/// <summary>

9
src/Perspex.Controls/Templates/ITreeDataTemplate.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.
using System.Collections;
using Perspex.Data;
namespace Perspex.Controls.Templates
{
@ -14,7 +14,10 @@ namespace Perspex.Controls.Templates
/// Selects the child items of an item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The child items, or null if no child items.</returns>
IEnumerable ItemsSelector(object item);
/// <returns>
/// An <see cref="InstancedBinding"/> holding the items, or an observable that tracks the
/// items. May return null if no child items.
/// </returns>
InstancedBinding ItemsSelector(object item);
}
}

16
src/Perspex.Controls/TextBlock.cs

@ -19,7 +19,7 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Background"/> property.
/// </summary>
public static readonly StyledProperty<Brush> BackgroundProperty =
public static readonly StyledProperty<IBrush> BackgroundProperty =
Border.BackgroundProperty.AddOwner<TextBlock>();
// TODO: Define these attached properties elswhere (e.g. on a Text class) and AddOwner
@ -63,8 +63,8 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Foreground"/> property.
/// </summary>
public static readonly AttachedProperty<Brush> ForegroundProperty =
PerspexProperty.RegisterAttached<TextBlock, Control, Brush>(
public static readonly AttachedProperty<IBrush> ForegroundProperty =
PerspexProperty.RegisterAttached<TextBlock, Control, IBrush>(
nameof(Foreground),
new SolidColorBrush(0xff000000),
inherits: true);
@ -128,7 +128,7 @@ namespace Perspex.Controls
/// <summary>
/// Gets or sets a brush used to paint the control's background.
/// </summary>
public Brush Background
public IBrush Background
{
get { return GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
@ -183,7 +183,7 @@ namespace Perspex.Controls
/// <summary>
/// Gets or sets a brush used to paint the text.
/// </summary>
public Brush Foreground
public IBrush Foreground
{
get { return GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
@ -268,7 +268,7 @@ namespace Perspex.Controls
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The foreground.</returns>
public static Brush GetForeground(Control control)
public static IBrush GetForeground(Control control)
{
return control.GetValue(ForegroundProperty);
}
@ -323,7 +323,7 @@ namespace Perspex.Controls
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
/// <returns>The font family.</returns>
public static void SetForeground(Control control, Brush value)
public static void SetForeground(Control control, IBrush value)
{
control.SetValue(ForegroundProperty, value);
}
@ -334,7 +334,7 @@ namespace Perspex.Controls
/// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context)
{
Brush background = Background;
var background = Background;
if (background != null)
{

76
src/Perspex.Controls/TextBox.cs

@ -26,15 +26,8 @@ namespace Perspex.Controls
public static readonly StyledProperty<bool> AcceptsTabProperty =
PerspexProperty.Register<TextBox, bool>("AcceptsTab");
// TODO: Remove these when "(attached.property)" syntax is supported in bindings. They are
// bound to in the TextBox template, but ideally they should just be bound to the attached
// property.
public static readonly StyledProperty<bool> CanScrollHorizontally =
ScrollViewer.CanScrollHorizontallyProperty.AddOwner<TextBox>();
public static readonly StyledProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty =
ScrollViewer.HorizontalScrollBarVisibilityProperty.AddOwner<TextBox>();
public static readonly StyledProperty<ScrollBarVisibility> VerticalScrollBarVisibilityProperty =
ScrollViewer.VerticalScrollBarVisibilityProperty.AddOwner<TextBox>();
public static readonly DirectProperty<TextBox, bool> CanScrollHorizontallyProperty =
PerspexProperty.RegisterDirect<TextBox, bool>("CanScrollHorizontally", o => o.CanScrollHorizontally);
// TODO: Should CaretIndex, SelectionStart/End and Text be direct properties?
public static readonly StyledProperty<int> CaretIndexProperty =
@ -78,6 +71,7 @@ namespace Perspex.Controls
public bool Equals(UndoRedoState other) => ReferenceEquals(Text, other.Text) || Equals(Text, other.Text);
}
private bool _canScrollHorizontally;
private TextPresenter _presenter;
private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
@ -89,13 +83,9 @@ namespace Perspex.Controls
public TextBox()
{
var canScrollHorizontally = this.GetObservable(AcceptsReturnProperty)
.Select(x => !x);
Bind(
ScrollViewer.CanScrollHorizontallyProperty,
canScrollHorizontally,
BindingPriority.Style);
var canScrollHorizontally = this.GetObservable(TextWrappingProperty)
.Select(x => x == TextWrapping.NoWrap)
.Subscribe(x => CanScrollHorizontally = x);
var horizontalScrollBarVisibility = this.GetObservable(AcceptsReturnProperty)
.Select(x => x ? ScrollBarVisibility.Auto : ScrollBarVisibility.Hidden);
@ -119,6 +109,12 @@ namespace Perspex.Controls
set { SetValue(AcceptsTabProperty, value); }
}
public bool CanScrollHorizontally
{
get { return _canScrollHorizontally; }
private set { SetAndRaise(CanScrollHorizontallyProperty, ref _canScrollHorizontally, value); }
}
public int CaretIndex
{
get { return GetValue(CaretIndexProperty); }
@ -262,8 +258,16 @@ namespace Perspex.Controls
{
Copy();
}
break;
case Key.X:
if(modifiers == InputModifiers.Control)
{
Copy();
DeleteSelection();
}
break;
case Key.V:
if (modifiers == InputModifiers.Control)
{
@ -271,6 +275,7 @@ namespace Perspex.Controls
}
break;
case Key.Z:
if (modifiers == InputModifiers.Control)
_undoRedoHelper.Undo();
@ -348,6 +353,10 @@ namespace Perspex.Controls
}
break;
default:
handled = false;
break;
}
if (movement && ((modifiers & InputModifiers.Shift) != 0))
@ -373,23 +382,26 @@ namespace Perspex.Controls
var index = CaretIndex = _presenter.GetCaretIndex(point);
var text = Text;
switch (e.ClickCount)
if (text != null)
{
case 1:
SelectionStart = SelectionEnd = index;
break;
case 2:
if (!StringUtils.IsStartOfWord(text, index))
{
SelectionStart = StringUtils.PreviousWord(text, index, false);
}
SelectionEnd = StringUtils.NextWord(text, index, false);
break;
case 3:
SelectionStart = 0;
SelectionEnd = text.Length;
break;
switch (e.ClickCount)
{
case 1:
SelectionStart = SelectionEnd = index;
break;
case 2:
if (!StringUtils.IsStartOfWord(text, index))
{
SelectionStart = StringUtils.PreviousWord(text, index, false);
}
SelectionEnd = StringUtils.NextWord(text, index, false);
break;
case 3:
SelectionStart = 0;
SelectionEnd = text.Length;
break;
}
}
e.Device.Capture(_presenter);

5
src/Perspex.Controls/ToolTip.cs

@ -7,11 +7,12 @@ using System.Reactive.Subjects;
using Perspex.Controls.Primitives;
using Perspex.Input;
using Perspex.Threading;
using Perspex.VisualTree;
namespace Perspex.Controls
{
/// <summary>
/// A tooltip control.
/// A control which pops up a hint when a control is hovered.
/// </summary>
/// <remarks>
/// You will probably not want to create a <see cref="ToolTip"/> control directly: if added to
@ -102,7 +103,7 @@ namespace Perspex.Controls
/// <param name="control">The control.</param>
private static void ShowToolTip(Control control)
{
if (control != null)
if (control != null && control.IsVisible && control.GetVisualRoot() != null)
{
if (s_popup == null)
{

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

Loading…
Cancel
Save