Browse Source

Merge branch 'master' into fix-osx-cursor

pull/1428/head
Florian 8 years ago
committed by GitHub
parent
commit
673dc566b3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  2. 127
      src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
  3. 12
      src/Avalonia.Controls/ItemsControl.cs
  4. 6
      src/Avalonia.Controls/LayoutTransformControl.cs
  5. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  6. 26
      src/Avalonia.Controls/ProgressBar.cs
  7. 4
      src/Avalonia.Controls/UserControl.cs
  8. 2
      src/Avalonia.Controls/Window.cs
  9. 58
      src/Avalonia.Themes.Default/ProgressBar.xaml
  10. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
  11. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  12. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  13. 5
      src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
  14. 6
      src/Gtk/Avalonia.Gtk3/Interop/GObject.cs
  15. 15
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  16. 22
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  17. 10
      src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs
  18. 33
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  19. 22
      src/Windows/Avalonia.Win32/WindowFramebuffer.cs
  20. 5
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs
  21. 20
      tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs
  22. 28
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  23. 16
      tests/Avalonia.Layout.UnitTests/ArrangeTests.cs
  24. 37
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

26
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@ -34,14 +34,18 @@ namespace Avalonia.Collections
/// <param name="reset">
/// An action called when the collection is reset.
/// </param>
/// <param name="weakSubscription">
/// Indicates if a weak subscription should be used to track changes to the collection.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable ForEachItem<T>(
this IAvaloniaReadOnlyList<T> collection,
Action<T> added,
Action<T> removed,
Action reset)
Action reset,
bool weakSubscription = false)
{
return collection.ForEachItem((_, i) => added(i), (_, i) => removed(i), reset);
return collection.ForEachItem((_, i) => added(i), (_, i) => removed(i), reset, weakSubscription);
}
/// <summary>
@ -63,12 +67,16 @@ namespace Avalonia.Collections
/// An action called when the collection is reset. This will be followed by calls to
/// <paramref name="added"/> for each item present in the collection after the reset.
/// </param>
/// <param name="weakSubscription">
/// Indicates if a weak subscription should be used to track changes to the collection.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable ForEachItem<T>(
this IAvaloniaReadOnlyList<T> collection,
Action<int, T> added,
Action<int, T> removed,
Action reset)
Action reset,
bool weakSubscription = false)
{
void Add(int index, IList items)
{
@ -118,9 +126,17 @@ namespace Avalonia.Collections
};
Add(0, (IList)collection);
collection.CollectionChanged += handler;
return Disposable.Create(() => collection.CollectionChanged -= handler);
if (weakSubscription)
{
return collection.WeakSubscribe(handler);
}
else
{
collection.CollectionChanged += handler;
return Disposable.Create(() => collection.CollectionChanged -= handler);
}
}
public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(

127
src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs

@ -0,0 +1,127 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Utilities;
namespace Avalonia.Collections
{
public static class NotifyCollectionChangedExtensions
{
/// <summary>
/// Gets a weak observable for the CollectionChanged event.
/// </summary>
/// <param name="collection">The collection.</param>
/// <returns>An observable.</returns>
public static IObservable<NotifyCollectionChangedEventArgs> GetWeakCollectionChangedObservable(
this INotifyCollectionChanged collection)
{
Contract.Requires<ArgumentNullException>(collection != null);
return new WeakCollectionChangedObservable(new WeakReference<INotifyCollectionChanged>(collection));
}
/// <summary>
/// Subcribes to the CollectionChanged event using a weak subscription.
/// </summary>
/// <param name="collection">The collection.</param>
/// <param name="handler">
/// An action called when the collection event is raised.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable WeakSubscribe(
this INotifyCollectionChanged collection,
NotifyCollectionChangedEventHandler handler)
{
Contract.Requires<ArgumentNullException>(collection != null);
Contract.Requires<ArgumentNullException>(handler != null);
return
collection.GetWeakCollectionChangedObservable()
.Subscribe(e => handler.Invoke(collection, e));
}
/// <summary>
/// Subcribes to the CollectionChanged event using a weak subscription.
/// </summary>
/// <param name="collection">The collection.</param>
/// <param name="handler">
/// An action called when the collection event is raised.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable WeakSubscribe(
this INotifyCollectionChanged collection,
Action<NotifyCollectionChangedEventArgs> handler)
{
Contract.Requires<ArgumentNullException>(collection != null);
Contract.Requires<ArgumentNullException>(handler != null);
return
collection.GetWeakCollectionChangedObservable()
.Subscribe(handler);
}
private class WeakCollectionChangedObservable : ObservableBase<NotifyCollectionChangedEventArgs>,
IWeakSubscriber<NotifyCollectionChangedEventArgs>
{
private WeakReference<INotifyCollectionChanged> _sourceReference;
private readonly Subject<NotifyCollectionChangedEventArgs> _changed = new Subject<NotifyCollectionChangedEventArgs>();
private int _count;
public WeakCollectionChangedObservable(WeakReference<INotifyCollectionChanged> source)
{
_sourceReference = source;
}
public void OnEvent(object sender, NotifyCollectionChangedEventArgs e)
{
_changed.OnNext(e);
}
protected override IDisposable SubscribeCore(IObserver<NotifyCollectionChangedEventArgs> observer)
{
if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
{
if (_count++ == 0)
{
WeakSubscriptionManager.Subscribe(
instance,
nameof(instance.CollectionChanged),
this);
}
return Observable.Using(() => Disposable.Create(DecrementCount), _ => _changed)
.Subscribe(observer);
}
else
{
_changed.OnCompleted();
observer.OnCompleted();
return Disposable.Empty;
}
}
private void DecrementCount()
{
if (--_count == 0)
{
if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
{
WeakSubscriptionManager.Unsubscribe(
instance,
nameof(instance.CollectionChanged),
this);
}
}
}
}
}
}

