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> |
|||
<startup> |
|||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" /> |
|||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> |
|||
</startup> |
|||
</configuration> |
|||
</configuration> |
|||
|
|||
@ -1,14 +1,14 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<configuration> |
|||
<startup> |
|||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" /> |
|||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> |
|||
</startup> |
|||
<runtime> |
|||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> |
|||
<dependentAssembly> |
|||
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral" /> |
|||
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" /> |
|||
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral"/> |
|||
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/> |
|||
</dependentAssembly> |
|||
</assemblyBinding> |
|||
</runtime> |
|||
</configuration> |
|||
</configuration> |
|||
|
|||
|
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; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Perspex.Controls; |
|||
|
|||
namespace Perspex |
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
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