From 9e8fec942e047d6294bde764f360e47e51573a88 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 16 Jan 2016 13:26:02 +0100 Subject: [PATCH 01/14] Log invalid binding values in PriorityValue. --- src/Perspex.Base/PerspexObject.cs | 6 ++++- src/Perspex.Base/PriorityValue.cs | 22 ++++++++++++++++--- .../Perspex.Base.UnitTests.csproj | 8 +++++++ tests/Perspex.Base.UnitTests/packages.config | 1 + 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 25e0ff48be..b95758a7e7 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -775,7 +775,11 @@ namespace Perspex validate2 = v => validate(this, v); } - PriorityValue result = new PriorityValue(property.Name, property.PropertyType, validate2); + PriorityValue result = new PriorityValue( + property.Name, + property.PropertyType, + validate2, + _propertyLog); result.Changed.Subscribe(x => { diff --git a/src/Perspex.Base/PriorityValue.cs b/src/Perspex.Base/PriorityValue.cs index 0bbdcc389e..e45eba172f 100644 --- a/src/Perspex.Base/PriorityValue.cs +++ b/src/Perspex.Base/PriorityValue.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Reactive.Subjects; using System.Text; using Perspex.Utilities; +using Serilog; namespace Perspex { @@ -54,19 +55,30 @@ namespace Perspex /// private readonly Func _validate; + /// + /// An optional logger. + /// + private ILogger _logger; + /// /// Initializes a new instance of the class. /// /// The name of the property. /// The value type. /// An optional validation function. - public PriorityValue(string name, Type valueType, Func validate = null) + /// An optional logger + public PriorityValue( + string name, + Type valueType, + Func validate = null, + ILogger logger = null) { _name = name; _valueType = valueType; _value = PerspexProperty.UnsetValue; ValuePriority = int.MaxValue; _validate = validate; + _logger = logger; } /// @@ -225,9 +237,13 @@ namespace Perspex _value = value; _changed.OnNext(Tuple.Create(old, _value)); } - else + else if (_logger != null) { - // TODO: Log error. + _logger.Error( + "Binding produced invalid value for {$Type} {$Property}: {$Value}", + _valueType, + _name, + value); } } diff --git a/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj b/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj index 078613a34d..be37a88865 100644 --- a/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj +++ b/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj @@ -40,6 +40,14 @@ 4 + + ..\..\packages\Serilog.1.5.9\lib\net45\Serilog.dll + True + + + ..\..\packages\Serilog.1.5.9\lib\net45\Serilog.FullNetFx.dll + True + ..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll diff --git a/tests/Perspex.Base.UnitTests/packages.config b/tests/Perspex.Base.UnitTests/packages.config index b87bf5fff7..00922fa4e2 100644 --- a/tests/Perspex.Base.UnitTests/packages.config +++ b/tests/Perspex.Base.UnitTests/packages.config @@ -3,6 +3,7 @@ + From 5773704f2d7fc97b2331ad50850536fb9b5c8d06 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 19 Jan 2016 20:27:55 +0100 Subject: [PATCH 02/14] Started updating DevTools to use XAML. --- samples/ControlCatalog/App.paml.cs | 12 ++ samples/ControlCatalog/ControlCatalog.csproj | 9 ++ samples/ControlCatalog/packages.config | 4 + .../Templates/TreeDataTemplate.cs | 9 +- src/Perspex.Base/PriorityValue.cs | 17 ++- .../Generators/TreeItemContainerGenerator.cs | 2 +- src/Perspex.Diagnostics/DevTools.cs | 120 ------------------ src/Perspex.Diagnostics/DevTools.paml | 17 +++ src/Perspex.Diagnostics/DevTools.paml.cs | 52 ++++++++ .../Perspex.Diagnostics.csproj | 27 +++- src/Perspex.Diagnostics/ViewLocator.cs | 2 +- .../ViewModels/DevToolsViewModel.cs | 57 +++++---- .../ViewModels/LogicalTreeViewModel.cs | 34 ----- .../ViewModels/TreeNode.cs | 7 + ...lTreeViewModel.cs => TreePageViewModel.cs} | 13 +- .../Views/LogicalTreeView.cs | 99 --------------- .../Views/{TreePage.cs => TreePage.paml.cs} | 18 ++- .../Views/TreePageView.paml | 24 ++++ .../Views/VisualTreeView.cs | 101 --------------- 19 files changed, 216 insertions(+), 408 deletions(-) create mode 100644 samples/ControlCatalog/packages.config delete mode 100644 src/Perspex.Diagnostics/DevTools.cs create mode 100644 src/Perspex.Diagnostics/DevTools.paml create mode 100644 src/Perspex.Diagnostics/DevTools.paml.cs delete mode 100644 src/Perspex.Diagnostics/ViewModels/LogicalTreeViewModel.cs rename src/Perspex.Diagnostics/ViewModels/{VisualTreeViewModel.cs => TreePageViewModel.cs} (71%) delete mode 100644 src/Perspex.Diagnostics/Views/LogicalTreeView.cs rename src/Perspex.Diagnostics/Views/{TreePage.cs => TreePage.paml.cs} (77%) create mode 100644 src/Perspex.Diagnostics/Views/TreePageView.paml delete mode 100644 src/Perspex.Diagnostics/Views/VisualTreeView.cs diff --git a/samples/ControlCatalog/App.paml.cs b/samples/ControlCatalog/App.paml.cs index b1aa671215..8a49ae20f1 100644 --- a/samples/ControlCatalog/App.paml.cs +++ b/samples/ControlCatalog/App.paml.cs @@ -5,6 +5,7 @@ using Perspex.Controls; using Perspex.Diagnostics; using Perspex.Markup.Xaml; using Perspex.Themes.Default; +using Serilog; namespace ControlCatalog { @@ -14,6 +15,7 @@ namespace ControlCatalog { RegisterServices(); InitializeSubsystems(GetPlatformId()); + InitializeLogging(); Styles = new DefaultTheme(); InitializeComponent(); } @@ -38,6 +40,16 @@ namespace ControlCatalog PerspexXamlLoader.Load(this); } + private void InitializeLogging() + { +#if DEBUG + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Error() + .WriteTo.Trace(outputTemplate: "{Message}") + .CreateLogger(); +#endif + } + private int GetPlatformId() { var args = Environment.GetCommandLineArgs(); diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 1a26286147..d3bf036619 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -36,6 +36,14 @@ + + ..\..\packages\Serilog.1.5.9\lib\net45\Serilog.dll + True + + + ..\..\packages\Serilog.1.5.9\lib\net45\Serilog.FullNetFx.dll + True + @@ -93,6 +101,7 @@ Designer + diff --git a/samples/ControlCatalog/packages.config b/samples/ControlCatalog/packages.config new file mode 100644 index 0000000000..76c9b4d2a5 --- /dev/null +++ b/samples/ControlCatalog/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs index 0ff1ec7a6d..ceabd09bb7 100644 --- a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs +++ b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Reactive.Linq; +using System.Reflection; using Perspex.Controls; using Perspex.Controls.Templates; using Perspex.Data; @@ -27,10 +28,12 @@ namespace Perspex.Markup.Xaml.Templates { if (DataType == null) { - throw new InvalidOperationException("DataTemplate must have a DataType."); + return true; + } + else + { + return DataType.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo()); } - - return DataType == data.GetType(); } public IEnumerable ItemsSelector(object item) diff --git a/src/Perspex.Base/PriorityValue.cs b/src/Perspex.Base/PriorityValue.cs index e45eba172f..b68fae1869 100644 --- a/src/Perspex.Base/PriorityValue.cs +++ b/src/Perspex.Base/PriorityValue.cs @@ -224,26 +224,29 @@ namespace Perspex /// The priority level that the value came from. private void UpdateValue(object value, int priority) { - if (TypeUtilities.TryCast(_valueType, value, out value)) + object castValue; + + if (TypeUtilities.TryCast(_valueType, value, out castValue)) { var old = _value; - if (_validate != null && value != PerspexProperty.UnsetValue) + if (_validate != null && castValue != PerspexProperty.UnsetValue) { - value = _validate(value); + castValue = _validate(castValue); } ValuePriority = priority; - _value = value; + _value = castValue; _changed.OnNext(Tuple.Create(old, _value)); } else if (_logger != null) { _logger.Error( - "Binding produced invalid value for {$Type} {$Property}: {$Value}", - _valueType, + "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", _name, - value); + _valueType, + value, + value.GetType()); } } diff --git a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs index e630924a97..046d018321 100644 --- a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs @@ -77,7 +77,7 @@ namespace Perspex.Controls.Generators result.SetValue(ContentProperty, template.Build(item)); result.SetValue(ItemsProperty, template.ItemsSelector(item)); - result.SetValue(IsExpandedProperty, template.IsExpanded(item)); + //result.SetValue(IsExpandedProperty, template.IsExpanded(item)); if (!(item is IControl)) { diff --git a/src/Perspex.Diagnostics/DevTools.cs b/src/Perspex.Diagnostics/DevTools.cs deleted file mode 100644 index a9364d2566..0000000000 --- a/src/Perspex.Diagnostics/DevTools.cs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) The Perspex Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using System.Reactive.Linq; -using Perspex.Controls; -using Perspex.Diagnostics.ViewModels; -using Perspex.Input; -using Perspex.Themes.Default; -using ReactiveUI; - -namespace Perspex.Diagnostics -{ - public class DevTools : Decorator - { - public static readonly PerspexProperty RootProperty = - PerspexProperty.Register("Root"); - - private readonly DevToolsViewModel _viewModel; - - public DevTools() - { - _viewModel = new DevToolsViewModel(); - this.GetObservable(RootProperty).Subscribe(x => _viewModel.Root = x); - - InitializeComponent(); - } - - public Control Root - { - get { return GetValue(RootProperty); } - set { SetValue(RootProperty, value); } - } - - public static IDisposable Attach(Window window) - { - return window.AddHandler( - KeyDownEvent, - WindowPreviewKeyDown, - Interactivity.RoutingStrategies.Tunnel); - } - - private static void WindowPreviewKeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.F12) - { - Window window = new Window - { - Width = 1024, - Height = 512, - Content = new DevTools - { - Root = (Window)sender, - }, - }; - - window.Show(); - } - } - - private void InitializeComponent() - { - DataTemplates.Add(new ViewLocator()); - Styles.Add(new DefaultTheme()); - - Child = new Grid - { - RowDefinitions = new RowDefinitions("*,Auto"), - Children = new Controls.Controls - { - new TabControl - { - Items = new[] - { - new TabItem - { - Header = "Logical Tree", - [!ContentControl.ContentProperty] = _viewModel.WhenAnyValue(x => x.LogicalTree), - }, - new TabItem - { - Header = "Visual Tree", - [!ContentControl.ContentProperty] = _viewModel.WhenAnyValue(x => x.VisualTree), - } - }, - }, - new StackPanel - { - Orientation = Orientation.Horizontal, - Gap = 4, - [Grid.RowProperty] = 1, - Children = new Controls.Controls - { - new TextBlock - { - Text = "Focused: " - }, - new TextBlock - { - [!TextBlock.TextProperty] = _viewModel - .WhenAnyValue(x => x.FocusedControl) - .Select(x => x?.GetType().Name ?? "(null)") - }, - new TextBlock - { - Text = "Pointer Over: " - }, - new TextBlock - { - [!TextBlock.TextProperty] = _viewModel - .WhenAnyValue(x => x.PointerOverElement) - .Select(x => x?.GetType().Name ?? "(null)") - } - } - } - } - }; - } - } -} diff --git a/src/Perspex.Diagnostics/DevTools.paml b/src/Perspex.Diagnostics/DevTools.paml new file mode 100644 index 0000000000..76f58222e1 --- /dev/null +++ b/src/Perspex.Diagnostics/DevTools.paml @@ -0,0 +1,17 @@ + + + + + + + + + + + Focused: + + Pointer Over: + + + + \ No newline at end of file diff --git a/src/Perspex.Diagnostics/DevTools.paml.cs b/src/Perspex.Diagnostics/DevTools.paml.cs new file mode 100644 index 0000000000..8b607a496f --- /dev/null +++ b/src/Perspex.Diagnostics/DevTools.paml.cs @@ -0,0 +1,52 @@ +using System; +using Perspex.Controls; +using Perspex.Controls.Templates; +using Perspex.Diagnostics.ViewModels; +using Perspex.Input; +using Perspex.Interactivity; +using Perspex.Markup.Xaml; +using ReactiveUI; + +namespace Perspex.Diagnostics +{ + public class DevTools : UserControl + { + public DevTools(IControl root) + { + this.InitializeComponent(); + this.DataContext = new DevToolsViewModel(root); + } + + public static IDisposable Attach(Window window) + { + return window.AddHandler( + KeyDownEvent, + WindowPreviewKeyDown, + RoutingStrategies.Tunnel); + } + + private static void WindowPreviewKeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.F12) + { + Window window = new Window + { + Width = 1024, + Height = 512, + Content = new DevTools((IControl)sender), + DataTemplates = new DataTemplates + { + new ViewLocator(), + } + }; + + window.Show(); + } + } + + private void InitializeComponent() + { + PerspexXamlLoader.Load(this); + } + } +} diff --git a/src/Perspex.Diagnostics/Perspex.Diagnostics.csproj b/src/Perspex.Diagnostics/Perspex.Diagnostics.csproj index 17db490d9b..e36079a39e 100644 --- a/src/Perspex.Diagnostics/Perspex.Diagnostics.csproj +++ b/src/Perspex.Diagnostics/Perspex.Diagnostics.csproj @@ -40,6 +40,14 @@ + + {3e53a01a-b331-47f3-b828-4a5717e77a24} + Perspex.Markup.Xaml + + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + {D211E587-D8BC-45B9-95A4-F297C8FA5200} Perspex.Animation @@ -86,23 +94,24 @@ Properties\SharedAssemblyInfo.cs - - - + + + TreePageView.paml + + + DevTools.paml + - - - @@ -124,6 +133,12 @@ + + Designer + + + Designer + + + + + + + \ No newline at end of file diff --git a/src/Perspex.Diagnostics/Views/VisualTreeView.cs b/src/Perspex.Diagnostics/Views/VisualTreeView.cs deleted file mode 100644 index d8d6ca03ce..0000000000 --- a/src/Perspex.Diagnostics/Views/VisualTreeView.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) The Perspex Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using System.Reactive.Linq; -using Perspex.Controls; -using Perspex.Controls.Templates; -using Perspex.Diagnostics.ViewModels; -using Perspex.Media; -using ReactiveUI; - -namespace Perspex.Diagnostics.Views -{ - using Controls = Controls.Controls; - - internal class VisualTreeView : TreePage - { - private static readonly PerspexProperty ViewModelProperty = - PerspexProperty.Register("ViewModel"); - - public VisualTreeView() - { - InitializeComponent(); - this.GetObservable(DataContextProperty) - .Subscribe(x => ViewModel = (VisualTreeViewModel)x); - } - - public VisualTreeViewModel ViewModel - { - get { return GetValue(ViewModelProperty); } - private set { SetValue(ViewModelProperty, value); } - } - - private void InitializeComponent() - { - TreeView tree; - - Content = new Grid - { - ColumnDefinitions = new ColumnDefinitions - { - new ColumnDefinition(1, GridUnitType.Star), - new ColumnDefinition(4, GridUnitType.Pixel), - new ColumnDefinition(3, GridUnitType.Star), - }, - Children = new Controls - { - (tree = new TreeView - { - DataTemplates = new DataTemplates - { - new FuncTreeDataTemplate(GetHeader, x => x.Children), - }, - [!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes), - }), - new GridSplitter - { - Width = 4, - Orientation = Orientation.Vertical, - [Grid.ColumnProperty] = 1, - }, - new ContentControl - { - [!ContentProperty] = this.WhenAnyValue(x => x.ViewModel.Details), - [Grid.ColumnProperty] = 2, - } - } - }; - - tree.GetObservable(TreeView.SelectedItemProperty) - .OfType() - .Subscribe(x => ViewModel.SelectedNode = x); - } - - private Control GetHeader(VisualTreeNode node) - { - var result = new StackPanel - { - Orientation = Orientation.Horizontal, - Gap = 8, - Children = new Controls - { - new TextBlock - { - FontStyle = node.IsInTemplate ? FontStyle.Italic : FontStyle.Normal, - Text = node.Type, - }, - new TextBlock - { - [!TextBlock.TextProperty] = node.WhenAnyValue(x => x.Classes), - } - } - }; - - result.PointerEnter += AddAdorner; - result.PointerLeave += RemoveAdorner; - - return result; - } - } -} From d69c283767d2eb3d0179e72658d72c15ee1dfd27 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 23 Jan 2016 19:38:38 +0100 Subject: [PATCH 03/14] Display Focused and PointerOver control. --- src/Perspex.Diagnostics/DevTools.paml | 1 + .../ViewModels/DevToolsViewModel.cs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Perspex.Diagnostics/DevTools.paml b/src/Perspex.Diagnostics/DevTools.paml index 76f58222e1..44204281fa 100644 --- a/src/Perspex.Diagnostics/DevTools.paml +++ b/src/Perspex.Diagnostics/DevTools.paml @@ -10,6 +10,7 @@ Focused: + Pointer Over: diff --git a/src/Perspex.Diagnostics/ViewModels/DevToolsViewModel.cs b/src/Perspex.Diagnostics/ViewModels/DevToolsViewModel.cs index 67fcc505f4..ef9dc5a371 100644 --- a/src/Perspex.Diagnostics/ViewModels/DevToolsViewModel.cs +++ b/src/Perspex.Diagnostics/ViewModels/DevToolsViewModel.cs @@ -21,9 +21,9 @@ namespace Perspex.Diagnostics.ViewModels private TreePageViewModel _visualTree; - private readonly ObservableAsPropertyHelper _focusedControl; + private readonly ObservableAsPropertyHelper _focusedControl; - private readonly ObservableAsPropertyHelper _pointerOverElement; + private readonly ObservableAsPropertyHelper _pointerOverElement; public DevToolsViewModel(IControl root) { @@ -46,12 +46,12 @@ namespace Perspex.Diagnostics.ViewModels _focusedControl = KeyboardDevice.Instance .WhenAnyValue(x => x.FocusedElement) + .Select(x => x?.GetType().Name) .ToProperty(this, x => x.FocusedControl); - //_pointerOverElement = this.WhenAnyValue(x => x.Root, x => x as TopLevel) - // .Select(x => x?.GetObservable(TopLevel.PointerOverElementProperty) ?? Observable.Empty()) - // .Switch() - // .ToProperty(this, x => x.PointerOverElement); + _pointerOverElement = root.GetObservable(TopLevel.PointerOverElementProperty) + .Select(x => x?.GetType().Name) + .ToProperty(this, x => x.PointerOverElement); } public ReactiveObject Content @@ -66,8 +66,8 @@ namespace Perspex.Diagnostics.ViewModels set { this.RaiseAndSetIfChanged(ref _selectedTab, value); } } - public IInputElement FocusedControl => _focusedControl.Value; + public string FocusedControl => _focusedControl.Value; - //public IInputElement PointerOverElement => _pointerOverElement.Value; + public string PointerOverElement => _pointerOverElement.Value; } } From 7f6b433d8ca47fd1477133cf0b118c0bc25e756b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 23 Jan 2016 19:49:37 +0100 Subject: [PATCH 04/14] Only open 1 devtools per window. --- src/Perspex.Diagnostics/DevTools.paml.cs | 50 ++++++++++++++++++------ 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/src/Perspex.Diagnostics/DevTools.paml.cs b/src/Perspex.Diagnostics/DevTools.paml.cs index 8b607a496f..ab374f09f1 100644 --- a/src/Perspex.Diagnostics/DevTools.paml.cs +++ b/src/Perspex.Diagnostics/DevTools.paml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Perspex.Controls; using Perspex.Controls.Templates; using Perspex.Diagnostics.ViewModels; @@ -11,12 +12,17 @@ namespace Perspex.Diagnostics { public class DevTools : UserControl { + private static Dictionary s_open = new Dictionary(); + public DevTools(IControl root) { - this.InitializeComponent(); - this.DataContext = new DevToolsViewModel(root); + InitializeComponent(); + Root = root; + DataContext = new DevToolsViewModel(root); } + public IControl Root { get; } + public static IDisposable Attach(Window window) { return window.AddHandler( @@ -29,21 +35,43 @@ namespace Perspex.Diagnostics { if (e.Key == Key.F12) { - Window window = new Window + var window = (Window)sender; + var devToolsWindow = default(Window); + + if (s_open.TryGetValue(window, out devToolsWindow)) + { + devToolsWindow.Activate(); + } + else { - Width = 1024, - Height = 512, - Content = new DevTools((IControl)sender), - DataTemplates = new DataTemplates + devToolsWindow = new Window { - new ViewLocator(), - } - }; + Width = 1024, + Height = 512, + Content = new DevTools(window), + DataTemplates = new DataTemplates + { + new ViewLocator(), + } + }; - window.Show(); + devToolsWindow.Closed += DevToolsClosed; + s_open.Add((Window)sender, devToolsWindow); + devToolsWindow.Show(); + } } } + private static void DevToolsClosed(object sender, EventArgs e) + { + var devToolsWindow = (Window)sender; + var devTools = (DevTools)devToolsWindow.Content; + var window = (Window)devTools.Root; + + s_open.Remove(window); + devToolsWindow.Closed -= DevToolsClosed; + } + private void InitializeComponent() { PerspexXamlLoader.Load(this); From 14b349e00667ff8949827d783dc2ebb4df701086 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 23 Jan 2016 20:23:41 +0100 Subject: [PATCH 05/14] Select node when ctrl+shift held down. TreeView selection isn't getting updated though. --- src/Perspex.Diagnostics/DevTools.paml.cs | 12 ++++ .../ViewModels/DevToolsViewModel.cs | 10 +++ .../ViewModels/LogicalTreeNode.cs | 8 +-- .../ViewModels/TreeNode.cs | 17 +++-- .../ViewModels/TreePageViewModel.cs | 69 +++++++++++++++++++ .../ViewModels/VisualTreeNode.cs | 10 +-- .../Views/TreePageView.paml | 4 +- 7 files changed, 113 insertions(+), 17 deletions(-) diff --git a/src/Perspex.Diagnostics/DevTools.paml.cs b/src/Perspex.Diagnostics/DevTools.paml.cs index ab374f09f1..3b96ebcf5a 100644 --- a/src/Perspex.Diagnostics/DevTools.paml.cs +++ b/src/Perspex.Diagnostics/DevTools.paml.cs @@ -19,6 +19,7 @@ namespace Perspex.Diagnostics InitializeComponent(); Root = root; DataContext = new DevToolsViewModel(root); + Root.PointerMoved += RootPointerMoved; } public IControl Root { get; } @@ -76,5 +77,16 @@ namespace Perspex.Diagnostics { PerspexXamlLoader.Load(this); } + + private void RootPointerMoved(object sender, PointerEventArgs e) + { + var modifiers = InputModifiers.Control | InputModifiers.Shift; + + if ((e.InputModifiers & modifiers) == modifiers) + { + var vm = (DevToolsViewModel)DataContext; + vm.SelectControl((IControl)e.Source); + } + } } } diff --git a/src/Perspex.Diagnostics/ViewModels/DevToolsViewModel.cs b/src/Perspex.Diagnostics/ViewModels/DevToolsViewModel.cs index ef9dc5a371..475579fdb5 100644 --- a/src/Perspex.Diagnostics/ViewModels/DevToolsViewModel.cs +++ b/src/Perspex.Diagnostics/ViewModels/DevToolsViewModel.cs @@ -69,5 +69,15 @@ namespace Perspex.Diagnostics.ViewModels public string FocusedControl => _focusedControl.Value; public string PointerOverElement => _pointerOverElement.Value; + + public void SelectControl(IControl control) + { + var tree = Content as TreePageViewModel; + + if (tree != null) + { + tree.SelectControl(control); + } + } } } diff --git a/src/Perspex.Diagnostics/ViewModels/LogicalTreeNode.cs b/src/Perspex.Diagnostics/ViewModels/LogicalTreeNode.cs index 0d15719128..bef186b246 100644 --- a/src/Perspex.Diagnostics/ViewModels/LogicalTreeNode.cs +++ b/src/Perspex.Diagnostics/ViewModels/LogicalTreeNode.cs @@ -9,16 +9,16 @@ namespace Perspex.Diagnostics.ViewModels { internal class LogicalTreeNode : TreeNode { - public LogicalTreeNode(ILogical logical) - : base((Control)logical) + public LogicalTreeNode(ILogical logical, TreeNode parent) + : base((Control)logical, parent) { - Children = logical.LogicalChildren.CreateDerivedCollection(x => new LogicalTreeNode(x)); + Children = logical.LogicalChildren.CreateDerivedCollection(x => new LogicalTreeNode(x, this)); } public static LogicalTreeNode[] Create(object control) { var logical = control as ILogical; - return logical != null ? new[] { new LogicalTreeNode(logical) } : null; + return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null; } } } diff --git a/src/Perspex.Diagnostics/ViewModels/TreeNode.cs b/src/Perspex.Diagnostics/ViewModels/TreeNode.cs index 9c46f5f344..62044d2a28 100644 --- a/src/Perspex.Diagnostics/ViewModels/TreeNode.cs +++ b/src/Perspex.Diagnostics/ViewModels/TreeNode.cs @@ -13,11 +13,12 @@ namespace Perspex.Diagnostics.ViewModels internal class TreeNode : ReactiveObject { private string _classes; - private bool _isExpanded = true; + private bool _isExpanded; - public TreeNode(Control control) + public TreeNode(Control control, TreeNode parent) { Control = control; + Parent = parent; Type = control.GetType().Name; var classesChanged = Observable.FromEventPattern< @@ -53,19 +54,23 @@ namespace Perspex.Diagnostics.ViewModels private set { this.RaiseAndSetIfChanged(ref _classes, value); } } + public Control Control + { + get; + } + public bool IsExpanded { get { return _isExpanded; } - private set { this.RaiseAndSetIfChanged(ref _isExpanded, value); } + set { this.RaiseAndSetIfChanged(ref _isExpanded, value); } } - public string Type + public TreeNode Parent { get; - private set; } - public Control Control + public string Type { get; private set; diff --git a/src/Perspex.Diagnostics/ViewModels/TreePageViewModel.cs b/src/Perspex.Diagnostics/ViewModels/TreePageViewModel.cs index 90e1b49492..346969abdc 100644 --- a/src/Perspex.Diagnostics/ViewModels/TreePageViewModel.cs +++ b/src/Perspex.Diagnostics/ViewModels/TreePageViewModel.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Reactive.Linq; +using Perspex.Controls; +using Perspex.VisualTree; using ReactiveUI; namespace Perspex.Diagnostics.ViewModels @@ -29,5 +31,72 @@ namespace Perspex.Diagnostics.ViewModels } public ControlDetailsViewModel Details => _details.Value; + + public TreeNode FindNode(IControl control) + { + foreach (var node in Nodes) + { + var result = FindNode(node, control); + + if (result != null) + { + return result; + } + } + + return null; + } + + public void SelectControl(IControl control) + { + var node = default(TreeNode); + + while (node == null && control != null) + { + node = FindNode(control); + + if (node == null) + { + control = control.GetVisualParent(); + } + } + + if (node != null) + { + SelectedNode = node; + ExpandNode(node.Parent); + } + } + + private void ExpandNode(TreeNode node) + { + if (node != null) + { + node.IsExpanded = true; + ExpandNode(node.Parent); + } + } + + private TreeNode FindNode(TreeNode node, IControl control) + { + if (node.Control == control) + { + return node; + } + else + { + foreach (var child in node.Children) + { + var result = FindNode(child, control); + + if (result != null) + { + return result; + } + } + } + + return null; + } } } diff --git a/src/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs b/src/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs index 64064df20a..f92a51439a 100644 --- a/src/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs @@ -9,18 +9,18 @@ namespace Perspex.Diagnostics.ViewModels { internal class VisualTreeNode : TreeNode { - public VisualTreeNode(IVisual visual) - : base((Control)visual) + public VisualTreeNode(IVisual visual, TreeNode parent) + : base((Control)visual, parent) { var host = visual as IVisualTreeHost; if (host?.Root == null) { - Children = visual.VisualChildren.CreateDerivedCollection(x => new VisualTreeNode(x)); + Children = visual.VisualChildren.CreateDerivedCollection(x => new VisualTreeNode(x, this)); } else { - Children = new ReactiveList(new[] { new VisualTreeNode(host.Root) }); + Children = new ReactiveList(new[] { new VisualTreeNode(host.Root, this) }); } if (Control != null) @@ -34,7 +34,7 @@ namespace Perspex.Diagnostics.ViewModels public static VisualTreeNode[] Create(object control) { var visual = control as IVisual; - return visual != null ? new[] { new VisualTreeNode(visual) } : null; + return visual != null ? new[] { new VisualTreeNode(visual, null) } : null; } } } diff --git a/src/Perspex.Diagnostics/Views/TreePageView.paml b/src/Perspex.Diagnostics/Views/TreePageView.paml index 4914d1e3dc..ba13d4e263 100644 --- a/src/Perspex.Diagnostics/Views/TreePageView.paml +++ b/src/Perspex.Diagnostics/Views/TreePageView.paml @@ -11,11 +11,11 @@ - + From 69d98efeeb914a7aaf8cd6f3677991c64c59480a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 24 Jan 2016 17:36:05 +0100 Subject: [PATCH 06/14] Highlight hovered control in devtools. --- .../Generators/ItemContainerEventArgs.cs | 13 +++++++++++++ .../Generators/TreeContainerIndex.cs | 19 +++++++++++++++++++ .../Primitives/HeaderedItemsControl.cs | 2 +- .../Views/TreePage.paml.cs | 19 +++++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/Perspex.Controls/Generators/ItemContainerEventArgs.cs b/src/Perspex.Controls/Generators/ItemContainerEventArgs.cs index 86e2b890d1..0a9c3544d7 100644 --- a/src/Perspex.Controls/Generators/ItemContainerEventArgs.cs +++ b/src/Perspex.Controls/Generators/ItemContainerEventArgs.cs @@ -12,6 +12,19 @@ namespace Perspex.Controls.Generators /// public class ItemContainerEventArgs : EventArgs { + /// + /// Initializes a new instance of the class. + /// + /// The index of the first container in the source items. + /// The container. + public ItemContainerEventArgs( + int startingIndex, + ItemContainer container) + { + StartingIndex = startingIndex; + Containers = new[] { container }; + } + /// /// Initializes a new instance of the class. /// diff --git a/src/Perspex.Controls/Generators/TreeContainerIndex.cs b/src/Perspex.Controls/Generators/TreeContainerIndex.cs index 9de4ca1050..ab07cb454d 100644 --- a/src/Perspex.Controls/Generators/TreeContainerIndex.cs +++ b/src/Perspex.Controls/Generators/TreeContainerIndex.cs @@ -1,6 +1,7 @@ // Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using System.Collections.Generic; namespace Perspex.Controls.Generators @@ -19,6 +20,16 @@ namespace Perspex.Controls.Generators private readonly Dictionary _itemToContainer = new Dictionary(); private readonly Dictionary _containerToItem = new Dictionary(); + /// + /// Signalled whenever new containers are materialized. + /// + public event EventHandler Materialized; + + /// + /// Event raised whenever containers are dematerialized. + /// + public event EventHandler Dematerialized; + /// /// Gets the currently materialized containers. /// @@ -33,6 +44,10 @@ namespace Perspex.Controls.Generators { _itemToContainer.Add(item, container); _containerToItem.Add(container, item); + + Materialized?.Invoke( + this, + new ItemContainerEventArgs(0, new ItemContainer(container, item, 0))); } /// @@ -44,6 +59,10 @@ namespace Perspex.Controls.Generators var item = _containerToItem[container]; _containerToItem.Remove(container); _itemToContainer.Remove(item); + + Dematerialized?.Invoke( + this, + new ItemContainerEventArgs(0, new ItemContainer(container, item, 0))); } /// diff --git a/src/Perspex.Controls/Primitives/HeaderedItemsControl.cs b/src/Perspex.Controls/Primitives/HeaderedItemsControl.cs index 5f543cae17..47d714e4da 100644 --- a/src/Perspex.Controls/Primitives/HeaderedItemsControl.cs +++ b/src/Perspex.Controls/Primitives/HeaderedItemsControl.cs @@ -51,8 +51,8 @@ namespace Perspex.Controls.Primitives /// protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { - base.OnTemplateApplied(e); HeaderPresenter = e.NameScope.Find("PART_HeaderPresenter"); + base.OnTemplateApplied(e); } } } diff --git a/src/Perspex.Diagnostics/Views/TreePage.paml.cs b/src/Perspex.Diagnostics/Views/TreePage.paml.cs index 43c9829a95..3937358ba1 100644 --- a/src/Perspex.Diagnostics/Views/TreePage.paml.cs +++ b/src/Perspex.Diagnostics/Views/TreePage.paml.cs @@ -1,4 +1,5 @@ using Perspex.Controls; +using Perspex.Controls.Generators; using Perspex.Controls.Primitives; using Perspex.Controls.Shapes; using Perspex.Diagnostics.ViewModels; @@ -11,10 +12,12 @@ namespace Perspex.Diagnostics.Views public class TreePageView : UserControl { private Control _adorner; + private TreeView _tree; public TreePageView() { this.InitializeComponent(); + _tree.ItemContainerGenerator.Index.Materialized += TreeViewItemMaterialized; } protected void AddAdorner(object sender, PointerEventArgs e) @@ -46,6 +49,22 @@ namespace Perspex.Diagnostics.Views private void InitializeComponent() { PerspexXamlLoader.Load(this); + _tree = this.FindControl("tree"); + } + + private void TreeViewItemMaterialized(object sender, ItemContainerEventArgs e) + { + var item = (TreeViewItem)e.Containers[0].ContainerControl; + item.TemplateApplied += TreeViewItemTemplateApplied; + } + + private void TreeViewItemTemplateApplied(object sender, TemplateAppliedEventArgs e) + { + var item = (TreeViewItem)sender; + var header = item.HeaderPresenter.Child; + header.PointerEnter += AddAdorner; + header.PointerLeave += RemoveAdorner; + item.TemplateApplied -= TreeViewItemTemplateApplied; } } } From 0faeaf47e34079b7c12184d5b6c5adbe5ad1c5a6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 25 Jan 2016 14:41:56 +0100 Subject: [PATCH 07/14] Scroll newly selected tree view node into view. --- src/Perspex.Base/Threading/DispatcherTimer.cs | 27 +++++++++ src/Perspex.Controls/TreeView.cs | 57 ++++++++++++++++++- src/Perspex.Controls/TreeViewItem.cs | 22 +------ 3 files changed, 84 insertions(+), 22 deletions(-) diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs index 376ca5c0c9..1ece331c13 100644 --- a/src/Perspex.Base/Threading/DispatcherTimer.cs +++ b/src/Perspex.Base/Threading/DispatcherTimer.cs @@ -146,6 +146,33 @@ namespace Perspex.Threading return Disposable.Create(() => timer.Stop()); } + /// + /// Runs a method once, after the specified interval. + /// + /// + /// The method to call after the interval has elapsed. + /// + /// The interval after which to call the method. + /// The priority to use. + /// An used to cancel the timer. + public static IDisposable RunOnce( + Action action, + TimeSpan interval, + DispatcherPriority priority = DispatcherPriority.Normal) + { + var timer = new DispatcherTimer(priority) { Interval = interval }; + + timer.Tick += (s, e) => + { + action(); + timer.Stop(); + }; + + timer.Start(); + + return Disposable.Create(() => timer.Stop()); + } + /// /// Starts the timer. /// diff --git a/src/Perspex.Controls/TreeView.cs b/src/Perspex.Controls/TreeView.cs index ea2b5d632c..dd36db7ec0 100644 --- a/src/Perspex.Controls/TreeView.cs +++ b/src/Perspex.Controls/TreeView.cs @@ -8,6 +8,7 @@ using Perspex.Controls.Primitives; using Perspex.Input; using Perspex.Interactivity; using Perspex.Styling; +using Perspex.Threading; using Perspex.VisualTree; namespace Perspex.Controls @@ -17,6 +18,14 @@ namespace Perspex.Controls /// public class TreeView : ItemsControl { + /// + /// Defines the property. + /// + public static readonly PerspexProperty AutoScrollToSelectedItemProperty = + PerspexProperty.Register( + nameof(AutoScrollToSelectedItem), + defaultValue: true); + /// /// Defines the property. /// @@ -41,6 +50,15 @@ namespace Perspex.Controls public new ITreeItemContainerGenerator ItemContainerGenerator => (ITreeItemContainerGenerator)base.ItemContainerGenerator; + /// + /// Gets or sets a value indicating whether to automatically scroll to newly selected items. + /// + public bool AutoScrollToSelectedItem + { + get { return GetValue(AutoScrollToSelectedItemProperty); } + set { SetValue(AutoScrollToSelectedItemProperty, value); } + } + /// /// Gets or sets the selected item. /// @@ -65,6 +83,11 @@ namespace Perspex.Controls { var container = ItemContainerGenerator.Index.ContainerFromItem(_selectedItem); MarkContainerSelected(container, true); + + if (AutoScrollToSelectedItem && container != null) + { + container.BringIntoView(); + } } } } @@ -72,12 +95,14 @@ namespace Perspex.Controls /// protected override IItemContainerGenerator CreateItemContainerGenerator() { - return new TreeItemContainerGenerator( + var result = new TreeItemContainerGenerator( this, TreeViewItem.HeaderProperty, TreeViewItem.ItemsProperty, TreeViewItem.IsExpandedProperty, new TreeContainerIndex()); + result.Index.Materialized += ContainerMaterialized; + return result; } /// @@ -190,6 +215,36 @@ namespace Perspex.Controls return null; } + /// + /// Called when a new item container is materialized, to set its selected state. + /// + /// The event sender. + /// The event args. + private void ContainerMaterialized(object sender, ItemContainerEventArgs e) + { + var selectedItem = SelectedItem; + + if (selectedItem != null) + { + foreach (var container in e.Containers) + { + if (container.Item == selectedItem) + { + ((TreeViewItem)container.ContainerControl).IsSelected = true; + + if (AutoScrollToSelectedItem) + { + DispatcherTimer.RunOnce( + container.ContainerControl.BringIntoView, + TimeSpan.Zero); + } + + break; + } + } + } + } + /// /// Sets a container's 'selected' class or . /// diff --git a/src/Perspex.Controls/TreeViewItem.cs b/src/Perspex.Controls/TreeViewItem.cs index e68a77b56e..e2d8107cfc 100644 --- a/src/Perspex.Controls/TreeViewItem.cs +++ b/src/Perspex.Controls/TreeViewItem.cs @@ -73,16 +73,12 @@ namespace Perspex.Controls /// protected override IItemContainerGenerator CreateItemContainerGenerator() { - var result = new TreeItemContainerGenerator( + return new TreeItemContainerGenerator( this, TreeViewItem.HeaderProperty, TreeViewItem.ItemsProperty, TreeViewItem.IsExpandedProperty, _treeView?.ItemContainerGenerator.Index ?? new TreeContainerIndex()); - - result.Materialized += ItemMaterialized; - - return result; } /// @@ -123,21 +119,5 @@ namespace Perspex.Controls base.OnKeyDown(e); } - - private void ItemMaterialized(object sender, ItemContainerEventArgs e) - { - var selectedItem = _treeView?.SelectedItem; - - if (selectedItem != null) - { - foreach (var container in e.Containers) - { - if (container.Item == selectedItem) - { - ((TreeViewItem)container.ContainerControl).IsSelected = true; - } - } - } - } } } From 3beea47bf7df74a7d8879c568856f1e6bb4bc697 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 26 Jan 2016 00:17:02 +0100 Subject: [PATCH 08/14] Replace DockPanel implementation. The old one was broken - replaced by the implementation from WinRTXamlToolkit which is both simpler and works correctly. Also added some tests. This closes #397, closes #348, closes #74. --- src/Perspex.Controls/DockPanel.cs | 513 +++++------------- src/Perspex.SceneGraph/Rect.cs | 40 ++ .../DockPanelTests.cs | 62 +++ .../Perspex.Controls.UnitTests.csproj | 1 + 4 files changed, 225 insertions(+), 391 deletions(-) create mode 100644 tests/Perspex.Controls.UnitTests/DockPanelTests.cs diff --git a/src/Perspex.Controls/DockPanel.cs b/src/Perspex.Controls/DockPanel.cs index a1224a5272..8108a8c3cb 100644 --- a/src/Perspex.Controls/DockPanel.cs +++ b/src/Perspex.Controls/DockPanel.cs @@ -1,445 +1,176 @@ namespace Perspex.Controls { using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Linq; - using Layout; - public class DockPanel : Panel + /// + /// Defines the available docking modes for a control in a . + /// + public enum Dock { - public static readonly PerspexProperty DockProperty = PerspexProperty.RegisterAttached("Dock"); + Left = 0, + Bottom, + Right, + Top + } + /// + /// A panel which arranges its children at the top, bottom, left, right or center. + /// + public class DockPanel : Panel + { + /// + /// Defines the Dock attached property. + /// + public static readonly PerspexProperty DockProperty = + PerspexProperty.RegisterAttached("Dock"); + + /// + /// Defines the property. + /// + public static readonly PerspexProperty LastChildFillProperty = + PerspexProperty.Register( + nameof(LastChildFillProperty), + defaultValue: true); + + /// + /// Initializes static members of the class. + /// static DockPanel() { AffectsArrange(DockProperty); } - // ReSharper disable once UnusedMember.Global - public static Dock GetDock(PerspexObject perspexObject) + /// + /// Gets the value of the Dock attached property on the specified control. + /// + /// The control. + /// The Dock attached property. + public static Dock GetDock(Control control) { - return perspexObject.GetValue(DockProperty); + return control.GetValue(DockProperty); } - // ReSharper disable once UnusedMember.Global - public static void SetDock(PerspexObject element, Dock dock) + /// + /// Sets the value of the Dock attached property on the specified control. + /// + /// The control. + /// The value of the Dock property. + public static void SetDock(Control control, Dock value) { - element.SetValue(DockProperty, dock); + control.SetValue(DockProperty, value); } - public static readonly PerspexProperty LastChildFillProperty = PerspexProperty.Register(nameof(LastChildFillProperty), defaultValue: true); - + /// + /// Gets or sets a value which indicates whether the last child of the + /// fills the remaining space in the panel. + /// public bool LastChildFill { get { return GetValue(LastChildFillProperty); } set { SetValue(LastChildFillProperty, value); } } - protected override Size MeasureOverride(Size availableSize) - { - if (!LastChildFill) - { - return MeasureItemsThatWillBeDocked(availableSize, Children); - } - - var sizeRequiredByDockingItems = MeasureItemsThatWillBeDocked(availableSize, Children.WithoutLast()); - var elementThatWillFill = Children.Last(); - elementThatWillFill.Measure(availableSize - sizeRequiredByDockingItems); - var finalSize = sizeRequiredByDockingItems.Inflate(new Thickness(elementThatWillFill.DesiredSize.Width, elementThatWillFill.DesiredSize.Height)); - return finalSize; - } - - private static Size MeasureItemsThatWillBeDocked(Size availableSize, IEnumerable children) + /// + protected override Size MeasureOverride(Size constraint) { - var requiredHorizontalLength = 0D; - var requiredVerticalLength = 0D; + double usedWidth = 0.0; + double usedHeight = 0.0; + double maximumWidth = 0.0; + double maximumHeight = 0.0; - foreach (var control in children) + // Measure each of the Children + foreach (Control element in Children) { - control.Measure(availableSize); - - var dock = control.GetValue(DockProperty); - if (IsHorizontal(dock)) - { - requiredHorizontalLength += control.DesiredSize.Width; - } - else + // Get the child's desired size + Size remainingSize = new Size( + Math.Max(0.0, constraint.Width - usedWidth), + Math.Max(0.0, constraint.Height - usedHeight)); + element.Measure(remainingSize); + Size desiredSize = element.DesiredSize; + + // Decrease the remaining space for the rest of the children + switch (GetDock(element)) { - requiredVerticalLength += control.DesiredSize.Height; + case Dock.Left: + case Dock.Right: + maximumHeight = Math.Max(maximumHeight, usedHeight + desiredSize.Height); + usedWidth += desiredSize.Width; + break; + case Dock.Top: + case Dock.Bottom: + maximumWidth = Math.Max(maximumWidth, usedWidth + desiredSize.Width); + usedHeight += desiredSize.Height; + break; } } - return new Size(requiredHorizontalLength, requiredVerticalLength); - } - - private static bool IsHorizontal(Dock dock) - { - return dock == Dock.Left || dock == Dock.Right; - } - - protected override Size ArrangeOverride(Size finalSize) - { - if (!LastChildFill) - { - return ArrangeAllChildren(finalSize); - } - else - { - return ArrangeChildrenAndFillLastChild(finalSize); - } - } - - private Size ArrangeChildrenAndFillLastChild(Size finalSize) - { - var docker = new DockingArranger(); - var requiredSize = docker.ArrangeAndGetUsedSize(finalSize, Children.WithoutLast()); - ArrangeToFill(Children.Last(), finalSize, docker.UsedMargin); - return requiredSize; - } - - private Size ArrangeAllChildren(Size finalSize) - { - return new DockingArranger().ArrangeAndGetUsedSize(finalSize, Children); + maximumWidth = Math.Max(maximumWidth, usedWidth); + maximumHeight = Math.Max(maximumHeight, usedHeight); + return new Size(maximumWidth, maximumHeight); } - private static void ArrangeToFill(ILayoutable layoutable, Size containerSize, Margin margin) + /// + protected override Size ArrangeOverride(Size arrangeSize) { - var containerRect = new Rect(new Point(0, 0), containerSize); - var marginsCutout = margin.AsThickness(); - var withoutMargins = containerRect.Deflate(marginsCutout); - - layoutable.Arrange(withoutMargins); - } + double left = 0.0; + double top = 0.0; + double right = 0.0; + double bottom = 0.0; - private class DockingArranger - { - public Margin UsedMargin { get; private set; } + // Arrange each of the Children + var children = Children; + int dockedCount = children.Count - (LastChildFill ? 1 : 0); + int index = 0; - public Size ArrangeAndGetUsedSize(Size availableSize, IEnumerable children) + foreach (Control element in children) { - var leftArranger = new LeftDocker(availableSize); - var rightArranger = new RightDocker(availableSize); - var topArranger = new LeftDocker(availableSize.Swap()); - var bottomArranger = new RightDocker(availableSize.Swap()); - - UsedMargin = new Margin(); - - foreach (var control in children) + // Determine the remaining space left to arrange the element + Rect remainingRect = new Rect( + left, + top, + Math.Max(0.0, arrangeSize.Width - left - right), + Math.Max(0.0, arrangeSize.Height - top - bottom)); + + // Trim the remaining Rect to the docked size of the element + // (unless the element should fill the remaining space because + // of LastChildFill) + if (index < dockedCount) { - Rect dockedRect; - var dock = control.GetValue(DockProperty); - switch (dock) + Size desiredSize = element.DesiredSize; + switch (GetDock(element)) { case Dock.Left: - dockedRect = leftArranger.GetDockedRect(control.DesiredSize, UsedMargin, control.GetAlignments()); + left += desiredSize.Width; + remainingRect = remainingRect.WithWidth(desiredSize.Width); break; - case Dock.Top: - UsedMargin.Swap(); - dockedRect = topArranger.GetDockedRect(control.DesiredSize.Swap(), UsedMargin, control.GetAlignments().Swap()).Swap(); - UsedMargin.Swap(); + top += desiredSize.Height; + remainingRect = remainingRect.WithHeight(desiredSize.Height); break; - case Dock.Right: - dockedRect = rightArranger.GetDockedRect(control.DesiredSize, UsedMargin, control.GetAlignments()); + right += desiredSize.Width; + remainingRect = new Rect( + Math.Max(0.0, arrangeSize.Width - right), + remainingRect.Y, + desiredSize.Width, + remainingRect.Height); break; - case Dock.Bottom: - UsedMargin.Swap(); - dockedRect = bottomArranger.GetDockedRect(control.DesiredSize.Swap(), UsedMargin, control.GetAlignments().Swap()).Swap(); - UsedMargin.Swap(); + bottom += desiredSize.Height; + remainingRect = new Rect( + remainingRect.X, + Math.Max(0.0, arrangeSize.Height - bottom), + remainingRect.Width, + desiredSize.Height); break; - - default: - throw new InvalidOperationException($"Invalid dock value {dock}"); } - - control.Arrange(dockedRect); } - return availableSize; - } - } - - private class LeftDocker : Docker - { - public LeftDocker(Size availableSize) : base(availableSize) - { - } - - public override Rect GetDockedRect(Size childSize, Margin margin, Alignments alignments) - { - var marginsCutout = margin.AsThickness(); - var availableRect = OriginalRect.Deflate(marginsCutout); - var alignedRect = AlignToLeft(availableRect, childSize, alignments.Vertical); - - AccumulatedOffset += childSize.Width; - margin.Horizontal = margin.Horizontal.Offset(childSize.Width, 0); - - return alignedRect; - } - - private static Rect AlignToLeft(Rect availableRect, Size childSize, Alignment verticalAlignment) - { - return availableRect.AlignChild(childSize, Alignment.Start, verticalAlignment); - } - } - - private class RightDocker : Docker - { - public RightDocker(Size availableSize) : base(availableSize) - { - } - - public override Rect GetDockedRect(Size childSize, Margin margin, Alignments alignments) - { - var marginsCutout = margin.AsThickness(); - var withoutMargins = OriginalRect.Deflate(marginsCutout); - var finalRect = withoutMargins.AlignChild(childSize, Alignment.End, alignments.Vertical); - - AccumulatedOffset += childSize.Width; - margin.Horizontal = margin.Horizontal.Offset(0, childSize.Width); - - return finalRect; - } - } - - private abstract class Docker - { - protected Docker(Size availableSize) - { - OriginalRect = new Rect(new Point(0, 0), availableSize); + element.Arrange(remainingRect); + index++; } - protected double AccumulatedOffset { get; set; } - - protected Rect OriginalRect { get; } - - public abstract Rect GetDockedRect(Size childSize, Margin margin, Alignments alignments); - } - } - - public class Margin - { - public Segment Horizontal { get; set; } - public Segment Vertical { get; set; } - } - - public enum Alignment - { - Stretch, Start, Middle, End, - } - - public static class SegmentMixin - { - public static Segment AlignToStart(this Segment container, double length) - { - return new Segment(container.Start, container.Start + length); - } - - public static Segment AlignToEnd(this Segment container, double length) - { - return new Segment(container.End - length, container.End); - } - - public static Segment AlignToMiddle(this Segment container, double length) - { - var start = container.Start + (container.Length - length) / 2; - return new Segment(start, start + length); - } - } - - public struct Alignments - { - public Alignments(Alignment horizontal, Alignment vertical) - { - Horizontal = horizontal; - Vertical = vertical; - } - - public Alignment Horizontal { get; } - - public Alignment Vertical { get; } - } - - public static class CoordinateMixin - { - private static Point Swap(this Point p) - { - return new Point(p.Y, p.X); - } - - public static Size Swap(this Size s) - { - return new Size(s.Height, s.Width); - } - - public static Rect Swap(this Rect r) - { - return new Rect(r.Position.Swap(), r.Size.Swap()); - } - - public static Segment Offset(this Segment l, double startOffset, double endOffset) - { - return new Segment(l.Start + startOffset, l.End + endOffset); - } - - public static void Swap(this Margin m) - { - var v = m.Vertical; - m.Vertical = m.Horizontal; - m.Horizontal = v; - } - - public static Thickness AsThickness(this Margin margin) - { - return new Thickness(margin.Horizontal.Start, margin.Vertical.Start, margin.Horizontal.End, margin.Vertical.End); - } - - private static Alignment AsAlignment(this HorizontalAlignment horz) - { - switch (horz) - { - case HorizontalAlignment.Stretch: - return Alignment.Stretch; - case HorizontalAlignment.Left: - return Alignment.Start; - case HorizontalAlignment.Center: - return Alignment.Middle; - case HorizontalAlignment.Right: - return Alignment.End; - default: - throw new ArgumentOutOfRangeException(nameof(horz), horz, null); - } - } - - private static Alignment AsAlignment(this VerticalAlignment vert) - { - switch (vert) - { - case VerticalAlignment.Stretch: - return Alignment.Stretch; - case VerticalAlignment.Top: - return Alignment.Start; - case VerticalAlignment.Center: - return Alignment.Middle; - case VerticalAlignment.Bottom: - return Alignment.End; - default: - throw new ArgumentOutOfRangeException(nameof(vert), vert, null); - } - } - - public static Alignments GetAlignments(this ILayoutable layoutable) - { - return new Alignments(layoutable.HorizontalAlignment.AsAlignment(), layoutable.VerticalAlignment.AsAlignment()); - } - - public static Alignments Swap(this Alignments alignments) - { - return new Alignments(alignments.Vertical, alignments.Horizontal); - } - } - - public enum Dock - { - Left = 0, - Bottom, - Right, - Top - } - - public static class RectMixin - { - public static Rect AlignChild(this Rect container, Size childSize, Alignment horizontalAlignment, Alignment verticalAlignment) - { - var horzSegment = container.GetHorizontalCoordinates(); - var vertSegment = container.GetVerticalCoordinates(); - - var horzResult = GetAlignedSegment(childSize.Width, horizontalAlignment, horzSegment); - var vertResult = GetAlignedSegment(childSize.Height, verticalAlignment, vertSegment); - - return FromSegments(horzResult, vertResult); - } - - private static Rect FromSegments(Segment horzSegment, Segment vertSegment) - { - return new Rect(horzSegment.Start, vertSegment.Start, horzSegment.Length, vertSegment.Length); - } - - private static Segment GetAlignedSegment(double width, Alignment alignment, Segment horzSegment) - { - switch (alignment) - { - case Alignment.Start: - return horzSegment.AlignToStart(width); - - case Alignment.Middle: - return horzSegment.AlignToMiddle(width); - - case Alignment.End: - return horzSegment.AlignToEnd(width); - - default: - return new Segment(horzSegment.Start, horzSegment.End); - } - } - - private static Segment GetHorizontalCoordinates(this Rect rect) - { - return new Segment(rect.X, rect.Right); - } - - private static Segment GetVerticalCoordinates(this Rect rect) - { - return new Segment(rect.Y, rect.Bottom); - } - } - - public struct Segment - { - public Segment(double start, double end) - { - Start = start; - End = end; - } - - public double Start { get; } - public double End { get; } - - public double Length => End - Start; - - public override string ToString() - { - return $"Start: {Start}, End: {End}"; - } - } - - public static class EnumerableMixin - { - private static IEnumerable Shrink(this IEnumerable source, int left, int right) - { - int i = 0; - var buffer = new Queue(right + 1); - - foreach (T x in source) - { - if (i >= left) // Read past left many elements at the start - { - buffer.Enqueue(x); - if (buffer.Count > right) // Build a buffer to drop right many elements at the end - yield return buffer.Dequeue(); - } - else i++; - } - } - public static IEnumerable WithoutLast(this IEnumerable source, int n = 1) - { - return source.Shrink(0, n); - } - public static IEnumerable WithoutFirst(this IEnumerable source, int n = 1) - { - return source.Shrink(n, 0); + return arrangeSize; } } } \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Rect.cs b/src/Perspex.SceneGraph/Rect.cs index af8a32a593..399623b579 100644 --- a/src/Perspex.SceneGraph/Rect.cs +++ b/src/Perspex.SceneGraph/Rect.cs @@ -402,6 +402,46 @@ namespace Perspex return new Rect(Position + offset, Size); } + /// + /// Returns a new with the specified X position. + /// + /// The x position. + /// The new . + public Rect WithX(double x) + { + return new Rect(x, _y, _width, _height); + } + + /// + /// Returns a new with the specified Y position. + /// + /// The y position. + /// The new . + public Rect WithY(double y) + { + return new Rect(_x, y, _width, _height); + } + + /// + /// Returns a new with the specified width. + /// + /// The width. + /// The new . + public Rect WithWidth(double width) + { + return new Rect(_x, _y, width, _height); + } + + /// + /// Returns a new with the specified height. + /// + /// The height. + /// The new . + public Rect WithHeight(double height) + { + return new Rect(_x, _y, _width, height); + } + /// /// Returns the string representation of the rectangle. /// diff --git a/tests/Perspex.Controls.UnitTests/DockPanelTests.cs b/tests/Perspex.Controls.UnitTests/DockPanelTests.cs new file mode 100644 index 0000000000..7318e857d6 --- /dev/null +++ b/tests/Perspex.Controls.UnitTests/DockPanelTests.cs @@ -0,0 +1,62 @@ +// 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 Xunit; + +namespace Perspex.Controls.UnitTests +{ + public class DockPanelTests + { + [Fact] + public void Should_Dock_Controls_Horizontal_First() + { + var target = new DockPanel + { + Children = new Controls + { + new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Top }, + new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom }, + new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Left }, + new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right }, + new Border { }, + } + }; + + target.Measure(Size.Infinity); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Rect(0, 0, 500, 500), target.Bounds); + Assert.Equal(new Rect(0, 0, 500, 50), target.Children[0].Bounds); + Assert.Equal(new Rect(0, 450, 500, 50), target.Children[1].Bounds); + Assert.Equal(new Rect(0, 50, 50, 400), target.Children[2].Bounds); + Assert.Equal(new Rect(450, 50, 50, 400), target.Children[3].Bounds); + Assert.Equal(new Rect(50, 50, 400, 400), target.Children[4].Bounds); + } + + [Fact] + public void Should_Dock_Controls_Vertical_First() + { + var target = new DockPanel + { + Children = new Controls + { + new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Left }, + new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right }, + new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Top }, + new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom }, + new Border { }, + } + }; + + target.Measure(Size.Infinity); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Rect(0, 0, 600, 400), target.Bounds); + Assert.Equal(new Rect(0, 0, 50, 400), target.Children[0].Bounds); + Assert.Equal(new Rect(550, 0, 50, 400), target.Children[1].Bounds); + Assert.Equal(new Rect(50, 0, 500, 50), target.Children[2].Bounds); + Assert.Equal(new Rect(50, 350, 500, 50), target.Children[3].Bounds); + Assert.Equal(new Rect(50, 50, 500, 300), target.Children[4].Bounds); + } + } +} diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index c4283ef06e..87230b7a55 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -82,6 +82,7 @@ + From 9ea66ea262f02fb164f88c6f741839623fe56698 Mon Sep 17 00:00:00 2001 From: Brendan Forster Date: Wed, 27 Jan 2016 09:35:28 +1030 Subject: [PATCH 09/14] dropped Perspex prefix folder from step --- docs/build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build.md b/docs/build.md index b3be7581c4..c501b02c38 100644 --- a/docs/build.md +++ b/docs/build.md @@ -19,7 +19,7 @@ is linked as a submodule in the git repository, so run: git submodule update --init -The next step is to download the Skia native libraries. Run ```getnatives.ps1``` PowerShell script which can be found under the folder ```Perspex\src\Skia\```. +The next step is to download the Skia native libraries. Run ```getnatives.ps1``` PowerShell script which can be found under the folder ```src\Skia\```. ## Linux From d4b27f1332bf439986a968e3b9ba4ea616f21f68 Mon Sep 17 00:00:00 2001 From: Brendan Forster Date: Wed, 27 Jan 2016 09:40:24 +1030 Subject: [PATCH 10/14] point to the Perspex organization --- docs/build.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/build.md b/docs/build.md index c501b02c38..db59f43154 100644 --- a/docs/build.md +++ b/docs/build.md @@ -39,7 +39,7 @@ Then install the needed packages: ### Clone the Perspex repository - git clone https://github.com/grokys/Perspex.git + git clone https://github.com/Perspex/Perspex.git We currently need to build our own private version of ReactiveUI as it doesn't work on mono. This is linked as a submodule in the git repository, so run: @@ -57,4 +57,4 @@ Set the TestApplication project as the startup project and click Run. There will be some compile errors in the tests, but ignore them for now. -You can track the Linux version's progress in the [Linux issue](https://github.com/grokys/Perspex/issues/78). +You can track the Linux version's progress in the [Linux issue](https://github.com/Perspex/Perspex/issues/78). From 18280bb8c646386979a89c069b17cdc577e70058 Mon Sep 17 00:00:00 2001 From: Brendan Forster Date: Wed, 27 Jan 2016 09:40:53 +1030 Subject: [PATCH 11/14] and the other reference to the Skia folder --- docs/build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build.md b/docs/build.md index db59f43154..e0a8698fc8 100644 --- a/docs/build.md +++ b/docs/build.md @@ -46,7 +46,7 @@ is linked as a submodule in the git repository, so run: git submodule update --init -The next step is to download the Skia native libraries. Run ```getnatives.sh``` script which can be found under the folder ```Perspex\src\Skia\```. +The next step is to download the Skia native libraries. Run ```getnatives.sh``` script which can be found under the folder ```src\Skia\```. ### Load the Project in MonoDevelop From 954fe6da11ba57bbcc085727801c16fb8efbdab3 Mon Sep 17 00:00:00 2001 From: Brendan Forster Date: Wed, 27 Jan 2016 09:48:18 +1030 Subject: [PATCH 12/14] added a third project which impacts Windows-only dev --- docs/build.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/build.md b/docs/build.md index e0a8698fc8..935341c0d5 100644 --- a/docs/build.md +++ b/docs/build.md @@ -7,8 +7,11 @@ Perspex requires Visual Studio 2015 to build on Windows. ### Install GTK Sharp To compile the full project under windows, you must have [gtk-sharp](http://www.mono-project.com/download/#download-win) installed. However, if you're -not interested in building the cross-platform bits you can simply unload the Perspex.Cairo and -Perspex.Gtk project in Visual Studio. +not interested in building the cross-platform bits you can simply unload these projects from Visual Studio: + + - Perspex.Cairo + - Perspex.Cairo.RenderTests + - Perspex.Gtk ### Clone the Perspex repository From a74e9b3ff7dd8a660c5648a71f68c396b2b93d74 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 27 Jan 2016 02:27:12 +0300 Subject: [PATCH 13/14] Fixed `Path_Tick_Scaled`, `Path_Tick_Scaled_Stroke_8px` and `Path_Expander_With_Border` for Skia --- src/Skia/Perspex.Skia/DrawingContextImpl.cs | 16 +---- src/Skia/Perspex.Skia/MethodTable.cs | 35 ++++++++++- src/Skia/Perspex.Skia/PerspexHandleHolder.cs | 59 +++++++++++++++++++ src/Skia/Perspex.Skia/StreamGeometryImpl.cs | 40 +++++++++++-- tests/Perspex.RenderTests/Shapes/PathTests.cs | 12 ---- 5 files changed, 129 insertions(+), 33 deletions(-) diff --git a/src/Skia/Perspex.Skia/DrawingContextImpl.cs b/src/Skia/Perspex.Skia/DrawingContextImpl.cs index 405f021515..cce05bd1ae 100644 --- a/src/Skia/Perspex.Skia/DrawingContextImpl.cs +++ b/src/Skia/Perspex.Skia/DrawingContextImpl.cs @@ -37,18 +37,13 @@ namespace Perspex.Skia public void DrawGeometry(Brush brush, Pen pen, Geometry geometry) { var impl = ((StreamGeometryImpl) geometry.PlatformImpl); - var oldTransform = Transform; - if (!impl.Transform.IsIdentity) - Transform = impl.Transform*Transform; - var size = geometry.Bounds.Size; using(var fill = brush!=null?CreateBrush(brush, size):null) using (var stroke = pen?.Brush != null ? CreateBrush(pen, size) : null) { - MethodTable.Instance.DrawGeometry(Handle, impl.Path.Handle, fill != null ? fill.Brush : null, + MethodTable.Instance.DrawGeometry(Handle, impl.EffectivePath, fill != null ? fill.Brush : null, stroke != null ? stroke.Brush : null, impl.FillRule == FillRule.EvenOdd); } - Transform = oldTransform; } unsafe NativeBrushContainer CreateBrush(Brush brush, Size targetSize) @@ -196,14 +191,7 @@ namespace Perspex.Skia if(_currentTransform == value) return; _currentTransform = value; - _fmatrix[0] = (float)value.M11; - _fmatrix[1] = (float)value.M21; - _fmatrix[2] = (float)value.M31; - - _fmatrix[3] = (float)value.M12; - _fmatrix[4] = (float)value.M22; - _fmatrix[5] = (float)value.M32; - MethodTable.Instance.SetTransform(Handle, _fmatrix); + MethodTable.Instance.SetTransform(Handle, value); } } } diff --git a/src/Skia/Perspex.Skia/MethodTable.cs b/src/Skia/Perspex.Skia/MethodTable.cs index ba21588333..d17ead8720 100644 --- a/src/Skia/Perspex.Skia/MethodTable.cs +++ b/src/Skia/Perspex.Skia/MethodTable.cs @@ -47,9 +47,9 @@ namespace Perspex.Skia public _PopClip PopClip; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void _SetTransform(IntPtr ctx, float[] matrix6); + public delegate void _SetTransform(IntPtr ctx, void* matrix6); - public _SetTransform SetTransform; + public _SetTransform SetTransformNative; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void _DrawLine(IntPtr ctx, void* brush, float x1, float y1, float x2, float y2); @@ -66,6 +66,11 @@ namespace Perspex.Skia public _DisposePath DisposePath; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr _TransformPath(IntPtr path, void* matrix6); + + public _TransformPath TransformPathNative; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void _DrawGeometry(IntPtr ctx, IntPtr path, void* fill, void* stroke, bool useEvenOdd); @@ -185,10 +190,34 @@ namespace Perspex.Skia typeof (_RebuildFormattedText), typeof (_DestroyFormattedText), typeof (_DrawFormattedText), - typeof (_SetOption) + typeof (_SetOption), + typeof (_TransformPath) }; + void ConvertMatrix(Matrix value, float* target) + { + target[0] = (float)value.M11; + target[1] = (float)value.M21; + target[2] = (float)value.M31; + + target[3] = (float)value.M12; + target[4] = (float)value.M22; + target[5] = (float)value.M32; + } + public unsafe IntPtr TransformPath(IntPtr path, Matrix matrix) + { + var tmp = stackalloc float[6]; + ConvertMatrix(matrix, tmp); + return TransformPathNative(path, tmp); + } + + public unsafe void SetTransform(IntPtr ctx, Matrix matrix) + { + var tmp = stackalloc float[6]; + ConvertMatrix(matrix, tmp); + SetTransformNative(ctx, tmp); + } protected MethodTable(IntPtr methodTable) { diff --git a/src/Skia/Perspex.Skia/PerspexHandleHolder.cs b/src/Skia/Perspex.Skia/PerspexHandleHolder.cs index d8f52e8f61..158a2f768c 100644 --- a/src/Skia/Perspex.Skia/PerspexHandleHolder.cs +++ b/src/Skia/Perspex.Skia/PerspexHandleHolder.cs @@ -44,4 +44,63 @@ namespace Perspex.Skia Dispose(); } } + + class RefCountable : IDisposable where T : PerspexHandleHolder + { + class Shared + { + public readonly T Target; + private int _refCount = 1; + + public Shared(T target) + { + Target = target; + } + + public void AddRef() => _refCount++; + public void Release() + { + _refCount--; + if (_refCount <= 0) + Target.Dispose(); + } + } + + public bool IsDisposed => _shared == null; + private Shared _shared; + public void CheckDisposed() + { + if (IsDisposed) + throw new ObjectDisposedException(GetType().FullName); + } + + public IntPtr Handle + { + get + { + CheckDisposed(); + return _shared.Target.Handle; + } + } + + public RefCountable(T handle) + { + _shared = new Shared(handle); + } + + public RefCountable(RefCountable other) + { + other._shared.Target.CheckDisposed(); + other._shared.AddRef(); + _shared = other._shared; + } + + public RefCountable Clone() => new RefCountable(this); + + public void Dispose() + { + _shared?.Release(); + _shared = null; + } + } } \ No newline at end of file diff --git a/src/Skia/Perspex.Skia/StreamGeometryImpl.cs b/src/Skia/Perspex.Skia/StreamGeometryImpl.cs index 3c62e65e7d..292af3d651 100644 --- a/src/Skia/Perspex.Skia/StreamGeometryImpl.cs +++ b/src/Skia/Perspex.Skia/StreamGeometryImpl.cs @@ -41,7 +41,11 @@ namespace Perspex.Skia class StreamGeometryImpl : IStreamGeometryImpl { - public SkPath Path; + RefCountable _path; + RefCountable _transformedPath; + private Matrix _transform = Matrix.Identity; + + public IntPtr EffectivePath => (_transformedPath ?? _path).Handle; public Rect GetRenderBounds(double strokeThickness) { @@ -51,11 +55,35 @@ namespace Perspex.Skia public Rect Bounds { get; private set; } - public Matrix Transform { get; set; } = Matrix.Identity; + public Matrix Transform + { + get { return _transform; } + set + { + if(_transform == value) + return; + _transform = value; + ApplyTransform(); + } + } + + void ApplyTransform() + { + if(_path == null) + return; + if (_transformedPath != null) + { + _transformedPath.Dispose(); + _transformedPath = null; + } + if (!_transform.IsIdentity) + _transformedPath = + new RefCountable(new SkPath(MethodTable.Instance.TransformPath(_path.Handle, Transform))); + } public IStreamGeometryImpl Clone() { - return new StreamGeometryImpl() {Path = Path, Transform = Transform, Bounds = Bounds}; + return new StreamGeometryImpl {_path = _path?.Clone(), _transformedPath = _transformedPath?.Clone(), _transform = Transform, Bounds = Bounds}; } public IStreamGeometryContextImpl Open() @@ -77,7 +105,11 @@ namespace Perspex.Skia { var arr = _elements.ToArray(); SkRect rc; - _geometryImpl.Path = new SkPath(MethodTable.Instance.CreatePath(arr, arr.Length, out rc)); + _geometryImpl._path?.Dispose(); + _geometryImpl._path = + new RefCountable(new SkPath(MethodTable.Instance.CreatePath(arr, arr.Length, out rc))); + _geometryImpl.ApplyTransform(); + _geometryImpl.Bounds = rc.ToRect(); } diff --git a/tests/Perspex.RenderTests/Shapes/PathTests.cs b/tests/Perspex.RenderTests/Shapes/PathTests.cs index 68b5956234..3d52b87ac2 100644 --- a/tests/Perspex.RenderTests/Shapes/PathTests.cs +++ b/tests/Perspex.RenderTests/Shapes/PathTests.cs @@ -46,11 +46,7 @@ namespace Perspex.Direct2D1.RenderTests.Shapes CompareImages(); } -#if PERSPEX_SKIA - [Fact(Skip = "FIXME")] -#else [Fact] -#endif public void Path_Tick_Scaled() { Decorator target = new Decorator @@ -73,11 +69,7 @@ namespace Perspex.Direct2D1.RenderTests.Shapes CompareImages(); } -#if PERSPEX_SKIA - [Fact(Skip = "FIXME")] -#else [Fact] -#endif public void Path_Tick_Scaled_Stroke_8px() { Decorator target = new Decorator @@ -100,11 +92,7 @@ namespace Perspex.Direct2D1.RenderTests.Shapes CompareImages(); } -#if PERSPEX_SKIA - [Fact(Skip = "FIXME")] -#else [Fact] -#endif public void Path_Expander_With_Border() { Decorator target = new Decorator From 3556a66b59481254f1d5631b48c0f6bb392f2bd8 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 27 Jan 2016 02:39:54 +0300 Subject: [PATCH 14/14] Use curl if available --- src/Skia/getnatives.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Skia/getnatives.sh b/src/Skia/getnatives.sh index 7ceace9db3..7486b35f80 100755 --- a/src/Skia/getnatives.sh +++ b/src/Skia/getnatives.sh @@ -1,7 +1,13 @@ #!/bin/sh -rm -rf native +rm -rf native native.zip mkdir -p native cd native +if which curl +then +curl `cat ../native.url` -o native.zip +else wget `cat ../native.url` -O native.zip -unzip native.zip +fi +unzip native.zip +chmod -R +x .