Browse Source

Merge branch 'master' into reduce-array-allocations

pull/2917/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
47cb0ebd81
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/Microsoft.Reactive.Testing.props
  2. 2
      build/ReactiveUI.props
  3. 2
      build/Rx.props
  4. 3
      dirs.proj
  5. 2
      native/Avalonia.Native/inc/avalonia-native.h
  6. 2
      native/Avalonia.Native/src/OSX/Screens.mm
  7. 30
      native/Avalonia.Native/src/OSX/window.mm
  8. 3
      samples/BindingDemo/MainWindow.xaml
  9. 7
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  10. 7
      samples/ControlCatalog/Pages/ScreenPage.cs
  11. 5
      samples/RenderDemo/ViewModels/MainWindowViewModel.cs
  12. 20
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  13. 7
      src/Avalonia.Animation/Animatable.cs
  14. 10
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  15. 2
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  16. 5
      src/Avalonia.Base/Data/Core/SettableNode.cs
  17. 23
      src/Avalonia.Base/PriorityBindingEntry.cs
  18. 49
      src/Avalonia.Base/PriorityLevel.cs
  19. 83
      src/Avalonia.Controls/Button.cs
  20. 5
      src/Avalonia.Controls/Platform/Screen.cs
  21. 9
      src/Avalonia.Controls/Primitives/Popup.cs
  22. 6
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs
  23. 20
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  24. 7
      src/Avalonia.Controls/Repeater/ViewManager.cs
  25. 204
      src/Avalonia.Controls/WrapPanel.cs
  26. 2
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  27. 1
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  28. 18
      src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs
  29. 2
      src/Avalonia.Native/PopupImpl.cs
  30. 1
      src/Avalonia.Native/ScreenImpl.cs
  31. 9
      src/Avalonia.Native/WindowImplBase.cs
  32. 2
      src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
  33. 2
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  34. 2
      src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs
  35. 4
      src/Avalonia.Themes.Default/ProgressBar.xaml
  36. 10
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  37. 2
      src/Avalonia.X11/X11Screens.cs
  38. 16
      src/Avalonia.X11/X11Window.cs
  39. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  40. 4
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  41. 1
      src/Shared/PlatformSupport/AssetLoader.cs
  42. 4
      src/Windows/Avalonia.Win32/ScreenImpl.cs
  43. 2
      src/Windows/Avalonia.Win32/WinScreen.cs
  44. 2
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  45. 22
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  46. 4
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  47. 24
      tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs
  48. 68
      tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
  49. 2
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs
  50. 8
      tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
  51. 99
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  52. 58
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

2
build/Microsoft.Reactive.Testing.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="Microsoft.Reactive.Testing" Version="4.0.0" />
<PackageReference Include="Microsoft.Reactive.Testing" Version="4.1.6" />
</ItemGroup>
</Project>

2
build/ReactiveUI.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="reactiveui" Version="9.0.1" />
<PackageReference Include="ReactiveUI" Version="10.3.6" />
</ItemGroup>
</Project>

2
build/Rx.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.Reactive" Version="4.0.0" />
<PackageReference Include="System.Reactive" Version="4.1.6" />
</ItemGroup>
</Project>

3
dirs.proj

@ -8,7 +8,8 @@
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/XamlIl/**/*.*proj" />
</ItemGroup>
<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">
<!--<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">-->
<ItemGroup>
<ProjectReference Remove="src/Android/**/*.*proj" />
<ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
</ItemGroup>

2
native/Avalonia.Native/inc/avalonia-native.h

@ -52,6 +52,7 @@ struct AvnScreen
{
AvnRect Bounds;
AvnRect WorkingArea;
float PixelDensity;
bool Primary;
};
@ -187,7 +188,6 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT Close() = 0;
virtual HRESULT Activate () = 0;
virtual HRESULT GetClientSize(AvnSize*ret) = 0;
virtual HRESULT GetMaxClientSize(AvnSize* ret) = 0;
virtual HRESULT GetScaling(double*ret)=0;
virtual HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize) = 0;
virtual HRESULT Resize(double width, double height) = 0;

2
native/Avalonia.Native/src/OSX/Screens.mm

@ -38,6 +38,8 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
ret->WorkingArea.Height = [screen visibleFrame].size.height;
ret->WorkingArea.Width = [screen visibleFrame].size.width;
ret->PixelDensity = [screen backingScaleFactor];
ret->Primary = index == 0;
return S_OK;

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

