Browse Source

Merge branch 'master' into patch-1

pull/2879/head
Jumar Macato 7 years ago
committed by GitHub
parent
commit
112fe15044
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      build/NetFX.props
  2. 6
      native/Avalonia.Native/src/OSX/window.mm
  3. 3
      samples/BindingDemo/MainWindow.xaml
  4. 12
      samples/ControlCatalog/MainView.xaml
  5. 10
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  6. 2
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  7. 5
      src/Avalonia.Base/Data/Core/SettableNode.cs
  8. 50
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  9. 3
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  10. 6
      src/Avalonia.Controls/ListBox.cs
  11. 5
      src/Avalonia.Controls/Notifications/ReversibleStackPanel.cs
  12. 11
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  13. 6
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  14. 12
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  15. 25
      src/Avalonia.Controls/RadioButton.cs
  16. 2
      src/Avalonia.Controls/StackPanel.cs
  17. 9
      src/Avalonia.Input/AccessKeyHandler.cs
  18. 129
      src/Avalonia.Input/KeyGesture.cs
  19. 57
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  20. 14
      src/Avalonia.Visuals/Rendering/RenderLoop.cs
  21. 4
      src/Avalonia.X11/X11KeyTransform.cs
  22. 4
      src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs
  23. 20
      tests/Avalonia.Base.UnitTests/WeakEventHandlerManagerTests.cs
  24. 20
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  25. 25
      tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
  26. 4
      tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
  27. 20
      tests/Avalonia.Input.UnitTests/KeyGestureTests.cs
  28. 68
      tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

14
build/NetFX.props

@ -1,11 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(TargetFramework)' == 'net461' and '$(OS)' == 'Unix' ">
<FrameworkPathOverride>/usr/lib/mono/4.6.1-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="$([MSBuild]::IsOsPlatform('OSX'))">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.6.1-api</FrameworkPathOverride>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net47' and '$(OS)' == 'Unix' ">
<FrameworkPathOverride>/usr/lib/mono/4.7-api/</FrameworkPathOverride>
<FrameworkPathOverride Condition="$([MSBuild]::IsOsPlatform('OSX'))">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.7-api</FrameworkPathOverride>
</PropertyGroup>
<Project>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0-preview.2" PrivateAssets="All" />
</ItemGroup>
</Project>

6
native/Avalonia.Native/src/OSX/window.mm

@ -369,12 +369,9 @@ public:
virtual void UpdateCursor()
{
[View resetCursorRects];
if (cursor != nil)
{
auto rect = [Window frame];
[View addCursorRect:rect cursor:cursor];
[cursor set];
[cursor set];
}
}
@ -425,6 +422,7 @@ private:
{
WindowEvents = events;
[Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
}
virtual HRESULT Show () override

3
samples/BindingDemo/MainWindow.xaml

@ -24,6 +24,9 @@
<TextBox Watermark="Two Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue}" Name="first"/>
<TextBox Watermark="One Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWay}"/>
<TextBox Watermark="One Time" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneTime}"/>
<!-- Removed due to #2983: reinstate when that's fixed.
<TextBox Watermark="One Way to Source" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWayToSource}"/>
-->
</StackPanel>
<StackPanel Margin="18" Spacing="4" Width="200">
<TextBlock FontSize="16" Text="Collection Bindings"/>

12
samples/ControlCatalog/MainView.xaml

@ -24,7 +24,6 @@
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="ComboBox"><pages:ComboBoxPage/></TabItem>
<TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
<!-- DataGrid is our special snowflake -->
<TabItem Header="DataGrid"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
@ -34,12 +33,15 @@
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="ItemsRepeater"><pages:ItemsRepeaterPage/></TabItem>
<TabItem Header="ItemsRepeater"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:ItemsRepeaterPage/>
</TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="ListBox"><pages:ListBoxPage/></TabItem>
<TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="Notifications"><pages:NotificationsPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="Pointers (Touch)"><pages:PointersPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
@ -50,12 +52,12 @@
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
<TabControl.Tag>
<TabControl.Tag>
<ComboBox x:Name="Themes" SelectedIndex="0" Width="100" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<ComboBoxItem>Light</ComboBoxItem>
<ComboBoxItem>Dark</ComboBoxItem>
</ComboBox>
</TabControl.Tag>
</TabControl.Tag>
</TabControl>
</Grid>
</UserControl>

10
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -8,9 +8,13 @@ namespace Avalonia.Data.Core
public abstract class ExpressionNode
{
private static readonly object CacheInvalid = new object();
protected static readonly WeakReference<object> UnsetReference =
new WeakReference<object>(AvaloniaProperty.UnsetValue);
protected static readonly WeakReference<object> NullReference =
new WeakReference<object>(null);
private WeakReference<object> _target = UnsetReference;
private Action<object> _subscriber;
private bool _listening;
@ -98,7 +102,7 @@ namespace Avalonia.Data.Core
if (notification == null)
{
LastValue = new WeakReference<object>(value);
LastValue = value != null ? new WeakReference<object>(value) : NullReference;
if (Next != null)
{
@ -111,7 +115,7 @@ namespace Avalonia.Data.Core
}
else
{
LastValue = new WeakReference<object>(notification.Value);
LastValue = notification.Value != null ? new WeakReference<object>(notification.Value) : NullReference;
if (Next != null)
{
@ -136,8 +140,8 @@ namespace Avalonia.Data.Core
}
else if (target != AvaloniaProperty.UnsetValue)
{
StartListeningCore(_target);
_listening = true;
StartListeningCore(_target);
}
else
{

2
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -103,8 +103,8 @@ namespace Avalonia.Data.Core.Plugins
protected override void SubscribeCore()
{
SendCurrentValue();
SubscribeToChanges();
SendCurrentValue();
}
protected override void UnsubscribeCore()

5
src/Avalonia.Base/Data/Core/SettableNode.cs

@ -29,6 +29,11 @@ namespace Avalonia.Data.Core
if (!isLastValueAlive)
{
if (value == null && LastValue == NullReference)
{
return true;
}
return false;
}

50
src/Avalonia.Base/Utilities/TypeUtilities.cs

@ -13,7 +13,7 @@ namespace Avalonia.Utilities
/// </summary>
public static class TypeUtilities
{
private static int[] Conversions =
private static readonly int[] Conversions =
{
0b101111111111101, // Boolean
0b100001111111110, // Char
@ -32,7 +32,7 @@ namespace Avalonia.Utilities
0b111111111111111, // String
};
private static int[] ImplicitConversions =
private static readonly int[] ImplicitConversions =
{
0b000000000000001, // Boolean
0b001110111100010, // Char
@ -51,7 +51,7 @@ namespace Avalonia.Utilities
0b100000000000000, // String
};
private static Type[] InbuiltTypes =
private static readonly Type[] InbuiltTypes =
{
typeof(Boolean),
typeof(Char),
@ -70,7 +70,7 @@ namespace Avalonia.Utilities
typeof(String),
};
private static readonly Type[] NumericTypes = new[]
private static readonly Type[] NumericTypes =
{
typeof(Byte),
typeof(Decimal),
@ -188,8 +188,7 @@ namespace Avalonia.Utilities
}
}
var cast = from.GetRuntimeMethods()
.FirstOrDefault(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") && m.ReturnType == to);
var cast = FindTypeConversionOperatorMethod(from, to, OperatorType.Implicit | OperatorType.Explicit);
if (cast != null)
{
@ -253,8 +252,7 @@ namespace Avalonia.Utilities
}
}
var cast = from.GetRuntimeMethods()
.FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == to);
var cast = FindTypeConversionOperatorMethod(from, to, OperatorType.Implicit);
if (cast != null)
{
@ -335,5 +333,41 @@ namespace Avalonia.Utilities
return NumericTypes.Contains(type);
}
}
[Flags]
private enum OperatorType
{
Implicit = 1,
Explicit = 2
}
private static MethodInfo FindTypeConversionOperatorMethod(Type fromType, Type toType, OperatorType operatorType)
{
const string implicitName = "op_Implicit";
const string explicitName = "op_Explicit";
bool allowImplicit = (operatorType & OperatorType.Implicit) != 0;
bool allowExplicit = (operatorType & OperatorType.Explicit) != 0;
foreach (MethodInfo method in fromType.GetMethods())
{
if (!method.IsSpecialName || method.ReturnType != toType)
{
continue;
}
if (allowImplicit && method.Name == implicitName)
{
return method;
}
if (allowExplicit && method.Name == explicitName)
{
return method;
}
}
return null;
}
}
}