12
src/Avalonia.Controls/ItemsControl.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
@ -54,6 +55,7 @@ namespace Avalonia.Controls
private IEnumerable _items = new AvaloniaList<object>();
private IItemContainerGenerator _itemContainerGenerator;
private IDisposable _itemsCollectionChangedSubscription;
/// <summary>
/// Initializes static members of the <see cref="ItemsControl"/> class.
@ -326,12 +328,8 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param>
protected virtual void ItemsChanged(AvaloniaPropertyChangedEventArgs e)
{
var incc = e.OldValue as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged -= ItemsCollectionChanged;
}
_itemsCollectionChangedSubscription?.Dispose();
_itemsCollectionChangedSubscription = null;
var oldValue = e.OldValue as IEnumerable;
var newValue = e.NewValue as IEnumerable;
@ -428,7 +426,7 @@ namespace Avalonia.Controls
if (incc != null)
{
incc.CollectionChanged += ItemsCollectionChanged;
_itemsCollectionChangedSubscription = incc.WeakSubscribe(ItemsCollectionChanged);
}
}

6
src/Avalonia.Controls/LayoutTransformControl.cs

@ -15,6 +15,9 @@ using System.Reactive.Linq;
namespace Avalonia.Controls
{
/// <summary>
/// Control that implements support for transformations as if applied by LayoutTransform.
/// </summary>
public class LayoutTransformControl : ContentControl
{
public static readonly AvaloniaProperty<Transform> LayoutTransformProperty =
@ -26,6 +29,9 @@ namespace Avalonia.Controls
.AddClassHandler<LayoutTransformControl>(x => x.OnLayoutTransformChanged);
}
/// <summary>
/// Gets or sets a graphics transformation that should apply to this element when layout is performed.
/// </summary>
public Transform LayoutTransform
{
get { return GetValue(LayoutTransformProperty); }

2
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -224,7 +224,7 @@ namespace Avalonia.Controls.Presenters
CanVerticallyScroll ? Math.Max(Child.DesiredSize.Height, finalSize.Height) : finalSize.Height);
ArrangeOverrideImpl(size, -Offset);
Viewport = finalSize;
Extent = Child.Bounds.Size;
Extent = Child.Bounds.Size.Inflate(Child.Margin);
return finalSize;
}

26
src/Avalonia.Controls/ProgressBar.cs

@ -26,12 +26,13 @@ namespace Avalonia.Controls
static ProgressBar()
{
PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical");
PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal");
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(
(p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
OrientationProperty.Changed.AddClassHandler<ProgressBar>(
(p, e) => { if (p._indicator != null) p.UpdateOrientation((Orientation)e.NewValue); });
}
public bool IsIndeterminate
@ -59,7 +60,6 @@ namespace Avalonia.Controls
_indicator = e.NameScope.Get<Border>("PART_Indicator");
UpdateIndicator(Bounds.Size);
UpdateOrientation(Orientation);
UpdateIsIndeterminate(IsIndeterminate);
}
@ -86,26 +86,6 @@ namespace Avalonia.Controls
}
}
private void UpdateOrientation(Orientation orientation)
{
if (orientation == Orientation.Horizontal)
{
MinHeight = 14;
MinWidth = 200;
_indicator.HorizontalAlignment = HorizontalAlignment.Left;
_indicator.VerticalAlignment = VerticalAlignment.Stretch;
}
else
{
MinHeight = 200;
MinWidth = 14;
_indicator.HorizontalAlignment = HorizontalAlignment.Stretch;
_indicator.VerticalAlignment = VerticalAlignment.Bottom;
}
}
private void UpdateIsIndeterminate(bool isIndeterminate)
{
if (isIndeterminate)

4
src/Avalonia.Controls/UserControl.cs

@ -6,6 +6,9 @@ using Avalonia.Styling;
namespace Avalonia.Controls
{
/// <summary>
/// Provides the base class for defining a new control that encapsulates related existing controls and provides its own logic.
/// </summary>
public class UserControl : ContentControl, IStyleable, INameScope
{
private readonly NameScope _nameScope = new NameScope();
@ -24,6 +27,7 @@ namespace Avalonia.Controls
remove { _nameScope.Unregistered -= value; }
}
/// <inheritdoc/>
Type IStyleable.StyleKey => typeof(ContentControl);
/// <inheritdoc/>

2
src/Avalonia.Controls/Window.cs

@ -358,7 +358,7 @@ namespace Avalonia.Controls
modal?.Dispose();
SetIsEnabled(affectedWindows, true);
activated?.Activate();
result.SetResult((TResult)_dialogResult);
result.SetResult((TResult)(_dialogResult ?? default(TResult)));
});
return result.Task;

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

@ -1,20 +1,38 @@
<Style xmlns="https://github.com/avaloniaui" Selector="ProgressBar">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Border Name="PART_Track"
BorderThickness="1"
BorderBrush="{TemplateBinding Background}"/>
<Border Name="PART_Indicator"
BorderThickness="1"
Background="{TemplateBinding Foreground}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="ProgressBar">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Border Name="PART_Track"
BorderThickness="1"
BorderBrush="{TemplateBinding Background}"/>
<Border Name="PART_Indicator"
BorderThickness="1"
Background="{TemplateBinding Foreground}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ProgressBar:horizontal /template/ Border#PART_Indicator">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
</Style>
<Style Selector="ProgressBar:vertical /template/ Border#PART_Indicator">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style Selector="ProgressBar:horizontal">
<Setter Property="MinWidth" Value="200"/>
<Setter Property="MinHeight" Value="14"/>
</Style>
<Style Selector="ProgressBar:vertical">
<Setter Property="MinWidth" Value="14"/>
<Setter Property="MinHeight" Value="200"/>
</Style>
</Styles>

5
src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs

@ -69,6 +69,11 @@ namespace Avalonia.Rendering.SceneGraph
/// </summary>
IReadOnlyList<IRef<IDrawOperation>> DrawOperations { get; }
/// <summary>
/// Gets the opacity of the scene graph node.
/// </summary>
double Opacity { get; }
/// <summary>
/// Sets up the drawing context for rendering the node's geometry.
/// </summary>

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

@ -170,8 +170,9 @@ namespace Avalonia.Rendering.SceneGraph
var clipBounds = bounds.TransformToAABB(contextImpl.Transform).Intersect(clip);
forceRecurse = forceRecurse ||
node.Transform != contextImpl.Transform ||
node.ClipBounds != clipBounds;
node.ClipBounds != clipBounds ||
node.Opacity != opacity ||
node.Transform != contextImpl.Transform;
node.Transform = contextImpl.Transform;
node.ClipBounds = clipBounds;

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

@ -67,9 +67,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <inheritdoc/>
public bool HasAncestorGeometryClip { get; }
/// <summary>
/// Gets or sets the opacity of the scene graph node.
/// </summary>
/// <inheritdoc/>
public double Opacity
{
get { return _opacity; }

5
src/Gtk/Avalonia.Gtk3/FramebufferManager.cs

@ -26,8 +26,9 @@ namespace Avalonia.Gtk3
{
// This method may be called from non-UI thread, don't touch anything that calls back to GTK/GDK
var s = _window.ClientSize;
var width = (int) s.Width;
var height = (int) s.Height;
var width = Math.Max(1, (int) s.Width);
var height = Math.Max(1, (int) s.Height);
if (!Dispatcher.UIThread.CheckAccess() && Gtk3Platform.DisplayClassName.ToLower().Contains("x11"))
{

6
src/Gtk/Avalonia.Gtk3/Interop/GObject.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
@ -21,7 +22,12 @@ namespace Avalonia.Gtk3.Interop
protected override bool ReleaseHandle()
{
if (handle != IntPtr.Zero)
{
Debug.Assert(Native.GTypeCheckInstanceIsFundamentallyA(handle, new IntPtr(Native.G_TYPE_OBJECT)),
"Handle is not a GObject");
Native.GObjectUnref(handle);
}
handle = IntPtr.Zero;
return true;
}

15
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@ -219,7 +219,10 @@ namespace Avalonia.Gtk3.Interop
public delegate void gtk_widget_queue_draw_area(GtkWidget widget, int x, int y, int width, int height);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_widget_add_tick_callback(GtkWidget widget, TickCallback callback, IntPtr userData, IntPtr destroy);
public delegate uint gtk_widget_add_tick_callback(GtkWidget widget, TickCallback callback, IntPtr userData, IntPtr destroy);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate uint gtk_widget_remove_tick_callback(GtkWidget widget, uint id);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate GtkImContext gtk_im_multicontext_new();
@ -256,6 +259,9 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_unmaximize(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_close(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask);
@ -341,6 +347,9 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)]
public delegate ulong g_free(IntPtr data);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
public delegate bool g_type_check_instance_is_fundamentally_a(IntPtr instance, IntPtr type);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)]
public unsafe delegate void g_slist_free(GSList* data);
@ -427,6 +436,7 @@ namespace Avalonia.Gtk3.Interop
public static D.g_timeout_add GTimeoutAdd;
public static D.g_timeout_add_full GTimeoutAddFull;
public static D.g_free GFree;
public static D.g_type_check_instance_is_fundamentally_a GTypeCheckInstanceIsFundamentallyA;
public static D.g_slist_free GSlistFree;
public static D.g_memory_input_stream_new_from_data GMemoryInputStreamNewFromData;
public static D.gtk_widget_set_double_buffered GtkWidgetSetDoubleBuffered;
@ -434,6 +444,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gdk_window_invalidate_rect GdkWindowInvalidateRect;
public static D.gtk_widget_queue_draw_area GtkWidgetQueueDrawArea;
public static D.gtk_widget_add_tick_callback GtkWidgetAddTickCallback;
public static D.gtk_widget_remove_tick_callback GtkWidgetRemoveTickCallback;
public static D.gtk_widget_activate GtkWidgetActivate;
public static D.gtk_clipboard_get_for_display GtkClipboardGetForDisplay;
public static D.gtk_clipboard_request_text GtkClipboardRequestText;
@ -456,6 +467,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_window_deiconify GtkWindowDeiconify;
public static D.gtk_window_maximize GtkWindowMaximize;
public static D.gtk_window_unmaximize GtkWindowUnmaximize;
public static D.gtk_window_close GtkWindowClose;
public static D.gdk_window_begin_move_drag GdkWindowBeginMoveDrag;
public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag;
public static D.gdk_event_request_motions GdkEventRequestMotions;
@ -490,6 +502,7 @@ namespace Avalonia.Gtk3.Interop
public static D.cairo_set_font_size CairoSetFontSize;
public static D.cairo_move_to CairoMoveTo;
public static D.cairo_destroy CairoDestroy;
public const int G_TYPE_OBJECT = 80;
}
public enum GtkWindowType

22
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -33,11 +33,11 @@ namespace Avalonia.Gtk3
private readonly AutoResetEvent _canSetNextOperation = new AutoResetEvent(true);
internal IntPtr? GdkWindowHandle;
private bool _overrideRedirect;
private uint? _tickCallback;
public WindowBaseImpl(GtkWindow gtkWidget)
{
GtkWidget = gtkWidget;
Disposables.Add(gtkWidget);
_framebuffer = new FramebufferManager(this);
_imContext = Native.GtkImMulticontextNew();
Disposables.Add(_imContext);
@ -62,7 +62,7 @@ namespace Avalonia.Gtk3
{
Native.GtkWidgetSetDoubleBuffered(gtkWidget, false);
_gcHandle = GCHandle.Alloc(this);
Native.GtkWidgetAddTickCallback(GtkWidget, PinnedStaticCallback, GCHandle.ToIntPtr(_gcHandle), IntPtr.Zero);
_tickCallback = Native.GtkWidgetAddTickCallback(GtkWidget, PinnedStaticCallback, GCHandle.ToIntPtr(_gcHandle), IntPtr.Zero);
}
}
@ -103,7 +103,7 @@ namespace Avalonia.Gtk3
private bool OnDestroy(IntPtr gtkwidget, IntPtr userdata)
{
Dispose();
DoDispose(true);
return false;
}
@ -297,14 +297,28 @@ namespace Avalonia.Gtk3
}
public void Dispose()
public void Dispose() => DoDispose(false);
void DoDispose(bool fromDestroy)
{
if (_tickCallback.HasValue)
{
if (!GtkWidget.IsClosed)
Native.GtkWidgetRemoveTickCallback(GtkWidget, _tickCallback.Value);
_tickCallback = null;
}
//We are calling it here, since signal handler will be detached
if (!GtkWidget.IsClosed)
Closed?.Invoke();
foreach(var d in Disposables.AsEnumerable().Reverse())
d.Dispose();
Disposables.Clear();
if (!fromDestroy && !GtkWidget.IsClosed)
Native.GtkWindowClose(GtkWidget);
GtkWidget.Dispose();
if (_gcHandle.IsAllocated)
{
_gcHandle.Free();

10
src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs

@ -12,6 +12,7 @@ namespace Avalonia.MonoMac
private readonly TopLevelImpl.TopLevelView _view;
private readonly CGSize _logicalSize;
private readonly bool _isDeferred;
private readonly IUnmanagedBlob _blob;
[DllImport("libc")]
static extern void memset(IntPtr p, int c, IntPtr size);
@ -29,13 +30,13 @@ namespace Avalonia.MonoMac
Dpi = new Vector(96 * pixelSize.Width / _logicalSize.Width, 96 * pixelSize.Height / _logicalSize.Height);
Format = PixelFormat.Rgba8888;
var size = Height * RowBytes;
Address = Marshal.AllocHGlobal(size);
_blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(size);
memset(Address, 0, new IntPtr(size));
}
public void Dispose()
{
if (Address == IntPtr.Zero)
if (_blob.IsDisposed)
return;
var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast;
CGImage image = null;
@ -71,14 +72,13 @@ namespace Avalonia.MonoMac
else
_view.SetBackBufferImage(new SavedImage(image, _logicalSize));
}
Marshal.FreeHGlobal(Address);
Address = IntPtr.Zero;
_blob.Dispose();
}
}
public IntPtr Address { get; private set; }
public IntPtr Address => _blob.Address;
public int Width { get; }
public int Height { get; }
public int RowBytes { get; }

33
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@ -28,11 +28,13 @@ namespace Avalonia.Shared.PlatformSupport
class UnmanagedBlob : IUnmanagedBlob
{
private readonly StandardRuntimePlatform _plat;
private IntPtr _address;
private readonly object _lock = new object();
#if DEBUG
private static readonly List<string> Backtraces = new List<string>();
private static Thread GCThread;
private readonly string _backtrace;
private static readonly object _btlock = new object();
class GCThreadDetector
{
@ -55,28 +57,35 @@ namespace Avalonia.Shared.PlatformSupport
public UnmanagedBlob(StandardRuntimePlatform plat, int size)
{
if (size <= 0)
throw new ArgumentException("Positive number required", nameof(size));
_plat = plat;
Address = plat.Alloc(size);
_address = plat.Alloc(size);
GC.AddMemoryPressure(size);
Size = size;
#if DEBUG
_backtrace = Environment.StackTrace;
Backtraces.Add(_backtrace);
lock (_btlock)
Backtraces.Add(_backtrace);
#endif
}
void DoDispose()
{
if (!IsDisposed)
lock (_lock)
{
if (!IsDisposed)
{
#if DEBUG
Backtraces.Remove(_backtrace);
lock (_btlock)
Backtraces.Remove(_backtrace);
#endif
_plat.Free(Address, Size);
GC.RemoveMemoryPressure(Size);
IsDisposed = true;
Address = IntPtr.Zero;
Size = 0;
_plat.Free(_address, Size);
GC.RemoveMemoryPressure(Size);
IsDisposed = true;
_address = IntPtr.Zero;
Size = 0;
}
}
}
@ -102,7 +111,7 @@ namespace Avalonia.Shared.PlatformSupport
DoDispose();
}
public IntPtr Address { get; private set; }
public IntPtr Address => IsDisposed ? throw new ObjectDisposedException("UnmanagedBlob") : _address;
public int Size { get; private set; }
public bool IsDisposed { get; private set; }
}
@ -155,4 +164,4 @@ namespace Avalonia.Shared.PlatformSupport
void Free(IntPtr ptr, int len) => Marshal.FreeHGlobal(ptr);
#endif
}
}
}

22
src/Windows/Avalonia.Win32/WindowFramebuffer.cs

@ -10,7 +10,7 @@ namespace Avalonia.Win32
public class WindowFramebuffer : ILockedFramebuffer
{
private readonly IntPtr _handle;
private IntPtr _pBitmap;
private IUnmanagedBlob _bitmapBlob;
private UnmanagedMethods.BITMAPINFOHEADER _bmpInfo;
public WindowFramebuffer(IntPtr handle, int width, int height)
@ -27,7 +27,7 @@ namespace Avalonia.Win32
_bmpInfo.Init();
_bmpInfo.biWidth = width;
_bmpInfo.biHeight = -height;
_pBitmap = Marshal.AllocHGlobal(width * height * 4);
_bitmapBlob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(width * height * 4);
}
~WindowFramebuffer()
@ -35,7 +35,7 @@ namespace Avalonia.Win32
Deallocate();
}
public IntPtr Address => _pBitmap;
public IntPtr Address => _bitmapBlob.Address;
public int RowBytes => Width * 4;
public PixelFormat Format => PixelFormat.Bgra8888;
@ -70,21 +70,18 @@ namespace Avalonia.Win32
public void DrawToDevice(IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1,
int height = -1)
{
if(_pBitmap == IntPtr.Zero)
throw new ObjectDisposedException("Framebuffer");
if (width == -1)
width = Width;
if (height == -1)
height = Height;
UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint) width, (uint) height, srcX, srcY,
0, (uint)Height, _pBitmap, ref _bmpInfo, 0);
0, (uint)Height, _bitmapBlob.Address, ref _bmpInfo, 0);
}
public bool DrawToWindow(IntPtr hWnd, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1,
int height = -1)
{
if (_pBitmap == IntPtr.Zero)
if (_bitmapBlob.IsDisposed)
throw new ObjectDisposedException("Framebuffer");
if (hWnd == IntPtr.Zero)
return false;
@ -102,13 +99,6 @@ namespace Avalonia.Win32
DrawToWindow(_handle);
}
public void Deallocate()
{
if (_pBitmap != IntPtr.Zero)
{
Marshal.FreeHGlobal(_pBitmap);
_pBitmap = IntPtr.Zero;
}
}
public void Deallocate() => _bitmapBlob.Dispose();
}
}