@ -54,7 +54,6 @@ public:
FORWARD_IUNKNOWN()
virtual ~WindowBaseImpl()
{
NSDebugLog(@"~WindowBaseImpl()");
View = NULL;
Window = NULL;
}
@ -161,22 +160,6 @@ public:
}
}
virtual HRESULT GetMaxClientSize(AvnSize* ret) override
{
@autoreleasepool
{
if(ret == nullptr)
return E_POINTER;
auto size = [NSScreen.screens objectAtIndex:0].frame.size;
ret->Height = size.height;
ret->Width = size.width;
return S_OK;
}
}
virtual HRESULT GetScaling (double* ret) override
{
@autoreleasepool
@ -369,12 +352,9 @@ public:
virtual void UpdateCursor()
{
[View resetCursorRects];
if (cursor != nil)
{
auto rect = [Window frame];
[View addCursorRect:rect cursor:cursor];
[cursor set];
[cursor set];
}
}
@ -416,8 +396,8 @@ private:
INHERIT_INTERFACE_MAP(WindowBaseImpl)
INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow)
END_INTERFACE_MAP()
virtual ~WindowImpl(){
NSDebugLog(@"~WindowImpl");
virtual ~WindowImpl()
{
}
ComPtr<IAvnWindowEvents> WindowEvents;
@ -425,6 +405,7 @@ private:
{
WindowEvents = events;
[Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
}
virtual HRESULT Show () override
@ -664,10 +645,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)dealloc
{
NSDebugLog(@"AvnView dealloc");
}
- (void)onClosed
{
_parent = NULL;
@ -1067,7 +1046,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)dealloc
{
NSDebugLog(@"AvnWindow dealloc");
}
- (void)pollModalSession:(nonnull NSModalSession)session

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"/>

7
samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@ -1,10 +1,11 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using ReactiveUI;
using System.Reactive;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Threading;
using ReactiveUI;
namespace BindingDemo.ViewModels
{
@ -56,7 +57,7 @@ namespace BindingDemo.ViewModels
public ObservableCollection<TestItem> Items { get; }
public ObservableCollection<TestItem> SelectedItems { get; }
public ReactiveCommand ShuffleItems { get; }
public ReactiveCommand<Unit, Unit> ShuffleItems { get; }
public string BooleanString
{
@ -89,7 +90,7 @@ namespace BindingDemo.ViewModels
}
public IObservable<string> CurrentTimeObservable { get; }
public ReactiveCommand StringValueCommand { get; }
public ReactiveCommand<object, Unit> StringValueCommand { get; }
public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel();
public ExceptionErrorViewModel ExceptionDataValidation { get; } = new ExceptionErrorViewModel();

7
samples/ControlCatalog/Pages/ScreenPage.cs

@ -57,12 +57,15 @@ namespace ControlCatalog.Pages
text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
text.Text = $"Scaling: {screen.PixelDensity * 100}%";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
text.Text = $"Primary: {screen.Primary}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text);
}
context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10));

5
samples/RenderDemo/ViewModels/MainWindowViewModel.cs

@ -1,4 +1,5 @@
using System;
using System.Reactive;
using ReactiveUI;
namespace RenderDemo.ViewModels
@ -26,7 +27,7 @@ namespace RenderDemo.ViewModels
set { this.RaiseAndSetIfChanged(ref drawFps, value); }
}
public ReactiveCommand ToggleDrawDirtyRects { get; }
public ReactiveCommand ToggleDrawFps { get; }
public ReactiveCommand<Unit, bool> ToggleDrawDirtyRects { get; }
public ReactiveCommand<Unit, bool> ToggleDrawFps { get; }
}
}

20
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@ -4,10 +4,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using ReactiveUI.Legacy;
using ReactiveUI;
using Avalonia.Layout;
@ -18,7 +18,7 @@ namespace VirtualizationDemo.ViewModels
private int _itemCount = 200;
private string _newItemString = "New Item";
private int _newItemIndex;
private IReactiveList<ItemViewModel> _items;
private AvaloniaList<ItemViewModel> _items;
private string _prefix = "Item";
private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto;
private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto;
@ -54,7 +54,7 @@ namespace VirtualizationDemo.ViewModels
public AvaloniaList<ItemViewModel> SelectedItems { get; }
= new AvaloniaList<ItemViewModel>();
public IReactiveList<ItemViewModel> Items
public AvaloniaList<ItemViewModel> Items
{
get { return _items; }
private set { this.RaiseAndSetIfChanged(ref _items, value); }
@ -93,11 +93,11 @@ namespace VirtualizationDemo.ViewModels
public IEnumerable<ItemVirtualizationMode> VirtualizationModes =>
Enum.GetValues(typeof(ItemVirtualizationMode)).Cast<ItemVirtualizationMode>();
public ReactiveCommand AddItemCommand { get; private set; }
public ReactiveCommand RecreateCommand { get; private set; }
public ReactiveCommand RemoveItemCommand { get; private set; }
public ReactiveCommand SelectFirstCommand { get; private set; }
public ReactiveCommand SelectLastCommand { get; private set; }
public ReactiveCommand<Unit, Unit> AddItemCommand { get; private set; }
public ReactiveCommand<Unit, Unit> RecreateCommand { get; private set; }
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; private set; }
public ReactiveCommand<Unit, Unit> SelectFirstCommand { get; private set; }
public ReactiveCommand<Unit, Unit> SelectLastCommand { get; private set; }
public void RandomizeSize()
{
@ -123,7 +123,7 @@ namespace VirtualizationDemo.ViewModels
{
var items = Enumerable.Range(0, count)
.Select(x => new ItemViewModel(x));
Items = new ReactiveList<ItemViewModel>(items);
Items = new AvaloniaList<ItemViewModel>(items);
}
else if (count > Items.Count)
{
@ -162,7 +162,7 @@ namespace VirtualizationDemo.ViewModels
_prefix = _prefix == "Item" ? "Recreated" : "Item";
var items = Enumerable.Range(0, _itemCount)
.Select(x => new ItemViewModel(x, _prefix));
Items = new ReactiveList<ItemViewModel>(items);
Items = new AvaloniaList<ItemViewModel>(items);
}
private void SelectItem(int index)

7
src/Avalonia.Animation/Animatable.cs

@ -55,6 +55,11 @@ namespace Avalonia.Animation
}
set
{
if (value is null)
return;
if (_previousTransitions is null)
_previousTransitions = new Dictionary<AvaloniaProperty, IDisposable>();
SetAndRaise(TransitionsProperty, ref _transitions, value);
}
@ -70,7 +75,7 @@ namespace Avalonia.Animation
if (_transitions is null || _previousTransitions is null || e.Priority == BindingPriority.Animation) return;
// PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations).
foreach (var transition in Transitions)
foreach (var transition in _transitions)
{
if (transition.Property == e.Property)
{

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;
}

23
src/Avalonia.Base/PriorityBindingEntry.cs

@ -99,37 +99,42 @@ namespace Avalonia
void IObserver<object>.OnNext(object value)
{
void Signal()
void Signal(PriorityBindingEntry instance, object newValue)
{
var notification = value as BindingNotification;
var notification = newValue as BindingNotification;
if (notification != null)
{
if (notification.HasValue || notification.ErrorType == BindingErrorType.Error)
{
Value = notification.Value;
_owner.Changed(this);
instance.Value = notification.Value;
instance._owner.Changed(instance);
}
if (notification.ErrorType != BindingErrorType.None)
{
_owner.Error(this, notification);
instance._owner.Error(instance, notification);
}
}
else
{
Value = value;
_owner.Changed(this);
instance.Value = newValue;
instance._owner.Changed(instance);
}
}
if (Dispatcher.UIThread.CheckAccess())
{
Signal();
Signal(this, value);
}
else
{
Dispatcher.UIThread.Post(Signal);
// To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
var instance = this;
var newValue = value;
Dispatcher.UIThread.Post(() => Signal(instance, newValue));
}
}

49
src/Avalonia.Base/PriorityLevel.cs

@ -110,20 +110,7 @@ namespace Avalonia
entry.Start(binding);
return Disposable.Create(() =>
{
if (!entry.HasCompleted)
{
Bindings.Remove(node);
entry.Dispose();
if (entry.Index >= ActiveBindingIndex)
{
ActivateFirstBinding();
}
}
});
return new RemoveBindingDisposable(node, Bindings, this);
}
/// <summary>
@ -191,5 +178,39 @@ namespace Avalonia
ActiveBindingIndex = -1;
Owner.LevelValueChanged(this);
}
private sealed class RemoveBindingDisposable : IDisposable
{
private readonly LinkedListNode<PriorityBindingEntry> _binding;
private readonly LinkedList<PriorityBindingEntry> _bindings;
private readonly PriorityLevel _priorityLevel;
public RemoveBindingDisposable(
LinkedListNode<PriorityBindingEntry> binding,
LinkedList<PriorityBindingEntry> bindings,
PriorityLevel priorityLevel)
{
_binding = binding;
_bindings = bindings;
_priorityLevel = priorityLevel;
}
public void Dispose()
{
PriorityBindingEntry entry = _binding.Value;
if (!entry.HasCompleted)
{
_bindings.Remove(_binding);
entry.Dispose();
if (entry.Index >= _priorityLevel.ActiveBindingIndex)
{
_priorityLevel.ActivateFirstBinding();
}
}
}
}
}
}

