Browse Source

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

pull/534/head
Steven Kirk 11 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"] [submodule "src/Markup/Perspex.Markup.Xaml/OmniXAML"]
path = src/Markup/Perspex.Markup.Xaml/OmniXAML path = src/Markup/Perspex.Markup.Xaml/OmniXAML
url = https://github.com/Perspex/OmniXAML.git 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}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup.Xaml", "src\Markup\Perspex.Markup.Xaml\Perspex.Markup.Xaml.csproj", "{3E53A01A-B331-47F3-B828-4A5717E77A24}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{9B9E3891-2366-4253-A952-D08BCEB71098}" 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication", "samples\TestApplication\TestApplication.csproj", "{E3A1060B-50D0-44E8-88B6-F44EF2E5BD72}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication", "samples\TestApplication\TestApplication.csproj", "{E3A1060B-50D0-44E8-88B6-F44EF2E5BD72}"
ProjectSection(ProjectDependencies) = postProject ProjectSection(ProjectDependencies) = postProject

1
appveyor.yml

@ -20,6 +20,7 @@ configuration:
- Release - Release
after_test: 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 - ps: nuget\build-appveyor.ps1
artifacts: artifacts:

9
samples/BindingTest/MainWindow.xaml

@ -68,5 +68,14 @@
</ContentControl> </ContentControl>
</StackPanel> </StackPanel>
</TabItem> </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> </TabControl>
</Window> </Window>

17
samples/BindingTest/ViewModels/MainWindowViewModel.cs

@ -2,6 +2,7 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using ReactiveUI; using ReactiveUI;
using System.Reactive.Linq;
namespace BindingTest.ViewModels namespace BindingTest.ViewModels
{ {
@ -10,6 +11,7 @@ namespace BindingTest.ViewModels
private string _booleanString = "True"; private string _booleanString = "True";
private double _doubleValue = 5.0; private double _doubleValue = 5.0;
private string _stringValue = "Simple Binding"; private string _stringValue = "Simple Binding";
private bool _booleanFlag = false;
public MainWindowViewModel() public MainWindowViewModel()
{ {
@ -28,6 +30,13 @@ namespace BindingTest.ViewModels
var r = new Random(); var r = new Random();
Items.Move(r.Next(Items.Count), 1); Items.Move(r.Next(Items.Count), 1);
}); });
StringValueCommand = ReactiveCommand.Create();
StringValueCommand.Subscribe(param =>
{
BooleanFlag = !BooleanFlag;
StringValue = param.ToString();
});
} }
public ObservableCollection<TestItem> Items { get; } public ObservableCollection<TestItem> Items { get; }
@ -51,5 +60,13 @@ namespace BindingTest.ViewModels
get { return _stringValue; } get { return _stringValue; }
set { this.RaiseAndSetIfChanged(ref _stringValue, value); } 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 System.Reflection;
using Perspex.Platform; 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 namespace ControlCatalog
{ {
internal class Program internal class Program
@ -34,7 +16,6 @@ namespace ControlCatalog
new App() new App()
.ConfigureRenderSystem(args) .ConfigureRenderSystem(args)
.UseAssetAssembly(typeof(App).Assembly)
.LoadFromXaml() .LoadFromXaml()
.RunWithMainWindow<MainWindow>(); .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> <TextBlock Classes="h2">A panel which lays out its children by explicit coordinates</TextBlock>
<Canvas Background="Yellow" Width="300" Height="400"> <Canvas Background="Yellow" Width="300" Height="400">
<Rectangle Fill="Blue" Width="63" Height="41" Canvas.Left="40" Canvas.Top="31"/> <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="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"/> <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"/> <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> </Canvas>
</StackPanel> </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)); CollapseNodesCommand.Subscribe(_ => ExpandNodes(false));
ExpandNodesCommand = ReactiveCommand.Create(); ExpandNodesCommand = ReactiveCommand.Create();
ExpandNodesCommand.Subscribe(_ => ExpandNodes(true)); ExpandNodesCommand.Subscribe(_ => ExpandNodes(true));
@ -77,6 +81,15 @@ namespace XamlTestApplication.ViewModels
ofd.ShowAsync(); ofd.ShowAsync();
}); });
shell = ShellViewModel.Instance;
}
private ShellViewModel shell;
public ShellViewModel Shell
{
get { return shell; }
set { shell = value; }
} }
public List<TestItem> Items { get; } public List<TestItem> Items { get; }

78
samples/XamlTestApplicationPcl/Views/MainWindow.xaml

@ -316,6 +316,84 @@
<local:TestScrollable/> <local:TestScrollable/>
</ScrollViewer> </ScrollViewer>
</TabItem> </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> </TabControl>
</Grid> </Grid>
</Window> </Window>