3
src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs

@ -161,9 +161,8 @@ namespace Avalonia.Utilities
for (int c = 0; c < _count; ++c)
{
var reference = _data[c].Subscriber;
TSubscriber instance;
if (reference != null && reference.TryGetTarget(out instance) && instance == s)
if (reference != null && reference.TryGetTarget(out TSubscriber instance) && Equals(instance, s.Target))
{
_data[c] = default;
removed = true;

6
src/Avalonia.Controls/ListBox.cs

@ -66,7 +66,11 @@ namespace Avalonia.Controls
}
/// <inheritdoc/>
public new IList SelectedItems => base.SelectedItems;
public new IList SelectedItems
{
get => base.SelectedItems;
set => base.SelectedItems = value;
}
/// <summary>
/// Gets or sets the selection mode.

5
src/Avalonia.Controls/Notifications/ReversibleStackPanel.cs

@ -39,6 +39,11 @@ namespace Avalonia.Controls
foreach (Control child in children)
{
if (!child.IsVisible)
{
continue;
}
double childWidth = child.DesiredSize.Width;
double childHeight = child.DesiredSize.Height;

11
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -295,11 +295,14 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
public override void ScrollIntoView(object item)
{
var index = Items.IndexOf(item);
if (index != -1)
if (Items != null)
{
ScrollIntoView(index);
var index = Items.IndexOf(item);
if (index != -1)
{
ScrollIntoView(index);
}
}
}

6
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

@ -100,6 +100,12 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
?? screens.FirstOrDefault(s => s.Bounds.Contains(parentGeometry.TopLeft))
?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry))
?? screens.FirstOrDefault();
if (targetScreen != null && targetScreen.WorkingArea.IsEmpty)
{
return targetScreen.Bounds;
}
return targetScreen?.WorkingArea
?? new Rect(0, 0, double.MaxValue, double.MaxValue);
}

12
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -112,7 +112,7 @@ namespace Avalonia.Controls.Primitives
private bool _syncingSelectedItems;
private int _updateCount;
private int _updateSelectedIndex;
private IList _updateSelectedItems;
private object _updateSelectedItem;
/// <summary>
/// Initializes static members of the <see cref="SelectingItemsControl"/> class.
@ -160,7 +160,7 @@ namespace Avalonia.Controls.Primitives
else
{
_updateSelectedIndex = value;
_updateSelectedItems = null;
_updateSelectedItem = null;
}
}
}
@ -183,7 +183,7 @@ namespace Avalonia.Controls.Primitives
}
else
{
_updateSelectedItems = new AvaloniaList<object>(value);
_updateSelectedItem = value;
_updateSelectedIndex = int.MinValue;
}
}
@ -1042,7 +1042,7 @@ namespace Avalonia.Controls.Primitives
RaiseEvent(e);
}
if (AutoScrollToSelectedItem)
if (AutoScrollToSelectedItem && _selectedIndex != -1)
{
ScrollIntoView(_selectedItem);
}
@ -1075,9 +1075,9 @@ namespace Avalonia.Controls.Primitives
{
SelectedIndex = _updateSelectedIndex;
}
else if (_updateSelectedItems != null)
else if (_updateSelectedItem != null)
{
SelectedItems = _updateSelectedItems;
SelectedItem = _updateSelectedItem;
}
}

25
src/Avalonia.Controls/RadioButton.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls
public static readonly RadioButtonGroupManager Default = new RadioButtonGroupManager();
static readonly ConditionalWeakTable<IRenderRoot, RadioButtonGroupManager> s_registeredVisualRoots
= new ConditionalWeakTable<IRenderRoot, RadioButtonGroupManager>();
readonly Dictionary<string, List<WeakReference<RadioButton>>> s_registeredGroups
= new Dictionary<string, List<WeakReference<RadioButton>>>();
@ -127,13 +127,11 @@ namespace Avalonia.Controls
{
if (!string.IsNullOrEmpty(GroupName))
{
var manager = RadioButtonGroupManager.GetOrCreateForRoot(e.Root);
if (manager != _groupManager)
{
_groupManager.Remove(this, _groupName);
_groupManager = manager;
manager.Add(this);
}
_groupManager?.Remove(this, _groupName);
_groupManager = RadioButtonGroupManager.GetOrCreateForRoot(e.Root);
_groupManager.Add(this);
}
base.OnAttachedToVisualTree(e);
}
@ -141,9 +139,10 @@ namespace Avalonia.Controls
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
if (!string.IsNullOrEmpty(GroupName) && _groupManager != null)
if (!string.IsNullOrEmpty(GroupName))
{
_groupManager.Remove(this, _groupName);
_groupManager?.Remove(this, _groupName);
}
}
@ -152,9 +151,9 @@ namespace Avalonia.Controls
string oldGroupName = GroupName;
if (newGroupName != oldGroupName)
{
if (!string.IsNullOrEmpty(oldGroupName) && _groupManager != null)
if (!string.IsNullOrEmpty(oldGroupName))
{
_groupManager.Remove(this, oldGroupName);
_groupManager?.Remove(this, oldGroupName);
}
_groupName = newGroupName;
if (!string.IsNullOrEmpty(newGroupName))
@ -181,7 +180,7 @@ namespace Avalonia.Controls
.GetVisualChildren()
.OfType<RadioButton>()
.Where(x => x != this);
foreach (var sibling in siblings)
{
if (sibling.IsChecked.GetValueOrDefault())

2
src/Avalonia.Controls/StackPanel.cs

@ -251,7 +251,7 @@ namespace Avalonia.Controls
{
var child = children[i];
if (child == null)
if (child == null || !child.IsVisible)
{ continue; }
if (fHorizontal)

9
src/Avalonia.Input/AccessKeyHandler.cs

@ -231,15 +231,6 @@ namespace Avalonia.Input
}
break;
case Key.F10:
_owner.ShowAccessKeys = _showingAccessKeys = true;
if (MainMenu != null)
{
MainMenu.Open();
e.Handled = true;
}
break;
}
}

129
src/Avalonia.Input/KeyGesture.cs

@ -1,41 +1,56 @@
// 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.Linq;
namespace Avalonia.Input
{
/// <summary>
/// Defines a keyboard input combination.
/// </summary>
public sealed class KeyGesture : IEquatable<KeyGesture>
{
public KeyGesture()
private static readonly Dictionary<string, Key> s_keySynonyms = new Dictionary<string, Key>
{
{ "+", Key.OemPlus }, { "-", Key.OemMinus }, { ".", Key.OemPeriod }
};
[Obsolete("Use constructor taking KeyModifiers")]
public KeyGesture(Key key, InputModifiers modifiers)
{
Key = key;
KeyModifiers = (KeyModifiers)(((int)modifiers) & 0xf);
}
public KeyGesture(Key key, InputModifiers modifiers = InputModifiers.None)
public KeyGesture(Key key, KeyModifiers modifiers = KeyModifiers.None)
{
Key = key;
Modifiers = modifiers;
KeyModifiers = modifiers;
}
public bool Equals(KeyGesture other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Key == other.Key && Modifiers == other.Modifiers;
return Key == other.Key && KeyModifiers == other.KeyModifiers;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj is KeyGesture && Equals((KeyGesture) obj);
return obj is KeyGesture && Equals((KeyGesture)obj);
}
public override int GetHashCode()
{
unchecked
{
return ((int) Key*397) ^ (int) Modifiers;
return ((int)Key * 397) ^ (int)KeyModifiers;
}
}
@ -49,85 +64,85 @@ namespace Avalonia.Input
return !Equals(left, right);
}
public Key Key { get; set; }
public Key Key { get; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers
{
get => (InputModifiers)KeyModifiers;
set => KeyModifiers = (KeyModifiers)(((int)value) & 0xf);
}
public KeyModifiers KeyModifiers { get; set; }
public InputModifiers Modifiers => (InputModifiers)KeyModifiers;
static readonly Dictionary<string, Key> KeySynonyms = new Dictionary<string, Key>
{
{"+", Key.OemPlus },
{"-", Key.OemMinus},
{".", Key.OemPeriod }
};
//TODO: Move that to external key parser
static Key ParseKey(string key)
{
Key rv;
if (KeySynonyms.TryGetValue(key.ToLower(), out rv))
return rv;
return (Key)Enum.Parse(typeof (Key), key, true);
}
static InputModifiers ParseModifier(string modifier)
{
if (modifier.Equals("ctrl", StringComparison.OrdinalIgnoreCase))
return InputModifiers.Control;
return (InputModifiers) Enum.Parse(typeof (InputModifiers), modifier, true);
}
public KeyModifiers KeyModifiers { get; }
public static KeyGesture Parse(string gesture)
{
//string.Split can't be used here because "Ctrl++" is a perfectly valid key gesture
// string.Split can't be used here because "Ctrl++" is a perfectly valid key gesture
var parts = new List<string>();
var key = Key.None;
var keyModifiers = KeyModifiers.None;
var cstart = 0;
for (var c = 0; c <= gesture.Length; c++)
{
var ch = c == gesture.Length ? '\0' : gesture[c];
if (c == gesture.Length || (ch == '+' && cstart != c))
bool isLast = c == gesture.Length;
if (isLast || (ch == '+' && cstart != c))
{
parts.Add(gesture.Substring(cstart, c - cstart));
var partSpan = gesture.AsSpan(cstart, c - cstart).Trim();
if (isLast)
{
key = ParseKey(partSpan.ToString());
}
else
{
keyModifiers |= ParseModifier(partSpan);
}
cstart = c + 1;
}
}
for (var c = 0; c < parts.Count; c++)
parts[c] = parts[c].Trim();
var rv = new KeyGesture();
for (var c = 0; c < parts.Count; c++)
{
if (c == parts.Count - 1)
rv.Key = ParseKey(parts[c]);
else
rv.Modifiers |= ParseModifier(parts[c]);
}
return rv;
return new KeyGesture(key, keyModifiers);
}
public override string ToString()
{
var parts = new List<string>();
foreach (var flag in Enum.GetValues(typeof (InputModifiers)).Cast<InputModifiers>())
foreach (var flag in Enum.GetValues(typeof(KeyModifiers)).Cast<KeyModifiers>())
{
if (Modifiers.HasFlag(flag) && flag != InputModifiers.None)
if (KeyModifiers.HasFlag(flag) && flag != KeyModifiers.None)
{
parts.Add(flag.ToString());
}
}
parts.Add(Key.ToString());
return string.Join(" + ", parts);
}
public bool Matches(KeyEventArgs keyEvent) => ResolveNumPadOperationKey(keyEvent.Key) == Key && keyEvent.Modifiers == Modifiers;
public bool Matches(KeyEventArgs keyEvent) => ResolveNumPadOperationKey(keyEvent.Key) == Key && keyEvent.KeyModifiers == KeyModifiers;
// TODO: Move that to external key parser
private static Key ParseKey(string key)
{
if (s_keySynonyms.TryGetValue(key.ToLower(), out Key rv))
return rv;
return (Key)Enum.Parse(typeof(Key), key, true);
}
private static KeyModifiers ParseModifier(ReadOnlySpan<char> modifier)
{
if (modifier.Equals("ctrl".AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return KeyModifiers.Control;
}
return (KeyModifiers)Enum.Parse(typeof(KeyModifiers), modifier.ToString(), true);
}
private Key ResolveNumPadOperationKey(Key key)
{

57
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -246,22 +246,7 @@ namespace Avalonia.Rendering
{
try
{
IDrawingContextImpl GetContext()
{
if (context != null)
return context;
if ((RenderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true)
{
RenderTarget.Dispose();
RenderTarget = null;
}
if (RenderTarget == null)
RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
return context = RenderTarget.CreateDrawingContext(this);
}
var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext);
var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context);
using (scene)
{
@ -271,9 +256,9 @@ namespace Avalonia.Rendering
if (DrawDirtyRects)
_dirtyRectsDisplay.Tick();
if (overlay)
RenderOverlay(scene.Item, GetContext());
RenderOverlay(scene.Item, ref context);
if (updated || forceComposite || overlay)
RenderComposite(scene.Item, GetContext());
RenderComposite(scene.Item, ref context);
}
}
}
@ -291,7 +276,7 @@ namespace Avalonia.Rendering
}
}
private (IRef<Scene> scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(Func<IDrawingContextImpl> contextFactory,
private (IRef<Scene> scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(ref IDrawingContextImpl context,
bool recursiveCall = false)
{
IRef<Scene> sceneRef;
@ -304,7 +289,8 @@ namespace Avalonia.Rendering
var scene = sceneRef.Item;
if (scene.Generation != _lastSceneId)
{
var context = contextFactory();
EnsureDrawingContext(ref context);
Layers.Update(scene, context);
RenderToLayers(scene);
@ -325,7 +311,7 @@ namespace Avalonia.Rendering
if (!recursiveCall && Dispatcher.UIThread.CheckAccess() && NeedsUpdate)
{
UpdateScene();
var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(contextFactory, true);
var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context, true);
return (rs, true);
}
@ -432,8 +418,10 @@ namespace Avalonia.Rendering
}
private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent)
private void RenderOverlay(Scene scene, ref IDrawingContextImpl parentContent)
{
EnsureDrawingContext(ref parentContent);
if (DrawDirtyRects)
{
var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling);
@ -460,8 +448,10 @@ namespace Avalonia.Rendering
}
}
private void RenderComposite(Scene scene, IDrawingContextImpl context)
private void RenderComposite(Scene scene, ref IDrawingContextImpl context)
{
EnsureDrawingContext(ref context);
context.Clear(Colors.Transparent);
var clientRect = new Rect(scene.Size);
@ -503,6 +493,27 @@ namespace Avalonia.Rendering
}
}
private void EnsureDrawingContext(ref IDrawingContextImpl context)
{
if (context != null)
{
return;
}
if ((RenderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true)
{
RenderTarget.Dispose();
RenderTarget = null;
}
if (RenderTarget == null)
{
RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
context = RenderTarget.CreateDrawingContext(this);
}
private void UpdateScene()
{
Dispatcher.UIThread.VerifyAccess();

14
src/Avalonia.Visuals/Rendering/RenderLoop.cs

@ -91,7 +91,19 @@ namespace Avalonia.Rendering
{
try
{
if (_items.Any(item => item.NeedsUpdate) &&
bool needsUpdate = false;
foreach (IRenderLoopTask item in _items)
{
if (item.NeedsUpdate)
{
needsUpdate = true;
break;
}
}
if (needsUpdate &&
Interlocked.CompareExchange(ref _inUpdate, 1, 0) == 0)
{
_dispatcher.Post(() =>

4
src/Avalonia.X11/X11KeyTransform.cs

@ -104,8 +104,8 @@ namespace Avalonia.X11
{X11Key.x, Key.X},
{X11Key.y, Key.Y},
{X11Key.z, Key.Z},
{X11Key.Meta_L, Key.LWin },
{X11Key.Meta_R, Key.RWin },
{X11Key.Super_L, Key.LWin },
{X11Key.Super_R, Key.RWin },
{X11Key.Menu, Key.Apps},
//{ X11Key.?, Key.Sleep }
{X11Key.KP_0, Key.NumPad0},

4
src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs

@ -32,9 +32,11 @@ namespace Avalonia.Win32
var allDrives = DriveInfo.GetDrives();
var mountVolInfos = allDrives
.Where(p => p.IsReady)
.Select(p => new MountedVolumeInfo()
{
VolumeLabel = p.VolumeLabel,
VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName
: $"{p.VolumeLabel} ({p.Name})",
VolumePath = p.RootDirectory.FullName,
VolumeSizeBytes = (ulong)p.TotalSize
})

20
tests/Avalonia.Base.UnitTests/WeakEventHandlerManagerTests.cs

@ -36,7 +36,7 @@ namespace Avalonia.Base.UnitTests
}
[Fact]
public void EventShoudBePassedToSubscriber()
public void EventShouldBePassedToSubscriber()
{
bool handled = false;
var subscriber = new Subscriber(() => handled = true);
@ -47,7 +47,23 @@ namespace Avalonia.Base.UnitTests
Assert.True(handled);
}
[Fact]
public void EventShouldNotBeRaisedAfterUnsubscribe()
{
bool handled = false;
var subscriber = new Subscriber(() => handled = true);
var source = new EventSource();
WeakEventHandlerManager.Subscribe<EventSource, EventArgs, Subscriber>(source, "Event",
subscriber.OnEvent);
WeakEventHandlerManager.Unsubscribe<EventArgs, Subscriber>(source, "Event",
subscriber.OnEvent);
source.Fire();
Assert.False(handled);
}
[Fact]
public void EventHandlerShouldNotBeKeptAlive()
{

20
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -921,6 +921,26 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.True(raised);
}
[Fact]
public void Can_Set_Both_SelectedItem_And_SelectedItems_During_Initialization()
{
// Issue #2969.
var target = new ListBox();
var selectedItems = new List<object>();
target.BeginInit();
target.Template = Template();
target.Items = new[] { "Foo", "Bar", "Baz" };
target.SelectedItems = selectedItems;
target.SelectedItem = "Bar";
target.EndInit();
Assert.Equal("Bar", target.SelectedItem);
Assert.Equal(1, target.SelectedIndex);
Assert.Same(selectedItems, target.SelectedItems);
Assert.Equal(new[] { "Bar" }, selectedItems);
}
private FuncControlTemplate Template()
{
return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>

25
tests/Avalonia.Controls.UnitTests/StackPanelTests.cs

@ -332,6 +332,31 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(sizeWithTwoChildren, sizeWithThreeChildren);
}
[Theory]
[InlineData(Orientation.Horizontal)]
[InlineData(Orientation.Vertical)]
public void Only_Arrange_Visible_Children(Orientation orientation)
{
var hiddenPanel = new Panel { Width = 10, Height = 10, IsVisible = false };
var panel = new Panel { Width = 10, Height = 10 };
var target = new StackPanel
{
Spacing = 40,
Orientation = orientation,
Children =
{
hiddenPanel,
panel
}
};
target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize));
Assert.Equal(new Rect(0, 0, 10, 10), panel.Bounds);
}
private class TestControl : Control
{
public Size MeasureConstraint { get; private set; }

4
tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs

@ -24,8 +24,8 @@ namespace Avalonia.Controls.UnitTests.Utils
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock())
.Bind<IStyler>().ToConstant(styler.Object);
var gesture1 = new KeyGesture {Key = Key.A, Modifiers = InputModifiers.Control};
var gesture2 = new KeyGesture {Key = Key.B, Modifiers = InputModifiers.Control};
var gesture1 = new KeyGesture(Key.A, InputModifiers.Control);
var gesture2 = new KeyGesture(Key.B, InputModifiers.Control);
var tl = new Window();
var button = new Button();

20
tests/Avalonia.Input.UnitTests/KeyGestureTests.cs

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Avalonia.Input.UnitTests
@ -11,13 +7,11 @@ namespace Avalonia.Input.UnitTests
{
public static readonly IEnumerable<object[]> SampleData = new object[][]
{
new object[]{"Ctrl+A", new KeyGesture {Key = Key.A, Modifiers = InputModifiers.Control}},
new object[]{" \tShift\t+Alt +B", new KeyGesture {Key = Key.B, Modifiers = InputModifiers.Shift|InputModifiers.Alt} },
new object[]{"Control++", new KeyGesture {Key = Key.OemPlus, Modifiers = InputModifiers.Control} }
new object[]{"Ctrl+A", new KeyGesture(Key.A, InputModifiers.Control)},
new object[]{" \tShift\t+Alt +B", new KeyGesture(Key.B, InputModifiers.Shift | InputModifiers.Alt) },
new object[]{"Control++", new KeyGesture(Key.OemPlus, InputModifiers.Control) }
};
[Theory]
[MemberData(nameof(SampleData))]
public void Key_Gesture_Is_Able_To_Parse_Sample_Data(string text, KeyGesture gesture)
@ -31,10 +25,8 @@ namespace Avalonia.Input.UnitTests
[InlineData(Key.OemPeriod, Key.Decimal)]
public void Key_Gesture_Matches_NumPad_To_Regular_Digit(Key gestureKey, Key pressedKey)
{
var keyGesture = new KeyGesture
{
Key = gestureKey
};
var keyGesture = new KeyGesture(gestureKey);
Assert.True(keyGesture.Matches(new KeyEventArgs
{
Key = pressedKey

68
tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

@ -154,6 +154,18 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal("bar", source.Foo);
}
[Fact]
public void OneTime_Binding_Releases_Subscription_If_DataContext_Set_Later()
{
var target = new TextBlock();
var source = new Source { Foo = "foo" };
target.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneTime));
target.DataContext = source;
Assert.Equal(0, source.SubscriberCount);
}
[Fact]
public void OneWayToSource_Binding_Should_Be_Set_Up()
{
@ -196,6 +208,30 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal("baz", target.Text);
}
[Fact]
public void OneWayToSource_Binding_Should_Not_StackOverflow_With_Null_Value()
{
// Issue #2912
var target = new TextBlock { Text = null };
var binding = new Binding
{
Path = "Foo",
Mode = BindingMode.OneWayToSource,
};
target.Bind(TextBox.TextProperty, binding);
var source = new Source { Foo = "foo" };
target.DataContext = source;
Assert.Null(source.Foo);
// When running tests under NCrunch, NCrunch replaces the standard StackOverflowException
// with its own, which will be caught by our code. Detect the stackoverflow anyway, by
// making sure the target property was only set once.
Assert.Equal(2, source.FooSetCount);
}
[Fact]
public void Default_BindingMode_Should_Be_Used()
{
@ -543,6 +579,23 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal(expected, child.DoubleValue);
}
[Fact]
public void Combined_OneTime_And_OneWayToSource_Bindings_Should_Release_Subscriptions()
{
var target1 = new TextBlock();
var target2 = new TextBlock();
var root = new Panel { Children = { target1, target2 } };
var source = new Source { Foo = "foo" };
using (target1.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneTime)))
using (target2.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneWayToSource)))
{
root.DataContext = source;
}
Assert.Equal(0, source.SubscriberCount);
}
private class StyledPropertyClass : AvaloniaObject
{
public static readonly StyledProperty<double> DoubleValueProperty =
@ -622,6 +675,7 @@ namespace Avalonia.Markup.UnitTests.Data
public class Source : INotifyPropertyChanged
{
private PropertyChangedEventHandler _propertyChanged;
private string _foo;
public string Foo
@ -630,15 +684,25 @@ namespace Avalonia.Markup.UnitTests.Data
set
{
_foo = value;
++FooSetCount;
RaisePropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public int FooSetCount { get; private set; }
public int SubscriberCount { get; private set; }
public event PropertyChangedEventHandler PropertyChanged
{
add { _propertyChanged += value; ++SubscriberCount; }
remove { _propertyChanged += value; --SubscriberCount; }
}
private void RaisePropertyChanged([CallerMemberName] string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
_propertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}

Loading…
Cancel
Save