83
src/Avalonia.Controls/Button.cs

@ -64,6 +64,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> IsDefaultProperty =
AvaloniaProperty.Register<Button, bool>(nameof(IsDefault));
/// <summary>
/// Defines the <see cref="IsCancelProperty"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsCancelProperty =
AvaloniaProperty.Register<Button, bool>(nameof(IsCancel));
/// <summary>
/// Defines the <see cref="Click"/> event.
/// </summary>
@ -84,6 +90,7 @@ namespace Avalonia.Controls
FocusableProperty.OverrideDefaultValue(typeof(Button), true);
CommandProperty.Changed.Subscribe(CommandChanged);
IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
IsCancelProperty.Changed.Subscribe(IsCancelChanged);
PseudoClass<Button>(IsPressedProperty, ":pressed");
}
@ -142,6 +149,16 @@ namespace Avalonia.Controls
set { SetValue(IsDefaultProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the button is the Cancel button for the
/// window.
/// </summary>
public bool IsCancel
{
get { return GetValue(IsCancelProperty); }
set { SetValue(IsCancelProperty, value); }
}
public bool IsPressed
{
get { return GetValue(IsPressedProperty); }
@ -162,6 +179,13 @@ namespace Avalonia.Controls
ListenForDefault(inputElement);
}
}
if (IsCancel)
{
if (e.Root is IInputElement inputElement)
{
ListenForCancel(inputElement);
}
}
}
/// <inheritdoc/>
@ -351,6 +375,28 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="IsCancel"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private static void IsCancelChanged(AvaloniaPropertyChangedEventArgs e)
{
var button = e.Sender as Button;
var isCancel = (bool)e.NewValue;
if (button?.VisualRoot is IInputElement inputRoot)
{
if (isCancel)
{
button.ListenForCancel(inputRoot);
}
else
{
button.StopListeningForCancel(inputRoot);
}
}
}
/// <summary>
/// Called when the <see cref="ICommand.CanExecuteChanged"/> event fires.
/// </summary>
@ -373,7 +419,16 @@ namespace Avalonia.Controls
/// <param name="root">The input root.</param>
private void ListenForDefault(IInputElement root)
{
root.AddHandler(KeyDownEvent, RootKeyDown);
root.AddHandler(KeyDownEvent, RootDefaultKeyDown);
}
/// <summary>
/// Starts listening for the Escape key when the button <see cref="IsCancel"/>.
/// </summary>
/// <param name="root">The input root.</param>
private void ListenForCancel(IInputElement root)
{
root.AddHandler(KeyDownEvent, RootCancelKeyDown);
}
/// <summary>
@ -382,7 +437,16 @@ namespace Avalonia.Controls
/// <param name="root">The input root.</param>
private void StopListeningForDefault(IInputElement root)
{
root.RemoveHandler(KeyDownEvent, RootKeyDown);
root.RemoveHandler(KeyDownEvent, RootDefaultKeyDown);
}
/// <summary>
/// Stops listening for the Escape key when the button is no longer <see cref="IsCancel"/>.
/// </summary>
/// <param name="root">The input root.</param>
private void StopListeningForCancel(IInputElement root)
{
root.RemoveHandler(KeyDownEvent, RootCancelKeyDown);
}
/// <summary>
@ -390,12 +454,25 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private void RootKeyDown(object sender, KeyEventArgs e)
private void RootDefaultKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && IsVisible && IsEnabled)
{
OnClick();
}
}
/// <summary>
/// Called when a key is pressed on the input root and the button <see cref="IsCancel"/>.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private void RootCancelKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape && IsVisible && IsEnabled)
{
OnClick();
}
}
}
}

5
src/Avalonia.Controls/Platform/Screen.cs

@ -2,14 +2,17 @@
{
public class Screen
{
public double PixelDensity { get; }
public PixelRect Bounds { get; }
public PixelRect WorkingArea { get; }
public bool Primary { get; }
public Screen(PixelRect bounds, PixelRect workingArea, bool primary)
public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary)
{
this.PixelDensity = pixelDensity;
this.Bounds = bounds;
this.WorkingArea = workingArea;
this.Primary = primary;

9
src/Avalonia.Controls/Primitives/Popup.cs

@ -450,7 +450,14 @@ namespace Avalonia.Controls.Primitives
private bool IsChildOrThis(IVisual child)
{
return _popupHost != null && ((IVisual)_popupHost).FindCommonVisualAncestor(child) == _popupHost;
IVisual root = child.GetVisualRoot();
while (root is IHostedVisualTreeRoot hostedRoot )
{
if (root == this._popupHost)
return true;
root = hostedRoot.Host?.GetVisualRoot();
}
return false;
}
public bool IsInsidePopup(IVisual visual)

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

@ -32,7 +32,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
{
// Popup positioner operates with abstract coordinates, but in our case they are pixel ones
var point = _parent.PointToScreen(default);
var size = PixelSize.FromSize(_parent.ClientSize, _parent.Scaling);
var size = TranslateSize(_parent.ClientSize);
return new Rect(point.X, point.Y, size.Width, size.Height);
}
@ -43,8 +43,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
_moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling);
}
public Point TranslatePoint(Point pt) => pt * _parent.Scaling;
public virtual Point TranslatePoint(Point pt) => pt * _parent.Scaling;
public Size TranslateSize(Size size) => size * _parent.Scaling;
public virtual Size TranslateSize(Size size) => size * _parent.Scaling;
}
}

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

@ -304,6 +304,11 @@ namespace Avalonia.Controls.Primitives
{
base.ItemsCollectionChanged(sender, e);
if (_updateCount > 0)
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
@ -1071,13 +1076,20 @@ namespace Avalonia.Controls.Primitives
private void UpdateFinished()
{
if (_updateSelectedIndex != int.MinValue)
if (_updateSelectedItem != null)
{
SelectedIndex = _updateSelectedIndex;
SelectedItem = _updateSelectedItem;
}
else if (_updateSelectedItem != null)
else
{
SelectedItem = _updateSelectedItem;
if (ItemCount == 0 && SelectedIndex != -1)
{
SelectedIndex = -1;
}
else
{
SelectedIndex = _updateSelectedIndex != int.MinValue ? _updateSelectedIndex : 0;
}
}
}

7
src/Avalonia.Controls/Repeater/ViewManager.cs

@ -128,8 +128,7 @@ namespace Avalonia.Controls
private void MoveFocusFromClearedIndex(int clearedIndex)
{
IControl focusedChild = null;
var focusCandidate = FindFocusCandidate(clearedIndex, focusedChild);
var focusCandidate = FindFocusCandidate(clearedIndex, out var focusedChild);
if (focusCandidate != null)
{
focusCandidate.Focus();
@ -145,7 +144,7 @@ namespace Avalonia.Controls
}
}
IControl FindFocusCandidate(int clearedIndex, IControl focusedChild)
IControl FindFocusCandidate(int clearedIndex, out IControl focusedChild)
{
// Walk through all the children and find elements with index before and after the cleared index.
// Note that during a delete the next element would now have the same index.
@ -183,7 +182,7 @@ namespace Avalonia.Controls
// TODO: Find the next element if one exists, if not use the previous element.
// If the container itself is not focusable, find a descendent that is.
focusedChild = nextElement;
return nextElement;
}

204
src/Avalonia.Controls/WrapPanel.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls
/// Positions child elements in sequential position from left to right,
/// breaking content to the next line at the edge of the containing box.
/// Subsequent ordering happens sequentially from top to bottom or from right to left,
/// depending on the value of the Orientation property.
/// depending on the value of the <see cref="Orientation"/> property.
/// </summary>
public class WrapPanel : Panel, INavigableContainer
{
@ -25,6 +25,18 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<WrapPanel, Orientation>(nameof(Orientation), defaultValue: Orientation.Horizontal);
/// <summary>
/// Defines the <see cref="ItemWidth"/> property.
/// </summary>
public static readonly StyledProperty<double> ItemWidthProperty =
AvaloniaProperty.Register<WrapPanel, double>(nameof(ItemWidth), double.NaN);
/// <summary>
/// Defines the <see cref="ItemHeight"/> property.
/// </summary>
public static readonly StyledProperty<double> ItemHeightProperty =
AvaloniaProperty.Register<WrapPanel, double>(nameof(ItemHeight), double.NaN);
/// <summary>
/// Initializes static members of the <see cref="WrapPanel"/> class.
/// </summary>
@ -42,6 +54,24 @@ namespace Avalonia.Controls
set { SetValue(OrientationProperty, value); }
}
/// <summary>
/// Gets or sets the width of all items in the WrapPanel.
/// </summary>
public double ItemWidth
{
get { return GetValue(ItemWidthProperty); }
set { SetValue(ItemWidthProperty, value); }
}
/// <summary>
/// Gets or sets the height of all items in the WrapPanel.
/// </summary>
public double ItemHeight
{
get { return GetValue(ItemHeightProperty); }
set { SetValue(ItemHeightProperty, value); }
}
/// <summary>
/// Gets the next control in the specified direction.
/// </summary>
@ -51,7 +81,9 @@ namespace Avalonia.Controls
/// <returns>The control.</returns>
IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from, bool wrap)
{
var horiz = Orientation == Orientation.Horizontal;
var orientation = Orientation;
var children = Children;
bool horiz = orientation == Orientation.Horizontal;
int index = Children.IndexOf((IControl)from);
switch (direction)
@ -60,7 +92,7 @@ namespace Avalonia.Controls
index = 0;
break;
case NavigationDirection.Last:
index = Children.Count - 1;
index = children.Count - 1;
break;
case NavigationDirection.Next:
++index;
@ -82,9 +114,9 @@ namespace Avalonia.Controls
break;
}
if (index >= 0 && index < Children.Count)
if (index >= 0 && index < children.Count)
{
return Children[index];
return children[index];
}
else
{
@ -95,116 +127,140 @@ namespace Avalonia.Controls
/// <inheritdoc/>
protected override Size MeasureOverride(Size constraint)
{
var curLineSize = new UVSize(Orientation);
var panelSize = new UVSize(Orientation);
var uvConstraint = new UVSize(Orientation, constraint.Width, constraint.Height);
var childConstraint = new Size(constraint.Width, constraint.Height);
for (int i = 0, count = Children.Count; i < count; i++)
double itemWidth = ItemWidth;
double itemHeight = ItemHeight;
var orientation = Orientation;
var children = Children;
var curLineSize = new UVSize(orientation);
var panelSize = new UVSize(orientation);
var uvConstraint = new UVSize(orientation, constraint.Width, constraint.Height);
bool itemWidthSet = !double.IsNaN(itemWidth);
bool itemHeightSet = !double.IsNaN(itemHeight);
var childConstraint = new Size(
itemWidthSet ? itemWidth : constraint.Width,
itemHeightSet ? itemHeight : constraint.Height);
for (int i = 0, count = children.Count; i < count; i++)
{
var child = Children[i];
if (child == null) continue;
//Flow passes its own constrint to children
child.Measure(childConstraint);
//this is the size of the child in UV space
var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) //need to switch to another line
var child = children[i];
if (child != null)
{
panelSize.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
// Flow passes its own constraint to children
child.Measure(childConstraint);
// This is the size of the child in UV space
var sz = new UVSize(orientation,
itemWidthSet ? itemWidth : child.DesiredSize.Width,
itemHeightSet ? itemHeight : child.DesiredSize.Height);
if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) //the element is wider then the constrint - give it a separate line
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) // Need to switch to another line
{
panelSize.U = Max(sz.U, panelSize.U);
panelSize.V += sz.V;
curLineSize = new UVSize(Orientation);
panelSize.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) // The element is wider then the constraint - give it a separate line
{
panelSize.U = Max(sz.U, panelSize.U);
panelSize.V += sz.V;
curLineSize = new UVSize(orientation);
}
}
else // Continue to accumulate a line
{
curLineSize.U += sz.U;
curLineSize.V = Max(sz.V, curLineSize.V);
}
}
else //continue to accumulate a line
{
curLineSize.U += sz.U;
curLineSize.V = Max(sz.V, curLineSize.V);
}
}
//the last line size, if any should be added
// The last line size, if any should be added
panelSize.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
//go from UV space to W/H space
// Go from UV space to W/H space
return new Size(panelSize.Width, panelSize.Height);
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
double itemWidth = ItemWidth;
double itemHeight = ItemHeight;
var orientation = Orientation;
var children = Children;
int firstInLine = 0;
double accumulatedV = 0;
UVSize curLineSize = new UVSize(Orientation);
UVSize uvFinalSize = new UVSize(Orientation, finalSize.Width, finalSize.Height);
for (int i = 0; i < Children.Count; i++)
double itemU = orientation == Orientation.Horizontal ? itemWidth : itemHeight;
var curLineSize = new UVSize(orientation);
var uvFinalSize = new UVSize(orientation, finalSize.Width, finalSize.Height);
bool itemWidthSet = !double.IsNaN(itemWidth);
bool itemHeightSet = !double.IsNaN(itemHeight);
bool useItemU = orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet;
for (int i = 0; i < children.Count; i++)
{
var child = Children[i];
if (child == null) continue;
var child = children[i];
if (child != null)
{
var sz = new UVSize(orientation,
itemWidthSet ? itemWidth : child.DesiredSize.Width,
itemHeightSet ? itemHeight : child.DesiredSize.Height);
var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) // Need to switch to another line
{
ArrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU);
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) //need to switch to another line
{
arrangeLine(accumulatedV, curLineSize.V, firstInLine, i);
accumulatedV += curLineSize.V;
curLineSize = sz;
accumulatedV += curLineSize.V;
curLineSize = sz;
if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) // The element is wider then the constraint - give it a separate line
{
// Switch to next line which only contain one element
ArrangeLine(accumulatedV, sz.V, i, ++i, useItemU, itemU);
if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) //the element is wider then the constraint - give it a separate line
accumulatedV += sz.V;
curLineSize = new UVSize(orientation);
}
firstInLine = i;
}
else // Continue to accumulate a line
{
//switch to next line which only contain one element
arrangeLine(accumulatedV, sz.V, i, ++i);
accumulatedV += sz.V;
curLineSize = new UVSize(Orientation);
curLineSize.U += sz.U;
curLineSize.V = Max(sz.V, curLineSize.V);
}
firstInLine = i;
}
else //continue to accumulate a line
{
curLineSize.U += sz.U;
curLineSize.V = Max(sz.V, curLineSize.V);
}
}
//arrange the last line, if any
if (firstInLine < Children.Count)
// Arrange the last line, if any
if (firstInLine < children.Count)
{
arrangeLine(accumulatedV, curLineSize.V, firstInLine, Children.Count);
ArrangeLine(accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU);
}
return finalSize;
}
private void arrangeLine(double v, double lineV, int start, int end)
private void ArrangeLine(double v, double lineV, int start, int end, bool useItemU, double itemU)
{
var orientation = Orientation;
var children = Children;
double u = 0;
bool isHorizontal = (Orientation == Orientation.Horizontal);
bool isHorizontal = orientation == Orientation.Horizontal;
for (int i = start; i < end; i++)
{
var child = Children[i];
var child = children[i];
if (child != null)
{
UVSize childSize = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
double layoutSlotU = childSize.U;
var childSize = new UVSize(orientation, child.DesiredSize.Width, child.DesiredSize.Height);
double layoutSlotU = useItemU ? itemU : childSize.U;
child.Arrange(new Rect(
(isHorizontal ? u : v),
(isHorizontal ? v : u),
(isHorizontal ? layoutSlotU : lineV),
(isHorizontal ? lineV : layoutSlotU)));
isHorizontal ? u : v,
isHorizontal ? v : u,
isHorizontal ? layoutSlotU : lineV,
isHorizontal ? lineV : layoutSlotU));
u += layoutSlotU;
}
}
@ -232,12 +288,12 @@ namespace Avalonia.Controls
internal double Width
{
get { return (_orientation == Orientation.Horizontal ? U : V); }
get { return _orientation == Orientation.Horizontal ? U : V; }
set { if (_orientation == Orientation.Horizontal) U = value; else V = value; }
}
internal double Height
{
get { return (_orientation == Orientation.Horizontal ? V : U); }
get { return _orientation == Orientation.Horizontal ? V : U; }
set { if (_orientation == Orientation.Horizontal) V = value; else U = value; }
}
}

2
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -178,6 +178,6 @@ namespace Avalonia.DesignerSupport.Remote
public int ScreenCount => 1;
public IReadOnlyList<Screen> AllScreens { get; } =
new Screen[] { new Screen(new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) };
new Screen[] { new Screen(1, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) };
}
}

1
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -1,7 +1,6 @@
// 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.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using Avalonia.Controls.Platform;
using Avalonia.Input;

18
src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs

@ -0,0 +1,18 @@
// 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.PopupPositioning;
using Avalonia.Platform;
namespace Avalonia.Native
{
class OsxManagedPopupPositionerPopupImplHelper : ManagedPopupPositionerPopupImplHelper
{
public OsxManagedPopupPositionerPopupImplHelper(IWindowBaseImpl parent, MoveResizeDelegate moveResize) : base(parent, moveResize)
{
}
public override Point TranslatePoint(Point pt) => pt;
public override Size TranslateSize(Size size) => size;
}
}

2
src/Avalonia.Native/PopupImpl.cs

@ -22,7 +22,7 @@ namespace Avalonia.Native
{
Init(factory.CreatePopup(e), factory.CreateScreens());
}
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
PopupPositioner = new ManagedPopupPositioner(new OsxManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}
private void MoveResize(PixelPoint position, Size size, double scaling)

1
src/Avalonia.Native/ScreenImpl.cs

@ -31,6 +31,7 @@ namespace Avalonia.Native
var screen = _native.GetScreen(i);
result[i] = new Screen(
screen.PixelDensity,
screen.Bounds.ToAvaloniaPixelRect(),
screen.WorkingArea.ToAvaloniaPixelRect(),
screen.Primary);

9
src/Avalonia.Native/WindowImplBase.cs

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
@ -48,6 +49,11 @@ namespace Avalonia.Native
Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize;
_savedScaling = Scaling;
var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
.FirstOrDefault(m => m.Bounds.Contains(Position));
Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d));
}
public Size ClientSize
@ -300,7 +306,8 @@ namespace Avalonia.Native
_native.BeginMoveDrag();
}
public Size MaxClientSize => _native.GetMaxClientSize().ToAvaloniaSize();
public Size MaxClientSize => Screen.AllScreens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity))
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault();
public void SetTopmost(bool value)
{

2
src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs

@ -27,7 +27,7 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// Returns activation observable for activatable Avalonia view.
/// </summary>
public IObservable<bool> GetActivationForView(IActivatable view)
public IObservable<bool> GetActivationForView(IActivatableView view)
{
if (!(view is IVisual visual)) return Observable.Return(false);
if (view is WindowBase window) return GetActivationForWindowBase(window);

2
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@ -53,7 +53,7 @@ namespace Avalonia.ReactiveUI
/// ReactiveUI routing documentation website</see> for more info.
/// </para>
/// </remarks>
public class RoutedViewHost : TransitioningContentControl, IActivatable, IEnableLogger
public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger
{
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="Router"/> property.

2
src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs → src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs

@ -3,7 +3,7 @@ using Avalonia.Reactive;
namespace Avalonia.Controls
{
public static class ResourceProviderExtensions
public static class ResourceNodeExtensions
{
/// <summary>
/// Finds the specified resource by searching up the logical tree and then global styles.

4
src/Avalonia.Themes.Default/ProgressBar.xaml

@ -7,9 +7,7 @@
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Border Name="PART_Indicator"
BorderThickness="1"
Background="{TemplateBinding Foreground}"/>
<Border Name="PART_Indicator" Background="{TemplateBinding Foreground}"/>
</Border>
</ControlTemplate>
</Setter>

10
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -53,6 +53,16 @@ namespace Avalonia.Rendering.SceneGraph
if (visual.VisualRoot != null)
{
if (node?.Parent != null &&
visual.VisualParent != null &&
node.Parent.Visual != visual.VisualParent)
{
// The control has changed parents. Remove the node and recurse into the new parent node.
((VisualNode)node.Parent).RemoveChild(node);
Deindex(scene, node);
node = (VisualNode)scene.FindNode(visual.VisualParent);
}
if (visual.IsVisible)
{
// If the node isn't yet part of the scene, find the nearest ancestor that is.

2
src/Avalonia.X11/X11Screens.cs

@ -157,7 +157,7 @@ namespace Avalonia.X11
public int ScreenCount => _impl.Screens.Length;
public IReadOnlyList<Screen> AllScreens =>
_impl.Screens.Select(s => new Screen(s.Bounds, s.WorkingArea, s.Primary)).ToArray();
_impl.Screens.Select(s => new Screen(s.PixelDensity, s.Bounds, s.WorkingArea, s.Primary)).ToArray();
}
interface IX11Screens

16
src/Avalonia.X11/X11Window.cs

@ -98,14 +98,26 @@ namespace Avalonia.X11
valueMask |= SetWindowValuemask.ColorMap;
}
_handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, 300, 200, 0,
int defaultWidth = 300, defaultHeight = 200;
if (!_popup)
{
var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
.FirstOrDefault(m => m.Bounds.Contains(Position));
// Emulate Window 7+'s default window size behavior.
defaultWidth = (int)(monitor.WorkingArea.Width * 0.75d);
defaultHeight = (int)(monitor.WorkingArea.Height * 0.7d);
}
_handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, defaultWidth, defaultHeight, 0,
depth,
(int)CreateWindowArgs.InputOutput,
visual,
new UIntPtr((uint)valueMask), ref attr);
if (_useRenderWindow)
_renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, depth,
_renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, defaultWidth, defaultHeight, 0, depth,
(int)CreateWindowArgs.InputOutput,
visual,
new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity |

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -18,7 +18,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
}
public DynamicResourceExtension(string resourceKey)
public DynamicResourceExtension(object resourceKey)
{
ResourceKey = resourceKey;
}

4
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -16,12 +16,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
}
public StaticResourceExtension(string resourceKey)
public StaticResourceExtension(object resourceKey)
{
ResourceKey = resourceKey;
}
public string ResourceKey { get; set; }
public object ResourceKey { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{

1
src/Shared/PlatformSupport/AssetLoader.cs

@ -242,6 +242,7 @@ namespace Avalonia.Shared.PlatformSupport
throw new InvalidOperationException(
$"Assembly {name} needs to be referenced and explicitly loaded before loading resources");
#else
name = Uri.UnescapeDataString(name);
AssemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name));
#endif
}

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

@ -30,6 +30,8 @@ namespace Avalonia.Win32
MONITORINFO monitorInfo = MONITORINFO.Create();
if (GetMonitorInfo(monitor,ref monitorInfo))
{
GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var x, out _);
RECT bounds = monitorInfo.rcMonitor;
RECT workingArea = monitorInfo.rcWork;
PixelRect avaloniaBounds = new PixelRect(bounds.left, bounds.top, bounds.right - bounds.left,
@ -38,7 +40,7 @@ namespace Avalonia.Win32
new PixelRect(workingArea.left, workingArea.top, workingArea.right - workingArea.left,
workingArea.bottom - workingArea.top);
screens[index] =
new WinScreen(avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1,
new WinScreen((double)x / 96.0d, avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1,
monitor);
index++;
}

2
src/Windows/Avalonia.Win32/WinScreen.cs

@ -7,7 +7,7 @@ namespace Avalonia.Win32
{
private readonly IntPtr _hMonitor;
public WinScreen(PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(bounds, workingArea, primary)
public WinScreen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(pixelDensity, bounds, workingArea, primary)
{
this._hMonitor = hMonitor;
}

2
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@ -198,7 +198,7 @@ namespace Avalonia.Controls.UnitTests
var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100));
var screenImpl = new Mock<IScreenImpl>();
screenImpl.Setup(x => x.ScreenCount).Returns(1);
screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(screen, screen, true) });
screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(1, screen, screen, true) });
popupImpl = MockWindowingPlatform.CreatePopupMock();
popupImpl.SetupGet(x => x.Scaling).Returns(1);

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

@ -109,6 +109,28 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.True(items[1].IsSelected);
}
[Fact]
public void Setting_SelectedIndex_During_Initialize_Should_Select_Item_When_AlwaysSelected_Is_Used()
{
var listBox = new ListBox
{
SelectionMode = SelectionMode.Single | SelectionMode.AlwaysSelected
};
listBox.BeginInit();
listBox.SelectedIndex = 1;
var items = new AvaloniaList<string>();
listBox.Items = items;
items.Add("A");
items.Add("B");
items.Add("C");
listBox.EndInit();
Assert.Equal("B", listBox.SelectedItem);
}
[Fact]
public void Setting_SelectedIndex_Before_ApplyTemplate_Should_Set_Item_IsSelected_True()
{

4
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -271,8 +271,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen()
{
var screen1 = new Mock<Screen>(new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true);
var screen2 = new Mock<Screen>(new PixelRect(new PixelSize(1366, 768)), new PixelRect(new PixelSize(1366, 728)), false);
var screen1 = new Mock<Screen>(1.0, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true);
var screen2 = new Mock<Screen>(1.0, new PixelRect(new PixelSize(1366, 768)), new PixelRect(new PixelSize(1366, 728)), false);
var screens = new Mock<IScreenImpl>();
screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object });

24
tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs

@ -93,5 +93,29 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(0, 0, 100, 50), target.Children[0].Bounds);
Assert.Equal(new Rect(100, 0, 100, 50), target.Children[1].Bounds);
}
[Fact]
public void Applies_ItemWidth_And_ItemHeight_Properties()
{
var target = new WrapPanel()
{
Orientation = Orientation.Horizontal,
Width = 50,
ItemWidth = 20,
ItemHeight = 15,
Children =
{
new Border(),
new Border(),
}
};
target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize));
Assert.Equal(new Size(50, 15), target.Bounds.Size);
Assert.Equal(new Rect(0, 0, 20, 15), target.Children[0].Bounds);
Assert.Equal(new Rect(20, 0, 20, 15), target.Children[1].Bounds);
}
}
}

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));
}
}

2
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs

@ -120,6 +120,8 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = run();
result.Item1.Subscribe(x => { });
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Assert.Null(result.Item2.Target);

8
tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs

@ -20,9 +20,9 @@ namespace Avalonia.ReactiveUI.UnitTests
{
public class AvaloniaActivationForViewFetcherTest
{
public class TestUserControl : UserControl, IActivatable { }
public class TestUserControl : UserControl, IActivatableView { }
public class TestUserControlWithWhenActivated : UserControl, IActivatable
public class TestUserControlWithWhenActivated : UserControl, IActivatableView
{
public bool Active { get; private set; }
@ -38,7 +38,7 @@ namespace Avalonia.ReactiveUI.UnitTests
}
}
public class TestWindowWithWhenActivated : Window, IActivatable
public class TestWindowWithWhenActivated : Window, IActivatableView
{
public bool Active { get; private set; }
@ -54,7 +54,7 @@ namespace Avalonia.ReactiveUI.UnitTests
}
}
public class ActivatableViewModel : ISupportsActivation
public class ActivatableViewModel : IActivatableViewModel
{
public ViewModelActivator Activator { get; }

99
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -96,6 +96,55 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Assert.Equal(new List<IVisual> { root, decorator, border, canvas }, result);
}
[Fact]
public void Should_Add_Dirty_Rect_On_Child_Remove()
{
var dispatcher = new ImmediateDispatcher();
var loop = new Mock<IRenderLoop>();
Decorator decorator;
Border border;
var root = new TestRoot
{
Width = 100,
Height= 100,
Child = decorator = new Decorator
{
Child = border = new Border
{
Width = 50,
Height = 50,
Background = Brushes.Red,
},
}
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
var sceneBuilder = new SceneBuilder();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder,
dispatcher: dispatcher);
root.Renderer = target;
target.Start();
RunFrame(target);
decorator.Child = null;
RunFrame(target);
var scene = target.UnitTestScene();
var stackNode = scene.FindNode(decorator);
var dirty = scene.Layers[0].Dirty.ToList();
Assert.Equal(1, dirty.Count);
Assert.Equal(new Rect(25, 25, 50, 50), dirty[0]);
}
[Fact]
public void Should_Update_VisualNode_Order_On_Child_Remove_Insert()
{
@ -270,6 +319,56 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Assert.Same(stackNode.Children[1].Visual, canvas1);
}
[Fact]
public void Should_Update_VisualNodes_When_Child_Moved_To_New_Parent()
{
var dispatcher = new ImmediateDispatcher();
var loop = new Mock<IRenderLoop>();
Decorator moveFrom;
Decorator moveTo;
Canvas moveMe;
var root = new TestRoot
{
Child = new StackPanel
{
Children =
{
(moveFrom = new Decorator
{
Child = moveMe = new Canvas(),
}),
(moveTo = new Decorator()),
}
}
};
var sceneBuilder = new SceneBuilder();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder,
dispatcher: dispatcher);
root.Renderer = target;
target.Start();
RunFrame(target);
moveFrom.Child = null;
moveTo.Child = moveMe;
RunFrame(target);
var scene = target.UnitTestScene();
var moveFromNode = (VisualNode)scene.FindNode(moveFrom);
var moveToNode = (VisualNode)scene.FindNode(moveTo);
Assert.Empty(moveFromNode.Children);
Assert.Equal(1, moveToNode.Children.Count);
Assert.Same(moveMe, moveToNode.Children[0].Visual);
}
[Fact]
public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity()
{

58
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@ -475,6 +475,64 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}
}
[Fact]
public void Should_Update_When_Control_Moved()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
Decorator moveFrom;
Decorator moveTo;
Canvas moveMe;
var tree = new TestRoot
{
Width = 100,
Height = 100,
Child = new StackPanel
{
Children =
{
(moveFrom = new Decorator
{
Child = moveMe = new Canvas(),
}),
(moveTo = new Decorator()),
}
}
};
tree.Measure(Size.Infinity);
tree.Arrange(new Rect(tree.DesiredSize));
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
var moveFromNode = (VisualNode)scene.FindNode(moveFrom);
var moveToNode = (VisualNode)scene.FindNode(moveTo);
Assert.Equal(1, moveFromNode.Children.Count);
Assert.Same(moveMe, moveFromNode.Children[0].Visual);
Assert.Empty(moveToNode.Children);
moveFrom.Child = null;
moveTo.Child = moveMe;
scene = scene.CloneScene();
moveFromNode = (VisualNode)scene.FindNode(moveFrom);
moveToNode = (VisualNode)scene.FindNode(moveTo);
moveFromNode.SortChildren(scene);
moveToNode.SortChildren(scene);
sceneBuilder.Update(scene, moveFrom);
sceneBuilder.Update(scene, moveTo);
sceneBuilder.Update(scene, moveMe);
Assert.Empty(moveFromNode.Children);
Assert.Equal(1, moveToNode.Children.Count);
Assert.Same(moveMe, moveToNode.Children[0].Visual);
}
}
[Fact]
public void Should_Update_When_Control_Made_Invisible()
{

Loading…
Cancel
Save