Browse Source

Merge branch 'master' of https://github.com/Perspex/Perspex

pull/534/head
Steven Kirk 10 years ago
parent
commit
a8e3b70a26
  1. 3
      .gitmodules
  2. 3
      Perspex.sln
  3. 1
      appveyor.yml
  4. 9
      samples/BindingTest/MainWindow.xaml
  5. 17
      samples/BindingTest/ViewModels/MainWindowViewModel.cs
  6. 19
      samples/ControlCatalog.Desktop/Program.cs
  7. 16
      samples/ControlCatalog/Pages/CanvasPage.xaml
  8. 103
      samples/XamlTestApplicationPcl/ViewModels/EditorViewModel.cs
  9. 15
      samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs
  10. 78
      samples/XamlTestApplicationPcl/Views/MainWindow.xaml
  11. 1
      samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj
  12. 2
      src/Markup/Perspex.Markup.Xaml/Context/PerspexNamespaceRegistry.cs
  13. 21
      src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs
  14. 1
      src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeFeatureProvider.cs
  15. 3
      src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeRepository.cs
  16. 10
      src/Markup/Perspex.Markup.Xaml/Converters/BitmapTypeConverter.cs
  17. 32
      src/Markup/Perspex.Markup.Xaml/Converters/SizeTypeConverter.cs
  18. 4
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/StaticExtension.cs
  19. 4
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TypeExtension.cs
  20. 2
      src/Markup/Perspex.Markup.Xaml/OmniXAML
  21. 25
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  22. 66
      src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs
  23. 14
      src/Markup/Perspex.Markup.Xaml/Styling/StyleInclude.cs
  24. 1
      src/Markup/Perspex.Markup.Xaml/glass
  25. 28
      src/Perspex.Base/PerspexObject.cs
  26. 11
      src/Perspex.Base/Platform/IAssetLoader.cs
  27. 12
      src/Perspex.Controls/Generators/ItemContainerGenerator.cs
  28. 15
      src/Perspex.Controls/Presenters/CarouselPresenter.cs
  29. 17
      src/Perspex.Controls/Primitives/SelectingItemsControl.cs
  30. 1
      src/Perspex.Controls/Primitives/ToggleButton.cs
  31. 11
      src/Perspex.Input/FocusManager.cs
  32. 10
      src/Perspex.Input/Gestures.cs
  33. 8
      src/Perspex.Input/InputElement.cs
  34. 103
      src/Perspex.SceneGraph/Media/ArcSegment.cs
  35. 65
      src/Perspex.SceneGraph/Media/BezierSegment .cs
  36. 2
      src/Perspex.SceneGraph/Media/Geometry.cs
  37. 31
      src/Perspex.SceneGraph/Media/LineSegment.cs
  38. 102
      src/Perspex.SceneGraph/Media/PathFigure.cs
  39. 123
      src/Perspex.SceneGraph/Media/PathGeometry.cs
  40. 15
      src/Perspex.SceneGraph/Media/PathGeometryCollections.cs
  41. 10
      src/Perspex.SceneGraph/Media/PathSegment.cs
  42. 46
      src/Perspex.SceneGraph/Media/QuadraticBezierSegment .cs
  43. 57
      src/Perspex.SceneGraph/Media/TransformGroup.cs
  44. 9
      src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
  45. 25
      src/Perspex.SceneGraph/Size.cs
  46. 187
      src/Shared/PlatformSupport/AssetLoader.cs
  47. 2
      src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj
  48. 4
      src/Skia/Perspex.Skia.Android/Perspex.Skia.Android.csproj
  49. 2
      src/Skia/Perspex.Skia.Android/packages.config
  50. 6
      src/Skia/Perspex.Skia.Desktop/Perspex.Skia.Desktop.csproj
  51. 2
      src/Skia/Perspex.Skia.Desktop/packages.config
  52. 2
      src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj
  53. 2
      src/Skia/Perspex.Skia.iOS/packages.config
  54. 10
      src/Skia/Perspex.Skia/DrawingContextImpl.cs
  55. 5
      src/Skia/Perspex.Skia/SkiaSharpExtensions.cs
  56. 40
      src/Skia/Perspex.Skia/StreamGeometryImpl.cs
  57. 7
      src/Skia/Perspex.Skia/readme.md
  58. 7
      src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs
  59. 1
      src/Windows/Perspex.Direct2D1/Media/StreamGeometryImpl.cs
  60. 2
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs
  61. 24
      tests/Perspex.Controls.UnitTests/ListBoxTests.cs
  62. 77
      tests/Perspex.Controls.UnitTests/Presenters/CarouselPresenterTests.cs
  63. 52
      tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  64. 49
      tests/Perspex.Input.UnitTests/InputElement_Focus.cs
  65. 5
      tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj
  66. 3
      tests/Perspex.UnitTests/TestRoot.cs
  67. 15
      tests/Perspex.UnitTests/TestServices.cs
  68. 2
      tests/Perspex.UnitTests/UnitTestApplication.cs

3
.gitmodules

@ -8,3 +8,6 @@
[submodule "src/Markup/Perspex.Markup.Xaml/OmniXAML"]
path = src/Markup/Perspex.Markup.Xaml/OmniXAML
url = https://github.com/Perspex/OmniXAML.git
[submodule "src/Markup/Perspex.Markup.Xaml/glass"]
path = src/Markup/Perspex.Markup.Xaml/glass
url = https://github.com/SuperJMN/glass

3
Perspex.sln

@ -60,9 +60,6 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup.Xaml", "src\Markup\Perspex.Markup.Xaml\Perspex.Markup.Xaml.csproj", "{3E53A01A-B331-47F3-B828-4A5717E77A24}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{9B9E3891-2366-4253-A952-D08BCEB71098}"
ProjectSection(SolutionItems) = preProject
samples\ControlCatalog\ControlCatalog.csproj = samples\ControlCatalog\ControlCatalog.csproj
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication", "samples\TestApplication\TestApplication.csproj", "{E3A1060B-50D0-44E8-88B6-F44EF2E5BD72}"
ProjectSection(ProjectDependencies) = postProject

1
appveyor.yml

@ -20,6 +20,7 @@ configuration:
- Release
after_test:
- .\packages\JetBrains.dotMemoryUnit.2.1.20150828.125449\tools\dotMemoryUnit.exe -targetExecutable="%xunit20%\xunit.console.x86.exe" -returnTargetExitCode --"tests\Perspex.LeakTests\bin\Release\Perspex.LeakTests.dll"
- ps: nuget\build-appveyor.ps1
artifacts:

9
samples/BindingTest/MainWindow.xaml

@ -68,5 +68,14 @@
</ContentControl>
</StackPanel>
</TabItem>
<TabItem Header="Commands">
<StackPanel Margin="18" Gap="4" Width="200">
<Button Content="Button" Command="{Binding StringValueCommand}" CommandParameter="Button"/>
<ToggleButton Content="ToggleButton" IsChecked="{Binding BooleanFlag, Mode=OneWay}" Command="{Binding StringValueCommand}" CommandParameter="ToggleButton"/>
<CheckBox Content="CheckBox" IsChecked="{Binding !BooleanFlag, Mode=OneWay}" Command="{Binding StringValueCommand}" CommandParameter="CheckBox"/>
<RadioButton Content="RadionButton" IsChecked="{Binding !!BooleanFlag, Mode=OneWay}" Command="{Binding StringValueCommand}" CommandParameter="RadioButton"/>
<TextBox Text="{Binding Path=StringValue}"/>
</StackPanel>
</TabItem>
</TabControl>
</Window>

17
samples/BindingTest/ViewModels/MainWindowViewModel.cs

@ -2,6 +2,7 @@
using System.Collections.ObjectModel;
using System.Linq;
using ReactiveUI;
using System.Reactive.Linq;
namespace BindingTest.ViewModels
{
@ -10,6 +11,7 @@ namespace BindingTest.ViewModels
private string _booleanString = "True";
private double _doubleValue = 5.0;
private string _stringValue = "Simple Binding";
private bool _booleanFlag = false;
public MainWindowViewModel()
{
@ -28,6 +30,13 @@ namespace BindingTest.ViewModels
var r = new Random();
Items.Move(r.Next(Items.Count), 1);
});
StringValueCommand = ReactiveCommand.Create();
StringValueCommand.Subscribe(param =>
{
BooleanFlag = !BooleanFlag;
StringValue = param.ToString();
});
}
public ObservableCollection<TestItem> Items { get; }
@ -51,5 +60,13 @@ namespace BindingTest.ViewModels
get { return _stringValue; }
set { this.RaiseAndSetIfChanged(ref _stringValue, value); }
}
public bool BooleanFlag
{
get { return _booleanFlag; }
set { this.RaiseAndSetIfChanged(ref _booleanFlag, value); }
}
public ReactiveCommand<object> StringValueCommand { get; }
}
}

19
samples/ControlCatalog.Desktop/Program.cs

@ -6,24 +6,6 @@ using Perspex;
using System.Reflection;
using Perspex.Platform;
// Not sure where the best home for this is
namespace Perspex
{
public static class SharedApplicationExtensions
{
// For true Portable apps we need to select the PCL assembly NOT the host platform exe. Unfortunately
// Win32 subsystem registers one by default (the wrong one) and so this can override that.
//
public static AppT UseAssetAssembly<AppT>(this AppT app, Assembly assembly) where AppT : Application
{
// Asset loading searches our own assembly?
PerspexLocator.CurrentMutable.GetService<IAssetLoader>().SetDefaultAssembly(assembly);
return app;
}
}
}
namespace ControlCatalog
{
internal class Program
@ -34,7 +16,6 @@ namespace ControlCatalog
new App()
.ConfigureRenderSystem(args)
.UseAssetAssembly(typeof(App).Assembly)
.LoadFromXaml()
.RunWithMainWindow<MainWindow>();
}

16
samples/ControlCatalog/Pages/CanvasPage.xaml