5
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs

@ -185,10 +185,5 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(new Rect(84, 0, 16, 16), content.Bounds);
}
[Fact]
public void Padding_Is_Applied_To_TopLeft_Aligned_Content()
{
}
}
}

20
tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs

@ -223,6 +223,26 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(100, child.Bounds.Width);
}
[Fact]
public void Extent_Should_Include_Content_Margin()
{
var target = new ScrollContentPresenter
{
Content = new Border
{
Width = 100,
Height = 100,
Margin = new Thickness(5),
}
};
target.UpdateChild();
target.Measure(new Size(50, 50));
target.Arrange(new Rect(0, 0, 50, 50));
Assert.Equal(new Size(110, 110), target.Extent);
}
[Fact]
public void Extent_Width_Should_Be_Arrange_Width_When_CanScrollHorizontally_False()
{

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

@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests;
@ -231,11 +232,23 @@ namespace Avalonia.Controls.UnitTests
}
}
private void ClearOpenWindows()
[Fact]
public async Task ShowDialog_With_ValueType_Returns_Default_When_Closed()
{
// HACK: We really need a decent way to have "statics" that can be scoped to
// AvaloniaLocator scopes.
((IList<Window>)Window.OpenWindows).Clear();
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.Scaling).Returns(1);
var target = new Window(windowImpl.Object);
var task = target.ShowDialog<bool>();
windowImpl.Object.Closed();
var result = await task;
Assert.False(result);
}
}
[Fact]
@ -321,5 +334,12 @@ namespace Avalonia.Controls.UnitTests
x.Scaling == 1 &&
x.CreateRenderer(It.IsAny<IRenderRoot>()) == renderer.Object);
}
private void ClearOpenWindows()
{
// HACK: We really need a decent way to have "statics" that can be scoped to
// AvaloniaLocator scopes.
((IList<Window>)Window.OpenWindows).Clear();
}
}
}

16
tests/Avalonia.Layout.UnitTests/ArrangeTests.cs

@ -8,6 +8,22 @@ namespace Avalonia.Layout.UnitTests
{
public class ArrangeTests
{
[Fact]
public void Bounds_Should_Not_Include_Margin()
{
var target = new Decorator
{
Width = 100,
Height = 100,
Margin = new Thickness(5),
};
Assert.False(target.IsMeasureValid);
target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize));
Assert.Equal(new Rect(5, 5, 100, 100), target.Bounds);
}
[Fact]
public void Margin_Should_Be_Subtracted_From_Arrange_FinalSize()
{

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

@ -656,6 +656,43 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}
}
[Fact]
public void Setting_Opacity_Should_Add_Descendent_Bounds_To_DirtyRects()
{
using (TestApplication())
{
Decorator decorator;
Border border;
var tree = new TestRoot
{
Child = decorator = new Decorator
{
Child = border = new Border
{
Background = Brushes.Red,
Width = 100,
Height = 100,
}
}
};
tree.Measure(Size.Infinity);
tree.Arrange(new Rect(tree.DesiredSize));
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
decorator.Opacity = 0.5;
scene = scene.CloneScene();
sceneBuilder.Update(scene, decorator);
Assert.NotEmpty(scene.Layers.Single().Dirty);
var dirty = scene.Layers.Single().Dirty.Single();
Assert.Equal(new Rect(0, 0, 100, 100), dirty);
}
}
[Fact]
public void Should_Set_GeometryClip()
{

Loading…
Cancel
Save