diff --git a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
index a8c3abe8f2..04ab17c4e1 100644
--- a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
@@ -1,7 +1,6 @@
- 1000
- True
+ 3000
True
\ No newline at end of file
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index efcbb57244..8b93cf2fb1 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -225,6 +225,19 @@ namespace Avalonia
return (T)GetValue((AvaloniaProperty)property);
}
+ ///
+ /// Checks whether a is animating.
+ ///
+ /// The property.
+ /// True if the property is animating, otherwise false.
+ public bool IsAnimating(AvaloniaProperty property)
+ {
+ Contract.Requires(property != null);
+ VerifyAccess();
+
+ return _values.TryGetValue(property, out PriorityValue value) ? value.IsAnimating : false;
+ }
+
///
/// Checks whether a is set on this object.
///
diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs
index a3c3015a38..41c2ad6e54 100644
--- a/src/Avalonia.Base/Collections/AvaloniaList.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaList.cs
@@ -350,14 +350,15 @@ namespace Avalonia.Collections
public void MoveRange(int oldIndex, int count, int newIndex)
{
var items = _inner.GetRange(oldIndex, count);
+ var modifiedNewIndex = newIndex;
_inner.RemoveRange(oldIndex, count);
if (newIndex > oldIndex)
{
- newIndex -= count;
+ modifiedNewIndex -= count;
}
- _inner.InsertRange(newIndex, items);
+ _inner.InsertRange(modifiedNewIndex, items);
if (_collectionChanged != null)
{
diff --git a/src/Avalonia.Base/Collections/IAvaloniaList.cs b/src/Avalonia.Base/Collections/IAvaloniaList.cs
index 0233cee7a9..48c36976a5 100644
--- a/src/Avalonia.Base/Collections/IAvaloniaList.cs
+++ b/src/Avalonia.Base/Collections/IAvaloniaList.cs
@@ -36,6 +36,21 @@ namespace Avalonia.Collections
/// The items.
void InsertRange(int index, IEnumerable items);
+ ///
+ /// Moves an item to a new index.
+ ///
+ /// The index of the item to move.
+ /// The index to move the item to.
+ void Move(int oldIndex, int newIndex);
+
+ ///
+ /// Moves multiple items to a new index.
+ ///
+ /// The first index of the items to move.
+ /// The number of items to move.
+ /// The index to move the items to.
+ void MoveRange(int oldIndex, int count, int newIndex);
+
///
/// Removes multiple items from the collection.
///
diff --git a/src/Avalonia.Base/IAvaloniaObject.cs b/src/Avalonia.Base/IAvaloniaObject.cs
index c11bab2236..c11f8ada7e 100644
--- a/src/Avalonia.Base/IAvaloniaObject.cs
+++ b/src/Avalonia.Base/IAvaloniaObject.cs
@@ -31,6 +31,13 @@ namespace Avalonia
/// The value.
T GetValue(AvaloniaProperty property);
+ ///
+ /// Checks whether a is animating.
+ ///
+ /// The property.
+ /// True if the property is animating, otherwise false.
+ bool IsAnimating(AvaloniaProperty property);
+
///
/// Checks whether a is set on this object.
///
diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs
index 3726fb7ae5..12a9e20528 100644
--- a/src/Avalonia.Base/PriorityValue.cs
+++ b/src/Avalonia.Base/PriorityValue.cs
@@ -52,6 +52,18 @@ namespace Avalonia
_validate = validate;
}
+ ///
+ /// Gets a value indicating whether the property is animating.
+ ///
+ public bool IsAnimating
+ {
+ get
+ {
+ return ValuePriority <= (int)BindingPriority.Animation &&
+ GetLevel(ValuePriority).ActiveBindingIndex != -1;
+ }
+ }
+
///
/// Gets the owner of the value.
///
diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs
index 8c79e5dce5..59281c5ad0 100644
--- a/src/Avalonia.Controls/Calendar/Calendar.cs
+++ b/src/Avalonia.Controls/Calendar/Calendar.cs
@@ -549,7 +549,7 @@ namespace Avalonia.Controls
}
else
{
- if (addedDate.HasValue && !(SelectedDates.Count > 0 && SelectedDates[0] == addedDate.Value))
+ if (!(SelectedDates.Count > 0 && SelectedDates[0] == addedDate.Value))
{
foreach (DateTime item in SelectedDates)
{
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index d4777b2f8a..6e1e1a05f1 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -621,7 +621,6 @@ namespace Avalonia.Controls
Contract.Requires(property != null);
Contract.Requires(selector != null);
Contract.Requires(className != null);
- Contract.Requires(property != null);
if (string.IsNullOrWhiteSpace(className))
{
diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs
index fa2e0c1e16..6b27c479ba 100644
--- a/src/Avalonia.Controls/DropDown.cs
+++ b/src/Avalonia.Controls/DropDown.cs
@@ -96,6 +96,16 @@ namespace Avalonia.Controls
this.UpdateSelectionBoxItem(this.SelectedItem);
}
+ protected override void OnGotFocus(GotFocusEventArgs e)
+ {
+ base.OnGotFocus(e);
+
+ if (!e.Handled && e.NavigationMethod == NavigationMethod.Directional)
+ {
+ e.Handled = UpdateSelectionFromEventSource(e.Source);
+ }
+ }
+
///
protected override void OnKeyDown(KeyEventArgs e)
{
@@ -104,7 +114,7 @@ namespace Avalonia.Controls
if (!e.Handled)
{
if (e.Key == Key.F4 ||
- (e.Key == Key.Down && ((e.Modifiers & InputModifiers.Alt) != 0)))
+ ((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0)))
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
@@ -114,6 +124,27 @@ namespace Avalonia.Controls
IsDropDownOpen = false;
e.Handled = true;
}
+
+ if (!IsDropDownOpen)
+ {
+ if (e.Key == Key.Down)
+ {
+ if (SelectedIndex == -1)
+ SelectedIndex = 0;
+
+ if (++SelectedIndex >= ItemCount)
+ SelectedIndex = 0;
+
+ e.Handled = true;
+ }
+ else if (e.Key == Key.Up)
+ {
+ if (--SelectedIndex < 0)
+ SelectedIndex = ItemCount - 1;
+
+ e.Handled = true;
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index aa209e0462..4366de1cd6 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -11,6 +11,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
+using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
@@ -106,6 +107,12 @@ namespace Avalonia.Controls
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
+ public int ItemCount
+ {
+ get;
+ private set;
+ }
+
///
/// Gets or sets the panel used to display the items.
///
@@ -352,6 +359,10 @@ namespace Avalonia.Controls
RemoveControlItemsFromLogicalChildren(e.OldItems);
break;
}
+
+ int? count = (Items as IList)?.Count;
+ if (count != null)
+ ItemCount = (int)count;
var collection = sender as ICollection;
PseudoClasses.Set(":empty", collection == null || collection.Count == 0);
diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs
index 3272d3779b..6448f11491 100644
--- a/src/Avalonia.Controls/Panel.cs
+++ b/src/Avalonia.Controls/Panel.cs
@@ -115,6 +115,11 @@ namespace Avalonia.Controls
VisualChildren.AddRange(e.NewItems.OfType());
break;
+ case NotifyCollectionChangedAction.Move:
+ LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+ VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+ break;
+
case NotifyCollectionChangedAction.Remove:
controls = e.OldItems.OfType().ToList();
LogicalChildren.RemoveAll(controls);
@@ -132,11 +137,7 @@ namespace Avalonia.Controls
break;
case NotifyCollectionChangedAction.Reset:
- controls = e.OldItems.OfType().ToList();
- LogicalChildren.Clear();
- VisualChildren.Clear();
- VisualChildren.AddRange(_children);
- break;
+ throw new NotSupportedException();
}
InvalidateMeasure();
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index ab09a4701d..563d394919 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -297,7 +297,7 @@ namespace Avalonia.Controls.Primitives
.OfType()
.FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1);
- return item as IControl;
+ return item;
}
///
diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs
index de02c10764..8cf6b149cb 100644
--- a/src/Avalonia.Controls/ProgressBar.cs
+++ b/src/Avalonia.Controls/ProgressBar.cs
@@ -28,9 +28,6 @@ namespace Avalonia.Controls
{
ValueProperty.Changed.AddClassHandler(x => x.ValueChanged);
- HorizontalAlignmentProperty.OverrideDefaultValue(HorizontalAlignment.Left);
- VerticalAlignmentProperty.OverrideDefaultValue(VerticalAlignment.Top);
-
IsIndeterminateProperty.Changed.AddClassHandler(
(p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
OrientationProperty.Changed.AddClassHandler(
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index 079e571d29..fa3ecdedef 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -176,10 +176,7 @@ namespace Avalonia.Controls
SelectedItem = item;
- if (SelectedItem != null)
- {
- MarkContainerSelected(container, true);
- }
+ MarkContainerSelected(container, true);
}
}
diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs
index 834f6d218b..409dd231ad 100644
--- a/src/Avalonia.Controls/VirtualizingStackPanel.cs
+++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs
@@ -134,12 +134,14 @@ namespace Avalonia.Controls
protected override IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
{
+ if (from == null)
+ return null;
+
var logicalScrollable = Parent as ILogicalScrollable;
- var fromControl = from as IControl;
- if (logicalScrollable?.IsLogicalScrollEnabled == true && fromControl != null)
+ if (logicalScrollable?.IsLogicalScrollEnabled == true)
{
- return logicalScrollable.GetControlInDirection(direction, fromControl);
+ return logicalScrollable.GetControlInDirection(direction, from);
}
else
{
diff --git a/src/Avalonia.Diagnostics/Views/TreePage.xaml.cs b/src/Avalonia.Diagnostics/Views/TreePage.xaml.cs
index 7e4e5a8564..d445f1cd70 100644
--- a/src/Avalonia.Diagnostics/Views/TreePage.xaml.cs
+++ b/src/Avalonia.Diagnostics/Views/TreePage.xaml.cs
@@ -27,6 +27,12 @@ namespace Avalonia.Diagnostics.Views
if (layer != null)
{
+ if (_adorner != null)
+ {
+ ((Panel)_adorner.Parent).Children.Remove(_adorner);
+ _adorner = null;
+ }
+
_adorner = new Rectangle
{
Fill = new SolidColorBrush(0x80a0c5e8),
diff --git a/src/Avalonia.Visuals/Media/PathMarkupParser.cs b/src/Avalonia.Visuals/Media/PathMarkupParser.cs
index fbc189546c..9e4a3cbeae 100644
--- a/src/Avalonia.Visuals/Media/PathMarkupParser.cs
+++ b/src/Avalonia.Visuals/Media/PathMarkupParser.cs
@@ -320,7 +320,7 @@ namespace Avalonia.Media
if (c == 'E')
{
readSign = false;
- readExponent = c == 'E';
+ readExponent = true;
}
}
else
diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
index f7befa646a..041d8f8f6b 100644
--- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
@@ -25,11 +25,9 @@ namespace Avalonia.Rendering
private readonly IRenderLoop _renderLoop;
private readonly IVisual _root;
private readonly ISceneBuilder _sceneBuilder;
- private readonly RenderLayers _layers;
private bool _running;
private Scene _scene;
- private IRenderTarget _renderTarget;
private DirtyVisuals _dirty;
private IRenderTargetBitmapImpl _overlay;
private bool _updateQueued;
@@ -56,7 +54,7 @@ namespace Avalonia.Rendering
_dispatcher = dispatcher ?? Dispatcher.UIThread;
_root = root;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
- _layers = new RenderLayers();
+ Layers = new RenderLayers();
_renderLoop = renderLoop;
}
@@ -78,9 +76,9 @@ namespace Avalonia.Rendering
Contract.Requires(renderTarget != null);
_root = root;
- _renderTarget = renderTarget;
+ RenderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
- _layers = new RenderLayers();
+ Layers = new RenderLayers();
}
///
@@ -94,6 +92,16 @@ namespace Avalonia.Rendering
///
public string DebugFramesPath { get; set; }
+ ///
+ /// Gets the render layers.
+ ///
+ internal RenderLayers Layers { get; }
+
+ ///
+ /// Gets the current render target.
+ ///
+ internal IRenderTarget RenderTarget { get; private set; }
+
///
public void AddDirty(IVisual visual)
{
@@ -173,9 +181,9 @@ namespace Avalonia.Rendering
bool renderOverlay = DrawDirtyRects || DrawFps;
bool composite = false;
- if (_renderTarget == null)
+ if (RenderTarget == null)
{
- _renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
+ RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
if (renderOverlay)
@@ -191,8 +199,8 @@ namespace Avalonia.Rendering
if (scene.Generation != _lastSceneId)
{
- context = _renderTarget.CreateDrawingContext(this);
- _layers.Update(scene, context);
+ context = RenderTarget.CreateDrawingContext(this);
+ Layers.Update(scene, context);
RenderToLayers(scene);
@@ -208,13 +216,13 @@ namespace Avalonia.Rendering
if (renderOverlay)
{
- context = context ?? _renderTarget.CreateDrawingContext(this);
+ context = context ?? RenderTarget.CreateDrawingContext(this);
RenderOverlay(scene, context);
RenderComposite(scene, context);
}
else if (composite)
{
- context = context ?? _renderTarget.CreateDrawingContext(this);
+ context = context ?? RenderTarget.CreateDrawingContext(this);
RenderComposite(scene, context);
}
@@ -224,8 +232,8 @@ namespace Avalonia.Rendering
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
- _renderTarget?.Dispose();
- _renderTarget = null;
+ RenderTarget?.Dispose();
+ RenderTarget = null;
}
}
@@ -235,9 +243,11 @@ namespace Avalonia.Rendering
{
clipBounds = node.ClipBounds.Intersect(clipBounds);
- if (!clipBounds.IsEmpty)
+ if (!clipBounds.IsEmpty && node.Opacity > 0)
{
- node.BeginRender(context);
+ var isLayerRoot = node.Visual == layer;
+
+ node.BeginRender(context, isLayerRoot);
foreach (var operation in node.DrawOperations)
{
@@ -251,7 +261,7 @@ namespace Avalonia.Rendering
Render(context, (VisualNode)child, layer, clipBounds);
}
- node.EndRender(context);
+ node.EndRender(context, isLayerRoot);
}
}
}
@@ -262,7 +272,7 @@ namespace Avalonia.Rendering
{
foreach (var layer in scene.Layers)
{
- var renderTarget = _layers[layer.LayerRoot].Bitmap;
+ var renderTarget = Layers[layer.LayerRoot].Bitmap;
var node = (VisualNode)scene.FindNode(layer.LayerRoot);
if (node != null)
@@ -322,7 +332,7 @@ namespace Avalonia.Rendering
foreach (var layer in scene.Layers)
{
- var bitmap = _layers[layer.LayerRoot].Bitmap;
+ var bitmap = Layers[layer.LayerRoot].Bitmap;
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
if (layer.GeometryClip != null)
@@ -353,7 +363,7 @@ namespace Avalonia.Rendering
if (DrawFps)
{
- RenderFps(context, clientRect, true);
+ RenderFps(context, clientRect, scene.Layers.Count);
}
}
@@ -442,7 +452,7 @@ namespace Avalonia.Rendering
{
var index = 0;
- foreach (var layer in _layers)
+ foreach (var layer in Layers)
{
var fileName = Path.Combine(DebugFramesPath, $"frame-{id}-layer-{index++}.png");
layer.Bitmap.Save(fileName);
diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
index 2d5a864089..84313f0906 100644
--- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
@@ -69,7 +69,7 @@ namespace Avalonia.Rendering
if (DrawFps)
{
- RenderFps(context.PlatformImpl, _root.Bounds, true);
+ RenderFps(context.PlatformImpl, _root.Bounds, null);
}
}
}
diff --git a/src/Avalonia.Visuals/Rendering/RendererBase.cs b/src/Avalonia.Visuals/Rendering/RendererBase.cs
index 707b31998a..eac362e997 100644
--- a/src/Avalonia.Visuals/Rendering/RendererBase.cs
+++ b/src/Avalonia.Visuals/Rendering/RendererBase.cs
@@ -22,15 +22,12 @@ namespace Avalonia.Rendering
};
}
- protected void RenderFps(IDrawingContextImpl context, Rect clientRect, bool incrementFrameCount)
+ protected void RenderFps(IDrawingContextImpl context, Rect clientRect, int? layerCount)
{
var now = _stopwatch.Elapsed;
var elapsed = now - _lastFpsUpdate;
- if (incrementFrameCount)
- {
- ++_framesThisSecond;
- }
+ ++_framesThisSecond;
if (elapsed.TotalSeconds > 1)
{
@@ -39,7 +36,15 @@ namespace Avalonia.Rendering
_lastFpsUpdate = now;
}
- _fpsText.Text = string.Format("FPS: {0:000}", _fps);
+ if (layerCount.HasValue)
+ {
+ _fpsText.Text = string.Format("Layers: {0} FPS: {1:000}", layerCount, _fps);
+ }
+ else
+ {
+ _fpsText.Text = string.Format("FPS: {0:000}", _fps);
+ }
+
var size = _fpsText.Measure();
var rect = new Rect(clientRect.Right - size.Width, 0, size.Width, size.Height);
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
index 0d2fc17b95..234cadbf31 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
@@ -72,13 +72,15 @@ namespace Avalonia.Rendering.SceneGraph
/// Sets up the drawing context for rendering the node's geometry.
///
/// The drawing context.
- void BeginRender(IDrawingContextImpl context);
+ /// Whether to skip pushing the control's opacity.
+ void BeginRender(IDrawingContextImpl context, bool skipOpacity);
///
/// Resets the drawing context after rendering the node's geometry.
///
/// The drawing context.
- void EndRender(IDrawingContextImpl context);
+ /// Whether to skip popping the control's opacity.
+ void EndRender(IDrawingContextImpl context, bool skipOpacity);
///
/// Hit test the geometry in this node.
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
index 90ef78de37..8f4f487e08 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
@@ -167,7 +167,6 @@ namespace Avalonia.Rendering.SceneGraph
using (context.PushPostTransform(m))
using (context.PushTransformContainer())
{
- var startLayer = opacity < 1 || visual.OpacityMask != null;
var clipBounds = bounds.TransformToAABB(contextImpl.Transform).Intersect(clip);
forceRecurse = forceRecurse ||
@@ -179,9 +178,11 @@ namespace Avalonia.Rendering.SceneGraph
node.ClipToBounds = clipToBounds;
node.GeometryClip = visual.Clip?.PlatformImpl;
node.Opacity = opacity;
- node.OpacityMask = visual.OpacityMask;
- if (startLayer)
+ // TODO: Check equality between node.OpacityMask and visual.OpacityMask before assigning.
+ node.OpacityMask = visual.OpacityMask?.ToImmutable();
+
+ if (ShouldStartLayer(visual))
{
if (node.LayerRoot != visual)
{
@@ -192,7 +193,7 @@ namespace Avalonia.Rendering.SceneGraph
UpdateLayer(node, scene.Layers[node.LayerRoot]);
}
}
- else if (!startLayer && node.LayerRoot == node.Visual && node.Parent != null)
+ else if (node.LayerRoot == node.Visual && node.Parent != null)
{
ClearLayer(scene, node);
}
@@ -366,6 +367,14 @@ namespace Avalonia.Rendering.SceneGraph
}
}
+ private static bool ShouldStartLayer(IVisual visual)
+ {
+ var o = visual as IAvaloniaObject;
+ return visual.VisualChildren.Count > 0 &&
+ o != null &&
+ o.IsAnimating(Visual.OpacityProperty);
+ }
+
private static IGeometryImpl CreateLayerGeometryClip(VisualNode node)
{
IGeometryImpl result = null;
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
index dd5740e4a9..6bea4d9bd6 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
@@ -22,6 +22,7 @@ namespace Avalonia.Rendering.SceneGraph
private List _children;
private List _drawOperations;
private bool _drawOperationsCloned;
+ private Matrix transformRestore;
///
/// Initializes a new instance of the class.
@@ -218,8 +219,10 @@ namespace Avalonia.Rendering.SceneGraph
}
///
- public void BeginRender(IDrawingContextImpl context)
+ public void BeginRender(IDrawingContextImpl context, bool skipOpacity)
{
+ transformRestore = context.Transform;
+
if (ClipToBounds)
{
context.Transform = Matrix.Identity;
@@ -228,24 +231,47 @@ namespace Avalonia.Rendering.SceneGraph
context.Transform = Transform;
+ if (Opacity != 1 && !skipOpacity)
+ {
+ context.PushOpacity(Opacity);
+ }
+
if (GeometryClip != null)
{
context.PushGeometryClip(GeometryClip);
}
+
+ if (OpacityMask != null)
+ {
+ context.PushOpacityMask(OpacityMask, ClipBounds);
+ }
}
///
- public void EndRender(IDrawingContextImpl context)
+ public void EndRender(IDrawingContextImpl context, bool skipOpacity)
{
+ if (OpacityMask != null)
+ {
+ context.PopOpacityMask();
+ }
+
if (GeometryClip != null)
{
context.PopGeometryClip();
}
+ if (Opacity != 1 && !skipOpacity)
+ {
+ context.PopOpacity();
+ }
+
if (ClipToBounds)
{
+ context.Transform = Matrix.Identity;
context.PopClip();
}
+
+ context.Transform = transformRestore;
}
private Rect CalculateBounds()
diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs
index bc65d4f69f..3662fe50be 100644
--- a/src/Avalonia.Visuals/Visual.cs
+++ b/src/Avalonia.Visuals/Visual.cs
@@ -537,6 +537,19 @@ namespace Avalonia
v.SetVisualParent(null);
}
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ foreach (Visual v in e.OldItems)
+ {
+ v.SetVisualParent(null);
+ }
+
+ foreach (Visual v in e.NewItems)
+ {
+ v.SetVisualParent(this);
+ }
+
break;
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
index eb5f87ea2a..7f750144df 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
@@ -66,7 +66,7 @@ namespace Avalonia.Markup.Xaml.Data
///
/// Gets or sets the binding path.
///
- public string Path { get; set; }
+ public string Path { get; set; } = "";
///
/// Gets or sets the binding priority.
@@ -93,53 +93,53 @@ namespace Avalonia.Markup.Xaml.Data
bool enableDataValidation = false)
{
Contract.Requires(target != null);
-
anchor = anchor ?? DefaultAnchor?.Target;
-
- var pathInfo = ParsePath(Path);
- ValidateState(pathInfo);
+
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
-
+
ExpressionObserver observer;
- if (pathInfo.ElementName != null || ElementName != null)
+ if (ElementName != null)
{
observer = CreateElementObserver(
(target as IControl) ?? (anchor as IControl),
- pathInfo.ElementName ?? ElementName,
- pathInfo.Path);
+ ElementName,
+ Path,
+ enableDataValidation);
}
else if (Source != null)
{
- observer = CreateSourceObserver(Source, pathInfo.Path, enableDataValidation);
+ observer = CreateSourceObserver(Source, Path, enableDataValidation);
}
else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
{
observer = CreateDataContexObserver(
target,
- pathInfo.Path,
+ Path,
targetProperty == Control.DataContextProperty,
anchor,
enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.Self)
{
- observer = CreateSourceObserver(target, pathInfo.Path, enableDataValidation);
+ observer = CreateSourceObserver(target, Path, enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
- observer = CreateTemplatedParentObserver(target, pathInfo.Path);
+ observer = CreateTemplatedParentObserver(target, Path, enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor)
{
- if (RelativeSource.AncestorType == null)
+ if (RelativeSource.Tree == TreeType.Visual && RelativeSource.AncestorType == null)
{
- throw new InvalidOperationException("AncestorType must be set for RelativeSourceModel.FindAncestor.");
+ throw new InvalidOperationException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.");
}
observer = CreateFindAncestorObserver(
(target as IControl) ?? (anchor as IControl),
- pathInfo.Path);
+ RelativeSource,
+ Path,
+ enableDataValidation);
}
else
{
@@ -168,53 +168,6 @@ namespace Avalonia.Markup.Xaml.Data
return new InstancedBinding(subject, Mode, Priority);
}
- private static PathInfo ParsePath(string path)
- {
- var result = new PathInfo();
-
- if (string.IsNullOrWhiteSpace(path) || path == ".")
- {
- result.Path = string.Empty;
- }
- else if (path.StartsWith("#"))
- {
- var dot = path.IndexOf('.');
-
- if (dot != -1)
- {
- result.Path = path.Substring(dot + 1);
- result.ElementName = path.Substring(1, dot - 1);
- }
- else
- {
- result.Path = string.Empty;
- result.ElementName = path.Substring(1);
- }
- }
- else
- {
- result.Path = path;
- }
-
- return result;
- }
-
- private void ValidateState(PathInfo pathInfo)
- {
- if (pathInfo.ElementName != null && ElementName != null)
- {
- throw new InvalidOperationException(
- "ElementName property cannot be set when an #elementName path is provided.");
- }
-
- if ((pathInfo.ElementName != null || ElementName != null) &&
- RelativeSource != null)
- {
- throw new InvalidOperationException(
- "ElementName property cannot be set with a RelativeSource.");
- }
- }
-
private ExpressionObserver CreateDataContexObserver(
IAvaloniaObject target,
string path,
@@ -256,7 +209,11 @@ namespace Avalonia.Markup.Xaml.Data
}
}
- private ExpressionObserver CreateElementObserver(IControl target, string elementName, string path)
+ private ExpressionObserver CreateElementObserver(
+ IControl target,
+ string elementName,
+ string path,
+ bool enableDataValidation)
{
Contract.Requires(target != null);
@@ -264,35 +221,39 @@ namespace Avalonia.Markup.Xaml.Data
var result = new ExpressionObserver(
ControlLocator.Track(target, elementName),
path,
- false,
+ enableDataValidation,
description);
return result;
}
private ExpressionObserver CreateFindAncestorObserver(
IControl target,
- string path)
+ RelativeSource relativeSource,
+ string path,
+ bool enableDataValidation)
{
Contract.Requires(target != null);
return new ExpressionObserver(
- ControlLocator.Track(target, RelativeSource.AncestorType, RelativeSource.AncestorLevel -1),
- path);
+ ControlLocator.Track(target, relativeSource.Tree, relativeSource.AncestorLevel - 1, relativeSource.AncestorType),
+ path,
+ enableDataValidation);
}
private ExpressionObserver CreateSourceObserver(
object source,
string path,
- bool enabledDataValidation)
+ bool enableDataValidation)
{
Contract.Requires(source != null);
- return new ExpressionObserver(source, path, enabledDataValidation);
+ return new ExpressionObserver(source, path, enableDataValidation);
}
private ExpressionObserver CreateTemplatedParentObserver(
IAvaloniaObject target,
- string path)
+ string path,
+ bool enableDataValidation)
{
Contract.Requires(target != null);
@@ -303,7 +264,8 @@ namespace Avalonia.Markup.Xaml.Data
var result = new ExpressionObserver(
() => target.GetValue(Control.TemplatedParentProperty),
path,
- update);
+ update,
+ enableDataValidation);
return result;
}
@@ -328,6 +290,7 @@ namespace Avalonia.Markup.Xaml.Data
{
public string Path { get; set; }
public string ElementName { get; set; }
+ public RelativeSource RelativeSource { get; set; }
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs b/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
index f77df6853b..825d3b8ba5 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
@@ -87,5 +87,7 @@ namespace Avalonia.Markup.Xaml.Data
/// Gets or sets a value that describes the type of relative source lookup.
///
public RelativeSourceMode Mode { get; set; }
+
+ public TreeType Tree { get; set; } = TreeType.Visual;
}
}
\ No newline at end of file
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
index 8984498393..c6705cbb4b 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
@@ -29,20 +29,167 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public override object ProvideValue(IServiceProvider serviceProvider)
{
+ var descriptorContext = (ITypeDescriptorContext)serviceProvider;
+
+ var pathInfo = ParsePath(Path, descriptorContext);
+ ValidateState(pathInfo);
+
return new Binding
{
Converter = Converter,
ConverterParameter = ConverterParameter,
- ElementName = ElementName,
+ ElementName = pathInfo.ElementName ?? ElementName,
FallbackValue = FallbackValue,
Mode = Mode,
- Path = Path,
+ Path = pathInfo.Path,
Priority = Priority,
- RelativeSource = RelativeSource,
+ RelativeSource = pathInfo.RelativeSource ?? RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
};
}
+ private class PathInfo
+ {
+ public string Path { get; set; }
+ public string ElementName { get; set; }
+ public RelativeSource RelativeSource { get; set; }
+ }
+
+ private void ValidateState(PathInfo pathInfo)
+ {
+ if (pathInfo.ElementName != null && ElementName != null)
+ {
+ throw new InvalidOperationException(
+ "ElementName property cannot be set when an #elementName path is provided.");
+ }
+
+ if (pathInfo.RelativeSource != null && RelativeSource != null)
+ {
+ throw new InvalidOperationException(
+ "ElementName property cannot be set when a $self or $parent path is provided.");
+ }
+
+ if ((pathInfo.ElementName != null || ElementName != null) &&
+ (pathInfo.RelativeSource != null || RelativeSource != null))
+ {
+ throw new InvalidOperationException(
+ "ElementName property cannot be set with a RelativeSource.");
+ }
+ }
+
+ private static PathInfo ParsePath(string path, ITypeDescriptorContext context)
+ {
+ var result = new PathInfo();
+
+ if (string.IsNullOrWhiteSpace(path) || path == ".")
+ {
+ result.Path = string.Empty;
+ }
+ else if (path.StartsWith("#"))
+ {
+ var dot = path.IndexOf('.');
+
+ if (dot != -1)
+ {
+ result.Path = path.Substring(dot + 1);
+ result.ElementName = path.Substring(1, dot - 1);
+ }
+ else
+ {
+ result.Path = string.Empty;
+ result.ElementName = path.Substring(1);
+ }
+ }
+ else if (path.StartsWith("$"))
+ {
+ var relativeSource = new RelativeSource
+ {
+ Tree = TreeType.Logical
+ };
+ result.RelativeSource = relativeSource;
+ var dot = path.IndexOf('.');
+ string relativeSourceMode;
+ if (dot != -1)
+ {
+ result.Path = path.Substring(dot + 1);
+ relativeSourceMode = path.Substring(1, dot - 1);
+ }
+ else
+ {
+ result.Path = string.Empty;
+ relativeSourceMode = path.Substring(1);
+ }
+
+ if (relativeSourceMode == "self")
+ {
+ relativeSource.Mode = RelativeSourceMode.Self;
+ }
+ else if (relativeSourceMode == "parent")
+ {
+ relativeSource.Mode = RelativeSourceMode.FindAncestor;
+ relativeSource.AncestorLevel = 1;
+ }
+ else if (relativeSourceMode.StartsWith("parent["))
+ {
+ relativeSource.Mode = RelativeSourceMode.FindAncestor;
+ var parentConfigStart = relativeSourceMode.IndexOf('[');
+ if (!relativeSourceMode.EndsWith("]"))
+ {
+ throw new InvalidOperationException("Invalid RelativeSource binding syntax. Expected matching ']' for '['.");
+ }
+ var parentConfigParams = relativeSourceMode.Substring(parentConfigStart + 1).TrimEnd(']').Split(';');
+ if (parentConfigParams.Length > 2 || parentConfigParams.Length == 0)
+ {
+ throw new InvalidOperationException("Expected either 1 or 2 parameters for RelativeSource binding syntax");
+ }
+ else if (parentConfigParams.Length == 1)
+ {
+ if (int.TryParse(parentConfigParams[0], out int level))
+ {
+ relativeSource.AncestorType = null;
+ relativeSource.AncestorLevel = level + 1;
+ }
+ else
+ {
+ relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
+ }
+ }
+ else
+ {
+ relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
+ relativeSource.AncestorLevel = int.Parse(parentConfigParams[1]) + 1;
+ }
+ }
+ else
+ {
+ throw new InvalidOperationException($"Invalid RelativeSource binding syntax: {relativeSourceMode}");
+ }
+ }
+ else
+ {
+ result.Path = path;
+ }
+
+ return result;
+ }
+
+ private static Type LookupAncestorType(string ancestorTypeName, ITypeDescriptorContext context)
+ {
+ var parts = ancestorTypeName.Split(':');
+ if (parts.Length == 0 || parts.Length > 2)
+ {
+ throw new InvalidOperationException("Invalid type name");
+ }
+
+ if (parts.Length == 1)
+ {
+ return context.ResolveType(string.Empty, parts[0]);
+ }
+ else
+ {
+ return context.ResolveType(parts[0], parts[1]);
+ }
+ }
private static object GetDefaultAnchor(ITypeDescriptorContext context)
{
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
index 4664947b8e..c5fe83977f 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
@@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
ElementName = ElementName,
Mode = Mode,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
- Path = Path,
+ Path = Path ?? string.Empty,
Priority = Priority,
};
}
diff --git a/src/Markup/Avalonia.Markup/ControlLocator.cs b/src/Markup/Avalonia.Markup/ControlLocator.cs
index de8415d6db..1a82c0a4fd 100644
--- a/src/Markup/Avalonia.Markup/ControlLocator.cs
+++ b/src/Markup/Avalonia.Markup/ControlLocator.cs
@@ -11,6 +11,21 @@ using Avalonia.VisualTree;
namespace Avalonia.Markup
{
+ ///
+ /// The type of tree via which to track a control.
+ ///
+ public enum TreeType
+ {
+ ///
+ /// The visual tree.
+ ///
+ Visual,
+ ///
+ /// The logical tree.
+ ///
+ Logical,
+ }
+
///
/// Locates controls relative to other controls.
///
@@ -27,13 +42,13 @@ namespace Avalonia.Markup
{
var attached = Observable.FromEventPattern(
x => relativeTo.AttachedToLogicalTree += x,
- x => relativeTo.DetachedFromLogicalTree += x)
+ x => relativeTo.AttachedToLogicalTree -= x)
.Select(x => ((IControl)x.Sender).FindNameScope())
.StartWith(relativeTo.FindNameScope());
var detached = Observable.FromEventPattern(
x => relativeTo.DetachedFromLogicalTree += x,
- x => relativeTo.DetachedFromLogicalTree += x)
+ x => relativeTo.DetachedFromLogicalTree -= x)
.Select(x => (INameScope)null);
return attached.Merge(detached).Select(nameScope =>
@@ -68,37 +83,75 @@ namespace Avalonia.Markup
///
/// The control relative from which the other control should be found.
///
- /// The type of the ancestor to find.
+ /// The tree via which to track the control.
///
/// The level of ancestor control to look for. Use 0 for the first ancestor of the
/// requested type.
///
- public static IObservable Track(IControl relativeTo, Type ancestorType, int ancestorLevel)
+ /// The type of the ancestor to find.
+ public static IObservable Track(IControl relativeTo, TreeType tree, int ancestorLevel, Type ancestorType = null)
+ {
+ return TrackAttachmentToTree(relativeTo, tree).Select(isAttachedToTree =>
+ {
+ if (isAttachedToTree)
+ {
+ if (tree == TreeType.Visual)
+ {
+ return relativeTo.GetVisualAncestors()
+ .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
+ .ElementAtOrDefault(ancestorLevel) as IControl;
+ }
+ else
+ {
+ return relativeTo.GetLogicalAncestors()
+ .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
+ .ElementAtOrDefault(ancestorLevel) as IControl;
+ }
+ }
+ else
+ {
+ return null;
+ }
+ });
+ }
+
+ private static IObservable TrackAttachmentToTree(IControl relativeTo, TreeType tree)
+ {
+ return tree == TreeType.Visual ? TrackAttachmentToVisualTree(relativeTo) : TrackAttachmentToLogicalTree(relativeTo);
+ }
+
+ private static IObservable TrackAttachmentToVisualTree(IControl relativeTo)
{
var attached = Observable.FromEventPattern(
x => relativeTo.AttachedToVisualTree += x,
- x => relativeTo.DetachedFromVisualTree += x)
+ x => relativeTo.AttachedToVisualTree -= x)
.Select(x => true)
.StartWith(relativeTo.IsAttachedToVisualTree);
var detached = Observable.FromEventPattern(
x => relativeTo.DetachedFromVisualTree += x,
- x => relativeTo.DetachedFromVisualTree += x)
+ x => relativeTo.DetachedFromVisualTree -= x)
.Select(x => false);
- return attached.Merge(detached).Select(isAttachedToVisualTree =>
- {
- if (isAttachedToVisualTree)
- {
- return relativeTo.GetVisualAncestors()
- .Where(x => ancestorType.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()))
- .ElementAtOrDefault(ancestorLevel) as IControl;
- }
- else
- {
- return null;
- }
- });
+ var attachmentStatus = attached.Merge(detached);
+ return attachmentStatus;
+ }
+
+ private static IObservable TrackAttachmentToLogicalTree(IControl relativeTo)
+ {
+ var attached = Observable.FromEventPattern(
+ x => relativeTo.AttachedToLogicalTree += x,
+ x => relativeTo.AttachedToLogicalTree -= x)
+ .Select(x => true)
+ .StartWith(relativeTo.IsAttachedToLogicalTree);
+
+ var detached = Observable.FromEventPattern(
+ x => relativeTo.DetachedFromLogicalTree += x,
+ x => relativeTo.DetachedFromLogicalTree -= x)
+ .Select(x => false);
+
+ var attachmentStatus = attached.Merge(detached);
+ return attachmentStatus;
}
}
}
diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs b/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
index a824a38867..563b372c78 100644
--- a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
+++ b/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
@@ -51,15 +51,7 @@ namespace Avalonia.Markup.Data.Parsers
}
}
- if (!r.End)
- {
- r.Take();
- return result;
- }
- else
- {
- throw new ExpressionParseException(r.Position, "Expected ']'.");
- }
+ throw new ExpressionParseException(r.Position, "Expected ']'.");
}
return null;
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 6a72923ce3..b1bfdcbfeb 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -54,7 +54,6 @@ namespace Avalonia.Direct2D1.Media
_finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory;
_imagingFactory = imagingFactory;
- _swapChain = swapChain;
_renderTarget.BeginDraw();
}
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
index 4d6559a078..cd77d9cc88 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
@@ -387,6 +387,46 @@ namespace Avalonia.Base.UnitTests
}
}
+ [Fact]
+ public void IsAnimating_On_Property_With_No_Value_Returns_False()
+ {
+ var target = new Class1();
+
+ Assert.False(target.IsAnimating(Class1.FooProperty));
+ }
+
+ [Fact]
+ public void IsAnimating_On_Property_With_Animation_Value_Returns_False()
+ {
+ var target = new Class1();
+
+ target.SetValue(Class1.FooProperty, "foo", BindingPriority.Animation);
+
+ Assert.False(target.IsAnimating(Class1.FooProperty));
+ }
+
+ [Fact]
+ public void IsAnimating_On_Property_With_Non_Animation_Binding_Returns_False()
+ {
+ var target = new Class1();
+ var source = new Subject();
+
+ target.Bind(Class1.FooProperty, source, BindingPriority.LocalValue);
+
+ Assert.False(target.IsAnimating(Class1.FooProperty));
+ }
+
+ [Fact]
+ public void IsAnimating_On_Property_With_Animation_Binding_Returns_True()
+ {
+ var target = new Class1();
+ var source = new BehaviorSubject("foo");
+
+ target.Bind(Class1.FooProperty, source, BindingPriority.Animation);
+
+ Assert.True(target.IsAnimating(Class1.FooProperty));
+ }
+
///
/// Returns an observable that returns a single value but does not complete.
///
diff --git a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
index 587816b07b..fd731455d8 100644
--- a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
@@ -83,6 +83,28 @@ namespace Avalonia.Base.UnitTests.Collections
Assert.Equal(new[] { 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 }, target);
}
+ [Fact]
+ public void MoveRange_Raises_Correct_CollectionChanged_Event()
+ {
+ var target = new AvaloniaList(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ var raised = false;
+
+ target.CollectionChanged += (s, e) =>
+ {
+ Assert.Equal(NotifyCollectionChangedAction.Move, e.Action);
+ Assert.Equal(0, e.OldStartingIndex);
+ Assert.Equal(10, e.NewStartingIndex);
+ Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, e.OldItems);
+ Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, e.NewItems);
+ raised = true;
+ };
+
+ target.MoveRange(0, 9, 10);
+
+ Assert.True(raised);
+ Assert.Equal(new[] { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, target);
+ }
+
[Fact]
public void Adding_Item_Should_Raise_CollectionChanged()
{
diff --git a/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs b/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
index 3a37585dc0..84ff492512 100644
--- a/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
+++ b/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Reactive.Subjects;
using Avalonia.Data;
using Xunit;
@@ -70,6 +71,17 @@ namespace Avalonia.Base.UnitTests
Assert.Same(p1.Initialized, p2.Initialized);
}
+ [Fact]
+ public void IsAnimating_On_DirectProperty_With_Binding_Returns_False()
+ {
+ var target = new Class1();
+ var source = new BehaviorSubject("foo");
+
+ target.Bind(Class1.FooProperty, source, BindingPriority.Animation);
+
+ Assert.False(target.IsAnimating(Class1.FooProperty));
+ }
+
private class Class1 : AvaloniaObject
{
public static readonly DirectProperty FooProperty =
diff --git a/tests/Avalonia.Controls.UnitTests/PanelTests.cs b/tests/Avalonia.Controls.UnitTests/PanelTests.cs
index fb1ae3ba1a..29acdaa8d4 100644
--- a/tests/Avalonia.Controls.UnitTests/PanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/PanelTests.cs
@@ -4,6 +4,7 @@
using System.Linq;
using Avalonia.Collections;
using Avalonia.LogicalTree;
+using Avalonia.VisualTree;
using Xunit;
namespace Avalonia.Controls.UnitTests
@@ -18,8 +19,9 @@ namespace Avalonia.Controls.UnitTests
panel.Children.Add(child);
- Assert.Equal(child.Parent, panel);
- Assert.Equal(child.GetLogicalParent(), panel);
+ Assert.Same(child.Parent, panel);
+ Assert.Same(child.GetLogicalParent(), panel);
+ Assert.Same(child.GetVisualParent(), panel);
}
[Fact]
@@ -45,6 +47,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(child.Parent);
Assert.Null(child.GetLogicalParent());
+ Assert.Null(child.GetVisualParent());
}
[Fact]
@@ -60,8 +63,10 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(child1.Parent);
Assert.Null(child1.GetLogicalParent());
+ Assert.Null(child1.GetVisualParent());
Assert.Null(child2.Parent);
Assert.Null(child2.GetLogicalParent());
+ Assert.Null(child2.GetVisualParent());
}
[Fact]
@@ -77,24 +82,32 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(child1.Parent);
Assert.Null(child1.GetLogicalParent());
+ Assert.Null(child1.GetVisualParent());
Assert.Null(child2.Parent);
Assert.Null(child2.GetLogicalParent());
+ Assert.Null(child2.GetVisualParent());
}
[Fact]
- public void Setting_Children_Should_Make_Controls_Appear_In_Panel_Children()
+ public void Replacing_Panel_Children_Should_Clear_And_Set_Control_Parent()
{
var panel = new Panel();
- var child = new Control();
+ var child1 = new Control();
+ var child2 = new Control();
- panel.Children = new Controls { child };
+ panel.Children.Add(child1);
+ panel.Children[0] = child2;
- Assert.Equal(new[] { child }, panel.Children);
- Assert.Equal(new[] { child }, panel.GetLogicalChildren());
+ Assert.Null(child1.Parent);
+ Assert.Null(child1.GetLogicalParent());
+ Assert.Null(child1.GetVisualParent());
+ Assert.Same(child2.Parent, panel);
+ Assert.Same(child2.GetLogicalParent(), panel);
+ Assert.Same(child2.GetVisualParent(), panel);
}
[Fact]
- public void Child_Control_Should_Appear_In_Panel_Children()
+ public void Child_Control_Should_Appear_In_Panel_Logical_And_Visual_Children()
{
var panel = new Panel();
var child = new Control();
@@ -103,10 +116,11 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { child }, panel.Children);
Assert.Equal(new[] { child }, panel.GetLogicalChildren());
+ Assert.Equal(new[] { child }, panel.GetVisualChildren());
}
[Fact]
- public void Removing_Child_Control_Should_Remove_From_Panel_Children()
+ public void Removing_Child_Control_Should_Remove_From_Panel_Logical_And_Visual_Children()
{
var panel = new Panel();
var child = new Control();
@@ -115,7 +129,36 @@ namespace Avalonia.Controls.UnitTests
panel.Children.Remove(child);
Assert.Equal(new Control[0], panel.Children);
- Assert.Equal(new ILogical[0], panel.GetLogicalChildren());
+ Assert.Empty(panel.GetLogicalChildren());
+ Assert.Empty(panel.GetVisualChildren());
+ }
+
+ [Fact]
+ public void Moving_Panel_Children_Should_Reoder_Logical_And_Visual_Children()
+ {
+ var panel = new Panel();
+ var child1 = new Control();
+ var child2 = new Control();
+
+ panel.Children.Add(child1);
+ panel.Children.Add(child2);
+ panel.Children.Move(1, 0);
+
+ Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren());
+ Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren());
+ }
+
+ [Fact]
+ public void Setting_Children_Should_Make_Controls_Appear_In_Logical_And_Visual_Children()
+ {
+ var panel = new Panel();
+ var child = new Control();
+
+ panel.Children = new Controls { child };
+
+ Assert.Equal(new[] { child }, panel.Children);
+ Assert.Equal(new[] { child }, panel.GetLogicalChildren());
+ Assert.Equal(new[] { child }, panel.GetVisualChildren());
}
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
index 230e61f300..9a08073920 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
@@ -13,6 +13,7 @@ using Moq;
using Xunit;
using System.ComponentModel;
using System.Runtime.CompilerServices;
+using Avalonia.UnitTests;
namespace Avalonia.Markup.Xaml.UnitTests.Data
{
@@ -337,6 +338,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
Assert.Equal("foo", target.Content);
}
+ [Fact]
+ public void Binding_With_Null_Path_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var textBlock = window.FindControl("textBlock");
+
+ window.DataContext = "foo";
+ window.ApplyTemplate();
+
+ Assert.Equal("foo", textBlock.Text);
+ }
+ }
+
private class TwoWayBindingTest : Control
{
public static readonly StyledProperty TwoWayProperty =
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
index 197afe46ee..ccb13039f1 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
@@ -11,6 +11,9 @@ using Avalonia.Markup.Xaml.Data;
using Avalonia.Styling;
using Xunit;
using System.Reactive.Disposables;
+using Avalonia.UnitTests;
+using Avalonia.VisualTree;
+using System.Linq;
namespace Avalonia.Markup.Xaml.UnitTests.Data
{
@@ -56,6 +59,35 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
BindingPriority.TemplatedParent));
}
+ [Fact]
+ public void TemplateBinding_With_Null_Path_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var button = window.FindControl