@ -4,10 +4,22 @@
<TextBlock Classes="h2">A panel which lays out its children by explicit coordinates</TextBlock>
<Canvas Background="Yellow" Width="300" Height="400">
<Rectangle Fill="Blue" Width="63" Height="41" Canvas.Left="40" Canvas.Top="31"/>
<Ellipse Fill="Green" Width="58" Height="58" Canvas.Left="160" Canvas.Top="79"/>
<Ellipse Fill="Green" Width="58" Height="58" Canvas.Left="88" Canvas.Top="100"/>
<Path Fill="Orange" Data="M 0,0 c 50,0 50,-50 c 50,0 50,50 h -50 v 50 l -50,-50 Z" Canvas.Left="30" Canvas.Top="250"/>
<Path Fill="OrangeRed" Canvas.Left="180" Canvas.Top="250">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="0,0" IsClosed="True">
<QuadraticBezierSegment Point1="50,0" Point2="50,-50" />
<QuadraticBezierSegment Point1="100,-50" Point2="100,0" />
<LineSegment Point="50,0" />
<LineSegment Point="50,50" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Line StartPoint="120,185" EndPoint="30,115" Stroke="Red" StrokeThickness="2"/>
<Polygon Points="75,0 120,120 0,45 150,45 30,120" Stroke="DarkBlue" StrokeThickness="1" Fill="Violet" Canvas.Left="150" Canvas.Top="180"/>
<Polygon Points="75,0 120,120 0,45 150,45 30,120" Stroke="DarkBlue" StrokeThickness="1" Fill="Violet" Canvas.Left="150" Canvas.Top="31"/>
<Polyline Points="0,0 65,0 78,-26 91,39 104,-39 117,13 130,0 195,0" Stroke="Brown" Canvas.Left="30" Canvas.Top="350"/>
</Canvas>
</StackPanel>

103
samples/XamlTestApplicationPcl/ViewModels/EditorViewModel.cs

@ -0,0 +1,103 @@
using Perspex.Threading;
namespace XamlTestApplication.ViewModels
{
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class ShellViewModel : ReactiveObject
{
private ShellViewModel()
{
documents = new ObservableCollection<EditorViewModel>();
AddDocumentCommand = ReactiveCommand.Create();
AddDocumentCommand.Subscribe(_ =>
{
Documents.Add(new EditorViewModel());
});
GCCommand = ReactiveCommand.Create();
GCCommand.Subscribe(_ =>
{
GC.Collect();
});
}
public static ShellViewModel Instance = new ShellViewModel();
private ObservableCollection<EditorViewModel> documents;
public ObservableCollection<EditorViewModel> Documents
{
get { return documents; }
set { this.RaiseAndSetIfChanged(ref documents, value); }
}
private EditorViewModel selectedDocument;
public EditorViewModel SelectedDocument
{
get { return selectedDocument; }
set { this.RaiseAndSetIfChanged(ref selectedDocument, value); }
}
private int instanceCount;
public int InstanceCount
{
get { return instanceCount; }
set { this.RaiseAndSetIfChanged(ref instanceCount, value); }
}
public ReactiveCommand<object> AddDocumentCommand { get; }
public ReactiveCommand<object> GCCommand { get; }
}
public class EditorViewModel : ReactiveObject
{
private static int InstanceCount = 0;
public EditorViewModel()
{
InstanceCount++;
ShellViewModel.Instance.InstanceCount = InstanceCount;
text = "This is some text.";
CloseCommand = ReactiveCommand.Create();
CloseCommand.Subscribe(_ =>
{
ShellViewModel.Instance.Documents.Remove(this);
});
}
~EditorViewModel()
{
//System.Console.WriteLine("EVM Destructed");
InstanceCount--;
Dispatcher.UIThread.InvokeAsync(() =>
{
ShellViewModel.Instance.InstanceCount = InstanceCount;
});
}
private string text;
public string Text
{
get { return text; }
set { this.RaiseAndSetIfChanged(ref text, value); }
}
public ReactiveCommand<object> CloseCommand { get; }
}
}

15
samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs

@ -56,7 +56,11 @@ namespace XamlTestApplication.ViewModels
}
};
CollapseNodesCommand = ReactiveCommand.Create();
CollapseNodesCommand = ReactiveCommand.Create();
CollapseNodesCommand.Subscribe(_ => ExpandNodes(false));
ExpandNodesCommand = ReactiveCommand.Create();
ExpandNodesCommand.Subscribe(_ => ExpandNodes(true));
@ -77,6 +81,15 @@ namespace XamlTestApplication.ViewModels
ofd.ShowAsync();
});
shell = ShellViewModel.Instance;
}
private ShellViewModel shell;
public ShellViewModel Shell
{
get { return shell; }
set { shell = value; }
}
public List<TestItem> Items { get; }

78
samples/XamlTestApplicationPcl/Views/MainWindow.xaml

@ -316,6 +316,84 @@
<local:TestScrollable/>
</ScrollViewer>
</TabItem>
<TabItem Header="Mem Leak Repro">
<Grid DataContext="{Binding Shell}">
<StackPanel>
<TabStrip Name="strip" Items="{Binding Documents}" SelectedItem="{Binding SelectedDocument, Mode=TwoWay}" Focusable="false">
<TabStrip.Styles>
<Style Selector="TabStripItem">
<Setter Property="Height" Value="20" />
<Setter Property="Background" Value="#2D2D30" />
<Setter Property="BorderBrush" Value="#3E3E42" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="0 0 0 -1" />
<Setter Property="Padding" Value="4 0 4 0" />
<Setter Property="FontSize" Value="12"/>
</Style>
<Style Selector="TabStripItem:pointerover">
<Setter Property="Background" Value="#1c97ea" />
</Style>
<Style Selector="TabStripItem:selected">
<Setter Property="Background" Value="#007ACC" />
</Style>
</TabStrip.Styles>
<TabStrip.DataTemplates>
<DataTemplate>
<StackPanel Orientation="Horizontal" Gap="2">
<TextBlock Text="{Binding Title}" Foreground="WhiteSmoke" Margin="2"/>
<Button Height="14" Width="14">
<Button.Styles>
<Style Selector="Button">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="#1c97ea" />
</Style>
</Button.Styles>
<Path Margin="2" Stretch="Uniform" UseLayoutRounding="False" Data="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" Fill="WhiteSmoke" />
</Button>
<Button Height="14" Width="14" Command="{Binding CloseCommand}">
<Button.Styles>
<Style Selector="Button">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="#1c97ea" />
</Style>
</Button.Styles>
<Path Margin="2" Stretch="Uniform" UseLayoutRounding="False" Data="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" Fill="WhiteSmoke" />
</Button>
</StackPanel>
</DataTemplate>
</TabStrip.DataTemplates>
</TabStrip>
<Grid Background="#007ACC" Height="2" />
<Button Content="AddTab" Command="{Binding AddDocumentCommand}" Width="50" Height="30" />
<Button Content="GC.Collecy" Command="{Binding GCCommand}" Width="50" Height="30" />
<TextBox Text="{Binding InstanceCount}" />
<Carousel Items="{Binding Documents}" SelectedIndex="{Binding #strip.SelectedIndex}" IsVirtualized="false">
<Carousel.DataTemplates>
<DataTemplate>
<TextBox Text="{Binding Text}" />
</DataTemplate>
</Carousel.DataTemplates>
</Carousel>
</StackPanel>
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>

1
samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj

@ -42,6 +42,7 @@
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestScrollable.cs" />
<Compile Include="ViewModels\EditorViewModel.cs" />
<Compile Include="ViewModels\MainWindowViewModel.cs" />
<Compile Include="ViewModels\TestItem.cs" />
<Compile Include="ViewModels\TestNode.cs" />

2
src/Markup/Perspex.Markup.Xaml/Context/PerspexNamespaceRegistry.cs

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Glass;
using OmniXaml.Builder;
using OmniXaml.Typing;
using Perspex.Controls;
@ -14,6 +13,7 @@ using Perspex.Media;
using Perspex.Metadata;
using Perspex.Platform;
using Perspex.Styling;
using Glass.Core;
namespace Perspex.Markup.Xaml.Context
{

21
src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs

@ -7,6 +7,8 @@ using OmniXaml.ObjectAssembler;
using OmniXaml.ObjectAssembler.Commands;
using OmniXaml.TypeConversion;
using Perspex.Markup.Xaml.Templates;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Perspex.Markup.Xaml.Context
{
@ -27,7 +29,8 @@ namespace Perspex.Markup.Xaml.Context
mapping.Map<TreeDataTemplate>(x => x.Content, new TemplateLoader());
mapping.Map<ItemsPanelTemplate>(x => x.Content, new TemplateLoader());
var valueContext = new ValueContext(typeSource, topDownValueContext);
var parsingDictionary = GetDictionary(settings);
var valueContext = new ValueContext(typeSource, topDownValueContext, parsingDictionary);
assembler = new ObjectAssembler(typeSource, valueContext, settings);
objectAssembler = new TemplateHostingObjectAssembler(assembler, mapping);
}
@ -55,5 +58,21 @@ namespace Perspex.Markup.Xaml.Context
{
objectAssembler.OverrideInstance(instance);
}
private static IReadOnlyDictionary<string, object> GetDictionary(Settings settings)
{
IReadOnlyDictionary<string, object> dict;
if (settings != null)
{
dict = settings.ParsingContext;
}
else
{
dict = new ReadOnlyDictionary<string, object>(new Dictionary<string, object>());
}
return dict;
}
}
}

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

@ -165,6 +165,7 @@ namespace Perspex.Markup.Xaml.Context
RegisterTypeConverter(typeof(RelativePoint), new RelativePointTypeConverter());
RegisterTypeConverter(typeof(RelativeRect), new RelativeRectTypeConverter());
RegisterTypeConverter(typeof(RowDefinitions), new RowDefinitionsTypeConverter());
RegisterTypeConverter(typeof(Size), new SizeTypeConverter());
RegisterTypeConverter(typeof(Selector), new SelectorTypeConverter());
RegisterTypeConverter(typeof(SolidColorBrush), new BrushTypeConverter());
RegisterTypeConverter(typeof(Thickness), new ThicknessTypeConverter());