1
samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj

@ -42,6 +42,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestScrollable.cs" /> <Compile Include="TestScrollable.cs" />
<Compile Include="ViewModels\EditorViewModel.cs" />
<Compile Include="ViewModels\MainWindowViewModel.cs" /> <Compile Include="ViewModels\MainWindowViewModel.cs" />
<Compile Include="ViewModels\TestItem.cs" /> <Compile Include="ViewModels\TestItem.cs" />
<Compile Include="ViewModels\TestNode.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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Glass;
using OmniXaml.Builder; using OmniXaml.Builder;
using OmniXaml.Typing; using OmniXaml.Typing;
using Perspex.Controls; using Perspex.Controls;
@ -14,6 +13,7 @@ using Perspex.Media;
using Perspex.Metadata; using Perspex.Metadata;
using Perspex.Platform; using Perspex.Platform;
using Perspex.Styling; using Perspex.Styling;
using Glass.Core;
namespace Perspex.Markup.Xaml.Context 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.ObjectAssembler.Commands;
using OmniXaml.TypeConversion; using OmniXaml.TypeConversion;
using Perspex.Markup.Xaml.Templates; using Perspex.Markup.Xaml.Templates;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Perspex.Markup.Xaml.Context namespace Perspex.Markup.Xaml.Context
{ {
@ -27,7 +29,8 @@ namespace Perspex.Markup.Xaml.Context
mapping.Map<TreeDataTemplate>(x => x.Content, new TemplateLoader()); mapping.Map<TreeDataTemplate>(x => x.Content, new TemplateLoader());
mapping.Map<ItemsPanelTemplate>(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); assembler = new ObjectAssembler(typeSource, valueContext, settings);
objectAssembler = new TemplateHostingObjectAssembler(assembler, mapping); objectAssembler = new TemplateHostingObjectAssembler(assembler, mapping);
} }
@ -55,5 +58,21 @@ namespace Perspex.Markup.Xaml.Context
{ {
objectAssembler.OverrideInstance(instance); 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(RelativePoint), new RelativePointTypeConverter());
RegisterTypeConverter(typeof(RelativeRect), new RelativeRectTypeConverter()); RegisterTypeConverter(typeof(RelativeRect), new RelativeRectTypeConverter());
RegisterTypeConverter(typeof(RowDefinitions), new RowDefinitionsTypeConverter()); RegisterTypeConverter(typeof(RowDefinitions), new RowDefinitionsTypeConverter());
RegisterTypeConverter(typeof(Size), new SizeTypeConverter());
RegisterTypeConverter(typeof(Selector), new SelectorTypeConverter()); RegisterTypeConverter(typeof(Selector), new SelectorTypeConverter());
RegisterTypeConverter(typeof(SolidColorBrush), new BrushTypeConverter()); RegisterTypeConverter(typeof(SolidColorBrush), new BrushTypeConverter());
RegisterTypeConverter(typeof(Thickness), new ThicknessTypeConverter()); 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Glass;
using OmniXaml; using OmniXaml;
using OmniXaml.Typing; using OmniXaml.Typing;
using Perspex.Markup.Xaml.Data; using Glass.Core;
namespace Perspex.Markup.Xaml.Context 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) public object ConvertFrom(IValueContext context, CultureInfo culture, object value)
{ {
var uri = new Uri((string)value, UriKind.RelativeOrAbsolute); var uri = new Uri((string)value, UriKind.RelativeOrAbsolute);
var baseUri = GetBaseUri(context);
var scheme = uri.IsAbsoluteUri ? uri.Scheme : "file"; var scheme = uri.IsAbsoluteUri ? uri.Scheme : "file";
switch (scheme) switch (scheme)
@ -32,7 +33,7 @@ namespace Perspex.Markup.Xaml.Converters
return new Bitmap((string)value); return new Bitmap((string)value);
default: default:
var assets = PerspexLocator.Current.GetService<IAssetLoader>(); 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(); 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;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Glass;
using OmniXaml; using OmniXaml;
using Glass.Core;
namespace Perspex.Markup.Xaml.MarkupExtensions namespace Perspex.Markup.Xaml.MarkupExtensions
{ {
@ -24,7 +24,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
public override object ProvideValue(MarkupExtensionContext markupExtensionContext) public override object ProvideValue(MarkupExtensionContext markupExtensionContext)
{ {
var typeRepository = markupExtensionContext.TypeRepository; var typeRepository = markupExtensionContext.ValueContext.TypeRepository;
var typeAndMember = GetTypeAndMember(Identifier); var typeAndMember = GetTypeAndMember(Identifier);
var prefixAndType = GetPrefixAndType(typeAndMember.Item1); var prefixAndType = GetPrefixAndType(typeAndMember.Item1);
var xamlType = typeRepository.GetByPrefix(prefixAndType.Item1, prefixAndType.Item2); 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Glass;
using OmniXaml; using OmniXaml;
using OmniXaml.Attributes; using OmniXaml.Attributes;
using OmniXaml.Typing; using OmniXaml.Typing;
using Glass.Core;
namespace Perspex.Markup.Xaml.MarkupExtensions namespace Perspex.Markup.Xaml.MarkupExtensions
{ {
@ -43,7 +43,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
return Type; 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\PerspexListTypeConverter.cs" />
<Compile Include="Converters\PerspexPropertyTypeConverter.cs" /> <Compile Include="Converters\PerspexPropertyTypeConverter.cs" />
<Compile Include="Converters\PointsListTypeConverter.cs" /> <Compile Include="Converters\PointsListTypeConverter.cs" />
<Compile Include="Converters\SizeTypeConverter.cs" />
<Compile Include="Converters\PointTypeConverter.cs" /> <Compile Include="Converters\PointTypeConverter.cs" />
<Compile Include="Converters\RelativePointTypeConverter.cs" /> <Compile Include="Converters\RelativePointTypeConverter.cs" />
<Compile Include="Converters\RelativeRectTypeConverter.cs" /> <Compile Include="Converters\RelativeRectTypeConverter.cs" />
@ -81,22 +82,22 @@
<Compile Include="Data\SourceBindingEndpoint.cs" /> <Compile Include="Data\SourceBindingEndpoint.cs" />
<Compile Include="Data\StyleResourceBinding.cs" /> <Compile Include="Data\StyleResourceBinding.cs" />
<Compile Include="Data\TargetBindingEndpoint.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\StyleResourceExtension.cs" />
<Compile Include="MarkupExtensions\BindingExtension.cs" /> <Compile Include="MarkupExtensions\BindingExtension.cs" />
<Compile Include="MarkupExtensions\RelativeSourceExtension.cs" /> <Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
<Compile Include="MarkupExtensions\StaticExtension.cs" /> <Compile Include="MarkupExtensions\StaticExtension.cs" />
<Compile Include="MarkupExtensions\TemplateBindingExtension.cs" /> <Compile Include="MarkupExtensions\TemplateBindingExtension.cs" />
<Compile Include="MarkupExtensions\TypeExtension.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\ContentPropertyAttribute.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Attributes\DependsOnAttribute.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Attributes\DependsOnAttribute.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Attributes\XmlnsDefinitionAttribute.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Attributes\XmlnsDefinitionAttribute.cs" />
@ -275,6 +276,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />
<None Include="glass\Glass.Core\Glass.Core.nuspec" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -333,6 +335,9 @@
<HintPath>..\..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll</HintPath> <HintPath>..\..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="glass\Glass.Core\Glass.Core.csproj" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> <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. <!-- 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. 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 Controls;
using Data; using Data;
using OmniXaml.ObjectAssembler; using OmniXaml.ObjectAssembler;
using System.Linq;
/// <summary> /// <summary>
/// Loads XAML for a perspex application. /// Loads XAML for a perspex application.
/// </summary> /// </summary>
@ -23,6 +23,7 @@ namespace Perspex.Markup.Xaml
{ {
private static PerspexParserFactory s_parserFactory; private static PerspexParserFactory s_parserFactory;
private static IInstanceLifeCycleListener s_lifeCycleListener = new PerspexLifeCycleListener(); private static IInstanceLifeCycleListener s_lifeCycleListener = new PerspexLifeCycleListener();
private static Stack<Uri> s_uriStack = new Stack<Uri>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PerspexXamlLoader"/> class. /// 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> /// <summary>
/// Loads the XAML into a Perspex component. /// Loads the XAML into a Perspex component.
/// </summary> /// </summary>
@ -84,7 +97,7 @@ namespace Perspex.Markup.Xaml
{ {
var initialize = rootInstance as ISupportInitialize; var initialize = rootInstance as ISupportInitialize;
initialize?.BeginInit(); initialize?.BeginInit();
return Load(stream, rootInstance); return Load(stream, rootInstance, uri);
} }
} }
} }
@ -96,11 +109,14 @@ namespace Perspex.Markup.Xaml
/// Loads XAML from a URI. /// Loads XAML from a URI.
/// </summary> /// </summary>
/// <param name="uri">The URI of the XAML file.</param> /// <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"> /// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded. /// The optional instance into which the XAML should be loaded.
/// </param> /// </param>
/// <returns>The loaded object.</returns> /// <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); Contract.Requires<ArgumentNullException>(uri != null);
@ -112,9 +128,9 @@ namespace Perspex.Markup.Xaml
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); "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"> /// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded. /// The optional instance into which the XAML should be loaded.
/// </param> /// </param>
/// <param name="uri">The URI of the XAML</param>
/// <returns>The loaded object.</returns> /// <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, if (uri != null)
InstanceLifeCycleListener = s_lifeCycleListener, {
}); 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() private static PerspexParserFactory GetParserFactory()

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

@ -11,8 +11,20 @@ namespace Perspex.Markup.Xaml.Styling
/// </summary> /// </summary>
public class StyleInclude : IStyle public class StyleInclude : IStyle
{ {
private Uri _baseUri;
private IStyle _loaded; 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> /// <summary>
/// Gets or sets the source URL. /// Gets or sets the source URL.
/// </summary> /// </summary>
@ -28,7 +40,7 @@ namespace Perspex.Markup.Xaml.Styling
if (_loaded == null) if (_loaded == null)
{ {
var loader = new PerspexXamlLoader(); var loader = new PerspexXamlLoader();
_loaded = (IStyle)loader.Load(Source); _loaded = (IStyle)loader.Load(Source, _baseUri);
} }
return _loaded; 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> /// </remarks>
public class PerspexObject : IPerspexObject, IPerspexObjectDebug, INotifyPropertyChanged, IPriorityValueOwner 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> /// <summary>
/// The parent object that inherited values are inherited from. /// The parent object that inherited values are inherited from.
/// </summary> /// </summary>
@ -45,6 +34,12 @@ namespace Perspex
private readonly Dictionary<PerspexProperty, PriorityValue> _values = private readonly Dictionary<PerspexProperty, PriorityValue> _values =
new Dictionary<PerspexProperty, PriorityValue>(); 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> /// <summary>
/// Event handler for <see cref="INotifyPropertyChanged"/> implementation. /// Event handler for <see cref="INotifyPropertyChanged"/> implementation.
/// </summary> /// </summary>
@ -403,22 +398,27 @@ namespace Perspex
IDisposable subscription = null; IDisposable subscription = null;
IDisposable validationSubcription = null; IDisposable validationSubcription = null;
if (_directBindings == null)
{
_directBindings = new List<IDisposable>();
}
subscription = source subscription = source
.Where(x => !(x is ValidationStatus)) .Where(x => !(x is ValidationStatus))
.Select(x => CastOrDefault(x, property.PropertyType)) .Select(x => CastOrDefault(x, property.PropertyType))
.Do(_ => { }, () => s_directBindings.Remove(subscription)) .Do(_ => { }, () => _directBindings.Remove(subscription))
.Subscribe(x => DirectBindingSet(property, x)); .Subscribe(x => DirectBindingSet(property, x));
validationSubcription = source validationSubcription = source
.OfType<ValidationStatus>() .OfType<ValidationStatus>()
.Subscribe(x => DataValidation(property, x)); .Subscribe(x => DataValidation(property, x));
s_directBindings.Add(subscription); _directBindings.Add(subscription);
return Disposable.Create(() => return Disposable.Create(() =>
{ {
validationSubcription.Dispose(); validationSubcription.Dispose();
subscription.Dispose(); subscription.Dispose();
s_directBindings.Remove(subscription); _directBindings.Remove(subscription);
}); });
} }
else else

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

@ -21,22 +21,27 @@ namespace Perspex.Platform
/// <param name="asm"></param> /// <param name="asm"></param>
void SetDefaultAssembly(Assembly asm); void SetDefaultAssembly(Assembly asm);
/// <summary> /// <summary>
/// Checks if an asset with the specified URI exists. /// Checks if an asset with the specified URI exists.
/// </summary> /// </summary>
/// <param name="uri">The URI.</param> /// <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> /// <returns>True if the asset could be found; otherwise false.</returns>
bool Exists(Uri uri); bool Exists(Uri uri, Uri baseUri = null);
/// <summary> /// <summary>
/// Opens the resource with the requested URI. /// Opens the resource with the requested URI.
/// </summary> /// </summary>
/// <param name="uri">The URI.</param> /// <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> /// <returns>A stream containing the resource contents.</returns>
/// <exception cref="FileNotFoundException"> /// <exception cref="FileNotFoundException">
/// The resource was not found. /// The resource was not found.
/// </exception> /// </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/> /// <inheritdoc/>
public virtual IEnumerable<ItemContainer> RemoveRange(int startingIndex, int count) public virtual IEnumerable<ItemContainer> RemoveRange(int startingIndex, int count)
{ {
var result = _containers.GetRange(startingIndex, count); List<ItemContainer> result = new List<ItemContainer>();
_containers.RemoveRange(startingIndex, count);
Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result)); if (startingIndex < _containers.Count)
{
result.AddRange(_containers.GetRange(startingIndex, count));
_containers.RemoveRange(startingIndex, count);
Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
}
return result; return result;
} }

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

@ -6,6 +6,7 @@ using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Perspex.Animation; using Perspex.Animation;
using Perspex.Controls.Generators;
using Perspex.Controls.Primitives; using Perspex.Controls.Primitives;
using Perspex.Controls.Utils; using Perspex.Controls.Utils;
using Perspex.Data; using Perspex.Data;
@ -103,7 +104,19 @@ namespace Perspex.Controls.Presenters
/// <inheritdoc/> /// <inheritdoc/>
protected override void ItemsChanged(NotifyCollectionChangedEventArgs e) 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> /// <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/> /// <inheritdoc/>
protected override void OnDataContextChanging() 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) protected override void OnClick(RoutedEventArgs e)
{ {
Toggle(); Toggle();
base.OnClick(e);
} }
protected virtual void Toggle() protected virtual void Toggle()

11
src/Perspex.Input/FocusManager.cs

@ -39,7 +39,7 @@ namespace Perspex.Input
/// <summary> /// <summary>
/// Gets the currently focused <see cref="IInputElement"/>. /// Gets the currently focused <see cref="IInputElement"/>.
/// </summary> /// </summary>
public IInputElement Current => KeyboardDevice.Instance.FocusedElement; public IInputElement Current => KeyboardDevice.Instance?.FocusedElement;
/// <summary> /// <summary>
/// Gets the current focus scope. /// Gets the current focus scope.
@ -82,9 +82,12 @@ namespace Perspex.Input
if (_focusScopes.TryGetValue(scope, out element)) if (_focusScopes.TryGetValue(scope, out element))
{ {
Focus(element, method); 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) 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) if (sender == e.Source)
{ {
var ev = (PointerPressedEventArgs)e; 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)) if (element == null || !CanFocus(element))
{ {

10
src/Perspex.Input/Gestures.cs

@ -18,7 +18,7 @@ namespace Perspex.Input
RoutingStrategies.Bubble, RoutingStrategies.Bubble,
typeof(Gestures)); typeof(Gestures));
private static IInteractive s_lastPress; private static WeakReference s_lastPress;
static Gestures() static Gestures()
{ {
@ -34,9 +34,9 @@ namespace Perspex.Input
if (e.ClickCount <= 1) 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)); e.Source.RaiseEvent(new RoutedEventArgs(DoubleTappedEvent));
} }
@ -49,9 +49,9 @@ namespace Perspex.Input
{ {
var e = (PointerReleasedEventArgs)ev; 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/> /// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTree(e); base.OnDetachedFromVisualTreeCore(e);
if (IsFocused) if (IsFocused)
{ {
@ -387,9 +387,9 @@ namespace Perspex.Input
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTreeCore(e);
UpdateIsEnabledCore(); 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> /// <summary>
/// Gets the platform-specific implementation of the geometry. /// Gets the platform-specific implementation of the geometry.
/// </summary> /// </summary>
public IGeometryImpl PlatformImpl public virtual IGeometryImpl PlatformImpl
{ {
get; get;
protected set; 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="Matrix.cs" />
<Compile Include="Media\AlignmentY.cs" /> <Compile Include="Media\AlignmentY.cs" />
<Compile Include="Media\AlignmentX.cs" /> <Compile Include="Media\AlignmentX.cs" />
<Compile Include="Media\ArcSegment.cs" />
<Compile Include="Media\BezierSegment .cs" />
<Compile Include="Media\Brush.cs" /> <Compile Include="Media\Brush.cs" />
<Compile Include="Media\Brushes.cs" /> <Compile Include="Media\Brushes.cs" />
<Compile Include="Media\BrushMappingMode.cs" /> <Compile Include="Media\BrushMappingMode.cs" />
<Compile Include="Media\Color.cs" /> <Compile Include="Media\Color.cs" />
<Compile Include="Media\Colors.cs" /> <Compile Include="Media\Colors.cs" />
<Compile Include="Media\TransformGroup.cs" />
<Compile Include="Media\DashStyle.cs" /> <Compile Include="Media\DashStyle.cs" />
<Compile Include="Media\DrawingContext.cs" /> <Compile Include="Media\DrawingContext.cs" />
<Compile Include="Media\FillRule.cs" /> <Compile Include="Media\FillRule.cs" />
@ -75,9 +78,15 @@
<Compile Include="Media\IBrush.cs" /> <Compile Include="Media\IBrush.cs" />
<Compile Include="Media\ISolidColorBrush.cs" /> <Compile Include="Media\ISolidColorBrush.cs" />
<Compile Include="Media\LineGeometry.cs" /> <Compile Include="Media\LineGeometry.cs" />
<Compile Include="Media\LineSegment.cs" />
<Compile Include="Media\Mutable\SolidColorBrush.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\PenLineJoin.cs" />
<Compile Include="Media\PolylineGeometry.cs" /> <Compile Include="Media\PolylineGeometry.cs" />
<Compile Include="Media\QuadraticBezierSegment .cs" />
<Compile Include="Media\RadialGradientBrush.cs" /> <Compile Include="Media\RadialGradientBrush.cs" />
<Compile Include="Media\LinearGradientBrush.cs" /> <Compile Include="Media\LinearGradientBrush.cs" />
<Compile Include="Media\MediaExtensions.cs" /> <Compile Include="Media\MediaExtensions.cs" />

25
src/Perspex.SceneGraph/Size.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq;
namespace Perspex namespace Perspex
{ {
@ -128,6 +129,28 @@ namespace Perspex
return new Size(size._width - toSubstract._width, size._height - toSubstract._height); 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> /// <summary>
/// Constrains the size. /// Constrains the size.
/// </summary> /// </summary>
@ -227,4 +250,4 @@ namespace Perspex
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", _width, _height); return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", _width, _height);
} }
} }
} }

187
src/Shared/PlatformSupport/AssetLoader.cs

@ -15,26 +15,6 @@ namespace Perspex.Shared.PlatformSupport
/// </summary> /// </summary>
public class AssetLoader : IAssetLoader 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 private static readonly Dictionary<string, AssemblyDescriptor> AssemblyNameCache
= new Dictionary<string, AssemblyDescriptor>(); = new Dictionary<string, AssemblyDescriptor>();
@ -53,7 +33,94 @@ namespace Perspex.Shared.PlatformSupport
_defaultAssembly = new AssemblyDescriptor(assembly); _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) if (name == null)
{ {
@ -82,13 +149,20 @@ namespace Perspex.Shared.PlatformSupport
return rv; 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 Assembly _asm;
private readonly string _name; private readonly string _name;
@ -104,69 +178,24 @@ namespace Perspex.Shared.PlatformSupport
return _asm.GetManifestResourceStream(_name); 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('?') Assembly = assembly;
.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;
#if __IOS__ if (assembly != null)
// 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"); 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> public Assembly Assembly { get; }
/// Opens the resource with the requested URI. public Dictionary<string, IAssetDescriptor> Resources { get; }
/// </summary> public string Name { get; }
/// <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();
} }
} }
} }

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

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

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

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

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

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

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

