Browse Source

Merge branch 'master' into fixes/1408-scrollcontentpresenter-margin

pull/1414/head
danwalmsley 8 years ago
committed by GitHub
parent
commit
c6db41d1af
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      build/System.Drawing.Common.props
  2. 4
      packages.cake
  3. 2
      readme.md
  4. 26
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  5. 127
      src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
  6. 12
      src/Avalonia.Controls/ItemsControl.cs
  7. 6
      src/Avalonia.Controls/LayoutTransformControl.cs
  8. 26
      src/Avalonia.Controls/ProgressBar.cs
  9. 2
      src/Avalonia.Controls/Screens.cs
  10. 4
      src/Avalonia.Controls/UserControl.cs
  11. 42
      src/Avalonia.Controls/Window.cs
  12. 19
      src/Avalonia.Controls/WindowBase.cs
  13. 23
      src/Avalonia.Controls/WindowStartupLocation.cs
  14. 3
      src/Avalonia.DotNetCoreRuntime/AppBuilder.cs
  15. 58
      src/Avalonia.Themes.Default/ProgressBar.xaml
  16. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
  17. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  18. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  19. 5
      src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
  20. 6
      src/Gtk/Avalonia.Gtk3/Interop/GObject.cs
  21. 15
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  22. 22
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  23. 3
      src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj
  24. 10
      src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs
  25. 33
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  26. 6
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  27. 4
      src/Windows/Avalonia.Win32/ScreenImpl.cs
  28. 22
      src/Windows/Avalonia.Win32/WindowFramebuffer.cs
  29. 111
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  30. 37
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

5
build/System.Drawing.Common.props

@ -0,0 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="4.5.0-preview1-25914-04" />
</ItemGroup>
</Project>

4
packages.cake

@ -370,10 +370,10 @@ public class Packages
new NuGetPackSettings()
{
Id = "Avalonia.Win32",
Dependencies = new []
Dependencies = new DependencyBuilder(this)
{
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
},
}.Deps(new string[]{null}, "System.Drawing.Common"),
Files = new []
{
new NuSpecContent { Source = "Avalonia.Win32/bin/" + parameters.DirSuffix + "/netstandard2.0/Avalonia.Win32.dll", Target = "lib/netstandard2.0" }

2
readme.md

@ -51,7 +51,7 @@ Please read the [contribution guidelines](http://avaloniaui.net/contributing/con
### Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing/contributing)].
<a href="graphs/contributors"><img src="https://opencollective.com/Avalonia/contributors.svg?width=890&button=false" /></a>

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

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)

2
src/Avalonia.Controls/Screens.cs

@ -39,7 +39,7 @@ namespace Avalonia.Controls
return currMaxScreen;
}
public Screen SceenFromPoint(Point point)
public Screen ScreenFromPoint(Point point)
{
return All.FirstOrDefault(x=>x.Bounds.Contains(point));
}

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

42
src/Avalonia.Controls/Window.cs

@ -85,9 +85,19 @@ namespace Avalonia.Controls
public static readonly StyledProperty<WindowIcon> IconProperty =
AvaloniaProperty.Register<Window, WindowIcon>(nameof(Icon));
/// <summary>
/// Defines the <see cref="WindowStartupLocation"/> proeprty.
/// </summary>
public static readonly DirectProperty<Window, WindowStartupLocation> WindowStartupLocationProperty =
AvaloniaProperty.RegisterDirect<Window, WindowStartupLocation>(
nameof(WindowStartupLocation),
o => o.WindowStartupLocation,
(o, v) => o.WindowStartupLocation = v);
private readonly NameScope _nameScope = new NameScope();
private object _dialogResult;
private readonly Size _maxPlatformClientSize;
private WindowStartupLocation _windowStartupLoction;
/// <summary>
/// Initializes static members of the <see cref="Window"/> class.
@ -205,6 +215,15 @@ namespace Avalonia.Controls
set { SetValue(IconProperty, value); }
}
/// <summary>
/// Gets or sets the startup location of the window.
/// </summary>
public WindowStartupLocation WindowStartupLocation
{
get { return _windowStartupLoction; }
set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLoction, value); }
}
/// <inheritdoc/>
Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize;
@ -274,6 +293,7 @@ namespace Avalonia.Controls
s_windows.Add(this);
EnsureInitialized();
SetWindowStartupLocation();
IsVisible = true;
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
@ -314,6 +334,7 @@ namespace Avalonia.Controls
s_windows.Add(this);
EnsureInitialized();
SetWindowStartupLocation();
IsVisible = true;
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
@ -337,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;
@ -352,6 +373,25 @@ namespace Avalonia.Controls
}
}
void SetWindowStartupLocation()
{
if (WindowStartupLocation == WindowStartupLocation.CenterScreen)
{
var screen = Screens.ScreenFromPoint(Bounds.Position);
if (screen != null)
Position = screen.WorkingArea.CenterRect(new Rect(ClientSize)).Position;
}
else if (WindowStartupLocation == WindowStartupLocation.CenterOwner)
{
if (Owner != null)
{
var positionAsSize = Owner.ClientSize / 2 - ClientSize / 2;
Position = Owner.Position + new Point(positionAsSize.Width, positionAsSize.Height);
}
}
}
/// <inheritdoc/>
void INameScope.Register(string name, object element)
{

19
src/Avalonia.Controls/WindowBase.cs

@ -29,9 +29,19 @@ namespace Avalonia.Controls
public static readonly DirectProperty<WindowBase, bool> IsActiveProperty =
AvaloniaProperty.RegisterDirect<WindowBase, bool>(nameof(IsActive), o => o.IsActive);
/// <summary>
/// Defines the <see cref="Owner"/> property.
/// </summary>
public static readonly DirectProperty<WindowBase, WindowBase> OwnerProperty =
AvaloniaProperty.RegisterDirect<WindowBase, WindowBase>(
nameof(Owner),
o => o.Owner,
(o, v) => o.Owner = v);
private bool _hasExecutedInitialLayoutPass;
private bool _isActive;
private bool _ignoreVisibilityChange;
private WindowBase _owner;
static WindowBase()
{
@ -100,6 +110,15 @@ namespace Avalonia.Controls
private set;
}
/// <summary>
/// Gets or sets the owner of the window.
/// </summary>
public WindowBase Owner
{
get { return _owner; }
set { SetAndRaise(OwnerProperty, ref _owner, value); }
}
/// <summary>
/// Activates the window.
/// </summary>

23
src/Avalonia.Controls/WindowStartupLocation.cs

@ -0,0 +1,23 @@
namespace Avalonia.Controls
{
/// <summary>
/// Determines the startup location of the window.
/// </summary>
public enum WindowStartupLocation
{
/// <summary>
/// The startup location is defined by the Position property.
/// </summary>
Manual,
/// <summary>
/// The startup location is the center of the screen.
/// </summary>
CenterScreen,
/// <summary>
/// The startup location is the center of the owner window. If the owner window is not specified, the startup location will be <see cref="Manual"/>.
/// </summary>
CenterOwner
}
}

3
src/Avalonia.DotNetCoreRuntime/AppBuilder.cs

@ -10,6 +10,9 @@ using Avalonia.Shared.PlatformSupport;
namespace Avalonia
{
/// <summary>
/// Initializes platform-specific services for an <see cref="Application"/>.
/// </summary>
public sealed class AppBuilder : AppBuilderBase<AppBuilder>
{
/// <summary>

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

3
src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj

@ -8,9 +8,6 @@
<Compile Include="..\..\Shared\SharedAssemblyInfo.cs" Link="SharedAssemblyInfo.cs" />
<Compile Include="..\..\Shared\WindowResizeDragHelper.cs" Link="WindowResizeDragHelper.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MonoMac.NetStandard" Version="0.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />

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

6
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -13,7 +13,5 @@
<ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="4.5.0-preview1-25914-04" />
</ItemGroup>
</Project>
<Import Project="$(MSBuildThisFileDirectory)\..\..\..\build\System.Drawing.Common.props" />
</Project>

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

@ -41,8 +41,8 @@ namespace Avalonia.Win32
Rect avaloniaBounds = new Rect(bounds.left, bounds.top, bounds.right - bounds.left,
bounds.bottom - bounds.top);
Rect avaloniaWorkArea =
new Rect(workingArea.left, workingArea.top, workingArea.right - bounds.left,
workingArea.bottom - bounds.top);
new Rect(workingArea.left, workingArea.top, workingArea.right - workingArea.left,
workingArea.bottom - workingArea.top);
screens[index] =
new WinScreen(avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1,
monitor);

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

111
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;
@ -70,7 +71,7 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
public void IsVisible_Should_Be_False_Atfer_Hide()
public void IsVisible_Should_Be_False_After_Hide()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
@ -84,7 +85,7 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
public void IsVisible_Should_Be_False_Atfer_Close()
public void IsVisible_Should_Be_False_After_Close()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
@ -98,7 +99,7 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
public void IsVisible_Should_Be_False_Atfer_Impl_Signals_Close()
public void IsVisible_Should_Be_False_After_Impl_Signals_Close()
{
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed);
@ -231,11 +232,100 @@ 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]
public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen()
{
var screen1 = new Mock<Screen>(new Rect(new Size(1920, 1080)), new Rect(new Size(1920, 1040)), true);
var screen2 = new Mock<Screen>(new Rect(new Size(1366, 768)), new Rect(new Size(1366, 728)), false);
var screens = new Mock<IScreenImpl>();
screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object });
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Position);
windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480));
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.Screen).Returns(screens.Object);
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window(windowImpl.Object);
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
window.Position = new Point(60, 40);
window.Show();
var expectedPosition = new Point(
screen1.Object.WorkingArea.Size.Width / 2 - window.ClientSize.Width / 2,
screen1.Object.WorkingArea.Size.Height / 2 - window.ClientSize.Height / 2);
Assert.Equal(window.Position, expectedPosition);
}
}
[Fact]
public void Window_Should_Be_Centered_Relative_To_Owner_When_WindowStartupLocation_Is_CenterOwner()
{
var parentWindowImpl = new Mock<IWindowImpl>();
parentWindowImpl.SetupProperty(x => x.Position);
parentWindowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480));
parentWindowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1920, 1080));
parentWindowImpl.Setup(x => x.Scaling).Returns(1);
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Position);
windowImpl.Setup(x => x.ClientSize).Returns(new Size(320, 200));
windowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1920, 1080));
windowImpl.Setup(x => x.Scaling).Returns(1);
var parentWindowServices = TestServices.StyledWindow.With(
windowingPlatform: new MockWindowingPlatform(() => parentWindowImpl.Object));
var windowServices = TestServices.StyledWindow.With(
windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object));
using (UnitTestApplication.Start(parentWindowServices))
{
var parentWindow = new Window();
parentWindow.Position = new Point(60, 40);
parentWindow.Show();
using (UnitTestApplication.Start(windowServices))
{
var window = new Window();
window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
window.Position = new Point(60, 40);
window.Owner = parentWindow;
window.Show();
var expectedPosition = new Point(
parentWindow.Position.X + parentWindow.ClientSize.Width / 2 - window.ClientSize.Width / 2,
parentWindow.Position.Y + parentWindow.ClientSize.Height / 2 - window.ClientSize.Height / 2);
Assert.Equal(window.Position, expectedPosition);
}
}
}
private IWindowImpl CreateImpl(Mock<IRenderer> renderer)
@ -244,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();
}
}
}

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