3
src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeRepository.cs

@ -2,10 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Glass;
using OmniXaml;
using OmniXaml.Typing;
using Perspex.Markup.Xaml.Data;
using Glass.Core;
namespace Perspex.Markup.Xaml.Context
{

10
src/Markup/Perspex.Markup.Xaml/Converters/BitmapTypeConverter.cs

@ -24,6 +24,7 @@ namespace Perspex.Markup.Xaml.Converters
public object ConvertFrom(IValueContext context, CultureInfo culture, object value)
{
var uri = new Uri((string)value, UriKind.RelativeOrAbsolute);
var baseUri = GetBaseUri(context);
var scheme = uri.IsAbsoluteUri ? uri.Scheme : "file";
switch (scheme)
@ -32,7 +33,7 @@ namespace Perspex.Markup.Xaml.Converters
return new Bitmap((string)value);
default:
var assets = PerspexLocator.Current.GetService<IAssetLoader>();
return new Bitmap(assets.Open(uri));
return new Bitmap(assets.Open(uri, baseUri));
}
}
@ -40,5 +41,12 @@ namespace Perspex.Markup.Xaml.Converters
{
throw new NotImplementedException();
}
private Uri GetBaseUri(IValueContext context)
{
object result;
context.ParsingDictionary.TryGetValue("Uri", out result);
return result as Uri;
}
}
}

32
src/Markup/Perspex.Markup.Xaml/Converters/SizeTypeConverter.cs

@ -0,0 +1,32 @@
// 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;
namespace Perspex.Markup.Xaml.Converters
{
public class SizeTypeConverter : 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 Size.Parse((string)value, culture);
}
public object ConvertTo(IValueContext context, CultureInfo culture, object value, Type destinationType)
{
throw new NotImplementedException();
}
}
}

4
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/StaticExtension.cs

@ -4,8 +4,8 @@
using System;
using System.Linq;
using System.Reflection;
using Glass;
using OmniXaml;
using Glass.Core;
namespace Perspex.Markup.Xaml.MarkupExtensions
{
@ -24,7 +24,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
public override object ProvideValue(MarkupExtensionContext markupExtensionContext)
{
var typeRepository = markupExtensionContext.TypeRepository;
var typeRepository = markupExtensionContext.ValueContext.TypeRepository;
var typeAndMember = GetTypeAndMember(Identifier);
var prefixAndType = GetPrefixAndType(typeAndMember.Item1);
var xamlType = typeRepository.GetByPrefix(prefixAndType.Item1, prefixAndType.Item2);

4
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TypeExtension.cs

@ -2,10 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Glass;
using OmniXaml;
using OmniXaml.Attributes;
using OmniXaml.Typing;
using Glass.Core;
namespace Perspex.Markup.Xaml.MarkupExtensions
{
@ -43,7 +43,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
return Type;
}
return ResolveFromString(TypeName, markupExtensionContext.TypeRepository);
return ResolveFromString(TypeName, markupExtensionContext.ValueContext.TypeRepository);
}
}
}

2
src/Markup/Perspex.Markup.Xaml/OmniXAML

@ -1 +1 @@
Subproject commit 75e0dc32fe9a6d97f5b59d2b7d689db2475f444f
Subproject commit b122549406107170bbe6e67c0d6a1a4252beef77

25
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@ -66,6 +66,7 @@
<Compile Include="Converters\PerspexListTypeConverter.cs" />
<Compile Include="Converters\PerspexPropertyTypeConverter.cs" />
<Compile Include="Converters\PointsListTypeConverter.cs" />
<Compile Include="Converters\SizeTypeConverter.cs" />
<Compile Include="Converters\PointTypeConverter.cs" />
<Compile Include="Converters\RelativePointTypeConverter.cs" />
<Compile Include="Converters\RelativeRectTypeConverter.cs" />
@ -81,22 +82,22 @@
<Compile Include="Data\SourceBindingEndpoint.cs" />
<Compile Include="Data\StyleResourceBinding.cs" />
<Compile Include="Data\TargetBindingEndpoint.cs" />
<Compile Include="glass\Glass.Core\AutoKeyDictionary.cs" />
<Compile Include="glass\Glass.Core\DependencySorter.cs" />
<Compile Include="glass\Glass.Core\EnumExtensions.cs" />
<Compile Include="glass\Glass.Core\Extensions.cs" />
<Compile Include="glass\Glass.Core\Guard.cs" />
<Compile Include="glass\Glass.Core\IAdd.cs" />
<Compile Include="glass\Glass.Core\IDependency.cs" />
<Compile Include="glass\Glass.Core\ReflectionExtensions.cs" />
<Compile Include="glass\Glass.Core\StackingLinkedList.cs" />
<Compile Include="glass\Glass.Core\StackingLinkedListMixin.cs" />
<Compile Include="MarkupExtensions\StyleResourceExtension.cs" />
<Compile Include="MarkupExtensions\BindingExtension.cs" />
<Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
<Compile Include="MarkupExtensions\StaticExtension.cs" />
<Compile Include="MarkupExtensions\TemplateBindingExtension.cs" />
<Compile Include="MarkupExtensions\TypeExtension.cs" />
<Compile Include="OmniXAML\Source\Glass\AutoKeyDictionary.cs" />
<Compile Include="OmniXAML\Source\Glass\DependencySorter.cs" />
<Compile Include="OmniXAML\Source\Glass\EnumExtensions.cs" />
<Compile Include="OmniXAML\Source\Glass\Extensions.cs" />
<Compile Include="OmniXAML\Source\Glass\Guard.cs" />
<Compile Include="OmniXAML\Source\Glass\IAdd.cs" />
<Compile Include="OmniXAML\Source\Glass\IDependency.cs" />
<Compile Include="OmniXAML\Source\Glass\ReflectionExtensions.cs" />
<Compile Include="OmniXAML\Source\Glass\StackingLinkedList.cs" />
<Compile Include="OmniXAML\Source\Glass\StackingLinkedListMixin.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Attributes\ContentPropertyAttribute.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Attributes\DependsOnAttribute.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Attributes\XmlnsDefinitionAttribute.cs" />
@ -275,6 +276,7 @@
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="glass\Glass.Core\Glass.Core.nuspec" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
@ -333,6 +335,9 @@
<HintPath>..\..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Content Include="glass\Glass.Core\Glass.Core.csproj" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

66
src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs

@ -15,7 +15,7 @@ namespace Perspex.Markup.Xaml
using Controls;
using Data;
using OmniXaml.ObjectAssembler;
using System.Linq;
/// <summary>
/// Loads XAML for a perspex application.
/// </summary>
@ -23,6 +23,7 @@ namespace Perspex.Markup.Xaml
{
private static PerspexParserFactory s_parserFactory;
private static IInstanceLifeCycleListener s_lifeCycleListener = new PerspexLifeCycleListener();
private static Stack<Uri> s_uriStack = new Stack<Uri>();
/// <summary>
/// Initializes a new instance of the <see cref="PerspexXamlLoader"/> class.
@ -41,6 +42,18 @@ namespace Perspex.Markup.Xaml
{
}
/// <summary>
/// Gets the URI of the XAML file currently being loaded.
/// </summary>
/// <remarks>
/// TODO: Making this internal for now as I'm not sure that this is the correct
/// thing to do, but its needd by <see cref="StyleInclude"/> to get the URL of
/// the currently loading XAML file, as we can't use the OmniXAML parsing context
/// there. Maybe we need a way to inject OmniXAML context into the objects its
/// constructing?
/// </remarks>
internal static Uri UriContext => s_uriStack.Count > 0 ? s_uriStack.Peek() : null;
/// <summary>
/// Loads the XAML into a Perspex component.
/// </summary>
@ -84,7 +97,7 @@ namespace Perspex.Markup.Xaml
{
var initialize = rootInstance as ISupportInitialize;
initialize?.BeginInit();
return Load(stream, rootInstance);
return Load(stream, rootInstance, uri);
}
}
}
@ -96,11 +109,14 @@ namespace Perspex.Markup.Xaml
/// Loads XAML from a URI.
/// </summary>
/// <param name="uri">The URI of the XAML file.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <returns>The loaded object.</returns>
public object Load(Uri uri, object rootInstance = null)
public object Load(Uri uri, Uri baseUri = null, object rootInstance = null)
{
Contract.Requires<ArgumentNullException>(uri != null);
@ -112,9 +128,9 @@ namespace Perspex.Markup.Xaml
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
}
using (var stream = assetLocator.Open(uri))
using (var stream = assetLocator.Open(uri, baseUri))
{
return Load(stream, rootInstance);
return Load(stream, rootInstance, uri);
}
}
@ -143,23 +159,43 @@ namespace Perspex.Markup.Xaml
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <param name="uri">The URI of the XAML</param>
/// <returns>The loaded object.</returns>
public object Load(Stream stream, object rootInstance = null)
public object Load(Stream stream, object rootInstance = null, Uri uri = null)
{
var result = base.Load(stream, new Settings
try
{
RootInstance = rootInstance,
InstanceLifeCycleListener = s_lifeCycleListener,
});
if (uri != null)
{
s_uriStack.Push(uri);
}
var topLevel = result as TopLevel;
var result = base.Load(stream, new Settings
{
RootInstance = rootInstance,
InstanceLifeCycleListener = s_lifeCycleListener,
ParsingContext = new Dictionary<string, object>
{
{ "Uri", uri }
}
});
var topLevel = result as TopLevel;
if (topLevel != null)
{
DelayedBinding.ApplyBindings(topLevel);
}
if (topLevel != null)
return result;
}
finally
{
DelayedBinding.ApplyBindings(topLevel);
if (uri != null)
{
s_uriStack.Pop();
}
}
return result;
}
private static PerspexParserFactory GetParserFactory()

14
src/Markup/Perspex.Markup.Xaml/Styling/StyleInclude.cs

@ -11,8 +11,20 @@ namespace Perspex.Markup.Xaml.Styling
/// </summary>
public class StyleInclude : IStyle
{
private Uri _baseUri;
private IStyle _loaded;
/// <summary>
/// Initializes a new instance of the <see cref="StyleInclude"/> class.
/// </summary>
public StyleInclude()
{
// StyleInclude will usually be loaded from XAML and its URI can be relative to the
// XAML file that its included in, so store the current XAML file's URI if any as
// a base URI.
_baseUri = PerspexXamlLoader.UriContext;
}
/// <summary>
/// Gets or sets the source URL.
/// </summary>
@ -28,7 +40,7 @@ namespace Perspex.Markup.Xaml.Styling
if (_loaded == null)
{
var loader = new PerspexXamlLoader();
_loaded = (IStyle)loader.Load(Source);
_loaded = (IStyle)loader.Load(Source, _baseUri);
}
return _loaded;

1
src/Markup/Perspex.Markup.Xaml/glass

@ -0,0 +1 @@
Subproject commit 9e17adbd3ac0b342e7922ee2d5a11e5d50b7e687

28
src/Perspex.Base/PerspexObject.cs

@ -23,17 +23,6 @@ namespace Perspex
/// </remarks>
public class PerspexObject : IPerspexObject, IPerspexObjectDebug, INotifyPropertyChanged, IPriorityValueOwner
{
/// <summary>
/// Maintains a list of direct property binding subscriptions so that the binding source
/// doesn't get collected.
/// </summary>
/// <remarks>
/// If/when we provide a ClearBindings() method, then this collection will be need to be
/// moved to an instance field and indexed by property, but until that point a static
/// collection will suffice.
/// </remarks>
private static List<IDisposable> s_directBindings = new List<IDisposable>();
/// <summary>
/// The parent object that inherited values are inherited from.
/// </summary>
@ -45,6 +34,12 @@ namespace Perspex
private readonly Dictionary<PerspexProperty, PriorityValue> _values =
new Dictionary<PerspexProperty, PriorityValue>();
/// <summary>
/// Maintains a list of direct property binding subscriptions so that the binding source
/// doesn't get collected.
/// </summary>
private List<IDisposable> _directBindings;
/// <summary>
/// Event handler for <see cref="INotifyPropertyChanged"/> implementation.
/// </summary>
@ -403,22 +398,27 @@ namespace Perspex
IDisposable subscription = null;
IDisposable validationSubcription = null;
if (_directBindings == null)
{
_directBindings = new List<IDisposable>();
}
subscription = source
.Where(x => !(x is ValidationStatus))
.Select(x => CastOrDefault(x, property.PropertyType))
.Do(_ => { }, () => s_directBindings.Remove(subscription))
.Do(_ => { }, () => _directBindings.Remove(subscription))
.Subscribe(x => DirectBindingSet(property, x));
validationSubcription = source
.OfType<ValidationStatus>()
.Subscribe(x => DataValidation(property, x));
s_directBindings.Add(subscription);
_directBindings.Add(subscription);
return Disposable.Create(() =>
{
validationSubcription.Dispose();
subscription.Dispose();
s_directBindings.Remove(subscription);
_directBindings.Remove(subscription);
});
}
else

11
src/Perspex.Base/Platform/IAssetLoader.cs

@ -21,22 +21,27 @@ namespace Perspex.Platform
/// <param name="asm"></param>
void SetDefaultAssembly(Assembly asm);
/// <summary>
/// Checks if an asset with the specified URI exists.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>True if the asset could be found; otherwise false.</returns>
bool Exists(Uri uri);
bool Exists(Uri uri, Uri baseUri = null);
/// <summary>
/// Opens the resource with the requested URI.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>A stream containing the resource contents.</returns>
/// <exception cref="FileNotFoundException">
/// The resource was not found.
/// </exception>
Stream Open(Uri uri);
Stream Open(Uri uri, Uri baseUri = null);
}
}

12
src/Perspex.Controls/Generators/ItemContainerGenerator.cs

@ -94,9 +94,15 @@ namespace Perspex.Controls.Generators
/// <inheritdoc/>
public virtual IEnumerable<ItemContainer> RemoveRange(int startingIndex, int count)
{
var result = _containers.GetRange(startingIndex, count);
_containers.RemoveRange(startingIndex, count);
Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
List<ItemContainer> result = new List<ItemContainer>();
if (startingIndex < _containers.Count)
{
result.AddRange(_containers.GetRange(startingIndex, count));
_containers.RemoveRange(startingIndex, count);
Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
}
return result;
}

15
src/Perspex.Controls/Presenters/CarouselPresenter.cs

@ -6,6 +6,7 @@ using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Perspex.Animation;
using Perspex.Controls.Generators;
using Perspex.Controls.Primitives;
using Perspex.Controls.Utils;
using Perspex.Data;
@ -103,7 +104,19 @@ namespace Perspex.Controls.Presenters
/// <inheritdoc/>
protected override void ItemsChanged(NotifyCollectionChangedEventArgs e)
{
// TODO: Handle items changing.
// TODO: Handle items changing.
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
if (!IsVirtualized)
{
var generator = ItemContainerGenerator;
var containers = generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
Panel.Children.RemoveAll(containers.Select(x => x.ContainerControl));
}
break;
}
}
/// <summary>

17
src/Perspex.Controls/Primitives/SelectingItemsControl.cs

@ -377,6 +377,23 @@ namespace Perspex.Controls.Primitives
}
}
/// <inheritdoc/>
protected override void OnContainersDematerialized(ItemContainerEventArgs e)
{
base.OnContainersDematerialized(e);
var panel = (InputElement)Presenter.Panel;
foreach (var container in e.Containers)
{
if (KeyboardNavigation.GetTabOnceActiveElement(panel) == container.ContainerControl)
{
KeyboardNavigation.SetTabOnceActiveElement(panel, null);
break;
}
}
}
/// <inheritdoc/>
protected override void OnDataContextChanging()
{

1
src/Perspex.Controls/Primitives/ToggleButton.cs

@ -32,6 +32,7 @@ namespace Perspex.Controls.Primitives
protected override void OnClick(RoutedEventArgs e)
{
Toggle();
base.OnClick(e);
}
protected virtual void Toggle()

11
src/Perspex.Input/FocusManager.cs

@ -39,7 +39,7 @@ namespace Perspex.Input
/// <summary>
/// Gets the currently focused <see cref="IInputElement"/>.
/// </summary>
public IInputElement Current => KeyboardDevice.Instance.FocusedElement;
public IInputElement Current => KeyboardDevice.Instance?.FocusedElement;
/// <summary>
/// Gets the current focus scope.
@ -82,9 +82,12 @@ namespace Perspex.Input
if (_focusScopes.TryGetValue(scope, out element))
{
Focus(element, method);
break;
return;
}
}
// Couldn't find a focus scope, clear focus.
SetFocusedElement(Scope, null);
}
}
@ -111,7 +114,7 @@ namespace Perspex.Input
if (Scope == scope)
{
KeyboardDevice.Instance.SetFocusedElement(element, method, modifiers);
KeyboardDevice.Instance?.SetFocusedElement(element, method, modifiers);
}
}
@ -176,7 +179,7 @@ namespace Perspex.Input
if (sender == e.Source)
{
var ev = (PointerPressedEventArgs)e;
var element = (ev.Device.Captured as IInputElement) ?? (e.Source as IInputElement);
var element = (ev.Device?.Captured as IInputElement) ?? (e.Source as IInputElement);
if (element == null || !CanFocus(element))
{

10
src/Perspex.Input/Gestures.cs

@ -18,7 +18,7 @@ namespace Perspex.Input
RoutingStrategies.Bubble,
typeof(Gestures));
private static IInteractive s_lastPress;
private static WeakReference s_lastPress;
static Gestures()
{
@ -34,9 +34,9 @@ namespace Perspex.Input
if (e.ClickCount <= 1)
{
s_lastPress = e.Source;
s_lastPress = new WeakReference(e.Source);
}
else if (e.ClickCount == 2 && s_lastPress == e.Source)
else if (s_lastPress?.IsAlive == true && e.ClickCount == 2 && s_lastPress.Target == e.Source)
{
e.Source.RaiseEvent(new RoutedEventArgs(DoubleTappedEvent));
}
@ -49,9 +49,9 @@ namespace Perspex.Input
{
var e = (PointerReleasedEventArgs)ev;
if (s_lastPress == e.Source)
if (s_lastPress?.IsAlive == true && s_lastPress.Target == e.Source)
{
s_lastPress.RaiseEvent(new RoutedEventArgs(TappedEvent));
((IInteractive)s_lastPress.Target).RaiseEvent(new RoutedEventArgs(TappedEvent));
}
}
}

8
src/Perspex.Input/InputElement.cs

@ -376,9 +376,9 @@ namespace Perspex.Input
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
base.OnDetachedFromVisualTreeCore(e);
if (IsFocused)
{
@ -387,9 +387,9 @@ namespace Perspex.Input
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
base.OnAttachedToVisualTreeCore(e);
UpdateIsEnabledCore();
}

103
src/Perspex.SceneGraph/Media/ArcSegment.cs

@ -0,0 +1,103 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Media
{
public sealed class ArcSegment : PathSegment
{
/// <summary>
/// Defines the <see cref="IsLargeArc"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsLargeArcProperty
= PerspexProperty.Register<ArcSegment, bool>(nameof(IsLargeArc), false);
/// <summary>
/// Defines the <see cref="Point"/> property.
/// </summary>
public static readonly StyledProperty<Point> PointProperty
= PerspexProperty.Register<ArcSegment, Point>(nameof(Point));
/// <summary>
/// Defines the <see cref="RotationAngle"/> property.
/// </summary>
public static readonly StyledProperty<double> RotationAngleProperty
= PerspexProperty.Register<ArcSegment, double>(nameof(RotationAngle), 0);
/// <summary>
/// Defines the <see cref="Size"/> property.
/// </summary>
public static readonly StyledProperty<Size> SizeProperty
= PerspexProperty.Register<ArcSegment, Size>(nameof(Size));
/// <summary>
/// Defines the <see cref="SweepDirection"/> property.
/// </summary>
public static readonly StyledProperty<SweepDirection> SweepDirectionProperty
= PerspexProperty.Register<ArcSegment, SweepDirection>(nameof(SweepDirection), SweepDirection.Clockwise);
/// <summary>
/// Gets or sets a value indicating whether this instance is large arc.
/// </summary>
/// <value>
/// <c>true</c> if this instance is large arc; otherwise, <c>false</c>.
/// </value>
public bool IsLargeArc
{
get { return GetValue(IsLargeArcProperty); }
set { SetValue(IsLargeArcProperty, value); }
}
/// <summary>
/// Gets or sets the point.
/// </summary>
/// <value>
/// The point.
/// </value>
public Point Point
{
get { return GetValue(PointProperty); }
set { SetValue(PointProperty, value); }
}
/// <summary>
/// Gets or sets the rotation angle.
/// </summary>
/// <value>
/// The rotation angle.
/// </value>
public double RotationAngle
{
get { return GetValue(RotationAngleProperty); }
set { SetValue(RotationAngleProperty, value); }
}
/// <summary>
/// Gets or sets the size.
/// </summary>
/// <value>
/// The size.
/// </value>
public Size Size
{
get { return GetValue(SizeProperty); }
set { SetValue(SizeProperty, value); }
}
/// <summary>
/// Gets or sets the sweep direction.
/// </summary>
/// <value>
/// The sweep direction.
/// </value>
public SweepDirection SweepDirection
{
get { return GetValue(SweepDirectionProperty); }
set { SetValue(SweepDirectionProperty, value); }
}
protected internal override void ApplyTo(StreamGeometryContext ctx)
{
ctx.ArcTo(Point, Size, RotationAngle, IsLargeArc, SweepDirection);
}
}
}

65
src/Perspex.SceneGraph/Media/BezierSegment .cs

@ -0,0 +1,65 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Media
{
public sealed class BezierSegment : PathSegment
{
/// <summary>
/// Defines the <see cref="Point1"/> property.
/// </summary>
public static readonly StyledProperty<Point> Point1Property
= PerspexProperty.Register<BezierSegment, Point>(nameof(Point1));
/// <summary>
/// Defines the <see cref="Point2"/> property.
/// </summary>
public static readonly StyledProperty<Point> Point2Property
= PerspexProperty.Register<BezierSegment, Point>(nameof(Point2));
/// <summary>
/// Defines the <see cref="Point3"/> property.
/// </summary>
public static readonly StyledProperty<Point> Point3Property
= PerspexProperty.Register<BezierSegment, Point>(nameof(Point3));
/// <summary>
/// Gets or sets the point1.
/// </summary>
/// <value>
/// The point1.
/// </value>
public Point Point1
{
get { return GetValue(Point1Property); }
set { SetValue(Point1Property, value); }
}
/// <summary>
/// Gets or sets the point2.
/// </summary>
/// <value>
/// The point2.
/// </value>
public Point Point2
{
get { return GetValue(Point2Property); }
set { SetValue(Point2Property, value); }
}
/// <summary>
/// Gets or sets the point3.
/// </summary>
/// <value>
/// The point3.
/// </value>
public Point Point3
{
get { return GetValue(Point3Property); }
set { SetValue(Point3Property, value); }
}
protected internal override void ApplyTo(StreamGeometryContext ctx)
{
ctx.CubicBezierTo(Point1, Point2, Point3);
}
}
}

2
src/Perspex.SceneGraph/Media/Geometry.cs

@ -36,7 +36,7 @@ namespace Perspex.Media
/// <summary>
/// Gets the platform-specific implementation of the geometry.
/// </summary>
public IGeometryImpl PlatformImpl
public virtual IGeometryImpl PlatformImpl
{
get;
protected set;

31
src/Perspex.SceneGraph/Media/LineSegment.cs

@ -0,0 +1,31 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Media
{
public sealed class LineSegment : PathSegment
{
/// <summary>
/// Defines the <see cref="Point"/> property.
/// </summary>
public static readonly StyledProperty<Point> PointProperty
= PerspexProperty.Register<LineSegment, Point>(nameof(Point));
/// <summary>
/// Gets or sets the point.
/// </summary>
/// <value>
/// The point.
/// </value>
public Point Point
{
get { return GetValue(PointProperty); }
set { SetValue(PointProperty, value); }
}
protected internal override void ApplyTo(StreamGeometryContext ctx)
{
ctx.LineTo(Point);
}
}
}

102
src/Perspex.SceneGraph/Media/PathFigure.cs

@ -0,0 +1,102 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Metadata;
namespace Perspex.Media
{
public sealed class PathFigure : PerspexObject
{
/// <summary>
/// Defines the <see cref="IsClosed"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsClosedProperty
= PerspexProperty.Register<PathFigure, bool>(nameof(IsClosed), true);
/// <summary>
/// Defines the <see cref="IsFilled"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsFilledProperty
= PerspexProperty.Register<PathFigure, bool>(nameof(IsFilled), true);
/// <summary>
/// Defines the <see cref="Segments"/> property.
/// </summary>
public static readonly DirectProperty<PathFigure, PathSegments> SegmentsProperty
= PerspexProperty.RegisterDirect<PathFigure, PathSegments>(nameof(Segments), f => f.Segments, (f, s) => f.Segments = s);
/// <summary>
/// Defines the <see cref="StartPoint"/> property.
/// </summary>
public static readonly StyledProperty<Point> StartPointProperty
= PerspexProperty.Register<PathFigure, Point>(nameof(StartPoint));
/// <summary>
/// Initializes a new instance of the <see cref="PathFigure"/> class.
/// </summary>
public PathFigure()
{
Segments = new PathSegments();
}
/// <summary>
/// Gets or sets a value indicating whether this instance is closed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is closed; otherwise, <c>false</c>.
/// </value>
public bool IsClosed
{
get { return GetValue(IsClosedProperty); }
set { SetValue(IsClosedProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether this instance is filled.
/// </summary>
/// <value>
/// <c>true</c> if this instance is filled; otherwise, <c>false</c>.
/// </value>
public bool IsFilled
{
get { return GetValue(IsFilledProperty); }
set { SetValue(IsFilledProperty, value); }
}
/// <summary>
/// Gets or sets the segments.
/// </summary>
/// <value>
/// The segments.
/// </value>
[Content]
public PathSegments Segments
{
get { return _segments; }
set { SetAndRaise(SegmentsProperty, ref _segments, value); }
}
/// <summary>
/// Gets or sets the start point.
/// </summary>
/// <value>
/// The start point.
/// </value>
public Point StartPoint
{
get { return GetValue(StartPointProperty); }
set { SetValue(StartPointProperty, value); }
}
internal void ApplyTo(StreamGeometryContext ctx)
{
ctx.BeginFigure(StartPoint, IsFilled);
foreach (var segment in Segments)
{
segment.ApplyTo(ctx);
}
ctx.EndFigure(IsClosed);
}
private PathSegments _segments;
}
}

123
src/Perspex.SceneGraph/Media/PathGeometry.cs

@ -0,0 +1,123 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Collections;
using Perspex.Metadata;
using Perspex.Platform;
using System;
namespace Perspex.Media
{
public class PathGeometry : StreamGeometry
{
/// <summary>
/// Defines the <see cref="Figures"/> property.
/// </summary>
public static readonly DirectProperty<PathGeometry, PathFigures> FiguresProperty =
PerspexProperty.RegisterDirect<PathGeometry, PathFigures>(nameof(Figures), g => g.Figures, (g, f) => g.Figures = f);
/// <summary>
/// Defines the <see cref="FillRule"/> property.
/// </summary>
public static readonly StyledProperty<FillRule> FillRuleProperty =
PerspexProperty.Register<PathGeometry, FillRule>(nameof(FillRule));
static PathGeometry()
{
FiguresProperty.Changed.Subscribe(onNext: v =>
{
(v.Sender as PathGeometry)?.OnFiguresChanged(v.OldValue as PathFigures, v.NewValue as PathFigures);
});
}
/// <summary>
/// Initializes a new instance of the <see cref="PathGeometry"/> class.
/// </summary>
public PathGeometry()
{
Figures = new PathFigures();
}
/// <summary>
/// Gets or sets the figures.
/// </summary>
/// <value>
/// The figures.
/// </value>
[Content]
public PathFigures Figures
{
get { return _figures; }
set { SetAndRaise(FiguresProperty, ref _figures, value); }
}
/// <summary>
/// Gets or sets the fill rule.
/// </summary>
/// <value>
/// The fill rule.
/// </value>
public FillRule FillRule
{
get { return GetValue(FillRuleProperty); }
set { SetValue(FillRuleProperty, value); }
}
public override IGeometryImpl PlatformImpl
{
get
{
PrepareIfNeeded();
return base.PlatformImpl;
}
protected set
{
base.PlatformImpl = value;
}
}
public override Geometry Clone()
{
PrepareIfNeeded();
return base.Clone();
}
public void PrepareIfNeeded()
{
if (_isDirty)
{
_isDirty = false;
using (var ctx = Open())
{
ctx.SetFillRule(FillRule);
foreach (var f in Figures)
{
f.ApplyTo(ctx);
}
}
}
}
internal void NotifyChanged()
{
_isDirty = true;
}
private PathFigures _figures;
private IDisposable _figuresObserver = null;
private IDisposable _figuresPropertiesObserver = null;
private bool _isDirty = true;
private void OnFiguresChanged(PathFigures oldValue, PathFigures newValue)
{
_figuresObserver?.Dispose();
_figuresPropertiesObserver?.Dispose();
_figuresObserver = newValue?.ForEachItem(f => NotifyChanged(), f => NotifyChanged(), () => NotifyChanged());
_figuresPropertiesObserver = newValue?.TrackItemPropertyChanged(t => NotifyChanged());
}
}
}

15
src/Perspex.SceneGraph/Media/PathGeometryCollections.cs

@ -0,0 +1,15 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Collections;
namespace Perspex.Media
{
public sealed class PathFigures : PerspexList<PathFigure>
{
}
public sealed class PathSegments : PerspexList<PathSegment>
{
}
}

10
src/Perspex.SceneGraph/Media/PathSegment.cs

@ -0,0 +1,10 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Media
{
public abstract class PathSegment : PerspexObject
{
protected internal abstract void ApplyTo(StreamGeometryContext ctx);
}
}

46
src/Perspex.SceneGraph/Media/QuadraticBezierSegment .cs

@ -0,0 +1,46 @@
namespace Perspex.Media
{
public sealed class QuadraticBezierSegment : PathSegment
{
/// <summary>
/// Defines the <see cref="Point1"/> property.
/// </summary>
public static readonly StyledProperty<Point> Point1Property
= PerspexProperty.Register<QuadraticBezierSegment, Point>(nameof(Point1));
/// <summary>
/// Defines the <see cref="Point2"/> property.
/// </summary>
public static readonly StyledProperty<Point> Point2Property
= PerspexProperty.Register<QuadraticBezierSegment, Point>(nameof(Point2));
/// <summary>
/// Gets or sets the point1.
/// </summary>
/// <value>
/// The point1.
/// </value>
public Point Point1
{
get { return GetValue(Point1Property); }
set { SetValue(Point1Property, value); }
}
/// <summary>
/// Gets or sets the point2.
/// </summary>
/// <value>
/// The point2.
/// </value>
public Point Point2
{
get { return GetValue(Point2Property); }
set { SetValue(Point2Property, value); }
}
protected internal override void ApplyTo(StreamGeometryContext ctx)
{
ctx.QuadraticBezierTo(Point1, Point2);
}
}
}

57
src/Perspex.SceneGraph/Media/TransformGroup.cs

@ -0,0 +1,57 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Collections;
using Perspex.Metadata;
namespace Perspex.Media
{
public class TransformGroup : Transform
{
/// <summary>
/// Defines the <see cref="Children"/> property.
/// </summary>
public static readonly PerspexProperty<Transforms> ChildrenProperty =
PerspexProperty.Register<TransformGroup, Transforms>(nameof(Children));
public TransformGroup()
{
Children = new Transforms();
}
/// <summary>
/// Gets or sets the children.
/// </summary>
/// <value>
/// The children.
/// </value>
[Content]
public Transforms Children
{
get { return GetValue(ChildrenProperty); }
set { SetValue(ChildrenProperty, value); }
}
/// <summary>
/// Gets the tranform's <see cref="Matrix" />.
/// </summary>
public override Matrix Value
{
get
{
Matrix result = Matrix.Identity;
foreach (var t in Children)
{
result *= t.Value;
}
return result;
}
}
}
public sealed class Transforms : PerspexList<Transform>
{
}
}

9
src/Perspex.SceneGraph/Perspex.SceneGraph.csproj

@ -61,11 +61,14 @@
<Compile Include="Matrix.cs" />
<Compile Include="Media\AlignmentY.cs" />
<Compile Include="Media\AlignmentX.cs" />
<Compile Include="Media\ArcSegment.cs" />
<Compile Include="Media\BezierSegment .cs" />
<Compile Include="Media\Brush.cs" />
<Compile Include="Media\Brushes.cs" />
<Compile Include="Media\BrushMappingMode.cs" />
<Compile Include="Media\Color.cs" />
<Compile Include="Media\Colors.cs" />
<Compile Include="Media\TransformGroup.cs" />
<Compile Include="Media\DashStyle.cs" />
<Compile Include="Media\DrawingContext.cs" />
<Compile Include="Media\FillRule.cs" />
@ -75,9 +78,15 @@
<Compile Include="Media\IBrush.cs" />
<Compile Include="Media\ISolidColorBrush.cs" />
<Compile Include="Media\LineGeometry.cs" />
<Compile Include="Media\LineSegment.cs" />
<Compile Include="Media\Mutable\SolidColorBrush.cs" />
<Compile Include="Media\PathGeometryCollections.cs" />
<Compile Include="Media\PathFigure.cs" />
<Compile Include="Media\PathGeometry.cs" />
<Compile Include="Media\PathSegment.cs" />
<Compile Include="Media\PenLineJoin.cs" />
<Compile Include="Media\PolylineGeometry.cs" />
<Compile Include="Media\QuadraticBezierSegment .cs" />
<Compile Include="Media\RadialGradientBrush.cs" />
<Compile Include="Media\LinearGradientBrush.cs" />
<Compile Include="Media\MediaExtensions.cs" />

25
src/Perspex.SceneGraph/Size.cs

@ -3,6 +3,7 @@
using System;
using System.Globalization;
using System.Linq;
namespace Perspex
{
@ -128,6 +129,28 @@ namespace Perspex
return new Size(size._width - toSubstract._width, size._height - toSubstract._height);
}
/// <summary>
/// Parses a <see cref="Size"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="Size"/>.</returns>
public static Size Parse(string s, CultureInfo culture)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
if (parts.Count == 2)
{
return new Size(double.Parse(parts[0], culture), double.Parse(parts[1], culture));
}
else
{
throw new FormatException("Invalid Size.");
}
}
/// <summary>
/// Constrains the size.
/// </summary>
@ -227,4 +250,4 @@ namespace Perspex
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", _width, _height);
}
}
}
}

187
src/Shared/PlatformSupport/AssetLoader.cs

@ -15,26 +15,6 @@ namespace Perspex.Shared.PlatformSupport
/// </summary>
public class AssetLoader : IAssetLoader
{
class AssemblyDescriptor
{
public AssemblyDescriptor(Assembly assembly)
{
Assembly = assembly;
if (assembly != null)
{
Resources = assembly.GetManifestResourceNames()
.ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
Name = assembly.GetName().Name;
}
}
public Assembly Assembly { get; }
public Dictionary<string, IAssetDescriptor> Resources { get; }
public string Name { get; }
}
private static readonly Dictionary<string, AssemblyDescriptor> AssemblyNameCache
= new Dictionary<string, AssemblyDescriptor>();
@ -53,7 +33,94 @@ namespace Perspex.Shared.PlatformSupport
_defaultAssembly = new AssemblyDescriptor(assembly);
}
AssemblyDescriptor GetAssembly(string name)
/// <summary>
/// Checks if an asset with the specified URI exists.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>True if the asset could be found; otherwise false.</returns>
public bool Exists(Uri uri, Uri baseUri = null)
{
return GetAsset(uri, baseUri) != null;
}
/// <summary>
/// Opens the resource with the requested URI.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>A stream containing the resource contents.</returns>
/// <exception cref="FileNotFoundException">
/// The resource was not found.
/// </exception>
public Stream Open(Uri uri, Uri baseUri = null)
{
var asset = GetAsset(uri, baseUri);
if (asset == null)
{
throw new FileNotFoundException($"The resource {uri} could not be found.");
}
return asset.GetStream();
}
private IAssetDescriptor GetAsset(Uri uri, Uri baseUri)
{
if (!uri.IsAbsoluteUri || uri.Scheme == "resm")
{
var uriQueryParams = ParseQueryString(uri);
var baseUriQueryParams = uri != null ? ParseQueryString(uri) : null;
var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultAssembly;
if (asm == null && _defaultAssembly == null)
{
throw new ArgumentException(
"No default assembly, entry assembly or explicit assembly specified; " +
"don't know where to look up for the resource, try specifiyng assembly explicitly.");
}
IAssetDescriptor rv;
var resourceKey = uri.AbsolutePath;
#if __IOS__
// TODO: HACK: to get iOS up and running. Using Shared projects for resources
// is flawed as this alters the reource key locations across platforms
// I think we need to use Portable libraries from now on to avoid that.
if(asm.Name.Contains("iOS"))
{
resourceKey = resourceKey.Replace("TestApplication", "Perspex.iOSTestApplication");
}
#endif
asm.Resources.TryGetValue(resourceKey, out rv);
return rv;
}
throw new ArgumentException($"Invalid uri, see https://github.com/Perspex/Perspex/issues/282#issuecomment-166982104", nameof(uri));
}
private AssemblyDescriptor GetAssembly(Uri uri)
{
if (uri != null)
{
var qs = ParseQueryString(uri);
string assemblyName;
if (qs.TryGetValue("assembly", out assemblyName))
{
return GetAssembly(assemblyName);
}
}
return null;
}
private AssemblyDescriptor GetAssembly(string name)
{
if (name == null)
{
@ -82,13 +149,20 @@ namespace Perspex.Shared.PlatformSupport
return rv;
}
interface IAssetDescriptor
private Dictionary<string, string> ParseQueryString(Uri uri)
{
Stream GetStream();
return uri.Query.TrimStart('?')
.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Split('='))
.ToDictionary(p => p[0], p => p[1]);
}
private interface IAssetDescriptor
{
Stream GetStream();
}
class AssemblyResourceDescriptor : IAssetDescriptor
private class AssemblyResourceDescriptor : IAssetDescriptor
{
private readonly Assembly _asm;
private readonly string _name;
@ -104,69 +178,24 @@ namespace Perspex.Shared.PlatformSupport
return _asm.GetManifestResourceStream(_name);
}
}
IAssetDescriptor GetAsset(Uri uri)
private class AssemblyDescriptor
{
if (!uri.IsAbsoluteUri || uri.Scheme == "resm")
public AssemblyDescriptor(Assembly assembly)
{
var qs = uri.Query.TrimStart('?')
.Split(new[] {'&'}, StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Split('='))
.ToDictionary(p => p[0], p => p[1]);
//TODO: Replace _defaultAssembly by current one (need support from OmniXAML)
var asm = _defaultAssembly;
if (qs.ContainsKey("assembly"))
asm = GetAssembly(qs["assembly"]);
if (asm == null && _defaultAssembly == null)
throw new ArgumentException(
"No defaultAssembly, entry assembly or explicit assembly specified, don't know where to look up for the resource, try specifiyng assembly explicitly");
IAssetDescriptor rv;
var resourceKey = uri.AbsolutePath;
Assembly = assembly;
#if __IOS__
// TODO: HACK: to get iOS up and running. Using Shared projects for resources
// is flawed as this alters the reource key locations across platforms
// I think we need to use Portable libraries from now on to avoid that.
if(asm.Name.Contains("iOS"))
if (assembly != null)
{
resourceKey = resourceKey.Replace("TestApplication", "Perspex.iOSTestApplication");
Resources = assembly.GetManifestResourceNames()
.ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
Name = assembly.GetName().Name;
}
#endif
asm.Resources.TryGetValue(resourceKey, out rv);
return rv;
}
throw new ArgumentException($"Invalid uri, see https://github.com/Perspex/Perspex/issues/282#issuecomment-166982104", nameof(uri));
}
/// <summary>
/// Checks if an asset with the specified URI exists.
/// </summary>
/// <param name="uri">The URI.</param>
/// <returns>True if the asset could be found; otherwise false.</returns>
public bool Exists(Uri uri)
{
return GetAsset(uri) != null;
}
/// <summary>
/// Opens the resource with the requested URI.
/// </summary>
/// <param name="uri">The URI.</param>
/// <returns>A stream containing the resource contents.</returns>
/// <exception cref="FileNotFoundException">
/// The resource was not found.
/// </exception>
public Stream Open(Uri uri)
{
var asset = GetAsset(uri);
if (asset == null)
throw new FileNotFoundException($"The resource {uri} could not be found.");
return asset.GetStream();
public Assembly Assembly { get; }
public Dictionary<string, IAssetDescriptor> Resources { get; }
public string Name { get; }
}
}
}

2
src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj

@ -15,7 +15,7 @@
<AndroidApplication>true</AndroidApplication>
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>True</AndroidUseLatestPlatformSdk>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>

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

@ -14,7 +14,7 @@
<FileAlignment>512</FileAlignment>
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>True</AndroidUseLatestPlatformSdk>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -40,7 +40,7 @@
<Reference Include="Mono.Android" />
<Reference Include="mscorlib" />
<Reference Include="SkiaSharp, Version=1.49.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\SkiaSharp.1.49.2.0-beta\lib\MonoAndroid\SkiaSharp.dll</HintPath>
<HintPath>..\..\..\packages\SkiaSharp.1.49.2.1-beta\lib\MonoAndroid\SkiaSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />

2
src/Skia/Perspex.Skia.Android/packages.config

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="SkiaSharp" version="1.49.2.0-beta" targetFramework="monoandroid60" />
<package id="SkiaSharp" version="1.49.2.1-beta" targetFramework="monoandroid50" />
</packages>

6
src/Skia/Perspex.Skia.Desktop/Perspex.Skia.Desktop.csproj

@ -57,7 +57,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="SkiaSharp, Version=1.49.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\SkiaSharp.1.49.2.0-beta\lib\net45\SkiaSharp.dll</HintPath>
<HintPath>..\..\..\packages\SkiaSharp.1.49.2.1-beta\lib\net45\SkiaSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@ -116,12 +116,12 @@
<Import Project="..\Perspex.Skia\Perspex.Skia.projitems" Label="Shared" />
<Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\..\packages\SkiaSharp.1.49.2.0-beta\build\net45\SkiaSharp.targets" Condition="Exists('..\..\..\packages\SkiaSharp.1.49.2.0-beta\build\net45\SkiaSharp.targets')" />
<Import Project="..\..\..\packages\SkiaSharp.1.49.2.1-beta\build\net45\SkiaSharp.targets" Condition="Exists('..\..\..\packages\SkiaSharp.1.49.2.1-beta\build\net45\SkiaSharp.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\SkiaSharp.1.49.2.0-beta\build\net45\SkiaSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\SkiaSharp.1.49.2.0-beta\build\net45\SkiaSharp.targets'))" />
<Error Condition="!Exists('..\..\..\packages\SkiaSharp.1.49.2.1-beta\build\net45\SkiaSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\SkiaSharp.1.49.2.1-beta\build\net45\SkiaSharp.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

2
src/Skia/Perspex.Skia.Desktop/packages.config

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="SkiaSharp" version="1.49.2.0-beta" targetFramework="net45" />
<package id="SkiaSharp" version="1.49.2.1-beta" targetFramework="net45" />
</packages>

2
src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj

@ -41,7 +41,7 @@
</ItemGroup>
<ItemGroup>
<Reference Include="SkiaSharp, Version=1.49.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\SkiaSharp.1.49.2.0-beta\lib\XamariniOS\SkiaSharp.dll</HintPath>
<HintPath>..\..\..\packages\SkiaSharp.1.49.2.1-beta\lib\XamariniOS\SkiaSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />

2
src/Skia/Perspex.Skia.iOS/packages.config

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="SkiaSharp" version="1.49.2.0-beta" targetFramework="xamarinios10" />
<package id="SkiaSharp" version="1.49.2.1-beta" targetFramework="xamarinios10" />
</packages>

10
src/Skia/Perspex.Skia/DrawingContextImpl.cs

@ -229,10 +229,7 @@ namespace Perspex.Skia
}
else
{
// TODO: DrawRRect (ore DrawRoundedRect) is not accesible in SkiaSharp yet. We should add that
// to SkiaSharp and initiate a PR....
Canvas.DrawRect(rc, paint.Paint);
//Canvas.DrawRoundedRect(rc, cornerRadius, cornerRadius, paint);
Canvas.DrawRoundRect(rc, cornerRadius, cornerRadius, paint.Paint);
}
}
}
@ -248,10 +245,7 @@ namespace Perspex.Skia
}
else
{
// TODO: this does not exist in SkiaSharp yet
//throw new NotImplementedException();
//Canvas.DrawRoundedRect(rc, cornerRadius, cornerRadius, paint);
Canvas.DrawRect(rc, paint.Paint);
Canvas.DrawRoundRect(rc, cornerRadius, cornerRadius, paint.Paint);
}
}
}

5
src/Skia/Perspex.Skia/SkiaSharpExtensions.cs

@ -65,5 +65,10 @@ namespace Perspex.Skia
case TextAlignment.Right: return SKTextAlign.Right;
}
}
public static SKPath Clone(this SKPath src)
{
return src != null ? new SKPath(src) : null;
}
}
}

40
src/Skia/Perspex.Skia/StreamGeometryImpl.cs

