217 changed files with 4052 additions and 1147 deletions
@ -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` |
||||
@ -0,0 +1,13 @@ |
|||||
|
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} |
||||
|
|
||||
|
<footer> |
||||
|
<div class="grad-bottom"></div> |
||||
|
<div class="footer"> |
||||
|
<div class="container"> |
||||
|
<span class="pull-right"> |
||||
|
<a href="#top">Back to top</a> |
||||
|
</span> |
||||
|
<span>Copyright © 2016 The Perspex Project<br>Generated by <strong>DocFX</strong></span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</footer> |
||||
@ -0,0 +1,176 @@ |
|||||
|
# Perspex for WPF Developers |
||||
|
|
||||
|
Perspex is in general very similar to WPF, but you will find differences. Here |
||||
|
are the most common: |
||||
|
|
||||
|
## Styling |
||||
|
|
||||
|
The most obvious difference from other XAML frameworks is that Perspex uses a |
||||
|
[CSS-like styling system](../spec/styles.md). Styles aren't stored in a |
||||
|
`Resources` collection as in WPF, they are stored in a separate `Styles` |
||||
|
collection: |
||||
|
|
||||
|
<UserControl> |
||||
|
<UserControl.Styles> |
||||
|
<!-- Make TextBlocks with the h1 style class have a font size of 24 points --> |
||||
|
<Style Selector="TextBlock.h1"> |
||||
|
<Setter Property="FontSize" Value="24"/> |
||||
|
</Style> |
||||
|
</UserControl.Styles> |
||||
|
<TextBlock Classes="h1">Header</TextBlock> |
||||
|
<UserControl> |
||||
|
|
||||
|
## DataTemplates |
||||
|
|
||||
|
As styles aren't stored in `Resources`, neither are `DataTemplates` ([in fact |
||||
|
there is no `Resources` collection](#resources)). Instead, `DataTemplates` are |
||||
|
placed in a `DataTemplates` collection on each control (and on `Application`): |
||||
|
|
||||
|
<UserControl xmlns:viewmodels="clr-namespace:MyApp.ViewModels;assembly=MyApp"> |
||||
|
<UserControl.DataTemplates> |
||||
|
<DataTemplate DataType="viewmodels:FooViewModel"> |
||||
|
<Border Background="Red" CornerRadius="8"> |
||||
|
<TextBox Text="{Binding Name}"/> |
||||
|
</Border> |
||||
|
</DataTemplate> |
||||
|
</UserControl.Styles> |
||||
|
<!-- Assuming that DataContext.Foo is an object of type |
||||
|
MyApp.ViewModels.FooViewModel then a red border with a corner |
||||
|
radius of 8 containing a TextBox will be displayed here --> |
||||
|
<ContentControl Content="{Binding Foo}"/> |
||||
|
<UserControl> |
||||
|
|
||||
|
`ItemsControl`s don't currently have an `ItemTemplate` property: instead just |
||||
|
place the template for your items into the control's `DataTemplates`, e.g. |
||||
|
|
||||
|
<ListBox Items="ItemsSource"> |
||||
|
<ListBox.DataTemplates> |
||||
|
<DataTemplate> |
||||
|
<TextBlock Text="{Binding Caption}"/> |
||||
|
</DataTemplate> |
||||
|
</ListBox.DataTemplates> |
||||
|
</ListBox> |
||||
|
|
||||
|
Data templates in Perspex can also target interfaces and derived classes (which |
||||
|
cannot be done in WPF) and so the order of `DataTemplate`s can be important: |
||||
|
`DataTemplate`s within the same collection are evaluated in declaration order |
||||
|
so you need to place them from most-specific to least-specific as you would in |
||||
|
code. |
||||
|
|
||||
|
## HierachicalDataTemplate |
||||
|
|
||||
|
WPF's `HierarchicalDataTemplate` is called `TreeDataTemplate` in Perspex (as the |
||||
|
former is difficult to type!). The two are almost entirely equivalent except |
||||
|
that the `ItemTemplate` property is not present in Perspex. |
||||
|
|
||||
|
## UIElement, FrameworkElement and Control |
||||
|
|
||||
|
WPF's `UIElement` and `FrameworkElement` are non-templated control base classes, |
||||
|
which roughly equate to the Perspex `Control` class. WPF's `Control` class on |
||||
|
the other hand is a templated control - Perspex's equivalent of this is |
||||
|
`TemplatedControl`. |
||||
|
|
||||
|
So to recap: |
||||
|
|
||||
|
- `UIElement`: `Control` |
||||
|
- `FrameworkElement`: `Control` |
||||
|
- `Control`: `TemplatedControl` |
||||
|
|
||||
|
## DependencyProperty |
||||
|
|
||||
|
The Perspex equivalent of `DependencyProperty` is `StyledProperty`, however |
||||
|
Perspex [has a richer property system than WPF](../spec/defining-properties.md), |
||||
|
and includes `DirectProperty` for turning standard CLR properties into Perspex |
||||
|
properties. The common base class of `StyledProperty` and `DirectProperty` |
||||
|
is `PerspexProperty`. |
||||
|
|
||||
|
# Resources |
||||
|
|
||||
|
There is no `Resources` collection on controls in Perspex, however `Style`s |
||||
|
do have a `Resources` collection for style-related resources. These can be |
||||
|
referred to using the `{StyleResource}` markup extension both inside and outside |
||||
|
styles. |
||||
|
|
||||
|
For non-style-related resources, we suggest defining them in code and referring |
||||
|
to them in markup using the `{Static}` markup extension. There are [various |
||||
|
reasons](http://www.codemag.com/article/1501091) for this, but briefly: |
||||
|
|
||||
|
- Resources have to be parsed |
||||
|
- The tree has to be traversed to find them |
||||
|
- XAML doesn't handle immutable objects |
||||
|
- XAML syntax can be long-winded compared to C# |
||||
|
|
||||
|
## Grid |
||||
|
|
||||
|
Column and row definitions can be specified in Perspex using strings, avoiding |
||||
|
the clunky syntax in WPF: |
||||
|
|
||||
|
<Grid ColumnDefinitions="Auto,*,32" RowDefinitions="*,Auto"> |
||||
|
|
||||
|
A common use of `Grid` in WPF is to stack two controls on top of each other. |
||||
|
For this purpose in Perspex you can just use a `Panel` which is more lightweight |
||||
|
than `Grid`. |
||||
|
|
||||
|
We don't yet support `SharedSizeScope` in `Grid`. |
||||
|
|
||||
|
## ItemsControl |
||||
|
|
||||
|
In WPF, `ItemsControl` and derived classes such as `ListBox` have two separate |
||||
|
items properties: `Items` and `ItemsSource`. Perspex however just has a single |
||||
|
one: `Items`. |
||||
|
|
||||
|
## Tunnelling Events |
||||
|
|
||||
|
Perspex has tunnelling events (unlike UWP!) but they're not exposed via |
||||
|
separate `Preview` CLR event handlers. To subscribe to a tunnelling event you |
||||
|
must call `AddHandler` with `RoutingStrategies.Tunnel`: |
||||
|
|
||||
|
``` |
||||
|
target.AddHandler(InputElement.KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel); |
||||
|
|
||||
|
void OnPreviewKeyDown(object sender, KeyEventArgs e) |
||||
|
{ |
||||
|
// Handler code |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Class Handlers |
||||
|
|
||||
|
In WPF, class handlers for events can be added by calling |
||||
|
[EventManager.RegisterClassHandler](https://msdn.microsoft.com/en-us/library/ms597875.aspx). |
||||
|
An example of registering a class handler in WPF might be: |
||||
|
|
||||
|
static MyControl() |
||||
|
{ |
||||
|
EventManager.RegisterClassHandler(typeof(MyControl), MyEvent, HandleMyEvent)); |
||||
|
} |
||||
|
|
||||
|
private static void HandleMyEvent(object sender, RoutedEventArgs e) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
The equivalent of this in Perspex would be: |
||||
|
|
||||
|
static MyControl() |
||||
|
{ |
||||
|
MyEvent.AddClassHandler<MyControl>(x => x.HandleMyEvent); |
||||
|
} |
||||
|
|
||||
|
private void HandleMyEvent(object sender, RoutedEventArgs e) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
Notice that in WPF you have to add the class handler as a static method, whereas |
||||
|
in Perspex the class handler is not static: the notification is automatically |
||||
|
directed to the correct instance. |
||||
|
|
||||
|
## PropertyChangedCallback |
||||
|
|
||||
|
Listening to changes on DependencyProperties in WPF can be complex. When you |
||||
|
register a `DependencyProperty` you can supply a static `PropertyChangedCallback` |
||||
|
but if you want to listen to changes from elsewhere [things can get complicated |
||||
|
and error-prone](http://stackoverflow.com/questions/23682232). |
||||
|
|
||||
|
In Perspex, there is no `PropertyChangedCallback` at the time of registration, |
||||
|
instead a class listener is [added to the control's static constructor in much |
||||
|
the same way that event class listeners are added](../spec/working-with-properties.md#subscribing-to-a-property-on-any-object). |
||||
@ -1,6 +1,6 @@ |
|||||
<?xml version="1.0" encoding="utf-8" ?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<configuration> |
<configuration> |
||||
<startup> |
<startup> |
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" /> |
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> |
||||
</startup> |
</startup> |
||||
</configuration> |
</configuration> |
||||
|
|||||
@ -1,14 +1,14 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<configuration> |
<configuration> |
||||
<startup> |
<startup> |
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" /> |
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> |
||||
</startup> |
</startup> |
||||
<runtime> |
<runtime> |
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> |
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> |
||||
<dependentAssembly> |
<dependentAssembly> |
||||
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral" /> |
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral"/> |
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" /> |
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/> |
||||
</dependentAssembly> |
</dependentAssembly> |
||||
</assemblyBinding> |
</assemblyBinding> |
||||
</runtime> |
</runtime> |
||||
</configuration> |
</configuration> |
||||
|
|||||
|
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,32 @@ |
|||||
|
<UserControl xmlns="https://github.com/perspex"> |
||||
|
<StackPanel Orientation="Vertical" Gap="4"> |
||||
|
<TextBlock Classes="h1">Expander</TextBlock> |
||||
|
<TextBlock Classes="h2">Expands to show nested content</TextBlock> |
||||
|
|
||||
|
<StackPanel Orientation="Vertical" |
||||
|
Margin="0,16,0,0" |
||||
|
HorizontalAlignment="Center" |
||||
|
Gap="16"> |
||||
|
<Expander Header="Expand Up" ExpandDirection="Up"> |
||||
|
<StackPanel> |
||||
|
<TextBlock>Expanded content</TextBlock> |
||||
|
</StackPanel> |
||||
|
</Expander> |
||||
|
<Expander Header="Expand Down" ExpandDirection="Down"> |
||||
|
<StackPanel> |
||||
|
<TextBlock>Expanded content</TextBlock> |
||||
|
</StackPanel> |
||||
|
</Expander> |
||||
|
<Expander Header="Expand Left" ExpandDirection="Left"> |
||||
|
<StackPanel> |
||||
|
<TextBlock>Expanded content</TextBlock> |
||||
|
</StackPanel> |
||||
|
</Expander> |
||||
|
<Expander Header="Expand Right" ExpandDirection="Right"> |
||||
|
<StackPanel> |
||||
|
<TextBlock>Expanded content</TextBlock> |
||||
|
</StackPanel> |
||||
|
</Expander> |
||||
|
</StackPanel> |
||||
|
</StackPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Perspex.Controls; |
||||
|
using Perspex.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class ExpanderPage : UserControl |
||||
|
{ |
||||
|
public ExpanderPage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
PerspexXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
<UserControl xmlns="https://github.com/perspex"> |
||||
|
<StackPanel Orientation="Vertical" Gap="4"> |
||||
|
<TextBlock Classes="h1">Image</TextBlock> |
||||
|
<TextBlock Classes="h2">Displays an image</TextBlock> |
||||
|
|
||||
|
<StackPanel Orientation="Horizontal" |
||||
|
Margin="0,16,0,0" |
||||
|
HorizontalAlignment="Center" |
||||
|
Gap="16"> |
||||
|
<StackPanel Orientation="Vertical"> |
||||
|
<TextBlock>No Stretch</TextBlock> |
||||
|
<Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg" |
||||
|
Width="100" Height="200" |
||||
|
Stretch="None"/> |
||||
|
</StackPanel> |
||||
|
|
||||
|
<StackPanel Orientation="Vertical"> |
||||
|
<TextBlock>Fill</TextBlock> |
||||
|
<Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg" |
||||
|
Width="100" Height="200" |
||||
|
Stretch="Fill"/> |
||||
|
</StackPanel> |
||||
|
|
||||
|
<StackPanel Orientation="Vertical"> |
||||
|
<TextBlock>Uniform</TextBlock> |
||||
|
<Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg" |
||||
|
Width="100" Height="200" |
||||
|
Stretch="Uniform"/> |
||||
|
</StackPanel> |
||||
|
|
||||
|
<StackPanel Orientation="Vertical"> |
||||
|
<TextBlock>UniformToFill</TextBlock> |
||||
|
<Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg" |
||||
|
Width="100" Height="200" |
||||
|
Stretch="UniformToFill"/> |
||||
|
</StackPanel> |
||||
|
</StackPanel> |
||||
|
</StackPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Perspex.Controls; |
||||
|
using Perspex.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class ImagePage : UserControl |
||||
|
{ |
||||
|
public ImagePage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
PerspexXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
<UserControl xmlns="https://github.com/perspex" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||
|
<DockPanel> |
||||
|
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto" Margin="16" DockPanel.Dock="Top"> |
||||
|
<TextBlock Grid.Column="0" Grid.Row="0">Rotation</TextBlock> |
||||
|
<Slider Name="rotation" Maximum="360" Grid.Column="1" Grid.Row="0"/> |
||||
|
</Grid> |
||||
|
|
||||
|
<Grid ColumnDefinitions="24,Auto,24" |
||||
|
RowDefinitions="24,Auto,24" |
||||
|
HorizontalAlignment="Center" |
||||
|
VerticalAlignment="Center"> |
||||
|
<Border Background="{StyleResource ThemeAccentBrush}" Grid.Column="1" Grid.Row="0"/> |
||||
|
<Border Background="{StyleResource ThemeAccentBrush}" Grid.Column="0" Grid.Row="1"/> |
||||
|
<Border Background="{StyleResource ThemeAccentBrush}" Grid.Column="2" Grid.Row="1"/> |
||||
|
<Border Background="{StyleResource ThemeAccentBrush}" Grid.Column="1" Grid.Row="2"/> |
||||
|
|
||||
|
<LayoutTransformControl Name="layoutTransform" Grid.Column="1" Grid.Row="1"> |
||||
|
<LayoutTransformControl.LayoutTransform> |
||||
|
<RotateTransform Angle="{Binding #rotation.Value}"/> |
||||
|
</LayoutTransformControl.LayoutTransform> |
||||
|
<TextBlock>Layout Transform</TextBlock> |
||||
|
</LayoutTransformControl> |
||||
|
</Grid> |
||||
|
</DockPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Perspex.Controls; |
||||
|
using Perspex.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class LayoutTransformControlPage : UserControl |
||||
|
{ |
||||
|
public LayoutTransformControlPage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
PerspexXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
<UserControl xmlns="https://github.com/perspex"> |
||||
|
<StackPanel Orientation="Vertical" Gap="4"> |
||||
|
<TextBlock Classes="h1">Menu</TextBlock> |
||||
|
<TextBlock Classes="h2">A window menu</TextBlock> |
||||
|
|
||||
|
<StackPanel Orientation="Horizontal" |
||||
|
Margin="0,16,0,0" |
||||
|
HorizontalAlignment="Center" |
||||
|
Gap="16"> |
||||
|
<Menu> |
||||
|
<MenuItem Header="_First"> |
||||
|
<MenuItem Header="Standard _Menu Item"/> |
||||
|
<MenuItem Header="Menu with _Submenu"> |
||||
|
<MenuItem Header="Submenu _1"/> |
||||
|
<MenuItem Header="Submenu _2"/> |
||||
|
</MenuItem> |
||||
|
<MenuItem Header="Menu Item with _Icon"> |
||||
|
<MenuItem.Icon> |
||||
|
<Image Source="resm:ControlCatalog.Assets.github_icon.png"/> |
||||
|
</MenuItem.Icon> |
||||
|
</MenuItem> |
||||
|
<MenuItem Header="Menu Item with _Checkbox"> |
||||
|
<MenuItem.Icon> |
||||
|
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/> |
||||
|
</MenuItem.Icon> |
||||
|
</MenuItem> |
||||
|
</MenuItem> |
||||
|
<MenuItem Header="_Second"> |
||||
|
<MenuItem Header="Second _Menu Item"/> |
||||
|
</MenuItem> |
||||
|
</Menu> |
||||
|
</StackPanel> |
||||
|
</StackPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Perspex.Controls; |
||||
|
using Perspex.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class MenuPage : UserControl |
||||
|
{ |
||||
|
public MenuPage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
PerspexXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
<UserControl xmlns="https://github.com/perspex"> |
||||
|
<StackPanel Orientation="Vertical" Gap="4"> |
||||
|
<TextBlock Classes="h1">RadioButton</TextBlock> |
||||
|
<TextBlock Classes="h2">Allows the selection of a single option of many</TextBlock> |
||||
|
|
||||
|
<StackPanel Orientation="Vertical" |
||||
|
Margin="0,16,0,0" |
||||
|
HorizontalAlignment="Center" |
||||
|
Gap="16"> |
||||
|
<RadioButton IsChecked="True">Option 1</RadioButton> |
||||
|
<RadioButton>Option 2</RadioButton> |
||||
|
<RadioButton IsEnabled="False">Disabled</RadioButton> |
||||
|
</StackPanel> |
||||
|
</StackPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Perspex.Controls; |
||||
|
using Perspex.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class RadioButtonPage : UserControl |
||||
|
{ |
||||
|
public RadioButtonPage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
PerspexXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
<UserControl xmlns="https://github.com/perspex"> |
||||
|
<StackPanel Orientation="Vertical" Gap="4"> |
||||
|
<TextBlock Classes="h1">TextBox</TextBlock> |
||||
|
<TextBlock Classes="h2">A control into which the user can input text</TextBlock> |
||||
|
|
||||
|
<StackPanel Orientation="Horizontal" |
||||
|
Margin="0,16,0,0" |
||||
|
HorizontalAlignment="Center" |
||||
|
Gap="16"> |
||||
|
<StackPanel Orientation="Vertical" Gap="8"> |
||||
|
<TextBox Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit." Width="200" /> |
||||
|
<TextBox Width="200" Watermark="Watermark" /> |
||||
|
<TextBox Width="200" |
||||
|
Watermark="Floating Watermark" |
||||
|
UseFloatingWatermark="True" |
||||
|
Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/> |
||||
|
<TextBox Width="200" Text="Left aligned text" TextAlignment="Left" /> |
||||
|
<TextBox Width="200" Text="Center aligned text" TextAlignment="Center" /> |
||||
|
<TextBox Width="200" Text="Right aligned text" TextAlignment="Right" /> |
||||
|
</StackPanel> |
||||
|
|
||||
|
<StackPanel Orientation="Vertical" Gap="8"> |
||||
|
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Width="200" Height="125" |
||||
|
Text="Multiline TextBox with TextWrapping.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." /> |
||||
|
<TextBox AcceptsReturn="True" Width="200" Height="125" |
||||
|
Text="Multiline TextBox with no TextWrapping.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." /> |
||||
|
</StackPanel> |
||||
|
</StackPanel> |
||||
|
</StackPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Perspex.Controls; |
||||
|
using Perspex.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class TextBoxPage : UserControl |
||||
|
{ |
||||
|
public TextBoxPage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
PerspexXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
<UserControl xmlns="https://github.com/perspex"> |
||||
|
<StackPanel Orientation="Vertical" Gap="4"> |
||||
|
<TextBlock Classes="h1">ToolTip</TextBlock> |
||||
|
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock> |
||||
|
|
||||
|
<StackPanel Orientation="Horizontal" |
||||
|
Margin="0,16,0,0" |
||||
|
HorizontalAlignment="Center" |
||||
|
Gap="16"> |
||||
|
<Border Background="{StyleResource ThemeAccentBrush}" |
||||
|
Padding="48,48,48,48"> |
||||
|
<ToolTip.Tip> |
||||
|
<StackPanel> |
||||
|
<TextBlock Classes="h1">ToolTip</TextBlock> |
||||
|
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock> |
||||
|
</StackPanel> |
||||
|
</ToolTip.Tip> |
||||
|
<TextBlock>Hover Here</TextBlock> |
||||
|
</Border> |
||||
|
</StackPanel> |
||||
|
</StackPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Perspex.Controls; |
||||
|
using Perspex.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class ToolTipPage : UserControl |
||||
|
{ |
||||
|
public ToolTipPage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
PerspexXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,33 +0,0 @@ |
|||||
// Copyright (c) The Perspex Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Globalization; |
|
||||
using OmniXaml.TypeConversion; |
|
||||
using Perspex.Media; |
|
||||
|
|
||||
namespace Perspex.Markup.Xaml.Converters |
|
||||
{ |
|
||||
public class SolidColorBrushTypeConverter : ITypeConverter |
|
||||
{ |
|
||||
public bool CanConvertFrom(IValueContext context, Type sourceType) |
|
||||
{ |
|
||||
return sourceType == typeof(string); |
|
||||
} |
|
||||
|
|
||||
public bool CanConvertTo(IValueContext context, Type destinationType) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
public object ConvertFrom(IValueContext context, CultureInfo culture, object value) |
|
||||
{ |
|
||||
return Brush.Parse((string)value); |
|
||||
} |
|
||||
|
|
||||
public object ConvertTo(IValueContext context, CultureInfo culture, object value, Type destinationType) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) The Perspex Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
namespace Perspex.Logging |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines a sink for Perspex logging messages.
|
||||
|
/// </summary>
|
||||
|
public interface ILogSink |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Logs a new event.
|
||||
|
/// </summary>
|
||||
|
/// <param name="level">The log event level.</param>
|
||||
|
/// <param name="area">The area that the event originates.</param>
|
||||
|
/// <param name="source">The object from which the event originates.</param>
|
||||
|
/// <param name="messageTemplate">The message template.</param>
|
||||
|
/// <param name="propertyValues">The message property values.</param>
|
||||
|
void Log( |
||||
|
LogEventLevel level, |
||||
|
string area, |
||||
|
object source, |
||||
|
string messageTemplate, |
||||
|
params object[] propertyValues); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// Copyright (c) The Perspex Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
namespace Perspex.Logging |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Specifies the area in which a log event occurred.
|
||||
|
/// </summary>
|
||||
|
public static class LogArea |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The log event comes from the property system.
|
||||
|
/// </summary>
|
||||
|
public const string Property = "Property"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The log event comes from the binding system.
|
||||
|
/// </summary>
|
||||
|
public const string Binding = "Binding"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The log event comes from the visual system.
|
||||
|
/// </summary>
|
||||
|
public const string Visual = "Visual"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The log event comes from the layout system.
|
||||
|
/// </summary>
|
||||
|
public const string Layout = "Layout"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The log event comes from the control system.
|
||||
|
/// </summary>
|
||||
|
public const string Control = "Control"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
// Copyright (c) The Perspex Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
namespace Perspex.Logging |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Specifies the meaning and relative importance of a log event.
|
||||
|
/// </summary>
|
||||
|
public enum LogEventLevel |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Anything and everything you might want to know about a running block of code.
|
||||
|
/// </summary>
|
||||
|
Verbose, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Internal system events that aren't necessarily observable from the outside.
|
||||
|
/// </summary>
|
||||
|
Debug, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The lifeblood of operational intelligence - things happen.
|
||||
|
/// </summary>
|
||||
|
Information, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Service is degraded or endangered.
|
||||
|
/// </summary>
|
||||
|
Warning, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Functionality is unavailable, invariants are broken or data is lost.
|
||||
|
/// </summary>
|
||||
|
Error, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// If you have a pager, it goes off when one of these occurs.
|
||||
|
/// </summary>
|
||||
|
Fatal |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,139 @@ |
|||||
|
// Copyright (c) The Perspex Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace Perspex.Logging |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Logs perspex messages.
|
||||
|
/// </summary>
|
||||
|
public static class Logger |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets the application-defined sink that recieves the messages.
|
||||
|
/// </summary>
|
||||
|
public static ILogSink Sink { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Logs an event.
|
||||
|
/// </summary>
|
||||
|
/// <param name="level">The log event level.</param>
|
||||
|
/// <param name="area">The area that the event originates.</param>
|
||||
|
/// <param name="source">The object from which the event originates.</param>
|
||||
|
/// <param name="messageTemplate">The message template.</param>
|
||||
|
/// <param name="propertyValues">The message property values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Log( |
||||
|
LogEventLevel level, |
||||
|
string area, |
||||
|
object source, |
||||
|
string messageTemplate, |
||||
|
params object[] propertyValues) |
||||
|
{ |
||||
|
Sink?.Log(level, area, source, messageTemplate, propertyValues); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Logs an event with the <see cref="LogEventLevel.Verbose"/> level.
|
||||
|
/// </summary>
|
||||
|
/// <param name="area">The area that the event originates.</param>
|
||||
|
/// <param name="source">The object from which the event originates.</param>
|
||||
|
/// <param name="messageTemplate">The message template.</param>
|
||||
|
/// <param name="propertyValues">The message property values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Verbose( |
||||
|
string area, |
||||
|
object source, |
||||
|
string messageTemplate, |
||||
|
params object[] propertyValues) |
||||
|
{ |
||||
|
Log(LogEventLevel.Verbose, area, source, messageTemplate, propertyValues); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Logs an event with the <see cref="LogEventLevel.Debug"/> level.
|
||||
|
/// </summary>
|
||||
|
/// <param name="area">The area that the event originates.</param>
|
||||
|
/// <param name="source">The object from which the event originates.</param>
|
||||
|
/// <param name="messageTemplate">The message template.</param>
|
||||
|
/// <param name="propertyValues">The message property values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Debug( |
||||
|
string area, |
||||
|
object source, |
||||
|
string messageTemplate, |
||||
|
params object[] propertyValues) |
||||
|
{ |
||||
|
Log(LogEventLevel.Debug, area, source, messageTemplate, propertyValues); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Logs an event with the <see cref="LogEventLevel.Information"/> level.
|
||||
|
/// </summary>
|
||||
|
/// <param name="area">The area that the event originates.</param>
|
||||
|
/// <param name="source">The object from which the event originates.</param>
|
||||
|
/// <param name="messageTemplate">The message template.</param>
|
||||
|
/// <param name="propertyValues">The message property values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Information( |
||||
|
string area, |
||||
|
object source, |
||||
|
string messageTemplate, |
||||
|
params object[] propertyValues) |
||||
|
{ |
||||
|
Log(LogEventLevel.Information, area, source, messageTemplate, propertyValues); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Logs an event with the <see cref="LogEventLevel.Warning"/> level.
|
||||
|
/// </summary>
|
||||
|
/// <param name="area">The area that the event originates.</param>
|
||||
|
/// <param name="source">The object from which the event originates.</param>
|
||||
|
/// <param name="messageTemplate">The message template.</param>
|
||||
|
/// <param name="propertyValues">The message property values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Warning( |
||||
|
string area, |
||||
|
object source, |
||||
|
string messageTemplate, |
||||
|
params object[] propertyValues) |
||||
|
{ |
||||
|
Log(LogEventLevel.Warning, area, source, messageTemplate, propertyValues); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Logs an event with the <see cref="LogEventLevel.Error"/> level.
|
||||
|
/// </summary>
|
||||
|
/// <param name="area">The area that the event originates.</param>
|
||||
|
/// <param name="source">The object from which the event originates.</param>
|
||||
|
/// <param name="messageTemplate">The message template.</param>
|
||||
|
/// <param name="propertyValues">The message property values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Error( |
||||
|
string area, |
||||
|
object source, |
||||
|
string messageTemplate, |
||||
|
params object[] propertyValues) |
||||
|
{ |
||||
|
Log(LogEventLevel.Error, area, source, messageTemplate, propertyValues); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Logs an event with the <see cref="LogEventLevel.Fatal"/> level.
|
||||
|
/// </summary>
|
||||
|
/// <param name="area">The area that the event originates.</param>
|
||||
|
/// <param name="source">The object from which the event originates.</param>
|
||||
|
/// <param name="messageTemplate">The message template.</param>
|
||||
|
/// <param name="propertyValues">The message property values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Fatal( |
||||
|
string area, |
||||
|
object source, |
||||
|
string messageTemplate, |
||||
|
params object[] propertyValues) |
||||
|
{ |
||||
|
Log(LogEventLevel.Fatal, area, source, messageTemplate, propertyValues); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
// Copyright (c) The Perspex Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Reactive.Disposables; |
||||
|
|
||||
|
namespace Perspex.Reactive |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides common observable methods not found in standard Rx framework.
|
||||
|
/// </summary>
|
||||
|
public static class ObservableEx |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Returns an observable that fires once with the specified value and never completes.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
|
/// <param name="value">The value.</param>
|
||||
|
/// <returns>The observable.</returns>
|
||||
|
public static IObservable<T> SingleValue<T>(T value) |
||||
|
{ |
||||
|
return new SingleValueImpl<T>(value); |
||||
|
} |
||||
|
|
||||
|
private class SingleValueImpl<T> : IObservable<T> |
||||
|
{ |
||||
|
private T _value; |
||||
|
|
||||
|
public SingleValueImpl(T value) |
||||
|
{ |
||||
|
_value = value; |
||||
|
} |
||||
|
|
||||
|
public IDisposable Subscribe(IObserver<T> observer) |
||||
|
{ |
||||
|
observer.OnNext(_value); |
||||
|
return Disposable.Empty; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,11 +1,5 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
namespace Perspex.Controls |
||||
using System.Linq; |
|
||||
using System.Text; |
|
||||
using System.Threading.Tasks; |
|
||||
using Perspex.Controls; |
|
||||
|
|
||||
namespace Perspex |
|
||||
{ |
{ |
||||
public static class Design |
public static class Design |
||||
{ |
{ |
||||
@ -0,0 +1,389 @@ |
|||||
|
// Copyright (c) The Perspex Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
//
|
||||
|
// Idea got from and adapted to work in perspex
|
||||
|
// http://silverlight.codeplex.com/SourceControl/changeset/view/74775#Release/Silverlight4/Source/Controls.Layout.Toolkit/LayoutTransformer/LayoutTransformer.cs
|
||||
|
//
|
||||
|
|
||||
|
using Perspex.Controls.Primitives; |
||||
|
using Perspex.Media; |
||||
|
using Perspex.VisualTree; |
||||
|
using System; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Linq; |
||||
|
using System.Reactive.Linq; |
||||
|
|
||||
|
namespace Perspex.Controls |
||||
|
{ |
||||
|
public class LayoutTransformControl : ContentControl |
||||
|
{ |
||||
|
public static readonly PerspexProperty<Transform> LayoutTransformProperty = |
||||
|
PerspexProperty.Register<LayoutTransformControl, Transform>(nameof(LayoutTransform)); |
||||
|
|
||||
|
static LayoutTransformControl() |
||||
|
{ |
||||
|
LayoutTransformProperty.Changed |
||||
|
.AddClassHandler<LayoutTransformControl>(x => x.OnLayoutTransformChanged); |
||||
|
} |
||||
|
|
||||
|
public Transform LayoutTransform |
||||
|
{ |
||||
|
get { return GetValue(LayoutTransformProperty); } |
||||
|
set { SetValue(LayoutTransformProperty, value); } |
||||
|
} |
||||
|
|
||||
|
public Control TransformRoot => _transformRoot ?? |
||||
|
(_transformRoot = this.GetVisualChildren().OfType<Control>().FirstOrDefault()); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Provides the behavior for the "Arrange" pass of layout.
|
||||
|
/// </summary>
|
||||
|
/// <param name="finalSize">The final area within the parent that this element should use to arrange itself and its children.</param>
|
||||
|
/// <returns>The actual size used.</returns>
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
if (TransformRoot == null || LayoutTransform == null) |
||||
|
{ |
||||
|
return base.ArrangeOverride(finalSize); |
||||
|
} |
||||
|
|
||||
|
// Determine the largest available size after the transformation
|
||||
|
Size finalSizeTransformed = ComputeLargestTransformedSize(finalSize); |
||||
|
if (IsSizeSmaller(finalSizeTransformed, TransformRoot.DesiredSize)) |
||||
|
{ |
||||
|
// Some elements do not like being given less space than they asked for (ex: TextBlock)
|
||||
|
// Bump the working size up to do the right thing by them
|
||||
|
finalSizeTransformed = TransformRoot.DesiredSize; |
||||
|
} |
||||
|
|
||||
|
// Transform the working size to find its width/height
|
||||
|
Rect transformedRect = new Rect(0, 0, finalSizeTransformed.Width, finalSizeTransformed.Height).TransformToAABB(_transformation); |
||||
|
// Create the Arrange rect to center the transformed content
|
||||
|
Rect finalRect = new Rect( |
||||
|
-transformedRect.X + ((finalSize.Width - transformedRect.Width) / 2), |
||||
|
-transformedRect.Y + ((finalSize.Height - transformedRect.Height) / 2), |
||||
|
finalSizeTransformed.Width, |
||||
|
finalSizeTransformed.Height); |
||||
|
|
||||
|
// Perform an Arrange on TransformRoot (containing Child)
|
||||
|
Size arrangedsize; |
||||
|
TransformRoot.Arrange(finalRect); |
||||
|
arrangedsize = TransformRoot.Bounds.Size; |
||||
|
|
||||
|
// This is the first opportunity under Silverlight to find out the Child's true DesiredSize
|
||||
|
if (IsSizeSmaller(finalSizeTransformed, arrangedsize) && (Size.Empty == _childActualSize)) |
||||
|
{ |
||||
|
//// Unfortunately, all the work so far is invalid because the wrong DesiredSize was used
|
||||
|
//// Make a note of the actual DesiredSize
|
||||
|
//_childActualSize = arrangedsize;
|
||||
|
//// Force a new measure/arrange pass
|
||||
|
//InvalidateMeasure();
|
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Clear the "need to measure/arrange again" flag
|
||||
|
_childActualSize = Size.Empty; |
||||
|
} |
||||
|
|
||||
|
// Return result to perform the transformation
|
||||
|
return finalSize; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Provides the behavior for the "Measure" pass of layout.
|
||||
|
/// </summary>
|
||||
|
/// <param name="availableSize">The available size that this element can give to child elements.</param>
|
||||
|
/// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
|
||||
|
protected override Size MeasureOverride(Size availableSize) |
||||
|
{ |
||||
|
if (TransformRoot == null || LayoutTransform == null) |
||||
|
{ |
||||
|
return base.MeasureOverride(availableSize); |
||||
|
} |
||||
|
|
||||
|
Size measureSize; |
||||
|
if (_childActualSize == Size.Empty) |
||||
|
{ |
||||
|
// Determine the largest size after the transformation
|
||||
|
measureSize = ComputeLargestTransformedSize(availableSize); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Previous measure/arrange pass determined that Child.DesiredSize was larger than believed
|
||||
|
measureSize = _childActualSize; |
||||
|
} |
||||
|
|
||||
|
// Perform a measure on the TransformRoot (containing Child)
|
||||
|
TransformRoot.Measure(measureSize); |
||||
|
|
||||
|
var desiredSize = TransformRoot.DesiredSize; |
||||
|
|
||||
|
// Transform DesiredSize to find its width/height
|
||||
|
Rect transformedDesiredRect = new Rect(0, 0, desiredSize.Width, desiredSize.Height).TransformToAABB(_transformation); |
||||
|
Size transformedDesiredSize = new Size(transformedDesiredRect.Width, transformedDesiredRect.Height); |
||||
|
|
||||
|
// Return result to allocate enough space for the transformation
|
||||
|
return transformedDesiredSize; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Builds the visual tree for the LayoutTransformerControl when a new
|
||||
|
/// template is applied.
|
||||
|
/// </summary>
|
||||
|
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) |
||||
|
{ |
||||
|
base.OnTemplateApplied(e); |
||||
|
|
||||
|
_matrixTransform = new MatrixTransform(); |
||||
|
|
||||
|
if (null != TransformRoot) |
||||
|
{ |
||||
|
TransformRoot.RenderTransform = _matrixTransform; |
||||
|
TransformRoot.TransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute); |
||||
|
} |
||||
|
|
||||
|
ApplyLayoutTransform(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Acceptable difference between two doubles.
|
||||
|
/// </summary>
|
||||
|
private const double AcceptableDelta = 0.0001; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Number of decimals to round the Matrix to.
|
||||
|
/// </summary>
|
||||
|
private const int DecimalsAfterRound = 4; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method).
|
||||
|
/// </summary>
|
||||
|
private Size _childActualSize = Size.Empty; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// RenderTransform/MatrixTransform applied to TransformRoot.
|
||||
|
/// </summary>
|
||||
|
private MatrixTransform _matrixTransform; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Transformation matrix corresponding to _matrixTransform.
|
||||
|
/// </summary>
|
||||
|
private Matrix _transformation; |
||||
|
private IDisposable _transformChangedEvent = null; |
||||
|
private Control _transformRoot; |
||||
|
/// <summary>
|
||||
|
/// Returns true if Size a is smaller than Size b in either dimension.
|
||||
|
/// </summary>
|
||||
|
/// <param name="a">Second Size.</param>
|
||||
|
/// <param name="b">First Size.</param>
|
||||
|
/// <returns>True if Size a is smaller than Size b in either dimension.</returns>
|
||||
|
private static bool IsSizeSmaller(Size a, Size b) |
||||
|
{ |
||||
|
return (a.Width + AcceptableDelta < b.Width) || (a.Height + AcceptableDelta < b.Height); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Rounds the non-offset elements of a Matrix to avoid issues due to floating point imprecision.
|
||||
|
/// </summary>
|
||||
|
/// <param name="matrix">Matrix to round.</param>
|
||||
|
/// <param name="decimals">Number of decimal places to round to.</param>
|
||||
|
/// <returns>Rounded Matrix.</returns>
|
||||
|
private static Matrix RoundMatrix(Matrix matrix, int decimals) |
||||
|
{ |
||||
|
return new Matrix( |
||||
|
Math.Round(matrix.M11, decimals), |
||||
|
Math.Round(matrix.M12, decimals), |
||||
|
Math.Round(matrix.M21, decimals), |
||||
|
Math.Round(matrix.M22, decimals), |
||||
|
matrix.M31, |
||||
|
matrix.M32); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the layout transform on the LayoutTransformerControl content.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Only used in advanced scenarios (like animating the LayoutTransform).
|
||||
|
/// Should be used to notify the LayoutTransformer control that some aspect
|
||||
|
/// of its Transform property has changed.
|
||||
|
/// </remarks>
|
||||
|
private void ApplyLayoutTransform() |
||||
|
{ |
||||
|
if (LayoutTransform == null) return; |
||||
|
|
||||
|
// Get the transform matrix and apply it
|
||||
|
_transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound); |
||||
|
|
||||
|
if (null != _matrixTransform) |
||||
|
{ |
||||
|
_matrixTransform.Matrix = _transformation; |
||||
|
} |
||||
|
|
||||
|
// New transform means re-layout is necessary
|
||||
|
InvalidateMeasure(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compute the largest usable size (greatest area) after applying the transformation to the specified bounds.
|
||||
|
/// </summary>
|
||||
|
/// <param name="arrangeBounds">Arrange bounds.</param>
|
||||
|
/// <returns>Largest Size possible.</returns>
|
||||
|
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Closely corresponds to WPF's FrameworkElement.FindMaximalAreaLocalSpaceRect.")] |
||||
|
private Size ComputeLargestTransformedSize(Size arrangeBounds) |
||||
|
{ |
||||
|
// Computed largest transformed size
|
||||
|
Size computedSize = Size.Empty; |
||||
|
|
||||
|
// Detect infinite bounds and constrain the scenario
|
||||
|
bool infiniteWidth = double.IsInfinity(arrangeBounds.Width); |
||||
|
if (infiniteWidth) |
||||
|
{ |
||||
|
// arrangeBounds.Width = arrangeBounds.Height;
|
||||
|
arrangeBounds = arrangeBounds.WithWidth(arrangeBounds.Height); |
||||
|
} |
||||
|
bool infiniteHeight = double.IsInfinity(arrangeBounds.Height); |
||||
|
if (infiniteHeight) |
||||
|
{ |
||||
|
//arrangeBounds.Height = arrangeBounds.Width;
|
||||
|
arrangeBounds = arrangeBounds.WithHeight(arrangeBounds.Width); |
||||
|
} |
||||
|
|
||||
|
// Capture the matrix parameters
|
||||
|
double a = _transformation.M11; |
||||
|
double b = _transformation.M12; |
||||
|
double c = _transformation.M21; |
||||
|
double d = _transformation.M22; |
||||
|
|
||||
|
// Compute maximum possible transformed width/height based on starting width/height
|
||||
|
// These constraints define two lines in the positive x/y quadrant
|
||||
|
double maxWidthFromWidth = Math.Abs(arrangeBounds.Width / a); |
||||
|
double maxHeightFromWidth = Math.Abs(arrangeBounds.Width / c); |
||||
|
double maxWidthFromHeight = Math.Abs(arrangeBounds.Height / b); |
||||
|
double maxHeightFromHeight = Math.Abs(arrangeBounds.Height / d); |
||||
|
|
||||
|
// The transformed width/height that maximize the area under each segment is its midpoint
|
||||
|
// At most one of the two midpoints will satisfy both constraints
|
||||
|
double idealWidthFromWidth = maxWidthFromWidth / 2; |
||||
|
double idealHeightFromWidth = maxHeightFromWidth / 2; |
||||
|
double idealWidthFromHeight = maxWidthFromHeight / 2; |
||||
|
double idealHeightFromHeight = maxHeightFromHeight / 2; |
||||
|
|
||||
|
// Compute slope of both constraint lines
|
||||
|
double slopeFromWidth = -(maxHeightFromWidth / maxWidthFromWidth); |
||||
|
double slopeFromHeight = -(maxHeightFromHeight / maxWidthFromHeight); |
||||
|
|
||||
|
if ((0 == arrangeBounds.Width) || (0 == arrangeBounds.Height)) |
||||
|
{ |
||||
|
// Check for empty bounds
|
||||
|
computedSize = new Size(arrangeBounds.Width, arrangeBounds.Height); |
||||
|
} |
||||
|
else if (infiniteWidth && infiniteHeight) |
||||
|
{ |
||||
|
// Check for completely unbound scenario
|
||||
|
computedSize = new Size(double.PositiveInfinity, double.PositiveInfinity); |
||||
|
} |
||||
|
else if (!_transformation.HasInverse) |
||||
|
{ |
||||
|
// Check for singular matrix
|
||||
|
computedSize = new Size(0, 0); |
||||
|
} |
||||
|
else if ((0 == b) || (0 == c)) |
||||
|
{ |
||||
|
// Check for 0/180 degree special cases
|
||||
|
double maxHeight = (infiniteHeight ? double.PositiveInfinity : maxHeightFromHeight); |
||||
|
double maxWidth = (infiniteWidth ? double.PositiveInfinity : maxWidthFromWidth); |
||||
|
if ((0 == b) && (0 == c)) |
||||
|
{ |
||||
|
// No constraints
|
||||
|
computedSize = new Size(maxWidth, maxHeight); |
||||
|
} |
||||
|
else if (0 == b) |
||||
|
{ |
||||
|
// Constrained by width
|
||||
|
double computedHeight = Math.Min(idealHeightFromWidth, maxHeight); |
||||
|
computedSize = new Size( |
||||
|
maxWidth - Math.Abs((c * computedHeight) / a), |
||||
|
computedHeight); |
||||
|
} |
||||
|
else if (0 == c) |
||||
|
{ |
||||
|
// Constrained by height
|
||||
|
double computedWidth = Math.Min(idealWidthFromHeight, maxWidth); |
||||
|
computedSize = new Size( |
||||
|
computedWidth, |
||||
|
maxHeight - Math.Abs((b * computedWidth) / d)); |
||||
|
} |
||||
|
} |
||||
|
else if ((0 == a) || (0 == d)) |
||||
|
{ |
||||
|
// Check for 90/270 degree special cases
|
||||
|
double maxWidth = (infiniteHeight ? double.PositiveInfinity : maxWidthFromHeight); |
||||
|
double maxHeight = (infiniteWidth ? double.PositiveInfinity : maxHeightFromWidth); |
||||
|
if ((0 == a) && (0 == d)) |
||||
|
{ |
||||
|
// No constraints
|
||||
|
computedSize = new Size(maxWidth, maxHeight); |
||||
|
} |
||||
|
else if (0 == a) |
||||
|
{ |
||||
|
// Constrained by width
|
||||
|
double computedHeight = Math.Min(idealHeightFromHeight, maxHeight); |
||||
|
computedSize = new Size( |
||||
|
maxWidth - Math.Abs((d * computedHeight) / b), |
||||
|
computedHeight); |
||||
|
} |
||||
|
else if (0 == d) |
||||
|
{ |
||||
|
// Constrained by height
|
||||
|
double computedWidth = Math.Min(idealWidthFromWidth, maxWidth); |
||||
|
computedSize = new Size( |
||||
|
computedWidth, |
||||
|
maxHeight - Math.Abs((a * computedWidth) / c)); |
||||
|
} |
||||
|
} |
||||
|
else if (idealHeightFromWidth <= ((slopeFromHeight * idealWidthFromWidth) + maxHeightFromHeight)) |
||||
|
{ |
||||
|
// Check the width midpoint for viability (by being below the height constraint line)
|
||||
|
computedSize = new Size(idealWidthFromWidth, idealHeightFromWidth); |
||||
|
} |
||||
|
else if (idealHeightFromHeight <= ((slopeFromWidth * idealWidthFromHeight) + maxHeightFromWidth)) |
||||
|
{ |
||||
|
// Check the height midpoint for viability (by being below the width constraint line)
|
||||
|
computedSize = new Size(idealWidthFromHeight, idealHeightFromHeight); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Neither midpoint is viable; use the intersection of the two constraint lines instead
|
||||
|
// Compute width by setting heights equal (m1*x+c1=m2*x+c2)
|
||||
|
double computedWidth = (maxHeightFromHeight - maxHeightFromWidth) / (slopeFromWidth - slopeFromHeight); |
||||
|
// Compute height from width constraint line (y=m*x+c; using height would give same result)
|
||||
|
computedSize = new Size( |
||||
|
computedWidth, |
||||
|
(slopeFromWidth * computedWidth) + maxHeightFromWidth); |
||||
|
} |
||||
|
|
||||
|
// Return result
|
||||
|
return computedSize; |
||||
|
} |
||||
|
|
||||
|
private void OnLayoutTransformChanged(PerspexPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
var newTransform = e.NewValue as Transform; |
||||
|
|
||||
|
if (_transformChangedEvent != null) |
||||
|
{ |
||||
|
_transformChangedEvent.Dispose(); |
||||
|
_transformChangedEvent = null; |
||||
|
} |
||||
|
|
||||
|
if (newTransform != null) |
||||
|
{ |
||||
|
_transformChangedEvent = Observable.FromEventPattern<EventHandler, EventArgs>( |
||||
|
v => newTransform.Changed += v, v => newTransform.Changed -= v) |
||||
|
.Subscribe(onNext: v => ApplyLayoutTransform()); |
||||
|
} |
||||
|
|
||||
|
ApplyLayoutTransform(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue