Browse Source

Merge branch 'master' into reactiveui-update

pull/2930/head
Dariusz Komosiński 7 years ago
committed by GitHub
parent
commit
f84ba185d6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      build/NetFX.props
  2. 2
      native/Avalonia.Native/inc/avalonia-native.h
  3. 2
      native/Avalonia.Native/src/OSX/Screens.mm
  4. 30
      native/Avalonia.Native/src/OSX/window.mm
  5. 3
      samples/BindingDemo/MainWindow.xaml
  6. 12
      samples/ControlCatalog/MainView.xaml
  7. 7
      samples/ControlCatalog/Pages/ScreenPage.cs
  8. 7
      src/Avalonia.Animation/Animatable.cs
  9. 10
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  10. 2
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  11. 5
      src/Avalonia.Base/Data/Core/SettableNode.cs
  12. 23
      src/Avalonia.Base/PriorityBindingEntry.cs
  13. 49
      src/Avalonia.Base/PriorityLevel.cs
  14. 3
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  15. 5
      src/Avalonia.Controls/Platform/Screen.cs
  16. 6
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs
  17. 7
      src/Avalonia.Controls/Repeater/ViewManager.cs
  18. 2
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  19. 129
      src/Avalonia.Input/KeyGesture.cs
  20. 1
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  21. 18
      src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs
  22. 2
      src/Avalonia.Native/PopupImpl.cs
  23. 1
      src/Avalonia.Native/ScreenImpl.cs
  24. 9
      src/Avalonia.Native/WindowImplBase.cs
  25. 4
      src/Avalonia.Themes.Default/ProgressBar.xaml
  26. 59
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  27. 14
      src/Avalonia.Visuals/Rendering/RenderLoop.cs
  28. 15
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  29. 4
      src/Avalonia.X11/X11KeyTransform.cs
  30. 2
      src/Avalonia.X11/X11Screens.cs
  31. 16
      src/Avalonia.X11/X11Window.cs
  32. 1
      src/Shared/PlatformSupport/AssetLoader.cs
  33. 4
      src/Windows/Avalonia.Win32/ScreenImpl.cs
  34. 2
      src/Windows/Avalonia.Win32/WinScreen.cs
  35. 4
      src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs
  36. 20
      tests/Avalonia.Base.UnitTests/WeakEventHandlerManagerTests.cs
  37. 2
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  38. 4
      tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
  39. 4
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  40. 20
      tests/Avalonia.Input.UnitTests/KeyGestureTests.cs
  41. 68
      tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
  42. 50
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  43. 58
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  44. 2
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs

14
build/NetFX.props

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

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

@ -52,6 +52,7 @@ struct AvnScreen
{ {
AvnRect Bounds; AvnRect Bounds;
AvnRect WorkingArea; AvnRect WorkingArea;
float PixelDensity;
bool Primary; bool Primary;
}; };
@ -187,7 +188,6 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT Close() = 0; virtual HRESULT Close() = 0;
virtual HRESULT Activate () = 0; virtual HRESULT Activate () = 0;
virtual HRESULT GetClientSize(AvnSize*ret) = 0; virtual HRESULT GetClientSize(AvnSize*ret) = 0;
virtual HRESULT GetMaxClientSize(AvnSize* ret) = 0;
virtual HRESULT GetScaling(double*ret)=0; virtual HRESULT GetScaling(double*ret)=0;
virtual HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize) = 0; virtual HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize) = 0;
virtual HRESULT Resize(double width, double height) = 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.Height = [screen visibleFrame].size.height;
ret->WorkingArea.Width = [screen visibleFrame].size.width; ret->WorkingArea.Width = [screen visibleFrame].size.width;
ret->PixelDensity = [screen backingScaleFactor];
ret->Primary = index == 0; ret->Primary = index == 0;
return S_OK; return S_OK;

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

@ -54,7 +54,6 @@ public:
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
virtual ~WindowBaseImpl() virtual ~WindowBaseImpl()
{ {
NSDebugLog(@"~WindowBaseImpl()");
View = NULL; View = NULL;
Window = 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 virtual HRESULT GetScaling (double* ret) override
{ {
@autoreleasepool @autoreleasepool
@ -369,12 +352,9 @@ public:
virtual void UpdateCursor() virtual void UpdateCursor()
{ {
[View resetCursorRects];
if (cursor != nil) if (cursor != nil)
{ {
auto rect = [Window frame]; [cursor set];
[View addCursorRect:rect cursor:cursor];
[cursor set];
} }
} }
@ -416,8 +396,8 @@ private:
INHERIT_INTERFACE_MAP(WindowBaseImpl) INHERIT_INTERFACE_MAP(WindowBaseImpl)
INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow) INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow)
END_INTERFACE_MAP() END_INTERFACE_MAP()
virtual ~WindowImpl(){ virtual ~WindowImpl()
NSDebugLog(@"~WindowImpl"); {
} }
ComPtr<IAvnWindowEvents> WindowEvents; ComPtr<IAvnWindowEvents> WindowEvents;
@ -425,6 +405,7 @@ private:
{ {
WindowEvents = events; WindowEvents = events;
[Window setCanBecomeKeyAndMain]; [Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
} }
virtual HRESULT Show () override virtual HRESULT Show () override
@ -664,10 +645,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)dealloc - (void)dealloc
{ {
NSDebugLog(@"AvnView dealloc");
} }
- (void)onClosed - (void)onClosed
{ {
_parent = NULL; _parent = NULL;
@ -1067,7 +1046,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)dealloc - (void)dealloc
{ {
NSDebugLog(@"AvnWindow dealloc");
} }
- (void)pollModalSession:(nonnull NSModalSession)session - (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="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 Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWay}"/>
<TextBox Watermark="One Time" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneTime}"/> <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>
<StackPanel Margin="18" Spacing="4" Width="200"> <StackPanel Margin="18" Spacing="4" Width="200">
<TextBlock FontSize="16" Text="Collection Bindings"/> <TextBlock FontSize="16" Text="Collection Bindings"/>

12
samples/ControlCatalog/MainView.xaml

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

7
samples/ControlCatalog/Pages/ScreenPage.cs

@ -57,12 +57,15 @@ namespace ControlCatalog.Pages
text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"; text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text); 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}"; 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))))}"; 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)); context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10));

7
src/Avalonia.Animation/Animatable.cs

@ -55,6 +55,11 @@ namespace Avalonia.Animation
} }
set set
{ {
if (value is null)
return;
if (_previousTransitions is null)
_previousTransitions = new Dictionary<AvaloniaProperty, IDisposable>();
SetAndRaise(TransitionsProperty, ref _transitions, value); SetAndRaise(TransitionsProperty, ref _transitions, value);
} }
@ -70,7 +75,7 @@ namespace Avalonia.Animation
if (_transitions is null || _previousTransitions is null || e.Priority == BindingPriority.Animation) return; 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). // 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) 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 public abstract class ExpressionNode
{ {
private static readonly object CacheInvalid = new object(); private static readonly object CacheInvalid = new object();
protected static readonly WeakReference<object> UnsetReference = protected static readonly WeakReference<object> UnsetReference =
new WeakReference<object>(AvaloniaProperty.UnsetValue); new WeakReference<object>(AvaloniaProperty.UnsetValue);
protected static readonly WeakReference<object> NullReference =
new WeakReference<object>(null);
private WeakReference<object> _target = UnsetReference; private WeakReference<object> _target = UnsetReference;
private Action<object> _subscriber; private Action<object> _subscriber;
private bool _listening; private bool _listening;
@ -98,7 +102,7 @@ namespace Avalonia.Data.Core
if (notification == null) if (notification == null)
{ {
LastValue = new WeakReference<object>(value); LastValue = value != null ? new WeakReference<object>(value) : NullReference;
if (Next != null) if (Next != null)
{ {
@ -111,7 +115,7 @@ namespace Avalonia.Data.Core
} }
else else
{ {
LastValue = new WeakReference<object>(notification.Value); LastValue = notification.Value != null ? new WeakReference<object>(notification.Value) : NullReference;
if (Next != null) if (Next != null)
{ {
@ -136,8 +140,8 @@ namespace Avalonia.Data.Core
} }
else if (target != AvaloniaProperty.UnsetValue) else if (target != AvaloniaProperty.UnsetValue)
{ {
StartListeningCore(_target);
_listening = true; _listening = true;
StartListeningCore(_target);
} }
else else
{ {

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

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

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

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

23
src/Avalonia.Base/PriorityBindingEntry.cs

@ -99,37 +99,42 @@ namespace Avalonia
void IObserver<object>.OnNext(object value) 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 != null)
{ {
if (notification.HasValue || notification.ErrorType == BindingErrorType.Error) if (notification.HasValue || notification.ErrorType == BindingErrorType.Error)
{ {
Value = notification.Value; instance.Value = notification.Value;
_owner.Changed(this); instance._owner.Changed(instance);
} }
if (notification.ErrorType != BindingErrorType.None) if (notification.ErrorType != BindingErrorType.None)
{ {
_owner.Error(this, notification); instance._owner.Error(instance, notification);
} }
} }
else else
{ {
Value = value; instance.Value = newValue;
_owner.Changed(this); instance._owner.Changed(instance);
} }
} }
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
{ {
Signal(); Signal(this, value);
} }
else 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); entry.Start(binding);
return Disposable.Create(() => return new RemoveBindingDisposable(node, Bindings, this);
{
if (!entry.HasCompleted)
{
Bindings.Remove(node);
entry.Dispose();
if (entry.Index >= ActiveBindingIndex)
{
ActivateFirstBinding();
}
}
});
} }
/// <summary> /// <summary>
@ -191,5 +178,39 @@ namespace Avalonia
ActiveBindingIndex = -1; ActiveBindingIndex = -1;
Owner.LevelValueChanged(this); 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();
}
}
}
}
} }
} }

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

@ -161,9 +161,8 @@ namespace Avalonia.Utilities
for (int c = 0; c < _count; ++c) for (int c = 0; c < _count; ++c)
{ {
var reference = _data[c].Subscriber; 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; _data[c] = default;
removed = true; removed = true;

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

@ -2,14 +2,17 @@
{ {
public class Screen public class Screen
{ {
public double PixelDensity { get; }
public PixelRect Bounds { get; } public PixelRect Bounds { get; }
public PixelRect WorkingArea { get; } public PixelRect WorkingArea { get; }
public bool Primary { 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.Bounds = bounds;
this.WorkingArea = workingArea; this.WorkingArea = workingArea;
this.Primary = primary; this.Primary = primary;

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 // Popup positioner operates with abstract coordinates, but in our case they are pixel ones
var point = _parent.PointToScreen(default); 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); 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); _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;
} }
} }

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

@ -128,8 +128,7 @@ namespace Avalonia.Controls
private void MoveFocusFromClearedIndex(int clearedIndex) private void MoveFocusFromClearedIndex(int clearedIndex)
{ {
IControl focusedChild = null; var focusCandidate = FindFocusCandidate(clearedIndex, out var focusedChild);
var focusCandidate = FindFocusCandidate(clearedIndex, focusedChild);
if (focusCandidate != null) if (focusCandidate != null)
{ {
focusCandidate.Focus(); 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. // 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. // 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. // 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. // If the container itself is not focusable, find a descendent that is.
focusedChild = nextElement;
return nextElement; return nextElement;
} }

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

@ -178,6 +178,6 @@ namespace Avalonia.DesignerSupport.Remote
public int ScreenCount => 1; public int ScreenCount => 1;
public IReadOnlyList<Screen> AllScreens { get; } = 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) };
} }
} }

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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Avalonia.Input namespace Avalonia.Input
{ {
/// <summary>
/// Defines a keyboard input combination.
/// </summary>
public sealed class KeyGesture : IEquatable<KeyGesture> 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; Key = key;
Modifiers = modifiers; KeyModifiers = modifiers;
} }
public bool Equals(KeyGesture other) public bool Equals(KeyGesture other)
{ {
if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true; 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) public override bool Equals(object obj)
{ {
if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true; if (ReferenceEquals(this, obj)) return true;
return obj is KeyGesture && Equals((KeyGesture) obj);
return obj is KeyGesture && Equals((KeyGesture)obj);
} }
public override int GetHashCode() public override int GetHashCode()
{ {
unchecked unchecked
{ {
return ((int) Key*397) ^ (int) Modifiers; return ((int)Key * 397) ^ (int)KeyModifiers;
} }
} }
@ -49,85 +64,85 @@ namespace Avalonia.Input
return !Equals(left, right); return !Equals(left, right);
} }
public Key Key { get; set; } public Key Key { get; }
[Obsolete("Use KeyModifiers")] [Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers public InputModifiers Modifiers => (InputModifiers)KeyModifiers;
{
get => (InputModifiers)KeyModifiers;
set => KeyModifiers = (KeyModifiers)(((int)value) & 0xf);
}
public KeyModifiers KeyModifiers { get; set; }
public KeyModifiers KeyModifiers { get; }
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 static KeyGesture Parse(string gesture) 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; var cstart = 0;
for (var c = 0; c <= gesture.Length; c++) for (var c = 0; c <= gesture.Length; c++)
{ {
var ch = c == gesture.Length ? '\0' : gesture[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; 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++) return new KeyGesture(key, keyModifiers);
{
if (c == parts.Count - 1)
rv.Key = ParseKey(parts[c]);
else
rv.Modifiers |= ParseModifier(parts[c]);
}
return rv;
} }
public override string ToString() public override string ToString()
{ {
var parts = new List<string>(); 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(flag.ToString());
}
} }
parts.Add(Key.ToString()); parts.Add(Key.ToString());
return string.Join(" + ", parts); 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) private Key ResolveNumPadOperationKey(Key key)
{ {

1
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -1,7 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Input; 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()); 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) 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); var screen = _native.GetScreen(i);
result[i] = new Screen( result[i] = new Screen(
screen.PixelDensity,
screen.Bounds.ToAvaloniaPixelRect(), screen.Bounds.ToAvaloniaPixelRect(),
screen.WorkingArea.ToAvaloniaPixelRect(), screen.WorkingArea.ToAvaloniaPixelRect(),
screen.Primary); screen.Primary);

9
src/Avalonia.Native/WindowImplBase.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input; using Avalonia.Input;
@ -48,6 +49,11 @@ namespace Avalonia.Native
Screen = new ScreenImpl(screens); Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize; _savedLogicalSize = ClientSize;
_savedScaling = Scaling; _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 public Size ClientSize
@ -300,7 +306,8 @@ namespace Avalonia.Native
_native.BeginMoveDrag(); _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) public void SetTopmost(bool value)
{ {

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

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

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

@ -246,22 +246,7 @@ namespace Avalonia.Rendering
{ {
try try
{ {
IDrawingContextImpl GetContext() var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context);
{
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);
using (scene) using (scene)
{ {
@ -271,9 +256,9 @@ namespace Avalonia.Rendering
if (DrawDirtyRects) if (DrawDirtyRects)
_dirtyRectsDisplay.Tick(); _dirtyRectsDisplay.Tick();
if (overlay) if (overlay)
RenderOverlay(scene.Item, GetContext()); RenderOverlay(scene.Item, ref context);
if (updated || forceComposite || overlay) 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) bool recursiveCall = false)
{ {
IRef<Scene> sceneRef; IRef<Scene> sceneRef;
@ -304,7 +289,8 @@ namespace Avalonia.Rendering
var scene = sceneRef.Item; var scene = sceneRef.Item;
if (scene.Generation != _lastSceneId) if (scene.Generation != _lastSceneId)
{ {
var context = contextFactory(); EnsureDrawingContext(ref context);
Layers.Update(scene, context); Layers.Update(scene, context);
RenderToLayers(scene); RenderToLayers(scene);
@ -325,7 +311,7 @@ namespace Avalonia.Rendering
if (!recursiveCall && Dispatcher.UIThread.CheckAccess() && NeedsUpdate) if (!recursiveCall && Dispatcher.UIThread.CheckAccess() && NeedsUpdate)
{ {
UpdateScene(); UpdateScene();
var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(contextFactory, true); var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context, true);
return (rs, 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) if (DrawDirtyRects)
{ {
var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling); 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); context.Clear(Colors.Transparent);
var clientRect = new Rect(scene.Size); 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() private void UpdateScene()
{ {
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
@ -529,7 +540,7 @@ namespace Avalonia.Rendering
foreach (var visual in _recalculateChildren) foreach (var visual in _recalculateChildren)
{ {
var node = scene.FindNode(visual); var node = scene.FindNode(visual);
((VisualNode)node)?.SortChildren(scene); ((VisualNode)node)?.UpdateChildren(scene);
} }
_recalculateChildren.Clear(); _recalculateChildren.Clear();

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

@ -91,7 +91,19 @@ namespace Avalonia.Rendering
{ {
try 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) Interlocked.CompareExchange(ref _inUpdate, 1, 0) == 0)
{ {
_dispatcher.Post(() => _dispatcher.Post(() =>

15
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@ -174,12 +174,12 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary> /// <summary>
/// Sorts the <see cref="Children"/> collection according to the order of the visual's /// Sorts the <see cref="Children"/> collection according to the order of the visual's
/// children and their z-index. /// children and their z-index and removes controls that are no longer children.
/// </summary> /// </summary>
/// <param name="scene">The scene that the node is a part of.</param> /// <param name="scene">The scene that the node is a part of.</param>
public void SortChildren(Scene scene) public void UpdateChildren(Scene scene)
{ {
if (_children == null || _children.Count <= 1) if (_children == null || _children.Count == 0)
{ {
return; return;
} }
@ -193,9 +193,12 @@ namespace Avalonia.Rendering.SceneGraph
keys.Add(((long)zIndex << 32) + i); keys.Add(((long)zIndex << 32) + i);
} }
var toRemove = _children.ToList();
keys.Sort(); keys.Sort();
_children.Clear(); _children.Clear();
foreach (var i in keys) foreach (var i in keys)
{ {
var child = Visual.VisualChildren[(int)(i & 0xffffffff)]; var child = Visual.VisualChildren[(int)(i & 0xffffffff)];
@ -204,8 +207,14 @@ namespace Avalonia.Rendering.SceneGraph
if (node != null) if (node != null)
{ {
_children.Add(node); _children.Add(node);
toRemove.Remove(node);
} }
} }
foreach (var node in toRemove)
{
scene.Remove(node);
}
} }
/// <summary> /// <summary>

4
src/Avalonia.X11/X11KeyTransform.cs

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

2
src/Avalonia.X11/X11Screens.cs

@ -157,7 +157,7 @@ namespace Avalonia.X11
public int ScreenCount => _impl.Screens.Length; public int ScreenCount => _impl.Screens.Length;
public IReadOnlyList<Screen> AllScreens => 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 interface IX11Screens

16
src/Avalonia.X11/X11Window.cs

@ -98,14 +98,26 @@ namespace Avalonia.X11
valueMask |= SetWindowValuemask.ColorMap; 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, depth,
(int)CreateWindowArgs.InputOutput, (int)CreateWindowArgs.InputOutput,
visual, visual,
new UIntPtr((uint)valueMask), ref attr); new UIntPtr((uint)valueMask), ref attr);
if (_useRenderWindow) 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, (int)CreateWindowArgs.InputOutput,
visual, visual,
new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity | new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity |

1
src/Shared/PlatformSupport/AssetLoader.cs

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

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

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

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

@ -7,7 +7,7 @@ namespace Avalonia.Win32
{ {
private readonly IntPtr _hMonitor; 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; this._hMonitor = hMonitor;
} }

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

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

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

@ -36,7 +36,7 @@ namespace Avalonia.Base.UnitTests
} }
[Fact] [Fact]
public void EventShoudBePassedToSubscriber() public void EventShouldBePassedToSubscriber()
{ {
bool handled = false; bool handled = false;
var subscriber = new Subscriber(() => handled = true); var subscriber = new Subscriber(() => handled = true);
@ -47,7 +47,23 @@ namespace Avalonia.Base.UnitTests
Assert.True(handled); 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] [Fact]
public void EventHandlerShouldNotBeKeptAlive() public void EventHandlerShouldNotBeKeptAlive()
{ {

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 screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100));
var screenImpl = new Mock<IScreenImpl>(); var screenImpl = new Mock<IScreenImpl>();
screenImpl.Setup(x => x.ScreenCount).Returns(1); 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 = MockWindowingPlatform.CreatePopupMock();
popupImpl.SetupGet(x => x.Scaling).Returns(1); popupImpl.SetupGet(x => x.Scaling).Returns(1);

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

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

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

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

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

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

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

@ -154,6 +154,18 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal("bar", source.Foo); 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] [Fact]
public void OneWayToSource_Binding_Should_Be_Set_Up() public void OneWayToSource_Binding_Should_Be_Set_Up()
{ {
@ -196,6 +208,30 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal("baz", target.Text); 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] [Fact]
public void Default_BindingMode_Should_Be_Used() public void Default_BindingMode_Should_Be_Used()
{ {
@ -543,6 +579,23 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal(expected, child.DoubleValue); 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 private class StyledPropertyClass : AvaloniaObject
{ {
public static readonly StyledProperty<double> DoubleValueProperty = public static readonly StyledProperty<double> DoubleValueProperty =
@ -622,6 +675,7 @@ namespace Avalonia.Markup.UnitTests.Data
public class Source : INotifyPropertyChanged public class Source : INotifyPropertyChanged
{ {
private PropertyChangedEventHandler _propertyChanged;
private string _foo; private string _foo;
public string Foo public string Foo
@ -630,15 +684,25 @@ namespace Avalonia.Markup.UnitTests.Data
set set
{ {
_foo = value; _foo = value;
++FooSetCount;
RaisePropertyChanged(); 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 = "") private void RaisePropertyChanged([CallerMemberName] string prop = "")
{ {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
} }
} }

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

@ -270,6 +270,56 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Assert.Same(stackNode.Children[1].Visual, canvas1); 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] [Fact]
public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity() 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.UpdateChildren(scene);
moveToNode.UpdateChildren(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] [Fact]
public void Should_Update_When_Control_Made_Invisible() public void Should_Update_When_Control_Made_Invisible()
{ {

2
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs

@ -99,7 +99,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var node = new VisualNode(Mock.Of<IVisual>(), null); var node = new VisualNode(Mock.Of<IVisual>(), null);
var scene = new Scene(Mock.Of<IVisual>()); var scene = new Scene(Mock.Of<IVisual>());
node.SortChildren(scene); node.UpdateChildren(scene);
} }
} }
} }

Loading…
Cancel
Save