diff --git a/.ncrunch/Avalonia.Native.v3.ncrunchproject b/.ncrunch/Avalonia.Native.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/Avalonia.Native.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index ec3bf799b4..7c2ae441d0 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -1,33 +1,41 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ Background="{DynamicResource ThemeBackgroundBrush}"
+ Foreground="{DynamicResource ThemeForegroundBrush}"
+ FontSize="{DynamicResource FontSizeNormal}">
+
+
+ Light
+ Dark
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index 0be5d25a09..a498b17bdd 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -2,6 +2,7 @@ using System.Collections;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.Markup.Xaml.Styling;
using Avalonia.Platform;
using ControlCatalog.Pages;
@@ -27,6 +28,22 @@ namespace ControlCatalog
});
}
+ var light = AvaloniaXamlLoader.Parse(@"");
+ var dark = AvaloniaXamlLoader.Parse(@"");
+ var themes = this.Find("Themes");
+ themes.SelectionChanged += (sender, e) =>
+ {
+ switch (themes.SelectedIndex)
+ {
+ case 0:
+ Styles[0] = light;
+ break;
+ case 1:
+ Styles[0] = dark;
+ break;
+ }
+ };
+ Styles.Add(light);
}
private void InitializeComponent()
diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml
new file mode 100644
index 0000000000..430ac28347
--- /dev/null
+++ b/samples/ControlCatalog/Pages/TabControlPage.xaml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This is the first page in the TabControl.
+
+
+
+
+
+ This is the second page in the TabControl.
+
+
+
+
+ You should not see this.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tab Placement:
+
+ Left
+ Bottom
+ Right
+ Top
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs
new file mode 100644
index 0000000000..808d90a49c
--- /dev/null
+++ b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs
@@ -0,0 +1,80 @@
+using System;
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+
+using ReactiveUI;
+
+namespace ControlCatalog.Pages
+{
+ using System.Collections.Generic;
+
+ public class TabControlPage : UserControl
+ {
+ public TabControlPage()
+ {
+ InitializeComponent();
+
+ DataContext = new PageViewModel
+ {
+ Tabs = new[]
+ {
+ new TabItemViewModel
+ {
+ Header = "Arch",
+ Text = "This is the first templated tab page.",
+ Image = LoadBitmap("resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg?assembly=ControlCatalog"),
+ },
+ new TabItemViewModel
+ {
+ Header = "Leaf",
+ Text = "This is the second templated tab page.",
+ Image = LoadBitmap("resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg?assembly=ControlCatalog"),
+ },
+ new TabItemViewModel
+ {
+ Header = "Disabled",
+ Text = "You should not see this.",
+ IsEnabled = false,
+ },
+ },
+ TabPlacement = Dock.Top,
+ };
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private IBitmap LoadBitmap(string uri)
+ {
+ var assets = AvaloniaLocator.Current.GetService();
+ return new Bitmap(assets.Open(new Uri(uri)));
+ }
+
+ private class PageViewModel : ReactiveObject
+ {
+ private Dock _tabPlacement;
+
+ public TabItemViewModel[] Tabs { get; set; }
+
+ public Dock TabPlacement
+ {
+ get { return _tabPlacement; }
+ set { this.RaiseAndSetIfChanged(ref _tabPlacement, value); }
+ }
+ }
+
+ private class TabItemViewModel
+ {
+ public string Header { get; set; }
+ public string Text { get; set; }
+ public IBitmap Image { get; set; }
+ public bool IsEnabled { get; set; } = true;
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml b/samples/ControlCatalog/Pages/ViewboxPage.xaml
new file mode 100644
index 0000000000..89a82c4791
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml
@@ -0,0 +1,65 @@
+
+
+
+ F1 M 16.6309,18.6563C 17.1309,
+ 8.15625 29.8809,14.1563 29.8809,
+ 14.1563C 30.8809,11.1563 34.1308,
+ 11.4063 34.1308,11.4063C 33.5,12
+ 34.6309,13.1563 34.6309,13.1563C
+ 32.1309,13.1562 31.1309,14.9062
+ 31.1309,14.9062C 41.1309,23.9062
+ 32.6309,27.9063 32.6309,27.9062C
+ 24.6309,24.9063 21.1309,22.1562
+ 16.6309,18.6563 Z M 16.6309,19.9063C
+ 21.6309,24.1563 25.1309,26.1562
+ 31.6309,28.6562C 31.6309,28.6562
+ 26.3809,39.1562 18.3809,36.1563C
+ 18.3809,36.1563 18,38 16.3809,36.9063C
+ 15,36 16.3809,34.9063 16.3809,34.9063C
+ 16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z
+
+
+
+
+
+ Viewbox
+ A control used to scale single child.
+
+
+ None
+ Fill
+ Uniform
+ UniformToFill
+
+
+ Hello World!
+
+
+ Hello World!
+
+
+ Hello World!
+
+
+ Hello World!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs
new file mode 100644
index 0000000000..1b5f4bc7f4
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+ public class ViewboxPage : UserControl
+ {
+ public ViewboxPage()
+ {
+ this.InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml
index cc3c31d13a..ae9ab7f6a6 100644
--- a/samples/ControlCatalog/SideBar.xaml
+++ b/samples/ControlCatalog/SideBar.xaml
@@ -1,52 +1,66 @@
-
+
-
-
-
-
-
+
+
+
+
+
diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml
index fc5b1ce94d..41164c7780 100644
--- a/samples/RenderDemo/MainWindow.xaml
+++ b/samples/RenderDemo/MainWindow.xaml
@@ -24,9 +24,6 @@
-
-
-
@@ -38,4 +35,4 @@
-
\ No newline at end of file
+
diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml
index 26da2cc556..624c1a7b28 100644
--- a/samples/RenderDemo/SideBar.xaml
+++ b/samples/RenderDemo/SideBar.xaml
@@ -1,53 +1,66 @@
-
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
+
-
-
-
-
-
+
+
+
+
+
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs
index 9ee4787e47..f2f3ed9bfc 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs
@@ -88,18 +88,21 @@ namespace Avalonia.Data.Core
_subscriber(value);
}
- protected void ValueChanged(object value)
+ protected void ValueChanged(object value) => ValueChanged(value, true);
+
+ private void ValueChanged(object value, bool notify)
{
var notification = value as BindingNotification;
if (notification == null)
{
LastValue = new WeakReference(value);
+
if (Next != null)
{
- Next.Target = new WeakReference(value);
+ Next.Target = LastValue;
}
- else
+ else if (notify)
{
_subscriber(value);
}
@@ -110,7 +113,7 @@ namespace Avalonia.Data.Core
if (Next != null)
{
- Next.Target = new WeakReference(notification.Value);
+ Next.Target = LastValue;
}
if (Next == null || notification.Error != null)
@@ -136,6 +139,7 @@ namespace Avalonia.Data.Core
}
else
{
+ ValueChanged(AvaloniaProperty.UnsetValue, notify:false);
_listening = false;
}
}
diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs
index 8596d06d2c..93b33e0589 100644
--- a/src/Avalonia.Controls/DropDown.cs
+++ b/src/Avalonia.Controls/DropDown.cs
@@ -2,9 +2,12 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Linq;
using Avalonia.Controls.Generators;
+using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
+using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Media;
@@ -12,12 +15,17 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls
{
-
///
/// A drop-down list control.
///
public class DropDown : SelectingItemsControl
{
+ ///
+ /// The default value for the property.
+ ///
+ private static readonly FuncTemplate DefaultPanel =
+ new FuncTemplate(() => new VirtualizingStackPanel());
+
///
/// Defines the property.
///
@@ -39,6 +47,12 @@ namespace Avalonia.Controls
public static readonly DirectProperty SelectionBoxItemProperty =
AvaloniaProperty.RegisterDirect(nameof(SelectionBoxItem), o => o.SelectionBoxItem);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty VirtualizationModeProperty =
+ ItemsPresenter.VirtualizationModeProperty.AddOwner();
+
private bool _isDropDownOpen;
private Popup _popup;
private object _selectionBoxItem;
@@ -48,6 +62,7 @@ namespace Avalonia.Controls
///
static DropDown()
{
+ ItemsPanelProperty.OverrideDefaultValue(DefaultPanel);
FocusableProperty.OverrideDefaultValue(true);
SelectedItemProperty.Changed.AddClassHandler(x => x.SelectedItemChanged);
KeyDownEvent.AddClassHandler(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel);
@@ -80,6 +95,15 @@ namespace Avalonia.Controls
set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); }
}
+ ///
+ /// Gets or sets the virtualization mode for the items.
+ ///
+ public ItemVirtualizationMode VirtualizationMode
+ {
+ get { return GetValue(VirtualizationModeProperty); }
+ set { SetValue(VirtualizationModeProperty, value); }
+ }
+
///
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
@@ -138,6 +162,16 @@ namespace Avalonia.Controls
e.Handled = true;
}
}
+ else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 &&
+ (e.Key == Key.Up || e.Key == Key.Down))
+ {
+ var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c));
+ if (firstChild != null)
+ {
+ FocusManager.Instance?.Focus(firstChild, NavigationMethod.Directional);
+ e.Handled = true;
+ }
+ }
}
///
@@ -159,6 +193,7 @@ namespace Avalonia.Controls
e.Handled = true;
}
}
+
base.OnPointerPressed(e);
}
@@ -168,28 +203,65 @@ namespace Avalonia.Controls
if (_popup != null)
{
_popup.Opened -= PopupOpened;
+ _popup.Closed -= PopupClosed;
}
_popup = e.NameScope.Get("PART_Popup");
_popup.Opened += PopupOpened;
+ _popup.Closed += PopupClosed;
+
+ base.OnTemplateApplied(e);
}
- private void PopupOpened(object sender, EventArgs e)
+ internal void ItemFocused(DropDownItem dropDownItem)
{
- var selectedIndex = SelectedIndex;
+ if (IsDropDownOpen && dropDownItem.IsFocused && dropDownItem.IsArrangeValid)
+ {
+ dropDownItem.BringIntoView();
+ }
+ }
- if (selectedIndex != -1)
+ private void PopupClosed(object sender, EventArgs e)
+ {
+ if (CanFocus(this))
{
- var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
- container?.Focus();
+ Focus();
}
}
+ private void PopupOpened(object sender, EventArgs e)
+ {
+ TryFocusSelectedItem();
+ }
+
private void SelectedItemChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateSelectionBoxItem(e.NewValue);
+ TryFocusSelectedItem();
+ }
+
+ private void TryFocusSelectedItem()
+ {
+ var selectedIndex = SelectedIndex;
+ if (IsDropDownOpen && selectedIndex != -1)
+ {
+ var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
+
+ if(container == null && SelectedItems.Count > 0)
+ {
+ ScrollIntoView(SelectedItems[0]);
+ container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
+ }
+
+ if (container != null && CanFocus(container))
+ {
+ container.Focus();
+ }
+ }
}
+ private bool CanFocus(IControl control) => control.Focusable && control.IsEnabledCore && control.IsVisible;
+
private void UpdateSelectionBoxItem(object item)
{
var contentControl = item as IContentControl;
@@ -219,7 +291,8 @@ namespace Avalonia.Controls
}
else
{
- SelectionBoxItem = item;
+ var selector = MemberSelector;
+ SelectionBoxItem = selector != null ? selector.Select(item) : item;
}
}
diff --git a/src/Avalonia.Controls/DropDownItem.cs b/src/Avalonia.Controls/DropDownItem.cs
index fb465e93ec..1e22ededf6 100644
--- a/src/Avalonia.Controls/DropDownItem.cs
+++ b/src/Avalonia.Controls/DropDownItem.cs
@@ -2,43 +2,19 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Reactive.Linq;
namespace Avalonia.Controls
{
///
/// A selectable item in a .
///
- public class DropDownItem : ContentControl, ISelectable
+ public class DropDownItem : ListBoxItem
{
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty IsSelectedProperty =
- AvaloniaProperty.Register(nameof(IsSelected));
-
- ///
- /// Initializes static members of the class.
- ///
- static DropDownItem()
- {
- FocusableProperty.OverrideDefaultValue(true);
- }
-
public DropDownItem()
{
- this.GetObservable(DropDownItem.IsFocusedProperty).Subscribe(focused =>
- {
- PseudoClasses.Set(":selected", focused);
- });
- }
-
- ///
- /// Gets or sets the selection state of the item.
- ///
- public bool IsSelected
- {
- get { return GetValue(IsSelectedProperty); }
- set { SetValue(IsSelectedProperty, value); }
+ this.GetObservable(DropDownItem.IsFocusedProperty).Where(focused => focused)
+ .Subscribe(_ => (Parent as DropDown)?.ItemFocused(this));
}
}
}
diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs
new file mode 100644
index 0000000000..a6a64e570b
--- /dev/null
+++ b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs
@@ -0,0 +1,59 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Controls.Primitives;
+
+namespace Avalonia.Controls.Generators
+{
+ public class TabItemContainerGenerator : ItemContainerGenerator
+ {
+ public TabItemContainerGenerator(TabControl owner)
+ : base(owner, ContentControl.ContentProperty, ContentControl.ContentTemplateProperty)
+ {
+ Owner = owner;
+ }
+
+ public new TabControl Owner { get; }
+
+ protected override IControl CreateContainer(object item)
+ {
+ var tabItem = (TabItem)base.CreateContainer(item);
+
+ tabItem.ParentTabControl = Owner;
+
+ tabItem[~TabControl.TabStripPlacementProperty] = Owner[~TabControl.TabStripPlacementProperty];
+
+ if (tabItem.HeaderTemplate == null)
+ {
+ tabItem[~HeaderedContentControl.HeaderTemplateProperty] = Owner[~ItemsControl.ItemTemplateProperty];
+ }
+
+ if (tabItem.Header == null)
+ {
+ if (item is IHeadered headered)
+ {
+ tabItem.Header = headered.Header;
+ }
+ else
+ {
+ if (!(tabItem.DataContext is IControl))
+ {
+ tabItem.Header = tabItem.DataContext;
+ }
+ }
+ }
+
+ if (!(tabItem.Content is IControl))
+ {
+ tabItem[~ContentControl.ContentTemplateProperty] = Owner[~TabControl.ContentTemplateProperty];
+ }
+
+ if (tabItem.Content == null)
+ {
+ tabItem[~ContentControl.ContentProperty] = tabItem[~StyledElement.DataContextProperty];
+ }
+
+ return tabItem;
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 9d4cbb9260..d74078c712 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -249,8 +249,6 @@ namespace Avalonia.Controls
if (containerControl != null)
{
((ISetLogicalParent)containerControl).SetParent(this);
- containerControl.SetValue(TemplatedParentProperty, null);
-
containerControl.UpdateChild();
if (containerControl.Child != null)
diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs
index 87e3853643..950d4f34da 100644
--- a/src/Avalonia.Controls/LayoutTransformControl.cs
+++ b/src/Avalonia.Controls/LayoutTransformControl.cs
@@ -5,28 +5,30 @@
// http://silverlight.codeplex.com/SourceControl/changeset/view/74775#Release/Silverlight4/Source/Controls.Layout.Toolkit/LayoutTransformer/LayoutTransformer.cs
//
-using Avalonia.Controls.Primitives;
-using Avalonia.Media;
-using Avalonia.VisualTree;
using System;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using System.Reactive.Linq;
+using Avalonia.Media;
namespace Avalonia.Controls
{
///
/// Control that implements support for transformations as if applied by LayoutTransform.
///
- public class LayoutTransformControl : ContentControl
+ public class LayoutTransformControl : Decorator
{
public static readonly AvaloniaProperty LayoutTransformProperty =
AvaloniaProperty.Register(nameof(LayoutTransform));
static LayoutTransformControl()
{
+ ClipToBoundsProperty.OverrideDefaultValue(true);
+
LayoutTransformProperty.Changed
.AddClassHandler(x => x.OnLayoutTransformChanged);
+
+ ChildProperty.Changed
+ .AddClassHandler(x => x.OnChildChanged);
}
///
@@ -38,8 +40,7 @@ namespace Avalonia.Controls
set { SetValue(LayoutTransformProperty, value); }
}
- public Control TransformRoot => _transformRoot ??
- (_transformRoot = this.GetVisualChildren().OfType().FirstOrDefault());
+ public IControl TransformRoot => Child;
///
/// Provides the behavior for the "Arrange" pass of layout.
@@ -132,16 +133,8 @@ namespace Avalonia.Controls
return transformedDesiredSize;
}
- ///
- /// Builds the visual tree for the LayoutTransformerControl when a new
- /// template is applied.
- ///
- protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+ private void OnChildChanged(AvaloniaPropertyChangedEventArgs e)
{
- base.OnTemplateApplied(e);
-
- _matrixTransform = new MatrixTransform();
-
if (null != TransformRoot)
{
TransformRoot.RenderTransform = _matrixTransform;
@@ -169,14 +162,14 @@ namespace Avalonia.Controls
///
/// RenderTransform/MatrixTransform applied to TransformRoot.
///
- private MatrixTransform _matrixTransform;
+ private MatrixTransform _matrixTransform = new MatrixTransform();
///
/// Transformation matrix corresponding to _matrixTransform.
///
private Matrix _transformation;
private IDisposable _transformChangedEvent = null;
- private Control _transformRoot;
+
///
/// Returns true if Size a is smaller than Size b in either dimension.
///
@@ -215,7 +208,8 @@ namespace Avalonia.Controls
///
private void ApplyLayoutTransform()
{
- if (LayoutTransform == null) return;
+ if (LayoutTransform == null)
+ return;
// Get the transform matrix and apply it
_transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound);
@@ -376,11 +370,8 @@ namespace Avalonia.Controls
{
var newTransform = e.NewValue as Transform;
- if (_transformChangedEvent != null)
- {
- _transformChangedEvent.Dispose();
- _transformChangedEvent = null;
- }
+ _transformChangedEvent?.Dispose();
+ _transformChangedEvent = null;
if (newTransform != null)
{
@@ -392,4 +383,4 @@ namespace Avalonia.Controls
ApplyLayoutTransform();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
index d67ebfd489..7a46e0f776 100644
--- a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
+++ b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
@@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using Avalonia.Controls.Templates;
+
namespace Avalonia.Controls.Primitives
{
///
@@ -12,7 +14,13 @@ namespace Avalonia.Controls.Primitives
/// Defines the property.
///
public static readonly StyledProperty
@@ -54,4 +60,4 @@
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Default/DropDownItem.xaml b/src/Avalonia.Themes.Default/DropDownItem.xaml
index f52608c0a8..f542a34d71 100644
--- a/src/Avalonia.Themes.Default/DropDownItem.xaml
+++ b/src/Avalonia.Themes.Default/DropDownItem.xaml
@@ -18,7 +18,7 @@
-
+
diff --git a/src/Avalonia.Themes.Default/FocusAdorner.xaml b/src/Avalonia.Themes.Default/FocusAdorner.xaml
index 573c43dc8d..2d5e369573 100644
--- a/src/Avalonia.Themes.Default/FocusAdorner.xaml
+++ b/src/Avalonia.Themes.Default/FocusAdorner.xaml
@@ -3,7 +3,8 @@
+ StrokeDashArray="1,2"
+ Margin="1"/>
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Default/LayoutTransformControl.xaml b/src/Avalonia.Themes.Default/LayoutTransformControl.xaml
deleted file mode 100644
index b26f053622..0000000000
--- a/src/Avalonia.Themes.Default/LayoutTransformControl.xaml
+++ /dev/null
@@ -1,13 +0,0 @@
-
\ No newline at end of file
diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml
index ae40929573..2cb8ce2d24 100644
--- a/src/Avalonia.Themes.Default/ScrollBar.xaml
+++ b/src/Avalonia.Themes.Default/ScrollBar.xaml
@@ -3,7 +3,7 @@
-
+
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml
new file mode 100644
index 0000000000..fcdb76524e
--- /dev/null
+++ b/src/Avalonia.Themes.Default/TabItem.xaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
index a268eff78a..b48efaa34e 100644
--- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
@@ -37,6 +37,7 @@ namespace Avalonia.Rendering
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
private IRef _currentDraw;
private readonly IDeferredRendererLock _lock;
+ private readonly object _sceneLock = new object();
///
/// Initializes a new instance of the class.
@@ -84,6 +85,7 @@ namespace Avalonia.Rendering
RenderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
Layers = new RenderLayers();
+ _lock = new ManagedDeferredRendererLock();
}
///
@@ -118,8 +120,13 @@ namespace Avalonia.Rendering
///
public void Dispose()
{
- var scene = Interlocked.Exchange(ref _scene, null);
- scene?.Dispose();
+ lock (_sceneLock)
+ {
+ var scene = _scene;
+ _scene = null;
+ scene?.Dispose();
+ }
+
Stop();
Layers.Clear();
@@ -134,7 +141,8 @@ namespace Avalonia.Rendering
// When unit testing the renderLoop may be null, so update the scene manually.
UpdateScene();
}
-
+ //It's safe to access _scene here without a lock since
+ //it's only changed from UI thread which we are currently on
return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty();
}
@@ -172,7 +180,8 @@ namespace Avalonia.Rendering
}
}
- bool IRenderLoopTask.NeedsUpdate => _dirty == null || _dirty.Count > 0;
+ bool NeedsUpdate => _dirty == null || _dirty.Count > 0;
+ bool IRenderLoopTask.NeedsUpdate => NeedsUpdate;
void IRenderLoopTask.Update(TimeSpan time) => UpdateScene();
@@ -197,79 +206,105 @@ namespace Avalonia.Rendering
internal void UnitTestUpdateScene() => UpdateScene();
- internal void UnitTestRender() => Render(_scene.Item, false);
+ internal void UnitTestRender() => Render(false);
private void Render(bool forceComposite)
{
using (var l = _lock.TryLock())
- if (l != null)
- using (var scene = _scene?.Clone())
- {
- Render(scene?.Item, forceComposite);
- }
- }
-
- private void Render(Scene scene, bool forceComposite)
- {
- bool renderOverlay = DrawDirtyRects || DrawFps;
- bool composite = false;
-
- if (RenderTarget == null)
{
- RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
- }
+ if (l == null)
+ return;
- if (renderOverlay)
- {
- _dirtyRectsDisplay.Tick();
- }
-
- try
- {
- if (scene != null && scene.Size != Size.Empty)
+ IDrawingContextImpl context = null;
+ try
{
- IDrawingContextImpl context = null;
-
- if (scene.Generation != _lastSceneId)
+ try
{
- context = RenderTarget.CreateDrawingContext(this);
- Layers.Update(scene, context);
+ IDrawingContextImpl GetContext()
+ {
+ if (context != null)
+ return context;
+ if (RenderTarget == null)
+ RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
+ return context = RenderTarget.CreateDrawingContext(this);
- RenderToLayers(scene);
+ }
- if (DebugFramesPath != null)
+ var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext);
+ using (scene)
{
- SaveDebugFrames(scene.Generation);
+ var overlay = DrawDirtyRects || DrawFps;
+ if (DrawDirtyRects)
+ _dirtyRectsDisplay.Tick();
+ if (overlay)
+ RenderOverlay(scene.Item, GetContext());
+ if (updated || forceComposite || overlay)
+ RenderComposite(scene.Item, GetContext());
}
+ }
+ finally
+ {
+ context?.Dispose();
+ }
+ }
+ catch (RenderTargetCorruptedException ex)
+ {
+ Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
+ RenderTarget?.Dispose();
+ RenderTarget = null;
+ }
+ }
+ }
- _lastSceneId = scene.Generation;
+ private (IRef scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(Func contextFactory,
+ bool recursiveCall = false)
+ {
+ IRef sceneRef;
+ lock (_sceneLock)
+ sceneRef = _scene?.Clone();
+ if (sceneRef == null)
+ return (null, false);
+ using (sceneRef)
+ {
+ var scene = sceneRef.Item;
+ if (scene.Generation != _lastSceneId)
+ {
+ var context = contextFactory();
+ Layers.Update(scene, context);
- composite = true;
- }
+ RenderToLayers(scene);
- if (renderOverlay)
+ if (DebugFramesPath != null)
{
- context = context ?? RenderTarget.CreateDrawingContext(this);
- RenderOverlay(scene, context);
- RenderComposite(scene, context);
+ SaveDebugFrames(scene.Generation);
}
- else if (composite || forceComposite)
+
+ lock (_sceneLock)
+ _lastSceneId = scene.Generation;
+
+
+ // We have consumed the previously available scene, but there might be some dirty
+ // rects since the last update. *If* we are on UI thread, we can force immediate scene
+ // rebuild before rendering anything on-screen
+ // We are calling the same method recursively here
+ if (!recursiveCall && Dispatcher.UIThread.CheckAccess() && NeedsUpdate)
{
- context = context ?? RenderTarget.CreateDrawingContext(this);
- RenderComposite(scene, context);
+ UpdateScene();
+ var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(contextFactory, true);
+ return (rs, true);
}
-
- context?.Dispose();
+
+ // Indicate that we have updated the layers
+ return (sceneRef.Clone(), true);
}
+
+ // Just return scene, layers weren't updated
+ return (sceneRef.Clone(), false);
}
- catch (RenderTargetCorruptedException ex)
- {
- Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
- RenderTarget?.Dispose();
- RenderTarget = null;
- }
+
}
+
private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds)
{
if (layer == null || node.LayerRoot == layer)
@@ -405,6 +440,11 @@ namespace Avalonia.Rendering
private void UpdateScene()
{
Dispatcher.UIThread.VerifyAccess();
+ lock (_sceneLock)
+ {
+ if (_scene?.Item.Generation > _lastSceneId)
+ return;
+ }
if (_root.IsVisible)
{
var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root));
@@ -423,15 +463,23 @@ namespace Avalonia.Rendering
}
}
- var oldScene = Interlocked.Exchange(ref _scene, sceneRef);
- oldScene?.Dispose();
+ lock (_sceneLock)
+ {
+ var oldScene = _scene;
+ _scene = sceneRef;
+ oldScene?.Dispose();
+ }
_dirty.Clear();
}
else
{
- var oldScene = Interlocked.Exchange(ref _scene, null);
- oldScene?.Dispose();
+ lock (_sceneLock)
+ {
+ var oldScene = _scene;
+ _scene = null;
+ oldScene?.Dispose();
+ }
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs
index ab82b7a28b..2f7256fa22 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs
@@ -1,13 +1,12 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
using Avalonia.Data;
+using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
- using Portable.Xaml.Markup;
- using System;
-
public class RelativeSourceExtension : MarkupExtension
{
public RelativeSourceExtension()
@@ -24,10 +23,19 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
return new RelativeSource
{
Mode = Mode,
+ AncestorType = AncestorType,
+ AncestorLevel = AncestorLevel,
+ Tree = Tree,
};
}
[ConstructorArgument("mode")]
- public RelativeSourceMode Mode { get; set; }
+ public RelativeSourceMode Mode { get; set; } = RelativeSourceMode.FindAncestor;
+
+ public Type AncestorType { get; set; }
+
+ public TreeType Tree { get; set; }
+
+ public int AncestorLevel { get; set; } = 1;
}
}
\ No newline at end of file
diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs
index 186c55d9eb..8a5a725594 100644
--- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs
+++ b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs
@@ -89,9 +89,11 @@ namespace Avalonia.Shared.PlatformSupport
#if DEBUG
if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId)
{
- Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: "
- + Environment.StackTrace
- + "\n\nBlob created by " + _backtrace);
+ lock(_lock)
+ if (!IsDisposed)
+ Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: "
+ + Environment.StackTrace
+ + "\n\nBlob created by " + _backtrace);
}
#endif
DoDispose();
diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs
index c910703181..87c5a1bb02 100644
--- a/src/Windows/Avalonia.Win32/FramebufferManager.cs
+++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs
@@ -5,7 +5,7 @@ using Avalonia.Win32.Interop;
namespace Avalonia.Win32
{
- class FramebufferManager : IFramebufferPlatformSurface, IDisposable
+ class FramebufferManager : IFramebufferPlatformSurface
{
private readonly IntPtr _hwnd;
private WindowFramebuffer _fb;
@@ -29,10 +29,5 @@ namespace Avalonia.Win32
}
return _fb;
}
-
- public void Dispose()
- {
- _fb?.Deallocate();
- }
}
}
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 4c08e985cd..18f0696cd8 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -13,6 +13,7 @@ using Avalonia.Input.Raw;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
+using Avalonia.Threading;
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
@@ -234,8 +235,6 @@ namespace Avalonia.Win32
public void Dispose()
{
- _framebuffer?.Dispose();
- _framebuffer = null;
if (_hwnd != IntPtr.Zero)
{
UnmanagedMethods.DestroyWindow(_hwnd);
diff --git a/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs b/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs
index d5f9818f89..13c946b549 100644
--- a/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs
@@ -1,6 +1,4 @@
-using Avalonia.Controls.Presenters;
using Avalonia.Controls.Shapes;
-using Avalonia.Controls.Templates;
using Avalonia.Media;
using Xunit;
@@ -311,20 +309,10 @@ namespace Avalonia.Controls.UnitTests
{
var lt = new LayoutTransformControl()
{
- LayoutTransform = transform,
- Template = new FuncControlTemplate(
- p => new ContentPresenter() { Content = p.Content })
+ LayoutTransform = transform
};
- lt.Content = new Rectangle() { Width = width, Height = height };
-
- lt.ApplyTemplate();
-
- //we need to force create visual child
- //so the measure after is correct
- (lt.Presenter as ContentPresenter).UpdateChild();
-
- Assert.NotNull(lt.Presenter?.Child);
+ lt.Child = new Rectangle() { Width = width, Height = height };
lt.Measure(Size.Infinity);
lt.Arrange(new Rect(lt.DesiredSize));
@@ -332,4 +320,4 @@ namespace Avalonia.Controls.UnitTests
return lt;
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
index 322c14c6bd..a5c3881d37 100644
--- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Controls.UnitTests
TabItem selected;
var target = new TabControl
{
- Template = new FuncControlTemplate(CreateTabControlTemplate),
+ Template = TabControlTemplate(),
Items = new[]
{
(selected = new TabItem
@@ -61,7 +61,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl
{
- Template = new FuncControlTemplate(CreateTabControlTemplate),
+ Template = TabControlTemplate(),
Items = items,
};
@@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl
{
- Template = new FuncControlTemplate(CreateTabControlTemplate),
+ Template = TabControlTemplate(),
Items = collection,
};
@@ -147,7 +147,7 @@ namespace Avalonia.Controls.UnitTests
},
Child = new TabControl
{
- Template = new FuncControlTemplate(CreateTabControlTemplate),
+ Template = TabControlTemplate(),
Items = collection,
}
};
@@ -172,7 +172,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl
{
- Template = new FuncControlTemplate(CreateTabControlTemplate),
+ Template = TabControlTemplate(),
DataContext = "Base",
DataTemplates =
{
@@ -182,41 +182,39 @@ namespace Avalonia.Controls.UnitTests
};
ApplyTemplate(target);
- var carousel = (Carousel)target.Pages;
- var container = (ContentPresenter)carousel.Presenter.Panel.Children.Single();
- container.UpdateChild();
- var dataContext = ((TextBlock)container.Child).DataContext;
+ target.ContentPart.UpdateChild();
+ var dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
Assert.Equal(items[0], dataContext);
target.SelectedIndex = 1;
- container = (ContentPresenter)carousel.Presenter.Panel.Children.Single();
- container.UpdateChild();
- dataContext = ((Button)container.Child).DataContext;
+ target.ContentPart.UpdateChild();
+ dataContext = ((Button)target.ContentPart.Child).DataContext;
Assert.Equal(items[1], dataContext);
target.SelectedIndex = 2;
- dataContext = ((TextBlock)carousel.Presenter.Panel.Children.Single()).DataContext;
+ target.ContentPart.UpdateChild();
+ dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
Assert.Equal("Base", dataContext);
target.SelectedIndex = 3;
- container = (ContentPresenter)carousel.Presenter.Panel.Children[0];
- container.UpdateChild();
- dataContext = ((TextBlock)container.Child).DataContext;
+ target.ContentPart.UpdateChild();
+ dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
Assert.Equal("Qux", dataContext);
target.SelectedIndex = 4;
- dataContext = ((TextBlock)carousel.Presenter.Panel.Children.Single()).DataContext;
+ target.ContentPart.UpdateChild();
+ dataContext = target.ContentPart.DataContext;
Assert.Equal("Base", dataContext);
}
///
- /// Non-headered control items should result in TabStripItems with empty content.
+ /// Non-headered control items should result in TabItems with empty header.
///
///
- /// If a TabStrip is created with non IHeadered controls as its items, don't try to
- /// display the control in the TabStripItem: if the TabStrip is part of a TabControl
- /// then *that* will also try to display the control, resulting in dual-parentage
+ /// If a TabControl is created with non IHeadered controls as its items, don't try to
+ /// display the control in the header: if the control is part of the header then
+ /// *that* control would also end up in the content region, resulting in dual-parentage
/// breakage.
///
[Fact]
@@ -230,18 +228,20 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl
{
- Template = new FuncControlTemplate(CreateTabControlTemplate),
+ Template = TabControlTemplate(),
Items = items,
};
ApplyTemplate(target);
- var result = target.TabStrip.GetLogicalChildren()
- .OfType()
- .Select(x => x.Content)
+ var logicalChildren = target.ItemsPresenterPart.Panel.GetLogicalChildren();
+
+ var result = logicalChildren
+ .OfType()
+ .Select(x => x.Header)
.ToList();
- Assert.Equal(new object[] { string.Empty, string.Empty }, result);
+ Assert.Equal(new object[] { null, null }, result);
}
[Fact]
@@ -249,7 +249,7 @@ namespace Avalonia.Controls.UnitTests
{
TabControl target = new TabControl
{
- Template = new FuncControlTemplate(CreateTabControlTemplate),
+ Template = TabControlTemplate(),
Items = new[]
{
new TabItem { Header = "Foo" },
@@ -262,70 +262,61 @@ namespace Avalonia.Controls.UnitTests
target.SelectedIndex = 2;
- var carousel = (Carousel)target.Pages;
- var page = (TabItem)carousel.SelectedItem;
+ var page = (TabItem)target.SelectedItem;
Assert.Null(page.Content);
}
- private Control CreateTabControlTemplate(TabControl parent)
+ private IControlTemplate TabControlTemplate()
{
- return new StackPanel
- {
- Children =
- {
- new TabStrip
- {
- Name = "PART_TabStrip",
- Template = new FuncControlTemplate(CreateTabStripTemplate),
- MemberSelector = TabControl.HeaderSelector,
- [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty],
- [!!TabStrip.SelectedIndexProperty] = parent[!!TabControl.SelectedIndexProperty]
- },
- new Carousel
- {
- Name = "PART_Content",
- Template = new FuncControlTemplate(CreateCarouselTemplate),
- MemberSelector = TabControl.ContentSelector,
- [!Carousel.ItemsProperty] = parent[!TabControl.ItemsProperty],
- [!Carousel.SelectedItemProperty] = parent[!TabControl.SelectedItemProperty],
- }
- }
- };
- }
+ return new FuncControlTemplate(parent =>
- private Control CreateTabStripTemplate(TabStrip parent)
- {
- return new ItemsPresenter
- {
- Name = "PART_ItemsPresenter",
- [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
- [!CarouselPresenter.MemberSelectorProperty] = parent[!ItemsControl.MemberSelectorProperty],
- };
+ new StackPanel
+ {
+ Children = {
+ new ItemsPresenter
+ {
+ Name = "PART_ItemsPresenter",
+ [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty],
+ [!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty],
+ },
+ new ContentPresenter
+ {
+ Name = "PART_Content",
+ [!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty],
+ [!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty],
+ }
+ }
+ });
}
- private Control CreateCarouselTemplate(Carousel control)
+ private IControlTemplate TabItemTemplate()
{
- return new CarouselPresenter
- {
- Name = "PART_ItemsPresenter",
- [!CarouselPresenter.ItemsProperty] = control[!ItemsControl.ItemsProperty],
- [!CarouselPresenter.ItemsPanelProperty] = control[!ItemsControl.ItemsPanelProperty],
- [!CarouselPresenter.MemberSelectorProperty] = control[!ItemsControl.MemberSelectorProperty],
- [!CarouselPresenter.SelectedIndexProperty] = control[!SelectingItemsControl.SelectedIndexProperty],
- [~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty],
- };
+ return new FuncControlTemplate(parent =>
+ new ContentPresenter
+ {
+ Name = "PART_ContentPresenter",
+ [!ContentPresenter.ContentProperty] = parent[!TabItem.HeaderProperty],
+ [!ContentPresenter.ContentTemplateProperty] = parent[!TabItem.HeaderTemplateProperty]
+ });
}
private void ApplyTemplate(TabControl target)
{
target.ApplyTemplate();
- var carousel = (Carousel)target.Pages;
- carousel.ApplyTemplate();
- carousel.Presenter.ApplyTemplate();
- var tabStrip = (TabStrip)target.TabStrip;
- tabStrip.ApplyTemplate();
- tabStrip.Presenter.ApplyTemplate();
+
+ target.Presenter.ApplyTemplate();
+
+ foreach (var tabItem in target.GetLogicalChildren().OfType())
+ {
+ tabItem.Template = TabItemTemplate();
+
+ tabItem.ApplyTemplate();
+
+ ((ContentPresenter)tabItem.Presenter).UpdateChild();
+ }
+
+ target.ContentPart.ApplyTemplate();
}
private class Item
diff --git a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
new file mode 100644
index 0000000000..ad0f318d2f
--- /dev/null
+++ b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
@@ -0,0 +1,105 @@
+using Avalonia.Controls.Shapes;
+using Avalonia.Media;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests
+{
+ public class ViewboxTests
+ {
+ [Fact]
+ public void Viewbox_Stretch_Uniform_Child()
+ {
+ var target = new Viewbox() { Child = new Rectangle() { Width = 100, Height = 50 } };
+
+ target.Measure(new Size(200, 200));
+ target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
+
+ Assert.Equal(new Size(200, 100), target.DesiredSize);
+ var scaleTransform = target.Child.RenderTransform as ScaleTransform;
+
+ Assert.NotNull(scaleTransform);
+ Assert.Equal(2.0, scaleTransform.ScaleX);
+ Assert.Equal(2.0, scaleTransform.ScaleY);
+ }
+
+ [Fact]
+ public void Viewbox_Stretch_None_Child()
+ {
+ var target = new Viewbox() { Stretch = Stretch.None, Child = new Rectangle() { Width = 100, Height = 50 } };
+
+ target.Measure(new Size(200, 200));
+ target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
+
+ Assert.Equal(new Size(100, 50), target.DesiredSize);
+ var scaleTransform = target.Child.RenderTransform as ScaleTransform;
+
+ Assert.NotNull(scaleTransform);
+ Assert.Equal(1.0, scaleTransform.ScaleX);
+ Assert.Equal(1.0, scaleTransform.ScaleY);
+ }
+
+ [Fact]
+ public void Viewbox_Stretch_Fill_Child()
+ {
+ var target = new Viewbox() { Stretch = Stretch.Fill, Child = new Rectangle() { Width = 100, Height = 50 } };
+
+ target.Measure(new Size(200, 200));
+ target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
+
+ Assert.Equal(new Size(200, 200), target.DesiredSize);
+ var scaleTransform = target.Child.RenderTransform as ScaleTransform;
+
+ Assert.NotNull(scaleTransform);
+ Assert.Equal(2.0, scaleTransform.ScaleX);
+ Assert.Equal(4.0, scaleTransform.ScaleY);
+ }
+
+ [Fact]
+ public void Viewbox_Stretch_UniformToFill_Child()
+ {
+ var target = new Viewbox() { Stretch = Stretch.UniformToFill, Child = new Rectangle() { Width = 100, Height = 50 } };
+
+ target.Measure(new Size(200, 200));
+ target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
+
+ Assert.Equal(new Size(200, 200), target.DesiredSize);
+ var scaleTransform = target.Child.RenderTransform as ScaleTransform;
+
+ Assert.NotNull(scaleTransform);
+ Assert.Equal(4.0, scaleTransform.ScaleX);
+ Assert.Equal(4.0, scaleTransform.ScaleY);
+ }
+
+ [Fact]
+ public void Viewbox_Stretch_Uniform_Child_With_Unrestricted_Width()
+ {
+ var target = new Viewbox() { Child = new Rectangle() { Width = 100, Height = 50 } };
+
+ target.Measure(new Size(double.PositiveInfinity, 200));
+ target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
+
+ Assert.Equal(new Size(400, 200), target.DesiredSize);
+ var scaleTransform = target.Child.RenderTransform as ScaleTransform;
+
+ Assert.NotNull(scaleTransform);
+ Assert.Equal(4.0, scaleTransform.ScaleX);
+ Assert.Equal(4.0, scaleTransform.ScaleY);
+ }
+
+ [Fact]
+ public void Viewbox_Stretch_Uniform_Child_With_Unrestricted_Height()
+ {
+ var target = new Viewbox() { Child = new Rectangle() { Width = 100, Height = 50 } };
+
+ target.Measure(new Size(200, double.PositiveInfinity));
+ target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
+
+ Assert.Equal(new Size(200, 100), target.DesiredSize);
+ var scaleTransform = target.Child.RenderTransform as ScaleTransform;
+
+ Assert.NotNull(scaleTransform);
+ Assert.Equal(2.0, scaleTransform.ScaleX);
+ Assert.Equal(2.0, scaleTransform.ScaleY);
+ }
+ }
+}
diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
index 78d0efcbf9..ffdb146eec 100644
--- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
+++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
@@ -99,6 +99,8 @@ namespace Avalonia.Layout.UnitTests
}
};
+ window.Resources["ScrollBarThickness"] = 10.0;
+
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass(window);
diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs
index f2ddec6f3c..4c572acab1 100644
--- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs
+++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs
@@ -1,9 +1,13 @@
// Copyright (c) The Avalonia 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;
+using System.Reactive.Subjects;
using Avalonia.Controls;
using Avalonia.Data;
-using Avalonia.Markup.Data;
+using Avalonia.Data.Core;
+using Avalonia.Markup.Parsers;
using Avalonia.UnitTests;
using Xunit;
@@ -162,5 +166,99 @@ namespace Avalonia.Markup.UnitTests.Data
decorator2.Child = target;
Assert.Equal("decorator2", target.Text);
}
+
+ [Fact]
+ public void Should_Update_When_Detached_And_Attached_To_Visual_Tree_With_BindingPath()
+ {
+ TextBlock target;
+ Decorator decorator1;
+ Decorator decorator2;
+
+ var viewModel = new { Value = "Foo" };
+
+ var root1 = new TestRoot
+ {
+ Child = decorator1 = new Decorator
+ {
+ Name = "decorator1",
+ Child = target = new TextBlock(),
+ },
+ DataContext = viewModel
+ };
+
+ var root2 = new TestRoot
+ {
+ Child = decorator2 = new Decorator
+ {
+ Name = "decorator2",
+ },
+ DataContext = viewModel
+ };
+
+ var binding = new Binding
+ {
+ Path = "DataContext.Value",
+ RelativeSource = new RelativeSource
+ {
+ AncestorType = typeof(Decorator),
+ }
+ };
+
+ target.Bind(TextBox.TextProperty, binding);
+ Assert.Equal("Foo", target.Text);
+
+ decorator1.Child = null;
+ Assert.Null(target.Text);
+
+ decorator2.Child = target;
+ Assert.Equal("Foo", target.Text);
+ }
+
+ [Fact]
+ public void Should_Update_When_Detached_And_Attached_To_Visual_Tree_With_ComplexBindingPath()
+ {
+ TextBlock target;
+ Decorator decorator1;
+ Decorator decorator2;
+
+ var vm = new { Foo = new { Value = "Foo" } };
+
+ var root1 = new TestRoot
+ {
+ Child = decorator1 = new Decorator
+ {
+ Name = "decorator1",
+ Child = target = new TextBlock(),
+ },
+ DataContext = vm
+ };
+
+ var root2 = new TestRoot
+ {
+ Child = decorator2 = new Decorator
+ {
+ Name = "decorator2",
+ },
+ DataContext = vm
+ };
+
+ var binding = new Binding
+ {
+ Path = "DataContext.Foo.Value",
+ RelativeSource = new RelativeSource
+ {
+ AncestorType = typeof(Decorator),
+ }
+ };
+
+ target.Bind(TextBox.TextProperty, binding);
+ Assert.Equal("Foo", target.Text);
+
+ decorator1.Child = null;
+ Assert.Null(target.Text);
+
+ decorator2.Child = target;
+ Assert.Equal("Foo", target.Text);
+ }
}
}
diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs
index a97c998264..4f7264f2f2 100644
--- a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs
+++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs
@@ -1,10 +1,10 @@
-using Avalonia.Data;
-using Avalonia.Markup.Parsers;
-using System;
+using System;
using System.Collections.Generic;
using System.Reactive.Linq;
-using System.Text;
+using System.Reactive.Subjects;
using System.Threading.Tasks;
+using Avalonia.Data;
+using Avalonia.Markup.Parsers;
using Xunit;
namespace Avalonia.Markup.UnitTests.Parsers
@@ -38,5 +38,55 @@ namespace Avalonia.Markup.UnitTests.Parsers
GC.KeepAlive(data);
}
+
+ [Fact]
+ public void Should_Update_Value_After_Root_Changes()
+ {
+ var root = new { DataContext = new { Value = "Foo" } };
+ var subject = new Subject();
+ var obs = ExpressionObserverBuilder.Build(subject, "DataContext.Value");
+
+ var values = new List();
+ obs.Subscribe(v => values.Add(v));
+
+ subject.OnNext(root);
+ subject.OnNext(null);
+ subject.OnNext(root);
+
+ Assert.Equal("Foo", values[0]);
+
+ Assert.IsType(values[1]);
+ var bn = values[1] as BindingNotification;
+ Assert.Equal(AvaloniaProperty.UnsetValue, bn.Value);
+ Assert.Equal(BindingErrorType.Error, bn.ErrorType);
+
+ Assert.Equal(3, values.Count);
+ Assert.Equal("Foo", values[2]);
+ }
+
+ [Fact]
+ public void Should_Update_Value_After_Root_Changes_With_ComplexPath()
+ {
+ var root = new { DataContext = new { Foo = new { Value = "Foo" } } };
+ var subject = new Subject();
+ var obs = ExpressionObserverBuilder.Build(subject, "DataContext.Foo.Value");
+
+ var values = new List();
+ obs.Subscribe(v => values.Add(v));
+
+ subject.OnNext(root);
+ subject.OnNext(null);
+ subject.OnNext(root);
+
+ Assert.Equal("Foo", values[0]);
+
+ Assert.IsType(values[1]);
+ var bn = values[1] as BindingNotification;
+ Assert.Equal(AvaloniaProperty.UnsetValue, bn.Value);
+ Assert.Equal(BindingErrorType.Error, bn.ErrorType);
+
+ Assert.Equal(3, values.Count);
+ Assert.Equal("Foo", values[2]);
+ }
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
index a53809a029..ab5f5f37f7 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
@@ -405,7 +405,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Renderer = new DeferredRenderer(root, null);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
-
+
+ root.Renderer.Paint(Rect.Empty);
var result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
Assert.Equal(item1, result);
@@ -421,6 +422,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
container.InvalidateArrange();
container.Arrange(new Rect(container.DesiredSize));
+ root.Renderer.Paint(Rect.Empty);
result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
Assert.Equal(item2, result);