@ -57,7 +57,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="SkiaSharp, Version=1.49.0.0, Culture=neutral, processorArchitecture=MSIL"> <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> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
@ -116,12 +116,12 @@
<Import Project="..\Perspex.Skia\Perspex.Skia.projitems" Label="Shared" /> <Import Project="..\Perspex.Skia\Perspex.Skia.projitems" Label="Shared" />
<Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" /> <Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <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"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <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> <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> </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> </Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- 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. 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"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="SkiaSharp" version="1.49.2.0-beta" targetFramework="net45" /> <package id="SkiaSharp" version="1.49.2.1-beta" targetFramework="net45" />
</packages> </packages>

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

@ -41,7 +41,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="SkiaSharp, Version=1.49.0.0, Culture=neutral, processorArchitecture=MSIL"> <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> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />

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

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

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

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

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

@ -65,5 +65,10 @@ namespace Perspex.Skia
case TextAlignment.Right: return SKTextAlign.Right; 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.Dispose();
_transformedPath = null; _transformedPath = null;
} }
// TODO: SkiaSharp does not expose Transform yet!!! if (!Transform.IsIdentity)
//if (!Transform.IsIdentity) {
//{ _transformedPath = new SKPath(_path);
// _transformedPath = new SKPath(); _transformedPath.Transform(Transform.ToSKMatrix());
// _path.Transform(Transform.ToSKMatrix(), _transformedPath); }
//}
} }
public IStreamGeometryImpl Clone() public IStreamGeometryImpl Clone()
{ {
// TODO: there is no SKPath.Clone yet!!!!!!!!!!!!! return new StreamGeometryImpl
// {
//return new StreamGeometryImpl _path = _path?.Clone(),
//{ _transformedPath = _transformedPath?.Clone(),
// _path = _path?.Clone(), _transform = Transform,
// _transformedPath = _transformedPath?.Clone(), Bounds = Bounds
// _transform = Transform, };
// Bounds = Bounds
//};
return this; // this will probably end bad!!
} }
public IStreamGeometryContextImpl Open() public IStreamGeometryContextImpl Open()
@ -94,15 +89,6 @@ namespace Perspex.Skia
public void Dispose() 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; SKRect rc;
_path.GetBounds(out rc); _path.GetBounds(out rc);
_geometryImpl.ApplyTransform(); _geometryImpl.ApplyTransform();

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

