Browse Source

Merge pull request #4193 from AvaloniaUI/foreign-embed-createahead

Create and resize the native control even if it's not currently effectively visible
stable/outsystems-0.9
danwalmsley 6 years ago
committed by Dan Walmsley
parent
commit
ce2ea77143
  1. 4
      native/Avalonia.Native/inc/avalonia-native.h
  2. 21
      native/Avalonia.Native/src/OSX/controlhost.mm
  3. 89
      src/Avalonia.Controls/NativeControlHost.cs
  4. 4
      src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
  5. 9
      src/Avalonia.Native/NativeControlHostImpl.cs
  6. 15
      src/Avalonia.Visuals/Rect.cs
  7. 22
      src/Avalonia.X11/X11NativeControlHost.cs
  8. 12
      src/Windows/Avalonia.Win32/Win32NativeControlHost.cs

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

@ -465,8 +465,8 @@ AVNCOM(IAvnNativeControlHostTopLevelAttachment, 1F) : IUnknown
virtual void* GetParentHandle() = 0;
virtual HRESULT InitializeWithChildHandle(void* child) = 0;
virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0;
virtual void MoveTo(float x, float y, float width, float height) = 0;
virtual void Hide() = 0;
virtual void ShowInBounds(float x, float y, float width, float height) = 0;
virtual void HideWithSize(float width, float height) = 0;
virtual void ReleaseChild() = 0;
};

21
native/Avalonia.Native/src/OSX/controlhost.mm

@ -97,7 +97,7 @@ public:
return S_OK;
};
virtual void MoveTo(float x, float y, float width, float height) override
virtual void ShowInBounds(float x, float y, float width, float height) override
{
if(_child == nil)
return;
@ -106,7 +106,7 @@ public:
IAvnNativeControlHostTopLevelAttachment* slf = this;
slf->AddRef();
dispatch_async(dispatch_get_main_queue(), ^{
slf->MoveTo(x, y, width, height);
slf->ShowInBounds(x, y, width, height);
slf->Release();
});
return;
@ -122,9 +122,24 @@ public:
[[_holder superview] setNeedsDisplay:true];
}
virtual void Hide() override
virtual void HideWithSize(float width, float height) override
{
if(_child == nil)
return;
if(AvnInsidePotentialDeadlock::IsInside())
{
IAvnNativeControlHostTopLevelAttachment* slf = this;
slf->AddRef();
dispatch_async(dispatch_get_main_queue(), ^{
slf->HideWithSize(width, height);
slf->Release();
});
return;
}
NSRect frame = {0, 0, width, height};
[_holder setHidden: true];
[_child setFrame: frame];
}
virtual void ReleaseChild() override