@ -50,28 +50,23 @@ namespace Perspex.Skia
_transformedPath.Dispose();
_transformedPath = null;
}
// TODO: SkiaSharp does not expose Transform yet!!!
//if (!Transform.IsIdentity)
//{
// _transformedPath = new SKPath();
// _path.Transform(Transform.ToSKMatrix(), _transformedPath);
//}
if (!Transform.IsIdentity)
{
_transformedPath = new SKPath(_path);
_transformedPath.Transform(Transform.ToSKMatrix());
}
}
public IStreamGeometryImpl Clone()
{
// TODO: there is no SKPath.Clone yet!!!!!!!!!!!!!
//
//return new StreamGeometryImpl
//{
// _path = _path?.Clone(),
// _transformedPath = _transformedPath?.Clone(),
// _transform = Transform,
// Bounds = Bounds
//};
return this; // this will probably end bad!!
return new StreamGeometryImpl
{
_path = _path?.Clone(),
_transformedPath = _transformedPath?.Clone(),
_transform = Transform,
Bounds = Bounds
};
}
public IStreamGeometryContextImpl Open()
@ -94,15 +89,6 @@ namespace Perspex.Skia
public void Dispose()
{
// TODO: Not sure what we need to do here. This code left here for reference.
//
// var arr = _elements.ToArray();
// SkRect rc;
// _path?.Dispose();
// _path = new SKPath(new SkPath(MethodTable.Instance.CreatePath(arr, arr.Length, out rc)));
// _geometryImpl.ApplyTransform();
// _geometryImpl.Bounds = rc.ToRect();
SKRect rc;
_path.GetBounds(out rc);
_geometryImpl.ApplyTransform();

7
src/Skia/Perspex.Skia/readme.md

@ -5,7 +5,6 @@ BitmapImpl
- Save
DrawingContextImpl
- DrawRoundRect is not properly implemented due to lack of support in SkiaSharp
- Alpha support missing as SkiaSharp does not expose this
- Gradient Shader caching?
- TileBrushes
@ -18,12 +17,6 @@ RenderTarget
- Figure out a cleaner implementation across all platforms
- HW acceleration
StreamGeometry
- Paths within Paths may not work right
- Paths cannot be Cloned (lack of SkiaSupport)
- Paths cannot be transformed (lack of SkiaSupport)
- ArcTo
App Bootstrapping
- Cleanup the testapplications across all platforms
- Add a cleaner Fluent API for the subsystems

7
src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs

@ -5,6 +5,7 @@ using Perspex.Media;
using Perspex.Platform;
using SharpDX.Direct2D1;
using SweepDirection = SharpDX.Direct2D1.SweepDirection;
using D2D = SharpDX.Direct2D1;
namespace Perspex.Direct2D1.Media
{
@ -24,7 +25,7 @@ namespace Perspex.Direct2D1.Media
bool isLargeArc,
Perspex.Media.SweepDirection sweepDirection)
{
_sink.AddArc(new ArcSegment
_sink.AddArc(new D2D.ArcSegment
{
Point = point.ToSharpDX(),
Size = size.ToSharpDX(),
@ -41,7 +42,7 @@ namespace Perspex.Direct2D1.Media
public void CubicBezierTo(Point point1, Point point2, Point point3)
{
_sink.AddBezier(new BezierSegment
_sink.AddBezier(new D2D.BezierSegment
{
Point1 = point1.ToSharpDX(),
Point2 = point2.ToSharpDX(),
@ -51,7 +52,7 @@ namespace Perspex.Direct2D1.Media
public void QuadraticBezierTo(Point control, Point dest)
{
_sink.AddQuadraticBezier(new QuadraticBezierSegment
_sink.AddQuadraticBezier(new D2D.QuadraticBezierSegment
{
Point1 = control.ToSharpDX(),
Point2 = dest.ToSharpDX()

1
src/Windows/Perspex.Direct2D1/Media/StreamGeometryImpl.cs

@ -1,7 +1,6 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Media;
using Perspex.Platform;
using SharpDX.Direct2D1;
using D2DGeometry = SharpDX.Direct2D1.Geometry;

2
tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs

@ -344,6 +344,7 @@ namespace Perspex.Base.UnitTests
{
var source = new Subject<string>();
var sub = target.Bind((PerspexProperty)Class1.FooProperty, source);
source.OnNext("foo");
return new WeakReference(source);
};
@ -351,6 +352,7 @@ namespace Perspex.Base.UnitTests
GC.Collect();
Assert.Equal("foo", target.Foo);
Assert.True(weakSource.IsAlive);
}

24
tests/Perspex.Controls.UnitTests/ListBoxTests.cs

@ -104,30 +104,6 @@ namespace Perspex.Controls.UnitTests
dataContexts);
}
[Fact]
public void Setting_SelectedItem_Should_Set_Panel_Keyboard_Navigation()
{
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
};
ApplyTemplate(target);
target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
var panel = target.Presenter.Panel;
Assert.Equal(
KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel),
panel.Children[1]);
}
private Control CreateListBoxTemplate(ITemplatedControl parent)
{
return new ScrollViewer

77
tests/Perspex.Controls.UnitTests/Presenters/CarouselPresenterTests.cs

@ -7,6 +7,7 @@ using Perspex.Controls.Generators;
using Perspex.Controls.Presenters;
using Perspex.Controls.Templates;
using Xunit;
using System.Collections.ObjectModel;
namespace Perspex.Controls.UnitTests.Presenters
{
@ -116,6 +117,82 @@ namespace Perspex.Controls.UnitTests.Presenters
Assert.Equal(2, target.Panel.Children.Count);
}
[Fact]
public void Should_Remove_Controls_When_IsVirtualized_Is_False()
{
ObservableCollection<string> items = new ObservableCollection<string>();
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 0,
IsVirtualized = false,
};
target.ApplyTemplate();
target.SelectedIndex = 0;
items.Add("foo");
target.SelectedIndex = 0;
Assert.Equal(1, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(1, target.Panel.Children.Count);
items.Add("bar");
Assert.Equal(1, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(1, target.Panel.Children.Count);
target.SelectedIndex = 1;
Assert.Equal(2, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(2, target.Panel.Children.Count);
items.Remove(items[0]);
Assert.Equal(1, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(1, target.Panel.Children.Count);
items.Remove(items[0]);
Assert.Equal(0, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(0, target.Panel.Children.Count);
}
[Fact]
public void Should_have_correct_index_itemscontainer()
{
ObservableCollection<string> items = new ObservableCollection<string>();
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 0,
IsVirtualized = false,
};
target.ApplyTemplate();
target.SelectedIndex = 0;
items.Add("foo");
target.SelectedIndex = 0;
Assert.Equal(1, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(1, target.Panel.Children.Count);
items.Add("bar");
Assert.Equal(1, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(1, target.Panel.Children.Count);
target.SelectedIndex = 1;
Assert.Equal(2, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(2, target.Panel.Children.Count);
Assert.Equal(0, target.ItemContainerGenerator.Containers.First().Index);
items.Remove(items[0]);
Assert.Equal(1, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(1, target.Panel.Children.Count);
Assert.Equal(1, target.ItemContainerGenerator.Containers.First().Index);
items.Remove(items[0]);
Assert.Equal(0, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(0, target.Panel.Children.Count);
}
private class TestItem : ContentControl
{
}

52
tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -8,6 +8,7 @@ using Perspex.Collections;
using Perspex.Controls.Presenters;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Input;
using Perspex.Interactivity;
using Perspex.Markup.Xaml.Data;
using Perspex.UnitTests;
@ -604,6 +605,57 @@ namespace Perspex.Controls.UnitTests.Primitives
Assert.Equal(0, root.SelectedIndex);
}
[Fact]
public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement()
{
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz " },
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
var panel = target.Presenter.Panel;
Assert.Equal(
KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel),
panel.Children[1]);
}
[Fact]
public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement()
{
var items = new ObservableCollection<string>(new[] { "Foo", "Bar", "Baz " });
var target = new ListBox
{
Template = Template(),
Items = items,
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
items.RemoveAt(1);
var panel = target.Presenter.Panel;
Assert.Null(KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel));
}
private FuncControlTemplate Template()
{

49
tests/Perspex.Input.UnitTests/InputElement_Focus.cs

@ -0,0 +1,49 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Controls;
using Perspex.UnitTests;
using Xunit;
namespace Perspex.Input.UnitTests
{
public class InputElement_Focus
{
[Fact]
public void Focus_Should_Set_FocusManager_Current()
{
Button target;
using (UnitTestApplication.Start(TestServices.RealFocus))
{
var root = new TestRoot
{
Child = target = new Button()
};
target.Focus();
Assert.Same(target, FocusManager.Instance.Current);
}
}
[Fact]
public void Focus_Should_Be_Cleared_When_Control_Is_Removed_From_VisualTree()
{
Button target;
using (UnitTestApplication.Start(TestServices.RealFocus))
{
var root = new TestRoot
{
Child = target = new Button()
};
target.Focus();
root.Child = null;
Assert.Null(FocusManager.Instance.Current);
}
}
}
}

5
tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj

@ -60,6 +60,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="InputElement_Focus.cs" />
<Compile Include="InputElement_HitTesting.cs" />
<Compile Include="KeyboardNavigationTests_Arrows.cs" />
<Compile Include="KeyboardNavigationTests_Tab.cs" />
@ -103,6 +104,10 @@
<Project>{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.UnitTests\Perspex.UnitTests.csproj">
<Project>{88060192-33D5-4932-B0F9-8BD2763E857D}</Project>
<Name>Perspex.UnitTests</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />

3
tests/Perspex.UnitTests/TestRoot.cs

@ -3,6 +3,7 @@
using System;
using Perspex.Controls;
using Perspex.Input;
using Perspex.Layout;
using Perspex.Platform;
using Perspex.Rendering;
@ -10,7 +11,7 @@ using Perspex.Styling;
namespace Perspex.UnitTests
{
public class TestRoot : Decorator, ILayoutRoot, INameScope, IRenderRoot, IStyleRoot
public class TestRoot : Decorator, IFocusScope, ILayoutRoot, INameScope, IRenderRoot, IStyleRoot
{
private readonly NameScope _nameScope = new NameScope();

15
tests/Perspex.UnitTests/TestServices.cs

@ -36,12 +36,19 @@ namespace Perspex.UnitTests
public static readonly TestServices MockThreadingInterface = new TestServices(
threadingInterface: Mock.Of<IPlatformThreadingInterface>(x => x.CurrentThreadIsLoopThread == true));
public static readonly TestServices RealFocus = new TestServices(
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice(),
inputManager: new InputManager());
public static readonly TestServices RealStyler = new TestServices(
styler: new Styler());
public TestServices(
IAssetLoader assetLoader = null,
IFocusManager focusManager = null,
IInputManager inputManager = null,
Func<IKeyboardDevice> keyboardDevice = null,
ILayoutManager layoutManager = null,
IPclPlatformWrapper platformWrapper = null,
IPlatformRenderInterface renderInterface = null,
@ -53,7 +60,9 @@ namespace Perspex.UnitTests
IWindowingPlatform windowingPlatform = null)
{
AssetLoader = assetLoader;
FocusManager = focusManager;
InputManager = inputManager;
KeyboardDevice = keyboardDevice;
LayoutManager = layoutManager;
PlatformWrapper = platformWrapper;
RenderInterface = renderInterface;
@ -67,6 +76,8 @@ namespace Perspex.UnitTests
public IAssetLoader AssetLoader { get; }
public IInputManager InputManager { get; }
public IFocusManager FocusManager { get; }
public Func<IKeyboardDevice> KeyboardDevice { get; }
public ILayoutManager LayoutManager { get; }
public IPclPlatformWrapper PlatformWrapper { get; }
public IPlatformRenderInterface RenderInterface { get; }
@ -79,7 +90,9 @@ namespace Perspex.UnitTests
public TestServices With(
IAssetLoader assetLoader = null,
IFocusManager focusManager = null,
IInputManager inputManager = null,
Func<IKeyboardDevice> keyboardDevice = null,
ILayoutManager layoutManager = null,
IPclPlatformWrapper platformWrapper = null,
IPlatformRenderInterface renderInterface = null,
@ -92,7 +105,9 @@ namespace Perspex.UnitTests
{
return new TestServices(
assetLoader: assetLoader ?? AssetLoader,
focusManager: focusManager ?? FocusManager,
inputManager: inputManager ?? InputManager,
keyboardDevice: keyboardDevice ?? KeyboardDevice,
layoutManager: layoutManager ?? LayoutManager,
platformWrapper: platformWrapper ?? PlatformWrapper,
renderInterface: renderInterface ?? RenderInterface,

2
tests/Perspex.UnitTests/UnitTestApplication.cs

@ -40,8 +40,10 @@ namespace Perspex.UnitTests
{
PerspexLocator.CurrentMutable
.Bind<IAssetLoader>().ToConstant(Services.AssetLoader)
.Bind<IFocusManager>().ToConstant(Services.FocusManager)
.BindToSelf<IGlobalStyles>(this)
.Bind<IInputManager>().ToConstant(Services.InputManager)
.Bind<IKeyboardDevice>().ToConstant(Services.KeyboardDevice?.Invoke())
.Bind<ILayoutManager>().ToConstant(Services.LayoutManager)
.Bind<IPclPlatformWrapper>().ToConstant(Services.PlatformWrapper)
.Bind<IPlatformRenderInterface>().ToConstant(Services.RenderInterface)

Loading…
Cancel
Save