diff --git a/Perspex.sln b/Perspex.sln index bc7e0560af..9cbaca445d 100644 --- a/Perspex.sln +++ b/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 diff --git a/docs/docfx.json b/docs/docfx.json index 8775904c6a..4e908b97da 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -50,6 +50,6 @@ "_appTitle": "Perspex Website" }, "dest": "_site", - "template": "default" + "template": [ "default", "template"] } } diff --git a/docs/spec/logging.md b/docs/spec/logging.md new file mode 100644 index 0000000000..91d35023e4 --- /dev/null +++ b/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` diff --git a/docs/spec/styles.md b/docs/spec/styles.md index 89d5348397..053d91b1e0 100644 --- a/docs/spec/styles.md +++ b/docs/spec/styles.md @@ -49,6 +49,16 @@ As in CSS, controls can be given *style classes* which can be used in selectors: +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 diff --git a/docs/spec/toc.yml b/docs/spec/toc.yml index e5c40d7544..3075a03619 100644 --- a/docs/spec/toc.yml +++ b/docs/spec/toc.yml @@ -5,4 +5,6 @@ - name: Defining Properties href: defining-properties.md - name: Working with Properties - href: working-with-properties.md \ No newline at end of file + href: working-with-properties.md +- name: Logging + href: logging.md diff --git a/docs/template/partials/footer.tmpl.partial b/docs/template/partials/footer.tmpl.partial new file mode 100644 index 0000000000..a94f934e92 --- /dev/null +++ b/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.}} + + diff --git a/docs/tutorial/from-wpf.md b/docs/tutorial/from-wpf.md new file mode 100644 index 0000000000..0b4fd82646 --- /dev/null +++ b/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: + + + + + + + Header + + +## 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`): + + + + + + + + + + + + + +`ItemsControl`s don't currently have an `ItemTemplate` property: instead just +place the template for your items into the control's `DataTemplates`, e.g. + + + + + + + + + +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: + + + +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(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). diff --git a/nuget/build-version.ps1 b/nuget/build-version.ps1 index 59031527d5..5561631858 100644 --- a/nuget/build-version.ps1 +++ b/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 diff --git a/samples/BindingTest/App.config b/samples/BindingTest/App.config index 8324aa6ff1..d1428ad712 100644 --- a/samples/BindingTest/App.config +++ b/samples/BindingTest/App.config @@ -1,6 +1,6 @@ - + - + - \ No newline at end of file + diff --git a/samples/BindingTest/App.xaml.cs b/samples/BindingTest/App.xaml.cs index 80fcaf8433..5ffac249e9 100644 --- a/samples/BindingTest/App.xaml.cs +++ b/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 + } } } diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj index 406c5b1cfb..ed4900a402 100644 --- a/samples/BindingTest/BindingTest.csproj +++ b/samples/BindingTest/BindingTest.csproj @@ -9,9 +9,10 @@ Properties BindingTest BindingTest - v4.6 + v4.5 512 true + AnyCPU @@ -111,10 +112,6 @@ {d211e587-d8bc-45b9-95a4-f297c8fa5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} Perspex.Base @@ -123,6 +120,10 @@ {d2221c82-4a25-4583-9b43-d791e3f6820c} Perspex.Controls + + {799a7bb5-3c2c-48b6-85a7-406a12c420da} + Perspex.DesignerSupport + {7062ae20-5dcc-4442-9645-8195bdece63e} Perspex.Diagnostics @@ -139,6 +140,10 @@ {42472427-4774-4c81-8aff-9f27b8e31721} Perspex.Layout + + {b61b66a3-b82d-4875-8001-89d3394fe0c9} + Perspex.Logging.Serilog + {6417b24e-49c2-4985-8db2-3ab9d898ec91} Perspex.ReactiveUI diff --git a/samples/ControlCatalog/App.config b/samples/ControlCatalog/App.config index 2e292d1c8a..7ef9d715f8 100644 --- a/samples/ControlCatalog/App.config +++ b/samples/ControlCatalog/App.config @@ -1,14 +1,14 @@ - + - + - - + + - \ No newline at end of file + diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index ac9f563c8c..2930fa746a 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/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 } diff --git a/samples/ControlCatalog/Assets/github_icon.png b/samples/ControlCatalog/Assets/github_icon.png new file mode 100644 index 0000000000..ed4f82f847 Binary files /dev/null and b/samples/ControlCatalog/Assets/github_icon.png differ diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index be2b312204..087998c805 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -9,9 +9,10 @@ Properties ControlCatalog ControlCatalog - v4.6 + v4.5 512 true + AnyCPU @@ -60,6 +61,27 @@ MainWindow.xaml + + ImagePage.xaml + + + ExpanderPage.xaml + + + RadioButtonPage.xaml + + + MenuPage.xaml + + + ToolTipPage.xaml + + + TextBoxPage.xaml + + + LayoutTransformControlPage.xaml + CheckBoxPage.xaml @@ -136,10 +158,6 @@ {d211e587-d8bc-45b9-95a4-f297c8fa5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} Perspex.Base @@ -148,6 +166,10 @@ {d2221c82-4a25-4583-9b43-d791e3f6820c} Perspex.Controls + + {799a7bb5-3c2c-48b6-85a7-406a12c420da} + Perspex.DesignerSupport + {7062ae20-5dcc-4442-9645-8195bdece63e} Perspex.Diagnostics @@ -168,6 +190,10 @@ {42472427-4774-4c81-8aff-9f27b8e31721} Perspex.Layout + + {b61b66a3-b82d-4875-8001-89d3394fe0c9} + Perspex.Logging.Serilog + {6417b24e-49c2-4985-8db2-3ab9d898ec91} Perspex.ReactiveUI @@ -200,6 +226,46 @@ + + + Designer + + + + + Designer + + + + + Designer + + + + + Designer + + + + + PreserveNewest + + + + + Designer + + + + + Designer + + + + + Designer + + + \ No newline at end of file diff --git a/src/Perspex.Logging.Serilog/Properties/AssemblyInfo.cs b/src/Perspex.Logging.Serilog/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..91c95c4982 --- /dev/null +++ b/src/Perspex.Logging.Serilog/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Serilog")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Serilog")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Perspex.Logging.Serilog/SerilogLogger.cs b/src/Perspex.Logging.Serilog/SerilogLogger.cs new file mode 100644 index 0000000000..beb5989d55 --- /dev/null +++ b/src/Perspex.Logging.Serilog/SerilogLogger.cs @@ -0,0 +1,44 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System.Collections.Generic; +using Serilog; +using PerspexLogEventLevel = Perspex.Logging.LogEventLevel; +using SerilogLogEventLevel = Serilog.Events.LogEventLevel; + +namespace Perspex.Logging.Serilog +{ + public class SerilogLogger : ILogSink + { + private readonly ILogger _output; + private readonly Dictionary _areas = new Dictionary(); + + public SerilogLogger(ILogger output) + { + _output = output; + } + + public static void Initialize(ILogger output) + { + Logger.Sink = new SerilogLogger(output); + } + + public void Log( + PerspexLogEventLevel level, + string area, + object source, + string messageTemplate, + params object[] propertyValues) + { + ILogger areaLogger; + + if (!_areas.TryGetValue(area, out areaLogger)) + { + areaLogger = _output.ForContext("Area", area); + _areas.Add(area, areaLogger); + } + + areaLogger.Write((SerilogLogEventLevel)level, messageTemplate, propertyValues); + } + } +} diff --git a/src/Perspex.Logging.Serilog/packages.config b/src/Perspex.Logging.Serilog/packages.config new file mode 100644 index 0000000000..dbc72b5331 --- /dev/null +++ b/src/Perspex.Logging.Serilog/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Animation/CrossFade.cs b/src/Perspex.SceneGraph/Animation/CrossFade.cs index 722082801d..65697d4245 100644 --- a/src/Perspex.SceneGraph/Animation/CrossFade.cs +++ b/src/Perspex.SceneGraph/Animation/CrossFade.cs @@ -89,7 +89,10 @@ namespace Perspex.Animation from.Opacity = 1; } - to.Opacity = 1; + if (to != null) + { + to.Opacity = 1; + } } /// diff --git a/src/Perspex.SceneGraph/Matrix.cs b/src/Perspex.SceneGraph/Matrix.cs index baf38fb767..121ad1667d 100644 --- a/src/Perspex.SceneGraph/Matrix.cs +++ b/src/Perspex.SceneGraph/Matrix.cs @@ -53,6 +53,11 @@ namespace Perspex /// public bool IsIdentity => Equals(Identity); + /// + /// HasInverse Property - returns true if this matrix is invertable, false otherwise. + /// + public bool HasInverse => GetDeterminant() != 0; + /// /// The first element of the first row /// @@ -209,6 +214,7 @@ namespace Perspex return (_m11 * _m22) - (_m12 * _m21); } + /// /// Returns a boolean indicating whether the matrix is equal to the other given matrix. /// diff --git a/src/Perspex.SceneGraph/Media/Brush.cs b/src/Perspex.SceneGraph/Media/Brush.cs index 6cf90502e3..6d53dd4092 100644 --- a/src/Perspex.SceneGraph/Media/Brush.cs +++ b/src/Perspex.SceneGraph/Media/Brush.cs @@ -10,7 +10,7 @@ namespace Perspex.Media /// /// Describes how an area is painted. /// - public abstract class Brush : PerspexObject + public abstract class Brush : PerspexObject, IBrush { /// /// Defines the property. @@ -32,7 +32,7 @@ namespace Perspex.Media /// /// The brush string. /// The . - public static Brush Parse(string s) + public static IBrush Parse(string s) { if (s[0] == '#') { @@ -46,7 +46,7 @@ namespace Perspex.Media if (member != null) { - return (Brush)member.GetValue(null); + return (IBrush)member.GetValue(null); } else { diff --git a/src/Perspex.SceneGraph/Media/DrawingContext.cs b/src/Perspex.SceneGraph/Media/DrawingContext.cs index 41a599858e..80d4be26ca 100644 --- a/src/Perspex.SceneGraph/Media/DrawingContext.cs +++ b/src/Perspex.SceneGraph/Media/DrawingContext.cs @@ -83,7 +83,7 @@ namespace Perspex.Media /// The fill brush. /// The stroke pen. /// The geometry. - public void DrawGeometry(Brush brush, Pen pen, Geometry geometry) => _impl.DrawGeometry(brush, pen, geometry); + public void DrawGeometry(IBrush brush, Pen pen, Geometry geometry) => _impl.DrawGeometry(brush, pen, geometry); /// /// Draws the outline of a rectangle. @@ -100,7 +100,7 @@ namespace Perspex.Media /// The foreground brush. /// The upper-left corner of the text. /// The text. - public void DrawText(Brush foreground, Point origin, FormattedText text) + public void DrawText(IBrush foreground, Point origin, FormattedText text) => _impl.DrawText(foreground, origin, text); /// @@ -109,7 +109,7 @@ namespace Perspex.Media /// The brush. /// The rectangle bounds. /// The corner radius. - public void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0.0f) + public void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f) => _impl.FillRectangle(brush, rect, cornerRadius); public struct PushedState : IDisposable @@ -178,7 +178,7 @@ namespace Perspex.Media /// The opacity. /// A disposable used to undo the opacity. public PushedState PushOpacity(double opacity) - //TODO: Elimintate platform-specific push opacity call + //TODO: Eliminate platform-specific push opacity call { _impl.PushOpacity(opacity); return new PushedState(this, PushedState.PushedStateType.Opacity); diff --git a/src/Perspex.SceneGraph/Media/FormattedText.cs b/src/Perspex.SceneGraph/Media/FormattedText.cs index e2db938612..b727a4fab9 100644 --- a/src/Perspex.SceneGraph/Media/FormattedText.cs +++ b/src/Perspex.SceneGraph/Media/FormattedText.cs @@ -206,7 +206,7 @@ namespace Perspex.Media /// The brush. /// The start of the text range. /// The length of the text range. - public void SetForegroundBrush(Brush brush, int startIndex, int length) + public void SetForegroundBrush(IBrush brush, int startIndex, int length) { CheckDisposed(); PlatformImpl.SetForegroundBrush(brush, startIndex, length); diff --git a/src/Perspex.SceneGraph/Media/IBrush.cs b/src/Perspex.SceneGraph/Media/IBrush.cs new file mode 100644 index 0000000000..368b01c43a --- /dev/null +++ b/src/Perspex.SceneGraph/Media/IBrush.cs @@ -0,0 +1,16 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +namespace Perspex.Media +{ + /// + /// Describes how an area is painted. + /// + public interface IBrush + { + /// + /// Gets the opacity of the brush. + /// + double Opacity { get; } + } +} \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Media/IDrawingContext.cs b/src/Perspex.SceneGraph/Media/IDrawingContext.cs index 4d144b33e0..2667856dc3 100644 --- a/src/Perspex.SceneGraph/Media/IDrawingContext.cs +++ b/src/Perspex.SceneGraph/Media/IDrawingContext.cs @@ -39,7 +39,7 @@ namespace Perspex.Media /// The fill brush. /// The stroke pen. /// The geometry. - void DrawGeometry(Brush brush, Pen pen, Geometry geometry); + void DrawGeometry(IBrush brush, Pen pen, Geometry geometry); /// /// Draws the outline of a rectangle. @@ -55,7 +55,7 @@ namespace Perspex.Media /// The foreground brush. /// The upper-left corner of the text. /// The text. - void DrawText(Brush foreground, Point origin, FormattedText text); + void DrawText(IBrush foreground, Point origin, FormattedText text); /// /// Draws a filled rectangle. @@ -63,7 +63,7 @@ namespace Perspex.Media /// The brush. /// The rectangle bounds. /// The corner radius. - void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0.0f); + void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f); /// /// Pushes a clip rectange. diff --git a/src/Perspex.SceneGraph/Media/ISolidColorBrush.cs b/src/Perspex.SceneGraph/Media/ISolidColorBrush.cs new file mode 100644 index 0000000000..56bd1b6f93 --- /dev/null +++ b/src/Perspex.SceneGraph/Media/ISolidColorBrush.cs @@ -0,0 +1,16 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +namespace Perspex.Media +{ + /// + /// Fills an area with a solid color. + /// + public interface ISolidColorBrush : IBrush + { + /// + /// Gets the color of the brush. + /// + Color Color { get; } + } +} \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Media/Mutable/SolidColorBrush.cs b/src/Perspex.SceneGraph/Media/Mutable/SolidColorBrush.cs index 73272e9ee6..928a9bd22c 100644 --- a/src/Perspex.SceneGraph/Media/Mutable/SolidColorBrush.cs +++ b/src/Perspex.SceneGraph/Media/Mutable/SolidColorBrush.cs @@ -10,7 +10,7 @@ namespace Perspex.Media.Mutable /// This is a mutable version of the normal immutable /// for use in XAML. XAML really needs support for immutable data... /// - public class SolidColorBrush : Brush + public class SolidColorBrush : Brush, ISolidColorBrush { public static readonly DirectProperty ColorProperty = PerspexProperty.RegisterDirect( diff --git a/src/Perspex.SceneGraph/Media/Pen.cs b/src/Perspex.SceneGraph/Media/Pen.cs index 489860a87a..d881ef53f4 100644 --- a/src/Perspex.SceneGraph/Media/Pen.cs +++ b/src/Perspex.SceneGraph/Media/Pen.cs @@ -20,7 +20,7 @@ namespace Perspex.Media /// The line join. /// The miter limit. public Pen( - Brush brush, + IBrush brush, double thickness = 1.0, DashStyle dashStyle = null, PenLineCap dashCap = PenLineCap.Flat, @@ -73,7 +73,7 @@ namespace Perspex.Media /// /// Gets the brush used to draw the stroke. /// - public Brush Brush { get; } + public IBrush Brush { get; } /// /// Gets the stroke thickness. diff --git a/src/Perspex.SceneGraph/Media/ScaleTransform.cs b/src/Perspex.SceneGraph/Media/ScaleTransform.cs new file mode 100644 index 0000000000..9e0f1ecb3a --- /dev/null +++ b/src/Perspex.SceneGraph/Media/ScaleTransform.cs @@ -0,0 +1,69 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + +namespace Perspex.Media +{ + /// + /// Scale an . + /// + public class ScaleTransform : Transform + { + /// + /// Defines the property. + /// + public static readonly StyledProperty ScaleXProperty = + PerspexProperty.Register(nameof(ScaleX), 1); + + /// + /// Defines the property. + /// + public static readonly StyledProperty ScaleYProperty = + PerspexProperty.Register(nameof(ScaleY), 1); + + /// + /// Initializes a new instance of the class. + /// + public ScaleTransform() + { + this.GetObservable(ScaleXProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(ScaleYProperty).Subscribe(_ => RaiseChanged()); + } + + /// + /// Initializes a new instance of the class. + /// + /// ScaleX + /// ScaleY + public ScaleTransform(double scaleX, double scaleY) + : this() + { + ScaleX = scaleX; + ScaleY = scaleY; + } + + /// + /// Gets or sets the ScaleX property. + /// + public double ScaleX + { + get { return GetValue(ScaleXProperty); } + set { SetValue(ScaleXProperty, value); } + } + + /// + /// Gets or sets the ScaleY property. + /// + public double ScaleY + { + get { return GetValue(ScaleYProperty); } + set { SetValue(ScaleYProperty, value); } + } + + /// + /// Gets the tranform's . + /// + public override Matrix Value => Matrix.CreateScale(ScaleX, ScaleY); + } +} \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Media/SolidColorBrush.cs b/src/Perspex.SceneGraph/Media/SolidColorBrush.cs index 48423de385..4be96eadd7 100644 --- a/src/Perspex.SceneGraph/Media/SolidColorBrush.cs +++ b/src/Perspex.SceneGraph/Media/SolidColorBrush.cs @@ -6,15 +6,17 @@ namespace Perspex.Media /// /// Fills an area with a solid color. /// - public class SolidColorBrush : Brush + public class SolidColorBrush : ISolidColorBrush { /// /// Initializes a new instance of the class. /// /// The color to use. - public SolidColorBrush(Color color) + /// The opacity of the brush. + public SolidColorBrush(Color color, double opacity = 1) { Color = color; + Opacity = opacity; } /// @@ -29,10 +31,12 @@ namespace Perspex.Media /// /// Gets the color of the brush. /// - public Color Color - { - get; - } + public Color Color { get; } + + /// + /// Gets the opacity of the brush. + /// + public double Opacity { get; } /// /// Returns a string representation of the brush. diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index 17ee3e98db..ea84ae710e 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -72,6 +72,8 @@ + + @@ -80,6 +82,7 @@ + @@ -139,10 +142,6 @@ - - ..\..\packages\Serilog.1.5.14\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+MonoTouch10\Serilog.dll - True - ..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll diff --git a/src/Perspex.SceneGraph/Platform/IFormattedTextImpl.cs b/src/Perspex.SceneGraph/Platform/IFormattedTextImpl.cs index 85a674c330..306e710f3f 100644 --- a/src/Perspex.SceneGraph/Platform/IFormattedTextImpl.cs +++ b/src/Perspex.SceneGraph/Platform/IFormattedTextImpl.cs @@ -61,6 +61,6 @@ namespace Perspex.Platform /// The brush. /// The start of the text range. /// The length of the text range. - void SetForegroundBrush(Brush brush, int startIndex, int length); + void SetForegroundBrush(IBrush brush, int startIndex, int length); } } diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index 63b7bf14f1..3dfe3412c8 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/src/Perspex.SceneGraph/Visual.cs @@ -2,19 +2,17 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Reactive.Linq; using Perspex.Animation; using Perspex.Collections; using Perspex.Data; +using Perspex.Logging; using Perspex.Media; using Perspex.Platform; using Perspex.Rendering; using Perspex.VisualTree; -using Serilog; -using Serilog.Core.Enrichers; namespace Perspex { @@ -79,7 +77,6 @@ namespace Perspex private Rect _bounds; private IVisual _visualParent; - private readonly ILogger _visualLogger; /// /// Initializes static members of the class. @@ -95,13 +92,6 @@ namespace Perspex /// public Visual() { - _visualLogger = Log.ForContext(new[] - { - new PropertyEnricher("Area", "Visual"), - new PropertyEnricher("SourceContext", GetType()), - new PropertyEnricher("Id", GetHashCode()), - }); - var visualChildren = new PerspexList(); visualChildren.ResetBehavior = ResetBehavior.Remove; visualChildren.Validate = ValidateLogicalChild; @@ -231,7 +221,7 @@ namespace Perspex /// /// Gets the root of the visual tree, if the control is attached to a visual tree. /// - IRenderRoot IVisual.VisualRoot { get; } + IRenderRoot IVisual.VisualRoot => VisualRoot; /// /// Invalidates the visual and queues a repaint. @@ -292,38 +282,74 @@ namespace Perspex } /// - /// Called when the control is added to a visual tree. + /// Calls the method + /// for this control and all of its visual descendents. /// /// The event args. - /// - /// It is vital that if you override this method you call the base implementation; - /// failing to do so will cause numerous features to not work as expected. - /// - protected virtual void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) { + Logger.Verbose(LogArea.Visual, this, "Attached to visual tree"); + + VisualRoot = e.Root; + if (RenderTransform != null) { RenderTransform.Changed += RenderTransformChanged; } - AttachedToVisualTree?.Invoke(this, e); + OnAttachedToVisualTree(e); + + if (VisualChildren != null) + { + foreach (Visual child in VisualChildren.OfType()) + { + child.OnAttachedToVisualTreeCore(e); + } + } } /// - /// Called when the control is removed from a visual tree. + /// Calls the method + /// for this control and all of its visual descendents. /// /// The event args. - /// - /// It is vital that if you override this method you call the base implementation; - /// failing to do so will cause numerous features to not work as expected. - /// - protected virtual void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) { + Logger.Verbose(LogArea.Visual, this, "Detached from visual tree"); + + VisualRoot = null; + if (RenderTransform != null) { RenderTransform.Changed -= RenderTransformChanged; } + OnDetachedFromVisualTree(e); + + if (VisualChildren != null) + { + foreach (Visual child in VisualChildren.OfType()) + { + child.OnDetachedFromVisualTreeCore(e); + } + } + } + + /// + /// Called when the control is added to a visual tree. + /// + /// The event args. + protected virtual void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + AttachedToVisualTree?.Invoke(this, e); + } + + /// + /// Called when the control is removed from a visual tree. + /// + /// The event args. + protected virtual void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { DetachedFromVisualTree?.Invoke(this, e); } @@ -427,14 +453,14 @@ namespace Perspex if (VisualRoot != null) { var e = new VisualTreeAttachmentEventArgs(VisualRoot); - NotifyDetachedFromVisualTree(e); + OnDetachedFromVisualTreeCore(e); } if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true) { var root = this.GetVisualAncestors().OfType().FirstOrDefault(); var e = new VisualTreeAttachmentEventArgs(root); - NotifyAttachedToVisualTree(e); + OnAttachedToVisualTreeCore(e); } RaisePropertyChanged(VisualParentProperty, old, value, BindingPriority.LocalValue); @@ -466,47 +492,5 @@ namespace Perspex break; } } - - /// - /// Calls the method - /// for this control and all of its visual descendents. - /// - /// The event args. - private void NotifyAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - _visualLogger.Verbose("Attached to visual tree"); - - VisualRoot = e.Root; - OnAttachedToVisualTree(e); - - if (VisualChildren != null) - { - foreach (Visual child in VisualChildren.OfType()) - { - child.NotifyAttachedToVisualTree(e); - } - } - } - - /// - /// Calls the method - /// for this control and all of its visual descendents. - /// - /// The event args. - private void NotifyDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) - { - _visualLogger.Verbose("Detached from visual tree"); - - VisualRoot = null; - OnDetachedFromVisualTree(e); - - if (VisualChildren != null) - { - foreach (Visual child in VisualChildren.OfType()) - { - child.NotifyDetachedFromVisualTree(e); - } - } - } } } diff --git a/src/Perspex.SceneGraph/VisualExtensions.cs b/src/Perspex.SceneGraph/VisualExtensions.cs index cb6362b7af..3228fa4b81 100644 --- a/src/Perspex.SceneGraph/VisualExtensions.cs +++ b/src/Perspex.SceneGraph/VisualExtensions.cs @@ -13,8 +13,6 @@ using Perspex.Media; using Perspex.Platform; using Perspex.Rendering; using Perspex.VisualTree; -using Serilog; -using Serilog.Core.Enrichers; namespace Perspex { diff --git a/src/Perspex.SceneGraph/packages.config b/src/Perspex.SceneGraph/packages.config index c1c997f8a7..d5af0dcd81 100644 --- a/src/Perspex.SceneGraph/packages.config +++ b/src/Perspex.SceneGraph/packages.config @@ -5,5 +5,4 @@ - \ No newline at end of file diff --git a/src/Perspex.Styling/Styling/ISetter.cs b/src/Perspex.Styling/Styling/ISetter.cs index 105a8ec6ac..8e5db104de 100644 --- a/src/Perspex.Styling/Styling/ISetter.cs +++ b/src/Perspex.Styling/Styling/ISetter.cs @@ -16,6 +16,6 @@ namespace Perspex.Styling /// The style that is being applied. /// The control. /// An optional activator. - void Apply(IStyle style, IStyleable control, IObservable activator); + IDisposable Apply(IStyle style, IStyleable control, IObservable activator); } } \ No newline at end of file diff --git a/src/Perspex.Styling/Styling/IStyleable.cs b/src/Perspex.Styling/Styling/IStyleable.cs index 36b092ff1d..aa1e749dad 100644 --- a/src/Perspex.Styling/Styling/IStyleable.cs +++ b/src/Perspex.Styling/Styling/IStyleable.cs @@ -3,7 +3,6 @@ using System; using Perspex.Collections; -using System.Reactive; namespace Perspex.Styling { @@ -13,9 +12,9 @@ namespace Perspex.Styling public interface IStyleable : IPerspexObject, INamed { /// - /// Raised when the control's style should be removed. + /// Signalled when the control's style should be removed. /// - IObservable StyleDetach { get; } + IObservable StyleDetach { get; } /// /// Gets the list of classes for the control. diff --git a/src/Perspex.Styling/Styling/Setter.cs b/src/Perspex.Styling/Styling/Setter.cs index 3c4592fa81..23b5aa7d7e 100644 --- a/src/Perspex.Styling/Styling/Setter.cs +++ b/src/Perspex.Styling/Styling/Setter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive.Disposables; using System.Reactive.Subjects; using Perspex.Data; using Perspex.Metadata; @@ -63,7 +64,7 @@ namespace Perspex.Styling /// The style that is being applied. /// The control. /// An optional activator. - public void Apply(IStyle style, IStyleable control, IObservable activator) + public IDisposable Apply(IStyle style, IStyleable control, IObservable activator) { Contract.Requires(control != null); @@ -80,16 +81,12 @@ namespace Perspex.Styling { if (activator == null) { - control.SetValue(Property, Value, BindingPriority.Style); + return control.Bind(Property, ObservableEx.SingleValue(Value), BindingPriority.Style); } else { var activated = new ActivatedValue(activator, Value, description); - var instanced = new InstancedBinding( - activated, - BindingMode.OneWay, - BindingPriority.StyleTrigger); - BindingOperations.Apply(control, Property, instanced, null); + return control.Bind(Property, activated, BindingPriority.StyleTrigger); } } else @@ -99,9 +96,11 @@ namespace Perspex.Styling if (source != null) { var cloned = Clone(source, style, activator); - BindingOperations.Apply(control, Property, cloned, null); + return BindingOperations.Apply(control, Property, cloned, null); } } + + return Disposable.Empty; } private InstancedBinding Clone(InstancedBinding sourceInstance, IStyle style, IObservable activator) diff --git a/src/Perspex.Styling/Styling/Style.cs b/src/Perspex.Styling/Styling/Style.cs index 7f0cb00e4b..73e5c19467 100644 --- a/src/Perspex.Styling/Styling/Style.cs +++ b/src/Perspex.Styling/Styling/Style.cs @@ -13,7 +13,9 @@ namespace Perspex.Styling /// public class Style : IStyle { - private static readonly IObservable True = Observable.Never().StartWith(true); + private static Dictionary> _applied = + new Dictionary>(); + private Dictionary _resources; /// @@ -85,20 +87,23 @@ namespace Perspex.Styling if (match.ImmediateResult != false) { - var activator = (match.ObservableResult ?? True) - .TakeUntil(control.StyleDetach); + var subs = GetSubscriptions(control); foreach (var setter in Setters) { - setter.Apply(this, control, activator); + var sub = setter.Apply(this, control, match.ObservableResult); + subs.Add(sub); } } } else if (control == container) { + var subs = GetSubscriptions(control); + foreach (var setter in Setters) { - setter.Apply(this, control, null); + var sub = setter.Apply(this, control, null); + subs.Add(sub); } } } @@ -139,5 +144,36 @@ namespace Perspex.Styling return "Style"; } } + + private static List GetSubscriptions(IStyleable control) + { + List subscriptions; + + if (!_applied.TryGetValue(control, out subscriptions)) + { + subscriptions = new List(2); + subscriptions.Add(control.StyleDetach.Subscribe(ControlDetach)); + _applied.Add(control, subscriptions); + } + + return subscriptions; + } + + /// + /// Called when a control's is signalled to remove + /// all applied styles. + /// + /// The control. + private static void ControlDetach(IStyleable control) + { + var subscriptions = _applied[control]; + + foreach (var subscription in subscriptions) + { + subscription.Dispose(); + } + + _applied.Remove(control); + } } } diff --git a/src/Perspex.Styling/Styling/StyleActivator.cs b/src/Perspex.Styling/Styling/StyleActivator.cs index dd2601e0bb..05b97a11ec 100644 --- a/src/Perspex.Styling/Styling/StyleActivator.cs +++ b/src/Perspex.Styling/Styling/StyleActivator.cs @@ -17,18 +17,40 @@ namespace Perspex.Styling public static class StyleActivator { - public static IObservable And(IEnumerable> inputs) + public static IObservable And(IList> inputs) { - return inputs.CombineLatest() - .Select(values => values.All(x => x)) - .DistinctUntilChanged(); + if (inputs.Count == 0) + { + throw new ArgumentException("StyleActivator.And inputs may not be empty."); + } + else if (inputs.Count == 1) + { + return inputs[0]; + } + else + { + return inputs.CombineLatest() + .Select(values => values.All(x => x)) + .DistinctUntilChanged(); + } } - public static IObservable Or(IEnumerable> inputs) + public static IObservable Or(IList> inputs) { - return inputs.CombineLatest() + if (inputs.Count == 0) + { + throw new ArgumentException("StyleActivator.Or inputs may not be empty."); + } + else if (inputs.Count == 1) + { + return inputs[0]; + } + else + { + return inputs.CombineLatest() .Select(values => values.Any(x => x)) .DistinctUntilChanged(); + } } } } diff --git a/src/Perspex.Themes.Default/DefaultTheme.xaml b/src/Perspex.Themes.Default/DefaultTheme.xaml index b25c85bbd1..f536a523f3 100644 --- a/src/Perspex.Themes.Default/DefaultTheme.xaml +++ b/src/Perspex.Themes.Default/DefaultTheme.xaml @@ -8,6 +8,7 @@ + diff --git a/src/Perspex.Themes.Default/DropDownItem.xaml b/src/Perspex.Themes.Default/DropDownItem.xaml index 26bbe9959b..4900c961e4 100644 --- a/src/Perspex.Themes.Default/DropDownItem.xaml +++ b/src/Perspex.Themes.Default/DropDownItem.xaml @@ -1,6 +1,8 @@  \ No newline at end of file diff --git a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj index e954fd0419..c26bb74ef0 100644 --- a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj +++ b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj @@ -200,6 +200,11 @@ Designer + + + Designer + + + \ No newline at end of file diff --git a/src/Skia/Perspex.Skia.iOS.TestApp/Perspex.Skia.iOS.TestApp.csproj b/src/Skia/Perspex.Skia.iOS.TestApp/Perspex.Skia.iOS.TestApp.csproj index 8b107002c3..950a0e729a 100644 --- a/src/Skia/Perspex.Skia.iOS.TestApp/Perspex.Skia.iOS.TestApp.csproj +++ b/src/Skia/Perspex.Skia.iOS.TestApp/Perspex.Skia.iOS.TestApp.csproj @@ -108,10 +108,6 @@ {d211e587-d8bc-45b9-95a4-f297c8fa5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} Perspex.Base diff --git a/src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj b/src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj index a583ceb5b2..731d3d5528 100644 --- a/src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj +++ b/src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj @@ -56,10 +56,6 @@ {d211e587-d8bc-45b9-95a4-f297c8fa5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} Perspex.Base @@ -72,6 +68,10 @@ {62024b2d-53eb-4638-b26b-85eeaa54866e} Perspex.Input + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + {42472427-4774-4c81-8aff-9f27b8e31721} Perspex.Layout diff --git a/src/Skia/Perspex.Skia/DrawingContextImpl.cs b/src/Skia/Perspex.Skia/DrawingContextImpl.cs index 61f4bc4a4d..0139932160 100644 --- a/src/Skia/Perspex.Skia/DrawingContextImpl.cs +++ b/src/Skia/Perspex.Skia/DrawingContextImpl.cs @@ -34,7 +34,7 @@ namespace Perspex.Skia } static readonly NativeBrushContainer _dummy = new NativeBrushContainer(null); - public void DrawGeometry(Brush brush, Pen pen, Geometry geometry) + public void DrawGeometry(IBrush brush, Pen pen, Geometry geometry) { var impl = ((StreamGeometryImpl) geometry.PlatformImpl); var size = geometry.Bounds.Size; @@ -46,7 +46,7 @@ namespace Perspex.Skia } } - unsafe NativeBrushContainer CreateBrush(Brush brush, Size targetSize) + unsafe NativeBrushContainer CreateBrush(IBrush brush, Size targetSize) { var rv = NativeBrushPool.Instance.Get(); rv.Brush->Opacity = brush.Opacity; @@ -141,7 +141,7 @@ namespace Perspex.Skia } } - public void FillRectangle(Brush pbrush, Rect rect, float cornerRadius = 0) + public void FillRectangle(IBrush pbrush, Rect rect, float cornerRadius = 0) { using (var brush = CreateBrush(pbrush, rect.Size)) { @@ -150,7 +150,7 @@ namespace Perspex.Skia } } - public void DrawText(Brush foreground, Point origin, FormattedText text) + public void DrawText(IBrush foreground, Point origin, FormattedText text) { using (var br = CreateBrush(foreground, text.Measure())) MethodTable.Instance.DrawFormattedText(Handle, br.Brush, ((FormattedTextImpl) text.PlatformImpl).Handle, diff --git a/src/Skia/Perspex.Skia/FormattedTextImpl.cs b/src/Skia/Perspex.Skia/FormattedTextImpl.cs index 808935e99e..13888334ad 100644 --- a/src/Skia/Perspex.Skia/FormattedTextImpl.cs +++ b/src/Skia/Perspex.Skia/FormattedTextImpl.cs @@ -73,7 +73,7 @@ namespace Perspex.Skia return _size; } - public void SetForegroundBrush(Brush brush, int startIndex, int length) + public void SetForegroundBrush(IBrush brush, int startIndex, int length) { } diff --git a/src/Windows/Perspex.Direct2D1/Media/BrushWrapper.cs b/src/Windows/Perspex.Direct2D1/Media/BrushWrapper.cs index 0c8f8d1373..ed4e44a7aa 100644 --- a/src/Windows/Perspex.Direct2D1/Media/BrushWrapper.cs +++ b/src/Windows/Perspex.Direct2D1/Media/BrushWrapper.cs @@ -8,11 +8,11 @@ namespace Perspex.Direct2D1.Media { internal class BrushWrapper : ComObject { - public BrushWrapper(Brush brush) + public BrushWrapper(IBrush brush) { Brush = brush; } - public Brush Brush { get; private set; } + public IBrush Brush { get; private set; } } } diff --git a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs index 6031d4a8e9..196588b5ad 100644 --- a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs +++ b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs @@ -114,7 +114,7 @@ namespace Perspex.Direct2D1.Media /// The fill brush. /// The stroke pen. /// The geometry. - public void DrawGeometry(Perspex.Media.Brush brush, Pen pen, Perspex.Media.Geometry geometry) + public void DrawGeometry(IBrush brush, Pen pen, Perspex.Media.Geometry geometry) { if (brush != null) { @@ -181,7 +181,7 @@ namespace Perspex.Direct2D1.Media /// The foreground brush. /// The upper-left corner of the text. /// The text. - public void DrawText(Perspex.Media.Brush foreground, Point origin, FormattedText text) + public void DrawText(IBrush foreground, Point origin, FormattedText text) { if (!string.IsNullOrEmpty(text.Text)) { @@ -204,7 +204,7 @@ namespace Perspex.Direct2D1.Media /// The brush. /// The rectangle bounds. /// The corner radius. - public void FillRectangle(Perspex.Media.Brush brush, Rect rect, float cornerRadius) + public void FillRectangle(IBrush brush, Rect rect, float cornerRadius) { using (var b = CreateBrush(brush, rect.Size)) { @@ -291,10 +291,9 @@ namespace Perspex.Direct2D1.Media /// The perspex brush. /// The size of the brush's target area. /// The Direct2D brush wrapper. - public BrushImpl CreateBrush(Perspex.Media.Brush brush, Size destinationSize) + public BrushImpl CreateBrush(IBrush brush, Size destinationSize) { - var solidColorBrush = brush as Perspex.Media.SolidColorBrush; - var mutableSolidColorBrush = brush as Perspex.Media.Mutable.SolidColorBrush; + var solidColorBrush = brush as Perspex.Media.ISolidColorBrush; var linearGradientBrush = brush as Perspex.Media.LinearGradientBrush; var radialGradientBrush = brush as Perspex.Media.RadialGradientBrush; var imageBrush = brush as Perspex.Media.ImageBrush; @@ -304,10 +303,6 @@ namespace Perspex.Direct2D1.Media { return new SolidColorBrushImpl(solidColorBrush, _renderTarget); } - if (mutableSolidColorBrush != null) - { - return new SolidColorBrushImpl(mutableSolidColorBrush, _renderTarget); - } else if (linearGradientBrush != null) { return new LinearGradientBrushImpl(linearGradientBrush, _renderTarget, destinationSize); diff --git a/src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs index 22f8b94491..4daeb685fb 100644 --- a/src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs @@ -118,7 +118,7 @@ namespace Perspex.Direct2D1.Media return new Size(width, TextLayout.Metrics.Height); } - public void SetForegroundBrush(Brush brush, int startIndex, int count) + public void SetForegroundBrush(IBrush brush, int startIndex, int count) { TextLayout.SetDrawingEffect( new BrushWrapper(brush), diff --git a/src/Windows/Perspex.Direct2D1/Media/SolidColorBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/SolidColorBrushImpl.cs index 88d5413843..09012934b0 100644 --- a/src/Windows/Perspex.Direct2D1/Media/SolidColorBrushImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/SolidColorBrushImpl.cs @@ -1,24 +1,13 @@ // Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Perspex.Media; + namespace Perspex.Direct2D1.Media { public class SolidColorBrushImpl : BrushImpl { - public SolidColorBrushImpl(Perspex.Media.SolidColorBrush brush, SharpDX.Direct2D1.RenderTarget target) - { - PlatformBrush = new SharpDX.Direct2D1.SolidColorBrush( - target, - brush?.Color.ToDirect2D() ?? new SharpDX.Mathematics.Interop.RawColor4(), - new SharpDX.Direct2D1.BrushProperties - { - Opacity = brush != null ? (float)brush.Opacity : 1.0f, - Transform = target.Transform - } - ); - } - - public SolidColorBrushImpl(Perspex.Media.Mutable.SolidColorBrush brush, SharpDX.Direct2D1.RenderTarget target) + public SolidColorBrushImpl(ISolidColorBrush brush, SharpDX.Direct2D1.RenderTarget target) { PlatformBrush = new SharpDX.Direct2D1.SolidColorBrush( target, diff --git a/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs index ab803e473e..2818e68627 100644 --- a/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs @@ -1,13 +1,9 @@ // Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; -using Perspex.Layout; using Perspex.Media; -using SharpDX; -using SharpDX.Direct2D1; using Perspex.RenderHelpers; - +using SharpDX.Direct2D1; namespace Perspex.Direct2D1.Media { @@ -38,7 +34,6 @@ namespace Perspex.Direct2D1.Media } } - private static BrushProperties GetBrushProperties(TileBrush brush, Rect destinationRect) { var tileTransform = diff --git a/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj b/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj index c091a58c34..dc1e582bf1 100644 --- a/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj +++ b/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj @@ -107,6 +107,18 @@ {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Perspex.Base + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Perspex.Controls + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Perspex.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + {42472427-4774-4C81-8AFF-9F27B8E31721} Perspex.Layout @@ -115,6 +127,10 @@ {EB582467-6ABB-43A1-B052-E981BA910E3A} Perspex.SceneGraph + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + diff --git a/src/Windows/Perspex.Direct2D1/RenderTarget.cs b/src/Windows/Perspex.Direct2D1/RenderTarget.cs index c835fc03e6..153b3be221 100644 --- a/src/Windows/Perspex.Direct2D1/RenderTarget.cs +++ b/src/Windows/Perspex.Direct2D1/RenderTarget.cs @@ -2,10 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using Perspex.Direct2D1.Media; -using Perspex.Media; using Perspex.Platform; -using Perspex.Rendering; using Perspex.Win32.Interop; using SharpDX; using SharpDX.Direct2D1; @@ -18,14 +15,13 @@ namespace Perspex.Direct2D1 { private readonly IntPtr _hwnd; private Size2 _savedSize; + private Size2F _savedDpi; /// /// The render target. /// private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; - - /// /// Initializes a new instance of the class. /// @@ -53,13 +49,6 @@ namespace Perspex.Direct2D1 hwndProperties); } - Size2 GetWindowSize() - { - UnmanagedMethods.RECT rc; - UnmanagedMethods.GetClientRect(_hwnd, out rc); - return new Size2(rc.right - rc.left, rc.bottom - rc.top); - } - /// /// Initializes a new instance of the class. /// @@ -94,11 +83,21 @@ namespace Perspex.Direct2D1 public DrawingContext CreateDrawingContext() { var window = _renderTarget as WindowRenderTarget; + if (window != null) { var size = GetWindowSize(); + var dpi = GetWindowDpi(); + if (size != _savedSize) + { window.Resize(_savedSize = size); + } + + if (dpi != _savedDpi) + { + window.DotsPerInch = _savedDpi = dpi; + } } return new DrawingContext(new Media.DrawingContext(_renderTarget, DirectWriteFactory)); @@ -108,5 +107,35 @@ namespace Perspex.Direct2D1 { _renderTarget.Dispose(); } + + private Size2F GetWindowDpi() + { + if (UnmanagedMethods.ShCoreAvailable) + { + uint dpix, dpiy; + + var monitor = UnmanagedMethods.MonitorFromWindow( + _hwnd, + UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); + + if (UnmanagedMethods.GetDpiForMonitor( + monitor, + UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, + out dpix, + out dpiy) == 0) + { + return new Size2F(dpix, dpiy); + } + } + + return new Size2F(96, 96); + } + + private Size2 GetWindowSize() + { + UnmanagedMethods.RECT rc; + UnmanagedMethods.GetClientRect(_hwnd, out rc); + return new Size2(rc.right - rc.left, rc.bottom - rc.top); + } } } diff --git a/src/Windows/Perspex.Win32/Perspex.Win32.csproj b/src/Windows/Perspex.Win32/Perspex.Win32.csproj index 0cd573d7b8..458e1de004 100644 --- a/src/Windows/Perspex.Win32/Perspex.Win32.csproj +++ b/src/Windows/Perspex.Win32/Perspex.Win32.csproj @@ -88,10 +88,6 @@ {D211E587-D8BC-45B9-95A4-F297C8FA5200} Perspex.Animation - - {799A7BB5-3C2C-48B6-85A7-406A12C420DA} - Perspex.Application - {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Perspex.Base diff --git a/src/Windows/Perspex.Win32/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs index 6711663270..f07839c09d 100644 --- a/src/Windows/Perspex.Win32/WindowImpl.cs +++ b/src/Windows/Perspex.Win32/WindowImpl.cs @@ -80,13 +80,14 @@ namespace Perspex.Win32 { UnmanagedMethods.RECT rect; UnmanagedMethods.GetClientRect(_hwnd, out rect); - return new Size(rect.right, rect.bottom); + return new Size(rect.right, rect.bottom) / Scaling; } set { if (value != ClientSize) { + value *= Scaling; value += BorderThickness; UnmanagedMethods.SetWindowPos( @@ -119,10 +120,10 @@ namespace Perspex.Win32 { get { - return new Size( + return (new Size( UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXMAXTRACK), UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYMAXTRACK)) - - BorderThickness; + - BorderThickness) / Scaling; } } @@ -221,12 +222,13 @@ namespace Perspex.Win32 public void Invalidate(Rect rect) { + var f = Scaling; var r = new UnmanagedMethods.RECT { - left = (int)rect.X, - top = (int)rect.Y, - right = (int)rect.Right, - bottom = (int)rect.Bottom, + left = (int)(rect.X * f), + top = (int)(rect.Y * f), + right = (int)(rect.Right * f), + bottom = (int)(rect.Bottom * f), }; UnmanagedMethods.InvalidateRect(_hwnd, ref r, false); @@ -236,11 +238,12 @@ namespace Perspex.Win32 { var p = new UnmanagedMethods.POINT { X = (int)point.X, Y = (int)point.Y }; UnmanagedMethods.ScreenToClient(_hwnd, ref p); - return new Point(p.X, p.Y); + return new Point(p.X, p.Y) / Scaling; } public Point PointToScreen(Point point) { + point *= Scaling; var p = new UnmanagedMethods.POINT { X = (int)point.X, Y = (int)point.Y }; UnmanagedMethods.ClientToScreen(_hwnd, ref p); return new Point(p.X, p.Y); @@ -459,7 +462,7 @@ namespace Perspex.Win32 : msg == (int)UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown, - PointFromLParam(lParam), GetMouseModifiers(wParam)); + DipFromLParam(lParam), GetMouseModifiers(wParam)); break; case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP: @@ -474,7 +477,7 @@ namespace Perspex.Win32 : msg == (int) UnmanagedMethods.WindowsMessage.WM_RBUTTONUP ? RawMouseEventType.RightButtonUp : RawMouseEventType.MiddleButtonUp, - PointFromLParam(lParam), GetMouseModifiers(wParam)); + DipFromLParam(lParam), GetMouseModifiers(wParam)); break; case UnmanagedMethods.WindowsMessage.WM_MOUSEMOVE: @@ -496,7 +499,7 @@ namespace Perspex.Win32 timestamp, _owner, RawMouseEventType.Move, - PointFromLParam(lParam), GetMouseModifiers(wParam)); + DipFromLParam(lParam), GetMouseModifiers(wParam)); break; @@ -505,7 +508,7 @@ namespace Perspex.Win32 WindowsMouseDevice.Instance, timestamp, _owner, - ScreenToClient(PointFromLParam(lParam)), + ScreenToClient(DipFromLParam(lParam)), new Vector(0, ((int)wParam >> 16) / wheelDelta), GetMouseModifiers(wParam)); break; @@ -528,7 +531,8 @@ namespace Perspex.Win32 { UnmanagedMethods.RECT r; UnmanagedMethods.GetUpdateRect(_hwnd, out r, false); - Paint(new Rect(r.left, r.top, r.right - r.left, r.bottom - r.top)); + var f = Scaling; + Paint(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, (r.bottom - r.top) / f)); UnmanagedMethods.EndPaint(_hwnd, ref ps); } } @@ -539,7 +543,7 @@ namespace Perspex.Win32 if (Resized != null) { var clientSize = new Size((int)lParam & 0xffff, (int)lParam >> 16); - Resized(clientSize); + Resized(clientSize / Scaling); } return IntPtr.Zero; @@ -605,13 +609,14 @@ namespace Perspex.Win32 Handle = new PlatformHandle(_hwnd, PlatformConstants.WindowHandleType); - var monitor = UnmanagedMethods.MonitorFromWindow( - _hwnd, - UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); - if (UnmanagedMethods.ShCoreAvailable) { uint dpix, dpiy; + + var monitor = UnmanagedMethods.MonitorFromWindow( + _hwnd, + UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); + if (UnmanagedMethods.GetDpiForMonitor( monitor, UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, @@ -623,6 +628,11 @@ namespace Perspex.Win32 } } + private Point DipFromLParam(IntPtr lParam) + { + return new Point((short)((int)lParam & 0xffff), (short)((int)lParam >> 16)) / Scaling; + } + private Point PointFromLParam(IntPtr lParam) { return new Point((short)((int)lParam & 0xffff), (short)((int)lParam >> 16)); diff --git a/src/iOS/Perspex.iOS/Perspex.iOS.csproj b/src/iOS/Perspex.iOS/Perspex.iOS.csproj index 0d670ac5ed..b0ce948a3c 100644 --- a/src/iOS/Perspex.iOS/Perspex.iOS.csproj +++ b/src/iOS/Perspex.iOS/Perspex.iOS.csproj @@ -49,10 +49,6 @@ {d211e587-d8bc-45b9-95a4-f297c8fa5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} Perspex.Base diff --git a/src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj b/src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj index 42c0c2eccd..63a5fc4e96 100644 --- a/src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj +++ b/src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj @@ -139,10 +139,6 @@ {d211e587-d8bc-45b9-95a4-f297c8fa5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} Perspex.Base diff --git a/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj b/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj index 08903a4dc2..879022611c 100644 --- a/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj +++ b/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj @@ -39,23 +39,31 @@ 4 - - ..\..\packages\Serilog.1.5.14\lib\net45\Serilog.dll - True - - - ..\..\packages\Serilog.1.5.14\lib\net45\Serilog.FullNetFx.dll + + ..\..\packages\Rx-Testing.2.2.5\lib\net45\Microsoft.Reactive.Testing.dll True + - + ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + True - + ..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + True - + ..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + True + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + True + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + True ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs index 40f542ae76..caa36f38a8 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs @@ -4,6 +4,7 @@ using System; using System.Reactive.Linq; using System.Reactive.Subjects; +using Microsoft.Reactive.Testing; using Perspex.Data; using Xunit; @@ -81,6 +82,22 @@ namespace Perspex.Base.UnitTests Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } + [Fact] + public void Observable_Is_Unsubscribed_When_Subscription_Disposed() + { + var scheduler = new TestScheduler(); + var source = scheduler.CreateColdObservable(); + var target = new Class1(); + + var subscription = target.Bind(Class1.FooProperty, source); + Assert.Equal(1, source.Subscriptions.Count); + Assert.Equal(Subscription.Infinite, source.Subscriptions[0].Unsubscribe); + + subscription.Dispose(); + Assert.Equal(1, source.Subscriptions.Count); + Assert.Equal(0, source.Subscriptions[0].Unsubscribe); + } + [Fact] public void Two_Way_Separate_Binding_Works() { diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs index 6c5b43888f..ebf0484ba8 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs @@ -333,6 +333,50 @@ namespace Perspex.Base.UnitTests Assert.Equal("second", target.Foo); } + [Fact] + public void Binding_To_Direct_Property_Does_Not_Get_Collected() + { + var target = new Class2(); + + Func setupBinding = () => + { + var source = new Subject(); + var sub = target.Bind((PerspexProperty)Class1.FooProperty, source); + return new WeakReference(source); + }; + + var weakSource = setupBinding(); + + GC.Collect(); + + Assert.True(weakSource.IsAlive); + } + + [Fact] + public void Binding_To_Direct_Property_Gets_Collected_When_Completed() + { + var target = new Class2(); + + Func setupBinding = () => + { + var source = new Subject(); + var sub = target.Bind((PerspexProperty)Class1.FooProperty, source); + return new WeakReference(source); + }; + + var weakSource = setupBinding(); + + Action completeSource = () => + { + ((ISubject)weakSource.Target).OnCompleted(); + }; + + completeSource(); + GC.Collect(); + + Assert.False(weakSource.IsAlive); + } + [Fact] public void Property_Notifies_Initialized() { diff --git a/tests/Perspex.Base.UnitTests/PriorityValueTests.cs b/tests/Perspex.Base.UnitTests/PriorityValueTests.cs index e4cfa408f7..5894ea12c3 100644 --- a/tests/Perspex.Base.UnitTests/PriorityValueTests.cs +++ b/tests/Perspex.Base.UnitTests/PriorityValueTests.cs @@ -14,7 +14,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Initial_Value_Should_Be_UnsetValue() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); Assert.Same(PerspexProperty.UnsetValue, target.Value); } @@ -22,7 +22,7 @@ namespace Perspex.Base.UnitTests [Fact] public void First_Binding_Sets_Value() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); target.Add(Single("foo"), 0); @@ -32,7 +32,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Changing_Binding_Should_Set_Value() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var subject = new BehaviorSubject("foo"); target.Add(subject, 0); @@ -44,7 +44,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Setting_Direct_Value_Should_Override_Binding() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); target.Add(Single("foo"), 0); target.SetValue("bar", 0); @@ -55,7 +55,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Binding_Firing_Should_Override_Direct_Value() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var source = new BehaviorSubject("initial"); target.Add(source, 0); @@ -69,7 +69,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Earlier_Binding_Firing_Should_Override_Later_Priority_0() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var nonActive = new BehaviorSubject("na"); var source = new BehaviorSubject("initial"); @@ -85,7 +85,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Earlier_Binding_Firing_Should_Not_Override_Later_Priority_1() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var nonActive = new BehaviorSubject("na"); var source = new BehaviorSubject("initial"); @@ -101,7 +101,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Binding_Completing_Should_Revert_To_Direct_Value() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var source = new BehaviorSubject("initial"); target.Add(source, 0); @@ -117,7 +117,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Binding_With_Lower_Priority_Has_Precedence() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); target.Add(Single("foo"), 1); target.Add(Single("bar"), 0); @@ -129,7 +129,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Later_Binding_With_Same_Priority_Should_Take_Precedence() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); target.Add(Single("foo"), 1); target.Add(Single("bar"), 0); @@ -142,7 +142,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Changing_Binding_With_Lower_Priority_Should_Set_Not_Value() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var subject = new BehaviorSubject("bar"); target.Add(Single("foo"), 0); @@ -155,7 +155,7 @@ namespace Perspex.Base.UnitTests [Fact] public void UnsetValue_Should_Fall_Back_To_Next_Binding() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var subject = new BehaviorSubject("bar"); target.Add(subject, 0); @@ -171,7 +171,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Adding_Value_Should_Call_OnNext() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); bool called = false; target.Changed.Subscribe(value => called = value.Item1 == PerspexProperty.UnsetValue && (string)value.Item2 == "foo"); @@ -183,7 +183,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Changing_Value_Should_Call_OnNext() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var subject = new BehaviorSubject("foo"); bool called = false; @@ -197,7 +197,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Disposing_A_Binding_Should_Revert_To_Next_Value() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); target.Add(Single("foo"), 0); var disposable = target.Add(Single("bar"), 0); @@ -210,7 +210,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Disposing_A_Binding_Should_Remove_BindingEntry() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); target.Add(Single("foo"), 0); var disposable = target.Add(Single("bar"), 0); @@ -223,7 +223,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Completing_A_Binding_Should_Revert_To_Previous_Binding() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var source = new BehaviorSubject("bar"); target.Add(Single("foo"), 0); @@ -237,7 +237,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Completing_A_Binding_Should_Revert_To_Lower_Priority() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var source = new BehaviorSubject("bar"); target.Add(Single("foo"), 1); @@ -251,7 +251,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Completing_A_Binding_Should_Remove_BindingEntry() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var subject = new BehaviorSubject("bar"); target.Add(Single("foo"), 0); @@ -265,7 +265,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Direct_Value_Should_Be_Coerced() { - var target = new PriorityValue("Test", typeof(int), x => Math.Min((int)x, 10)); + var target = new PriorityValue(null, "Test", typeof(int), x => Math.Min((int)x, 10)); target.SetValue(5, 0); Assert.Equal(5, target.Value); @@ -276,7 +276,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Bound_Value_Should_Be_Coerced() { - var target = new PriorityValue("Test", typeof(int), x => Math.Min((int)x, 10)); + var target = new PriorityValue(null, "Test", typeof(int), x => Math.Min((int)x, 10)); var source = new Subject(); target.Add(source, 0); @@ -290,7 +290,7 @@ namespace Perspex.Base.UnitTests public void Revalidate_Should_ReCoerce_Value() { var max = 10; - var target = new PriorityValue("Test", typeof(int), x => Math.Min((int)x, max)); + var target = new PriorityValue(null, "Test", typeof(int), x => Math.Min((int)x, max)); var source = new Subject(); target.Add(source, 0); diff --git a/tests/Perspex.Base.UnitTests/packages.config b/tests/Perspex.Base.UnitTests/packages.config index 3047aefeea..be674ecc80 100644 --- a/tests/Perspex.Base.UnitTests/packages.config +++ b/tests/Perspex.Base.UnitTests/packages.config @@ -3,7 +3,9 @@ - + + + diff --git a/tests/Perspex.Benchmarks/Perspex.Benchmarks.csproj b/tests/Perspex.Benchmarks/Perspex.Benchmarks.csproj index 253a26cfa1..7460e34af4 100644 --- a/tests/Perspex.Benchmarks/Perspex.Benchmarks.csproj +++ b/tests/Perspex.Benchmarks/Perspex.Benchmarks.csproj @@ -78,10 +78,6 @@ {d211e587-d8bc-45b9-95a4-f297c8fa5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} Perspex.Base diff --git a/tests/Perspex.Controls.UnitTests/ContentControlTests.cs b/tests/Perspex.Controls.UnitTests/ContentControlTests.cs index c3709ed4ff..9eed733da4 100644 --- a/tests/Perspex.Controls.UnitTests/ContentControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/ContentControlTests.cs @@ -98,6 +98,24 @@ namespace Perspex.Controls.UnitTests Assert.Equal(new[] { child }, target.GetLogicalChildren()); } + [Fact] + public void Control_Content_Should_Be_Logical_Child_After_ApplyTemplate() + { + var target = new ContentControl + { + Template = GetTemplate(), + }; + + var child = new Control(); + target.Content = child; + target.ApplyTemplate(); + ((ContentPresenter)target.Presenter).UpdateChild(); + + Assert.Equal(child.Parent, target); + Assert.Equal(child.GetLogicalParent(), target); + Assert.Equal(new[] { child }, target.GetLogicalChildren()); + } + [Fact] public void DataTemplate_Created_Control_Should_Be_Logical_Child_After_ApplyTemplate() { diff --git a/tests/Perspex.Controls.UnitTests/ControlTests.cs b/tests/Perspex.Controls.UnitTests/ControlTests.cs index 2d09da00d5..5ec83ce6f4 100644 --- a/tests/Perspex.Controls.UnitTests/ControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/ControlTests.cs @@ -230,6 +230,36 @@ namespace Perspex.Controls.UnitTests } } + [Fact] + public void EndInit_Should_Raise_Initialized() + { + var root = new TestRoot(); + var target = new Border(); + var called = false; + + target.Initialized += (s, e) => called = true; + ((ISupportInitialize)target).BeginInit(); + root.Child = target; + ((ISupportInitialize)target).EndInit(); + + Assert.True(called); + Assert.True(target.IsInitialized); + } + + [Fact] + public void Attaching_To_Visual_Tree_Should_Raise_Initialized() + { + var root = new TestRoot(); + var target = new Border(); + var called = false; + + target.Initialized += (s, e) => called = true; + root.Child = target; + + Assert.True(called); + Assert.True(target.IsInitialized); + } + private class TestControl : Control { public new PerspexObject InheritanceParent => base.InheritanceParent; diff --git a/tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs b/tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs new file mode 100644 index 0000000000..10c4caa381 --- /dev/null +++ b/tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs @@ -0,0 +1,245 @@ +using Perspex.Controls.Presenters; +using Perspex.Controls.Shapes; +using Perspex.Controls.Templates; +using Perspex.Media; +using Xunit; + +namespace Perspex.Controls.UnitTests +{ + public class LayoutTransformControlTests + { + [Fact] + public void Measure_On_Scale_x2_Is_Correct() + { + double scale = 2; + + TransformMeasureSizeTest( + new Size(100, 50), + new ScaleTransform() { ScaleX = scale, ScaleY = scale }, + new Size(200, 100)); + } + + [Fact] + public void Measure_On_Scale_x0_5_Is_Correct() + { + double scale = 0.5; + + TransformMeasureSizeTest( + new Size(100, 50), + new ScaleTransform() { ScaleX = scale, ScaleY = scale }, + new Size(50, 25)); + } + + [Fact] + public void Measure_On_Rotate_90_degrees_Is_Correct() + { + TransformMeasureSizeTest( + new Size(100, 25), + new RotateTransform() { Angle = 90 }, + new Size(25, 100)); + } + + [Fact] + public void Measure_On_Rotate_minus_90_degrees_Is_Correct() + { + TransformMeasureSizeTest( + new Size(100, 25), + new RotateTransform() { Angle = -90 }, + new Size(25, 100)); + } + + [Fact] + public void Measure_On_Rotate_0_degrees_Is_Correct() + { + TransformMeasureSizeTest( + new Size(100, 25), + new RotateTransform() { Angle = 0 }, + new Size(100, 25)); + } + + [Fact] + public void Measure_On_Rotate_180_degrees_Is_Correct() + { + TransformMeasureSizeTest( + new Size(100, 25), + new RotateTransform() { Angle = 180 }, + new Size(100, 25)); + } + + [Fact] + public void Bounds_On_Scale_x2_Are_correct() + { + double scale = 2; + + TransformRootBoundsTest( + new Size(100, 50), + new ScaleTransform() { ScaleX = scale, ScaleY = scale }, + new Rect(0, 0, 100, 50)); + } + + [Fact] + public void Bounds_On_Scale_x0_5_Are_correct() + { + double scale = 0.5; + + TransformRootBoundsTest( + new Size(100, 50), + new ScaleTransform() { ScaleX = scale, ScaleY = scale }, + new Rect(0, 0, 100, 50)); + } + + [Fact] + public void Bounds_On_Rotate_180_degrees_Are_correct() + { + TransformRootBoundsTest( + new Size(100, 25), + new RotateTransform() { Angle = 180 }, + new Rect(100, 25, 100, 25)); + } + + [Fact] + public void Bounds_On_Rotate_0_degrees_Are_correct() + { + TransformRootBoundsTest( + new Size(100, 25), + new RotateTransform() { Angle = 0 }, + new Rect(0, 0, 100, 25)); + } + + [Fact] + public void Bounds_On_Rotate_90_degrees_Are_correct() + { + TransformRootBoundsTest( + new Size(100, 25), + new RotateTransform() { Angle = 90 }, + new Rect(25, 0, 100, 25)); + } + + [Fact] + public void Bounds_On_Rotate_minus_90_degrees_Are_correct() + { + TransformRootBoundsTest( + new Size(100, 25), + new RotateTransform() { Angle = -90 }, + new Rect(0, 100, 100, 25)); + } + + [Fact] + public void Should_Generate_RenderTransform_90_degrees() + { + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( + 100, + 25, + new RotateTransform() { Angle = 90 }); + + Assert.NotNull(lt.TransformRoot.RenderTransform); + + Matrix m = lt.TransformRoot.RenderTransform.Value; + + Matrix res = Matrix.CreateRotation(Matrix.ToRadians(90)); + + Assert.Equal(m.M11, res.M11, 3); + Assert.Equal(m.M12, res.M12, 3); + Assert.Equal(m.M21, res.M21, 3); + Assert.Equal(m.M22, res.M22, 3); + Assert.Equal(m.M31, res.M31, 3); + Assert.Equal(m.M32, res.M32, 3); + } + + [Fact] + public void Should_Generate_RenderTransform_minus_90_degrees() + { + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( + 100, + 25, + new RotateTransform() { Angle = -90 }); + + Assert.NotNull(lt.TransformRoot.RenderTransform); + + var m = lt.TransformRoot.RenderTransform.Value; + + var res = Matrix.CreateRotation(Matrix.ToRadians(-90)); + + Assert.Equal(m.M11, res.M11, 3); + Assert.Equal(m.M12, res.M12, 3); + Assert.Equal(m.M21, res.M21, 3); + Assert.Equal(m.M22, res.M22, 3); + Assert.Equal(m.M31, res.M31, 3); + Assert.Equal(m.M32, res.M32, 3); + } + + [Fact] + public void Should_Generate_ScaleTransform_x2() + { + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( + 100, + 50, + new ScaleTransform() { ScaleX = 2, ScaleY = 2 }); + + Assert.NotNull(lt.TransformRoot.RenderTransform); + + Matrix m = lt.TransformRoot.RenderTransform.Value; + Matrix res = Matrix.CreateScale(2, 2); + + Assert.Equal(m.M11, res.M11, 3); + Assert.Equal(m.M12, res.M12, 3); + Assert.Equal(m.M21, res.M21, 3); + Assert.Equal(m.M22, res.M22, 3); + Assert.Equal(m.M31, res.M31, 3); + Assert.Equal(m.M32, res.M32, 3); + } + + private static void TransformMeasureSizeTest(Size size, Transform transform, Size expectedSize) + { + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( + size.Width, + size.Height, + transform); + + Size outSize = lt.DesiredSize; + + Assert.Equal(outSize.Width, expectedSize.Width); + Assert.Equal(outSize.Height, expectedSize.Height); + } + + private static void TransformRootBoundsTest(Size size, Transform transform, Rect expectedBounds) + { + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(size.Width, size.Height, transform); + + Rect outBounds = lt.TransformRoot.Bounds; + + Assert.Equal(outBounds.X, expectedBounds.X); + Assert.Equal(outBounds.Y, expectedBounds.Y); + Assert.Equal(outBounds.Width, expectedBounds.Width); + Assert.Equal(outBounds.Height, expectedBounds.Height); + } + + private static LayoutTransformControl CreateWithChildAndMeasureAndTransform( + double width, + double height, + Transform transform) + { + var lt = new LayoutTransformControl() + { + LayoutTransform = transform, + Template = new FuncControlTemplate( + p => new ContentPresenter() { Content = p.Content }) + }; + + lt.Content = new Rectangle() { Width = width, Height = height }; + + lt.ApplyTemplate(); + + //we need to force create visual child + //so the measure after is correct + (lt.Presenter as ContentPresenter).UpdateChild(); + + Assert.NotNull(lt.Presenter?.Child); + + lt.Measure(Size.Infinity); + lt.Arrange(new Rect(lt.DesiredSize)); + + return lt; + } + } +} \ No newline at end of file diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index 57a6b16868..932af42724 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -90,6 +90,7 @@ + @@ -147,14 +148,14 @@ {3e53a01a-b331-47f3-b828-4a5717e77a24} Perspex.Markup.Xaml + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + {D211E587-D8BC-45B9-95A4-F297C8FA5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Perspex.Base diff --git a/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests_IScrollable.cs b/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests_IScrollable.cs index 22f024f2a3..d4e60ce94d 100644 --- a/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests_IScrollable.cs +++ b/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests_IScrollable.cs @@ -208,6 +208,22 @@ namespace Perspex.Controls.UnitTests } } + public Size ScrollSize + { + get + { + return new Size(double.PositiveInfinity, 1); + } + } + + public Size PageScrollSize + { + get + { + return new Size(double.PositiveInfinity, Viewport.Height); + } + } + protected override Size MeasureOverride(Size availableSize) { AvailableSize = availableSize; diff --git a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs index d121ad06ff..edb1ac1977 100644 --- a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs @@ -251,7 +251,7 @@ namespace Perspex.Controls.UnitTests Key.A, InputModifiers.None); impl.Object.Input(input); - inputManagerMock.Verify(x => x.Process(input)); + inputManagerMock.Verify(x => x.ProcessInput(input)); } } diff --git a/tests/Perspex.Controls.UnitTests/TreeViewTests.cs b/tests/Perspex.Controls.UnitTests/TreeViewTests.cs index fdc86e4a2b..d4e44a44b3 100644 --- a/tests/Perspex.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Perspex.Controls.UnitTests/TreeViewTests.cs @@ -1,13 +1,16 @@ // Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using System.Collections.Generic; using System.Linq; using Perspex.Collections; using Perspex.Controls.Presenters; using Perspex.Controls.Templates; +using Perspex.Data; using Perspex.Input; using Perspex.LogicalTree; +using Perspex.Markup.Data; using Perspex.UnitTests; using Xunit; @@ -252,6 +255,39 @@ namespace Perspex.Controls.UnitTests Assert.NotNull(NameScope.GetNameScope((TreeViewItem)item)); } + [Fact] + public void Should_React_To_Children_Changing() + { + var data = CreateTestTreeData(); + + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = data, + DataTemplates = CreateNodeDataTemplate(), + }; + + ApplyTemplates(target); + + Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); + Assert.Equal(new[] { "Child1", "Child2" }, ExtractItemHeader(target, 1)); + Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2)); + + // Make sure that the binding to Node.Children does not get collected. + GC.Collect(); + + data[0].Children = new PerspexList + { + new Node + { + Value = "NewChild1", + } + }; + + Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); + Assert.Equal(new[] { "NewChild1" }, ExtractItemHeader(target, 1)); + } + private void ApplyTemplates(TreeView tree) { tree.ApplyTemplate(); @@ -303,9 +339,7 @@ namespace Perspex.Controls.UnitTests { return new DataTemplates { - new FuncTreeDataTemplate( - x => new TextBlock { Text = x.Value }, - x => x.Children), + new TestTreeDataTemplate() }; } @@ -360,10 +394,45 @@ namespace Perspex.Controls.UnitTests } } - private class Node + private class Node : NotifyingBase { + private IPerspexList _children; + public string Value { get; set; } - public IPerspexList Children { get; set; } + + public IPerspexList Children + { + get + { + return _children; + } + + set + { + _children = value; + RaisePropertyChanged(nameof(Children)); + } + } + } + + private class TestTreeDataTemplate : ITreeDataTemplate + { + public IControl Build(object param) + { + var node = (Node)param; + return new TextBlock { Text = node.Value }; + } + + public InstancedBinding ItemsSelector(object item) + { + var obs = new ExpressionObserver(item, nameof(Node.Children)); + return new InstancedBinding(obs); + } + + public bool Match(object data) + { + return data is Node; + } } } } diff --git a/tests/Perspex.Layout.UnitTests/ArrangeTests.cs b/tests/Perspex.Layout.UnitTests/ArrangeTests.cs index 88e52c1303..e6f11b8c46 100644 --- a/tests/Perspex.Layout.UnitTests/ArrangeTests.cs +++ b/tests/Perspex.Layout.UnitTests/ArrangeTests.cs @@ -8,6 +8,73 @@ namespace Perspex.Layout.UnitTests { public class ArrangeTests { + [Fact] + public void Margin_Should_Be_Subtracted_From_Arrange_FinalSize() + { + var target = new TestControl + { + Width = 100, + Height = 100, + Margin = new Thickness(8), + }; + + target.Measure(Size.Infinity); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Size(100, 100), target.ArrangeFinalSize); + } + + [Fact] + public void ArrangeOverride_Receives_Desired_Size_When_Centered() + { + var target = new TestControl + { + MeasureResult = new Size(100, 100), + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(8), + }; + + target.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + target.Arrange(new Rect(0, 0, 200, 200)); + + Assert.Equal(new Size(100, 100), target.ArrangeFinalSize); + } + + [Fact] + public void ArrangeOverride_Receives_Available_Size_Minus_Margin_When_Stretched() + { + var target = new TestControl + { + MeasureResult = new Size(100, 100), + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Stretch, + Margin = new Thickness(8), + }; + + target.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + target.Arrange(new Rect(0, 0, 200, 200)); + + Assert.Equal(new Size(184, 184), target.ArrangeFinalSize); + } + + [Fact] + public void ArrangeOverride_Receives_Requested_Size_When_Arranged_To_DesiredSize() + { + var target = new TestControl + { + MeasureResult = new Size(100, 100), + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(8), + }; + + target.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Size(100, 100), target.ArrangeFinalSize); + } + [Fact] public void Arrange_With_IsMeasureValid_False_Calls_Measure() { @@ -32,14 +99,24 @@ namespace Perspex.Layout.UnitTests Assert.Equal(new Size(120, 120), target.MeasureConstraint); } - private class TestControl : Border + private class TestControl : Decorator { public Size MeasureConstraint { get; private set; } + public Size MeasureResult { get; set; } + + public Size ArrangeFinalSize { get; private set; } + protected override Size MeasureOverride(Size constraint) { MeasureConstraint = constraint; - return base.MeasureOverride(constraint); + return MeasureResult; + } + + protected override Size ArrangeOverride(Size finalSize) + { + ArrangeFinalSize = finalSize; + return base.ArrangeOverride(finalSize); } } } diff --git a/tests/Perspex.Layout.UnitTests/MeasureTests.cs b/tests/Perspex.Layout.UnitTests/MeasureTests.cs index bbc69e90a2..7e0f4b7ce5 100644 --- a/tests/Perspex.Layout.UnitTests/MeasureTests.cs +++ b/tests/Perspex.Layout.UnitTests/MeasureTests.cs @@ -8,6 +8,21 @@ namespace Perspex.Layout.UnitTests { public class MeasureTests { + [Fact] + public void Margin_Should_Be_Included_In_DesiredSize() + { + var decorator = new Decorator + { + Width = 100, + Height = 100, + Margin = new Thickness(8), + }; + + decorator.Measure(Size.Infinity); + + Assert.Equal(new Size(116, 116), decorator.DesiredSize); + } + [Fact] public void Invalidating_Child_Should_Not_Invalidate_Parent() { diff --git a/tests/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj b/tests/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj index 49506cb582..d48634c6ed 100644 --- a/tests/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj +++ b/tests/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj @@ -105,10 +105,6 @@ {D211E587-D8BC-45B9-95A4-F297C8FA5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Perspex.Base diff --git a/tests/Perspex.LeakTests/Perspex.LeakTests.csproj b/tests/Perspex.LeakTests/Perspex.LeakTests.csproj index 7330f47fbe..e28ec17f3c 100644 --- a/tests/Perspex.LeakTests/Perspex.LeakTests.csproj +++ b/tests/Perspex.LeakTests/Perspex.LeakTests.csproj @@ -111,10 +111,6 @@ {d211e587-d8bc-45b9-95a4-f297c8fa5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} Perspex.Base diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_AttachedProperty.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_AttachedProperty.cs new file mode 100644 index 0000000000..b3aa287269 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_AttachedProperty.cs @@ -0,0 +1,147 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using Perspex.Diagnostics; +using Perspex.Markup.Data; +using Xunit; + +namespace Perspex.Markup.UnitTests.Data +{ + public class ExpressionObserverTests_AttachedProperty + { + public ExpressionObserverTests_AttachedProperty() + { + var foo = Owner.FooProperty; + } + + [Fact] + public async void Should_Get_Attached_Property_Value() + { + var data = new Class1(); + var target = new ExpressionObserver(data, "(Owner.Foo)"); + var result = await target.Take(1); + + Assert.Equal("foo", result); + + Assert.Null(((IPerspexObjectDebug)data).GetPropertyChangedSubscribers()); + } + + [Fact] + public async void Should_Get_Chained_Attached_Property_Value() + { + var data = new Class1 + { + Next = new Class1 + { + [Owner.FooProperty] = "bar", + } + }; + + var target = new ExpressionObserver(data, "Next.(Owner.Foo)"); + var result = await target.Take(1); + + Assert.Equal("bar", result); + + Assert.Null(((IPerspexObjectDebug)data).GetPropertyChangedSubscribers()); + } + + [Fact] + public void Should_Track_Simple_Attached_Value() + { + var data = new Class1(); + var target = new ExpressionObserver(data, "(Owner.Foo)"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x)); + data.SetValue(Owner.FooProperty, "bar"); + + Assert.Equal(new[] { "foo", "bar" }, result); + + sub.Dispose(); + + Assert.Null(((IPerspexObjectDebug)data).GetPropertyChangedSubscribers()); + } + + [Fact] + public void Should_Track_Chained_Attached_Value() + { + var data = new Class1 + { + Next = new Class1 + { + [Owner.FooProperty] = "foo", + } + }; + + var target = new ExpressionObserver(data, "Next.(Owner.Foo)"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x)); + data.Next.SetValue(Owner.FooProperty, "bar"); + + Assert.Equal(new[] { "foo", "bar" }, result); + + sub.Dispose(); + + Assert.Null(((IPerspexObjectDebug)data).GetPropertyChangedSubscribers()); + } + + [Fact] + public void Should_Not_Keep_Source_Alive() + { + Func> run = () => + { + var source = new Class1(); + var target = new ExpressionObserver(source, "(Owner.Foo)"); + return Tuple.Create(target, new WeakReference(source)); + }; + + var result = run(); + result.Item1.Subscribe(x => { }); + + GC.Collect(); + + Assert.Null(result.Item2.Target); + } + + [Fact] + public void Should_Fail_With_Attached_Property_With_Only_1_Part() + { + var data = new Class1(); + + Assert.Throws(() => new ExpressionObserver(data, "(Owner)")); + } + + [Fact] + public void Should_Fail_With_Attached_Property_With_More_Than_2_Parts() + { + var data = new Class1(); + + Assert.Throws(() => new ExpressionObserver(data, "(Owner.Foo.Bar)")); + } + + private static class Owner + { + public static readonly AttachedProperty FooProperty = + PerspexProperty.RegisterAttached( + "Foo", + typeof(Owner), + defaultValue: "foo"); + } + + private class Class1 : PerspexObject + { + public static readonly StyledProperty NextProperty = + PerspexProperty.Register("Next"); + + public Class1 Next + { + get { return GetValue(NextProperty); } + set { SetValue(NextProperty, value); } + } + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs index 3ae9bb5ef5..35cd4a9917 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs @@ -8,6 +8,7 @@ using System.Reactive.Linq; using Perspex.Collections; using Perspex.Diagnostics; using Perspex.Markup.Data; +using Perspex.UnitTests; using Xunit; namespace Perspex.Markup.UnitTests.Data diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs index bf8803912e..f11c8f25e8 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Reactive.Linq; using System.Reactive.Subjects; using Perspex.Markup.Data; +using Perspex.UnitTests; using Xunit; namespace Perspex.Markup.UnitTests.Data diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs index ee3ef3df78..8586f89ab6 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs @@ -8,6 +8,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using Microsoft.Reactive.Testing; using Perspex.Markup.Data; +using Perspex.UnitTests; using Xunit; namespace Perspex.Markup.UnitTests.Data @@ -385,11 +386,6 @@ namespace Perspex.Markup.UnitTests.Data RaisePropertyChanged(nameof(Next)); } } - - public void RaisePropertyChanged(string propertyName) - { - base.RaisePropertyChanged(propertyName); - } } private class Class2 : NotifyingBase, INext diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs index 166c11eb42..4281a7e590 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Reactive.Linq; using Perspex.Markup.Data; +using Perspex.UnitTests; using Xunit; namespace Perspex.Markup.UnitTests.Data diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs index b6c39ab28c..0ae2011ad4 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs @@ -7,6 +7,7 @@ using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using Perspex.Markup.Data; +using Perspex.UnitTests; using Xunit; namespace Perspex.Markup.UnitTests.Data diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index 185013373c..f32a584d1d 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -91,12 +91,12 @@ + - diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs index b25542de0c..a6314f14e1 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs @@ -97,7 +97,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Converters get { throw new NotImplementedException(); } } - IObservable IStyleable.StyleDetach { get; } + IObservable IStyleable.StyleDetach { get; } } private class AttachedOwner diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj index f9b43771f1..e52711ab46 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj +++ b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj @@ -126,10 +126,6 @@ {D211E587-D8BC-45B9-95A4-F297C8FA5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Perspex.Base @@ -178,6 +174,16 @@ + + + Designer + + + + + Designer + + diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Xaml/Style1.xaml b/tests/Perspex.Markup.Xaml.UnitTests/Xaml/Style1.xaml new file mode 100644 index 0000000000..f37af38aac --- /dev/null +++ b/tests/Perspex.Markup.Xaml.UnitTests/Xaml/Style1.xaml @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Xaml/Style2.xaml b/tests/Perspex.Markup.Xaml.UnitTests/Xaml/Style2.xaml new file mode 100644 index 0000000000..8ffb7088d7 --- /dev/null +++ b/tests/Perspex.Markup.Xaml.UnitTests/Xaml/Style2.xaml @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Xaml/StyleTests.cs index 8bfb9e8377..23ad100d05 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Xaml/StyleTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Xaml/StyleTests.cs @@ -198,5 +198,31 @@ namespace Perspex.Markup.Xaml.UnitTests.Xaml Assert.Equal(0xff506070, brush.Color.ToUint32()); } + + [Fact(Skip = "TODO: Issue #492")] + public void StyleResource_Can_Be_Found_Across_Xaml_Files() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + + +"; + + var loader = new PerspexXamlLoader(); + var window = (Window)loader.Load(xaml); + var border = window.FindControl("border"); + var borderBrush = (Perspex.Media.Mutable.SolidColorBrush)border.Background; + + Assert.NotNull(borderBrush); + Assert.Equal(0xffff0000, borderBrush.Color.ToUint32()); + } + } } } diff --git a/tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj b/tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj index e4b44699d4..d358a6ccb5 100644 --- a/tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj +++ b/tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj @@ -87,10 +87,6 @@ {d211e587-d8bc-45b9-95a4-f297c8fa5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} Perspex.Base diff --git a/tests/Perspex.RenderTests/app.config b/tests/Perspex.RenderTests/app.config index 6d348f8066..7940b2ce88 100644 --- a/tests/Perspex.RenderTests/app.config +++ b/tests/Perspex.RenderTests/app.config @@ -15,16 +15,16 @@ - - + + - - + + - - + + diff --git a/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs index 4921aeaa7b..ca31d1f86f 100644 --- a/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs +++ b/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Reactive.Linq; using System.Reactive.Subjects; +using Microsoft.Reactive.Testing; using Xunit; namespace Perspex.Styling.UnitTests @@ -38,5 +39,23 @@ namespace Perspex.Styling.UnitTests Assert.True(completed); } + + [Fact] + public void Should_Unsubscribe_From_Activator_When_All_Subscriptions_Disposed() + { + var scheduler = new TestScheduler(); + var activator1 = scheduler.CreateColdObservable(); + var activator2 = scheduler.CreateColdObservable(); + var activator = StyleActivator.And(new[] { activator1, activator2 }); + var target = new ActivatedValue(activator, 1, string.Empty); + + var subscription = target.Subscribe(_ => { }); + Assert.Equal(1, activator1.Subscriptions.Count); + Assert.Equal(Subscription.Infinite, activator1.Subscriptions[0].Unsubscribe); + + subscription.Dispose(); + Assert.Equal(1, activator1.Subscriptions.Count); + Assert.Equal(0, activator1.Subscriptions[0].Unsubscribe); + } } } diff --git a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj index d775308d8d..419d1c2064 100644 --- a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj +++ b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj @@ -49,17 +49,21 @@ True - + ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + True - + ..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + True - + ..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + True - + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + True ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs index 55eff92311..465610b045 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs @@ -105,7 +105,7 @@ namespace Perspex.Styling.UnitTests public ITemplatedControl TemplatedParent { get; } - IObservable IStyleable.StyleDetach { get; } + IObservable IStyleable.StyleDetach { get; } IPerspexReadOnlyList IStyleable.Classes => Classes; diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs index 07bb16ee14..286a5ec269 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs @@ -138,7 +138,7 @@ namespace Perspex.Styling.UnitTests IPerspexReadOnlyList IStyleable.Classes => Classes; - IObservable IStyleable.StyleDetach { get; } + IObservable IStyleable.StyleDetach { get; } public object GetValue(PerspexProperty property) { diff --git a/tests/Perspex.Styling.UnitTests/TestControlBase.cs b/tests/Perspex.Styling.UnitTests/TestControlBase.cs index 3a0f0e5434..d5bd85f1dd 100644 --- a/tests/Perspex.Styling.UnitTests/TestControlBase.cs +++ b/tests/Perspex.Styling.UnitTests/TestControlBase.cs @@ -35,7 +35,7 @@ namespace Perspex.Styling.UnitTests IPerspexReadOnlyList IStyleable.Classes => Classes; - IObservable IStyleable.StyleDetach { get; } + IObservable IStyleable.StyleDetach { get; } public object GetValue(PerspexProperty property) { diff --git a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs b/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs index 847a124066..3e16e8a18c 100644 --- a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs +++ b/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs @@ -35,7 +35,7 @@ namespace Perspex.Styling.UnitTests IPerspexReadOnlyList IStyleable.Classes => Classes; - IObservable IStyleable.StyleDetach { get; } + IObservable IStyleable.StyleDetach { get; } public object GetValue(PerspexProperty property) { diff --git a/tests/Perspex.Markup.UnitTests/Data/NotifyingBase.cs b/tests/Perspex.UnitTests/NotifyingBase.cs similarity index 90% rename from tests/Perspex.Markup.UnitTests/Data/NotifyingBase.cs rename to tests/Perspex.UnitTests/NotifyingBase.cs index 384c3ede02..0d2e1c968c 100644 --- a/tests/Perspex.Markup.UnitTests/Data/NotifyingBase.cs +++ b/tests/Perspex.UnitTests/NotifyingBase.cs @@ -4,7 +4,7 @@ using System.ComponentModel; using System.Linq; -namespace Perspex.Markup.UnitTests.Data +namespace Perspex.UnitTests { public class NotifyingBase : INotifyPropertyChanged { @@ -34,7 +34,7 @@ namespace Perspex.Markup.UnitTests.Data private set; } - protected void RaisePropertyChanged(string propertyName) + public void RaisePropertyChanged(string propertyName) { _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } diff --git a/tests/Perspex.UnitTests/Perspex.UnitTests.csproj b/tests/Perspex.UnitTests/Perspex.UnitTests.csproj index 675485d81b..d06a4e98da 100644 --- a/tests/Perspex.UnitTests/Perspex.UnitTests.csproj +++ b/tests/Perspex.UnitTests/Perspex.UnitTests.csproj @@ -62,6 +62,7 @@ + @@ -82,10 +83,6 @@ {d211e587-d8bc-45b9-95a4-f297c8fa5200} Perspex.Animation - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Perspex.Application - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} Perspex.Base