89
src/Avalonia.Controls/NativeControlHost.cs

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls.Platform;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -12,14 +14,18 @@ namespace Avalonia.Controls
private INativeControlHostControlTopLevelAttachment _attachment;
private IPlatformHandle _nativeControlHandle;
private bool _queuedForDestruction;
private bool _queuedForMoveResize;
private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>();
private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler;
static NativeControlHost()
{
IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged);
TransformedBoundsProperty.Changed.AddClassHandler<NativeControlHost>(OnBoundsChanged);
}
private static void OnBoundsChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
=> host.UpdateHost();
public NativeControlHost()
{
_propertyChangedHandler = PropertyChangedHandler;
}
private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
=> host.UpdateHost();
@ -27,21 +33,46 @@ namespace Avalonia.Controls
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_currentRoot = e.Root as TopLevel;
var visual = (IVisual)this;
while (visual != _currentRoot)
{
if (visual is Visual v)
{
v.PropertyChanged += _propertyChangedHandler;
_propertyChangedSubscriptions.Add(v);
}
visual = visual.GetVisualParent();
}
UpdateHost();
}
private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.IsEffectiveValueChange && e.Property == BoundsProperty)
EnqueueForMoveResize();
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_currentRoot = null;
if (_propertyChangedSubscriptions != null)
{
foreach (var v in _propertyChangedSubscriptions)
v.PropertyChanged -= _propertyChangedHandler;
_propertyChangedSubscriptions.Clear();
}
UpdateHost();
}
void UpdateHost()
private void UpdateHost()
{
_queuedForMoveResize = false;
_currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost;
var needsAttachment = _currentHost != null;
var needsShow = needsAttachment && IsEffectivelyVisible && TransformedBounds.HasValue;
if (needsAttachment)
{
@ -93,22 +124,46 @@ namespace Avalonia.Controls
}
}
if (needsShow)
_attachment?.ShowInBounds(TransformedBounds.Value);
else if (needsAttachment)
_attachment?.Hide();
if (_attachment?.AttachedTo != _currentHost)
return;
TryUpdateNativeControlPosition();
}
private Rect? GetAbsoluteBounds()
{
var bounds = Bounds;
var position = this.TranslatePoint(bounds.Position, _currentRoot);
if (position == null)
return null;
return new Rect(position.Value, bounds.Size);
}
void EnqueueForMoveResize()
{
if(_queuedForMoveResize)
return;
_queuedForMoveResize = true;
Dispatcher.UIThread.Post(UpdateHost, DispatcherPriority.Render);
}
public bool TryUpdateNativeControlPosition()
{
var needsShow = _currentHost != null && IsEffectivelyVisible && TransformedBounds.HasValue;
if (_currentHost == null)
return false;
var bounds = GetAbsoluteBounds();
var needsShow = IsEffectivelyVisible && bounds.HasValue;
if(needsShow)
_attachment?.ShowInBounds(TransformedBounds.Value);
return needsShow;
if (needsShow)
_attachment?.ShowInBounds(bounds.Value);
else
_attachment?.HideWithSize(Bounds.Size);
return false;
}
void CheckDestruction()
private void CheckDestruction()
{
_queuedForDestruction = false;
if (_currentRoot == null)
@ -117,10 +172,12 @@ namespace Avalonia.Controls
protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
if (_currentHost == null)
throw new InvalidOperationException();
return _currentHost.CreateDefaultChild(parent);
}
void DestroyNativeControl()
private void DestroyNativeControl()
{
if (_nativeControlHandle != null)
{

4
src/Avalonia.Controls/Platform/INativeControlHostImpl.cs

@ -21,8 +21,8 @@ namespace Avalonia.Controls.Platform
{
INativeControlHostImpl AttachedTo { get; set; }
bool IsCompatibleWith(INativeControlHostImpl host);
void Hide();
void ShowInBounds(TransformedBounds transformedBounds);
void HideWithSize(Size size);
void ShowInBounds(Rect rect);
}
public interface ITopLevelImplWithNativeControlHost

9
src/Avalonia.Native/NativeControlHostImpl.cs

@ -114,19 +114,18 @@ namespace Avalonia.Native
public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl;
public void Hide()
public void HideWithSize(Size size)
{
_native?.Hide();
_native.HideWithSize(Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height));
}
public void ShowInBounds(TransformedBounds transformedBounds)
public void ShowInBounds(Rect bounds)
{
if (_attachedTo == null)
throw new InvalidOperationException("Native control isn't attached to a toplevel");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform);
bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width),
Math.Max(1, bounds.Height));
_native.MoveTo((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height);
_native.ShowInBounds((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height);
}
public void InitWithChild(IPlatformHandle handle)

15
src/Avalonia.Visuals/Rect.cs

@ -204,6 +204,21 @@ namespace Avalonia
rect.Width * scale.X,
rect.Height * scale.Y);
}
/// <summary>
/// Multiplies a rectangle by a scale.
/// </summary>
/// <param name="rect">The rectangle.</param>
/// <param name="scale">The scale.</param>
/// <returns>The scaled rectangle.</returns>
public static Rect operator *(Rect rect, double scale)
{
return new Rect(
rect.X * scale,
rect.Y * scale,
rect.Width * scale,
rect.Height * scale);
}
/// <summary>
/// Divides a rectangle by a vector.

22
src/Avalonia.X11/X11NativeControlHost.cs

@ -157,21 +157,30 @@ namespace Avalonia.X11
public bool IsCompatibleWith(INativeControlHostImpl host) => host is X11NativeControlHost;
public void Hide()
public void HideWithSize(Size size)
{
if(_attachedTo == null || _child == null)
return;
_mapped = false;
XUnmapWindow(_display, _holder.Handle);
if (_mapped)
{
_mapped = false;
XUnmapWindow(_display, _holder.Handle);
}
size *= _attachedTo.Window.Scaling;
XResizeWindow(_display, _child.Handle,
Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height));
}
public void ShowInBounds(TransformedBounds transformedBounds)
public void ShowInBounds(Rect bounds)
{
CheckDisposed();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) *
new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
bounds *= _attachedTo.Window.Scaling;
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
Math.Max(1, (int)bounds.Height));
XMoveResizeWindow(_display, _child.Handle, 0, 0, pixelRect.Width, pixelRect.Height);
@ -183,7 +192,6 @@ namespace Avalonia.X11
XRaiseWindow(_display, _holder.Handle);
_mapped = true;
}
Console.WriteLine($"Moved {_child.Handle} to {pixelRect}");
}
}
}

12
src/Windows/Avalonia.Win32/Win32NativeControlHost.cs

@ -168,21 +168,25 @@ namespace Avalonia.Win32
public bool IsCompatibleWith(INativeControlHostImpl host) => host is Win32NativeControlHost;
public void Hide()
public void HideWithSize(Size size)
{
UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero,
-100, -100, 1, 1,
UnmanagedMethods.SetWindowPosFlags.SWP_HIDEWINDOW |
UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE);
if (_attachedTo == null || _child == null)
return;
size *= _attachedTo.Window.Scaling;
UnmanagedMethods.MoveWindow(_child.Handle, 0, 0,
Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height), false);
}
public unsafe void ShowInBounds(TransformedBounds transformedBounds)
public unsafe void ShowInBounds(Rect bounds)
{
CheckDisposed();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) *
new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
bounds *= _attachedTo.Window.Scaling;
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
Math.Max(1, (int)bounds.Height));

Loading…
Cancel
Save