@ -5,7 +5,6 @@ BitmapImpl
- Save - Save
DrawingContextImpl DrawingContextImpl
- DrawRoundRect is not properly implemented due to lack of support in SkiaSharp
- Alpha support missing as SkiaSharp does not expose this - Alpha support missing as SkiaSharp does not expose this
- Gradient Shader caching? - Gradient Shader caching?
- TileBrushes - TileBrushes
@ -18,12 +17,6 @@ RenderTarget
- Figure out a cleaner implementation across all platforms - Figure out a cleaner implementation across all platforms
- HW acceleration - 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 App Bootstrapping
- Cleanup the testapplications across all platforms - Cleanup the testapplications across all platforms
- Add a cleaner Fluent API for the subsystems - 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 Perspex.Platform;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using SweepDirection = SharpDX.Direct2D1.SweepDirection; using SweepDirection = SharpDX.Direct2D1.SweepDirection;
using D2D = SharpDX.Direct2D1;
namespace Perspex.Direct2D1.Media namespace Perspex.Direct2D1.Media
{ {
@ -24,7 +25,7 @@ namespace Perspex.Direct2D1.Media
bool isLargeArc, bool isLargeArc,
Perspex.Media.SweepDirection sweepDirection) Perspex.Media.SweepDirection sweepDirection)
{ {
_sink.AddArc(new ArcSegment _sink.AddArc(new D2D.ArcSegment
{ {
Point = point.ToSharpDX(), Point = point.ToSharpDX(),
Size = size.ToSharpDX(), Size = size.ToSharpDX(),
@ -41,7 +42,7 @@ namespace Perspex.Direct2D1.Media
public void CubicBezierTo(Point point1, Point point2, Point point3) public void CubicBezierTo(Point point1, Point point2, Point point3)
{ {
_sink.AddBezier(new BezierSegment _sink.AddBezier(new D2D.BezierSegment
{ {
Point1 = point1.ToSharpDX(), Point1 = point1.ToSharpDX(),
Point2 = point2.ToSharpDX(), Point2 = point2.ToSharpDX(),
@ -51,7 +52,7 @@ namespace Perspex.Direct2D1.Media
public void QuadraticBezierTo(Point control, Point dest) public void QuadraticBezierTo(Point control, Point dest)
{ {
_sink.AddQuadraticBezier(new QuadraticBezierSegment _sink.AddQuadraticBezier(new D2D.QuadraticBezierSegment
{ {
Point1 = control.ToSharpDX(), Point1 = control.ToSharpDX(),
Point2 = dest.ToSharpDX() Point2 = dest.ToSharpDX()

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

@ -1,7 +1,6 @@
// Copyright (c) The Perspex Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Media;
using Perspex.Platform; using Perspex.Platform;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using D2DGeometry = SharpDX.Direct2D1.Geometry; 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 source = new Subject<string>();
var sub = target.Bind((PerspexProperty)Class1.FooProperty, source); var sub = target.Bind((PerspexProperty)Class1.FooProperty, source);
source.OnNext("foo");
return new WeakReference(source); return new WeakReference(source);
}; };
@ -351,6 +352,7 @@ namespace Perspex.Base.UnitTests
GC.Collect(); GC.Collect();
Assert.Equal("foo", target.Foo);
Assert.True(weakSource.IsAlive); Assert.True(weakSource.IsAlive);
} }

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

@ -104,30 +104,6 @@ namespace Perspex.Controls.UnitTests
dataContexts); 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) private Control CreateListBoxTemplate(ITemplatedControl parent)
{ {
return new ScrollViewer 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.Presenters;
using Perspex.Controls.Templates; using Perspex.Controls.Templates;
using Xunit; using Xunit;
using System.Collections.ObjectModel;
namespace Perspex.Controls.UnitTests.Presenters namespace Perspex.Controls.UnitTests.Presenters
{ {
@ -116,6 +117,82 @@ namespace Perspex.Controls.UnitTests.Presenters
Assert.Equal(2, target.Panel.Children.Count); 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 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.Presenters;
using Perspex.Controls.Primitives; using Perspex.Controls.Primitives;
using Perspex.Controls.Templates; using Perspex.Controls.Templates;
using Perspex.Input;
using Perspex.Interactivity; using Perspex.Interactivity;
using Perspex.Markup.Xaml.Data; using Perspex.Markup.Xaml.Data;
using Perspex.UnitTests; using Perspex.UnitTests;
@ -604,6 +605,57 @@ namespace Perspex.Controls.UnitTests.Primitives
Assert.Equal(0, root.SelectedIndex); 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() 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> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="InputElement_Focus.cs" />
<Compile Include="InputElement_HitTesting.cs" /> <Compile Include="InputElement_HitTesting.cs" />
<Compile Include="KeyboardNavigationTests_Arrows.cs" /> <Compile Include="KeyboardNavigationTests_Arrows.cs" />
<Compile Include="KeyboardNavigationTests_Tab.cs" /> <Compile Include="KeyboardNavigationTests_Tab.cs" />
@ -103,6 +104,10 @@
<Project>{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}</Project> <Project>{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}</Project>
<Name>Perspex.Styling</Name> <Name>Perspex.Styling</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\Perspex.UnitTests\Perspex.UnitTests.csproj">
<Project>{88060192-33D5-4932-B0F9-8BD2763E857D}</Project>
<Name>Perspex.UnitTests</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />

3
tests/Perspex.UnitTests/TestRoot.cs

@ -3,6 +3,7 @@
using System; using System;
using Perspex.Controls; using Perspex.Controls;
using Perspex.Input;
using Perspex.Layout; using Perspex.Layout;
using Perspex.Platform; using Perspex.Platform;
using Perspex.Rendering; using Perspex.Rendering;
@ -10,7 +11,7 @@ using Perspex.Styling;
namespace Perspex.UnitTests 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(); 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( public static readonly TestServices MockThreadingInterface = new TestServices(
threadingInterface: Mock.Of<IPlatformThreadingInterface>(x => x.CurrentThreadIsLoopThread == true)); 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( public static readonly TestServices RealStyler = new TestServices(
styler: new Styler()); styler: new Styler());
public TestServices( public TestServices(
IAssetLoader assetLoader = null, IAssetLoader assetLoader = null,
IFocusManager focusManager = null,
IInputManager inputManager = null, IInputManager inputManager = null,
Func<IKeyboardDevice> keyboardDevice = null,
ILayoutManager layoutManager = null, ILayoutManager layoutManager = null,
IPclPlatformWrapper platformWrapper = null, IPclPlatformWrapper platformWrapper = null,
IPlatformRenderInterface renderInterface = null, IPlatformRenderInterface renderInterface = null,
@ -53,7 +60,9 @@ namespace Perspex.UnitTests
IWindowingPlatform windowingPlatform = null) IWindowingPlatform windowingPlatform = null)
{ {
AssetLoader = assetLoader; AssetLoader = assetLoader;
FocusManager = focusManager;
InputManager = inputManager; InputManager = inputManager;
KeyboardDevice = keyboardDevice;
LayoutManager = layoutManager; LayoutManager = layoutManager;
PlatformWrapper = platformWrapper; PlatformWrapper = platformWrapper;
RenderInterface = renderInterface; RenderInterface = renderInterface;
@ -67,6 +76,8 @@ namespace Perspex.UnitTests
public IAssetLoader AssetLoader { get; } public IAssetLoader AssetLoader { get; }
public IInputManager InputManager { get; } public IInputManager InputManager { get; }
public IFocusManager FocusManager { get; }
public Func<IKeyboardDevice> KeyboardDevice { get; }
public ILayoutManager LayoutManager { get; } public ILayoutManager LayoutManager { get; }
public IPclPlatformWrapper PlatformWrapper { get; } public IPclPlatformWrapper PlatformWrapper { get; }
public IPlatformRenderInterface RenderInterface { get; } public IPlatformRenderInterface RenderInterface { get; }
@ -79,7 +90,9 @@ namespace Perspex.UnitTests
public TestServices With( public TestServices With(
IAssetLoader assetLoader = null, IAssetLoader assetLoader = null,
IFocusManager focusManager = null,
IInputManager inputManager = null, IInputManager inputManager = null,
Func<IKeyboardDevice> keyboardDevice = null,
ILayoutManager layoutManager = null, ILayoutManager layoutManager = null,
IPclPlatformWrapper platformWrapper = null, IPclPlatformWrapper platformWrapper = null,
IPlatformRenderInterface renderInterface = null, IPlatformRenderInterface renderInterface = null,
@ -92,7 +105,9 @@ namespace Perspex.UnitTests
{ {
return new TestServices( return new TestServices(
assetLoader: assetLoader ?? AssetLoader, assetLoader: assetLoader ?? AssetLoader,
focusManager: focusManager ?? FocusManager,
inputManager: inputManager ?? InputManager, inputManager: inputManager ?? InputManager,
keyboardDevice: keyboardDevice ?? KeyboardDevice,
layoutManager: layoutManager ?? LayoutManager, layoutManager: layoutManager ?? LayoutManager,
platformWrapper: platformWrapper ?? PlatformWrapper, platformWrapper: platformWrapper ?? PlatformWrapper,
renderInterface: renderInterface ?? RenderInterface, renderInterface: renderInterface ?? RenderInterface,

2
tests/Perspex.UnitTests/UnitTestApplication.cs

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

Loading…
Cancel
Save