Browse Source
Conflicts: src/Gtk/Avalonia.Gtk/TopLevelImpl.cs tests/Avalonia.Controls.UnitTests/TopLevelTests.csscenegraph-after-breakage
41 changed files with 595 additions and 583 deletions
@ -0,0 +1,69 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Platform |
||||
|
{ |
||||
|
public interface IWindowBaseImpl : ITopLevelImpl |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Shows the toplevel.
|
||||
|
/// </summary>
|
||||
|
void Show(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Hides the window.
|
||||
|
/// </summary>
|
||||
|
void Hide(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
|
||||
|
/// </summary>
|
||||
|
void BeginMoveDrag(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Starts resizing a window. This function is used if an application has window resizing controls.
|
||||
|
/// Should be called from left mouse button press event handler
|
||||
|
/// </summary>
|
||||
|
void BeginResizeDrag(WindowEdge edge); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets position of the window relatively to the screen
|
||||
|
/// </summary>
|
||||
|
Point Position { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a method called when the window's position changes.
|
||||
|
/// </summary>
|
||||
|
Action<Point> PositionChanged { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Activates the window.
|
||||
|
/// </summary>
|
||||
|
void Activate(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a method called when the window is deactivated (loses focus).
|
||||
|
/// </summary>
|
||||
|
Action Deactivated { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a method called when the window is activated (receives focus).
|
||||
|
/// </summary>
|
||||
|
Action Activated { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the platform window handle.
|
||||
|
/// </summary>
|
||||
|
IPlatformHandle Handle { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the maximum size of a window on the system.
|
||||
|
/// </summary>
|
||||
|
Size MaxClientSize { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the client size of the toplevel.
|
||||
|
/// </summary>
|
||||
|
void Resize(Size clientSize); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,194 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Reactive.Disposables; |
||||
|
using System.Reactive.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Layout; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Base class for top-level windows.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// This class acts as a base for top level windows such as <see cref="Window"/> and
|
||||
|
/// <see cref="PopupRoot"/>. It handles scheduling layout, styling and rendering as well as
|
||||
|
/// tracking the window <see cref="TopLevel.ClientSize"/> and <see cref="IsActive"/> state.
|
||||
|
/// </remarks>
|
||||
|
public class WindowBase : TopLevel |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="IsActive"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<WindowBase, bool> IsActiveProperty = |
||||
|
AvaloniaProperty.RegisterDirect<WindowBase, bool>(nameof(IsActive), o => o.IsActive); |
||||
|
|
||||
|
private bool _isActive; |
||||
|
|
||||
|
public WindowBase(IWindowBaseImpl impl) : this(impl, AvaloniaLocator.Current) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(impl, dependencyResolver) |
||||
|
{ |
||||
|
PlatformImpl.Activated = HandleActivated; |
||||
|
PlatformImpl.Deactivated = HandleDeactivated; |
||||
|
PlatformImpl.PositionChanged = HandlePositionChanged; |
||||
|
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.Resize(x)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Fired when the window is activated.
|
||||
|
/// </summary>
|
||||
|
public event EventHandler Activated; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Fired when the window is deactivated.
|
||||
|
/// </summary>
|
||||
|
public event EventHandler Deactivated; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Fired when the window position is changed.
|
||||
|
/// </summary>
|
||||
|
public event EventHandler<PointEventArgs> PositionChanged; |
||||
|
|
||||
|
public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl; |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether the window is active.
|
||||
|
/// </summary>
|
||||
|
public bool IsActive |
||||
|
{ |
||||
|
get { return _isActive; } |
||||
|
private set { SetAndRaise(IsActiveProperty, ref _isActive, value); } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the window position in screen coordinates.
|
||||
|
/// </summary>
|
||||
|
public Point Position |
||||
|
{ |
||||
|
get { return PlatformImpl.Position; } |
||||
|
set { PlatformImpl.Position = value; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Whether an auto-size operation is in progress.
|
||||
|
/// </summary>
|
||||
|
protected bool AutoSizing |
||||
|
{ |
||||
|
get; |
||||
|
private set; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Activates the window.
|
||||
|
/// </summary>
|
||||
|
public void Activate() |
||||
|
{ |
||||
|
PlatformImpl.Activate(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Begins an auto-resize operation.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A disposable used to finish the operation.</returns>
|
||||
|
/// <remarks>
|
||||
|
/// When an auto-resize operation is in progress any resize events received will not be
|
||||
|
/// cause the new size to be written to the <see cref="Layoutable.Width"/> and
|
||||
|
/// <see cref="Layoutable.Height"/> properties.
|
||||
|
/// </remarks>
|
||||
|
protected IDisposable BeginAutoSizing() |
||||
|
{ |
||||
|
AutoSizing = true; |
||||
|
return Disposable.Create(() => AutoSizing = false); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Carries out the arrange pass of the window.
|
||||
|
/// </summary>
|
||||
|
/// <param name="finalSize">The final window size.</param>
|
||||
|
/// <returns>The <paramref name="finalSize"/> parameter unchanged.</returns>
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
using (BeginAutoSizing()) |
||||
|
{ |
||||
|
PlatformImpl.Resize(finalSize); |
||||
|
} |
||||
|
|
||||
|
return base.ArrangeOverride(PlatformImpl.ClientSize); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Handles a resize notification from <see cref="ITopLevelImpl.Resized"/>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="clientSize">The new client size.</param>
|
||||
|
protected override void HandleResized(Size clientSize) |
||||
|
{ |
||||
|
if (!AutoSizing) |
||||
|
{ |
||||
|
Width = clientSize.Width; |
||||
|
Height = clientSize.Height; |
||||
|
} |
||||
|
ClientSize = clientSize; |
||||
|
LayoutManager.Instance.ExecuteLayoutPass(); |
||||
|
PlatformImpl.Invalidate(new Rect(clientSize)); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Handles a window position change notification from
|
||||
|
/// <see cref="IWindowBaseImpl.PositionChanged"/>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="pos">The window position.</param>
|
||||
|
private void HandlePositionChanged(Point pos) |
||||
|
{ |
||||
|
PositionChanged?.Invoke(this, new PointEventArgs(pos)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Handles an activated notification from <see cref="IWindowBaseImpl.Activated"/>.
|
||||
|
/// </summary>
|
||||
|
private void HandleActivated() |
||||
|
{ |
||||
|
Activated?.Invoke(this, EventArgs.Empty); |
||||
|
|
||||
|
var scope = this as IFocusScope; |
||||
|
|
||||
|
if (scope != null) |
||||
|
{ |
||||
|
FocusManager.Instance.SetFocusScope(scope); |
||||
|
} |
||||
|
|
||||
|
IsActive = true; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Handles a deactivated notification from <see cref="IWindowBaseImpl.Deactivated"/>.
|
||||
|
/// </summary>
|
||||
|
private void HandleDeactivated() |
||||
|
{ |
||||
|
IsActive = false; |
||||
|
|
||||
|
Deactivated?.Invoke(this, EventArgs.Empty); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler
|
||||
|
/// </summary>
|
||||
|
public void BeginMoveDrag() => PlatformImpl.BeginMoveDrag(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Starts resizing a window. This function is used if an application has window resizing controls.
|
||||
|
/// Should be called from left mouse button press event handler
|
||||
|
/// </summary>
|
||||
|
public void BeginResizeDrag(WindowEdge edge) => PlatformImpl.BeginResizeDrag(edge); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,126 @@ |
|||||
|
// 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.Reactive; |
||||
|
using System.Reactive.Subjects; |
||||
|
using Moq; |
||||
|
using Avalonia.Controls.Presenters; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using Avalonia.Layout; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering; |
||||
|
using Avalonia.Styling; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Ploeh.AutoFixture; |
||||
|
using Ploeh.AutoFixture.AutoMoq; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Controls.UnitTests |
||||
|
{ |
||||
|
public class WindowBaseTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Impl_ClientSize_Should_Be_Set_After_Layout_Pass() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var impl = Mock.Of<IWindowBaseImpl>(x => x.Scaling == 1); |
||||
|
|
||||
|
var target = new TestWindowBase(impl) |
||||
|
{ |
||||
|
Template = CreateTemplate(), |
||||
|
Content = new TextBlock |
||||
|
{ |
||||
|
Width = 321, |
||||
|
Height = 432, |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
LayoutManager.Instance.ExecuteInitialLayoutPass(target); |
||||
|
|
||||
|
Mock.Get(impl).Verify(x => x.Resize(new Size(321, 432))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
[Fact] |
||||
|
public void Activate_Should_Call_Impl_Activate() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var impl = new Mock<IWindowBaseImpl>(); |
||||
|
var target = new TestWindowBase(impl.Object); |
||||
|
|
||||
|
target.Activate(); |
||||
|
|
||||
|
impl.Verify(x => x.Activate()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Impl_Activate_Should_Call_Raise_Activated_Event() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var impl = new Mock<IWindowBaseImpl>(); |
||||
|
impl.SetupAllProperties(); |
||||
|
|
||||
|
bool raised = false; |
||||
|
var target = new TestWindowBase(impl.Object); |
||||
|
target.Activated += (s, e) => raised = true; |
||||
|
|
||||
|
impl.Object.Activated(); |
||||
|
|
||||
|
Assert.True(raised); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
[Fact] |
||||
|
public void Impl_Deactivate_Should_Call_Raise_Deativated_Event() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var impl = new Mock<IWindowBaseImpl>(); |
||||
|
impl.SetupAllProperties(); |
||||
|
|
||||
|
bool raised = false; |
||||
|
var target = new TestWindowBase(impl.Object); |
||||
|
target.Deactivated += (s, e) => raised = true; |
||||
|
|
||||
|
impl.Object.Deactivated(); |
||||
|
|
||||
|
Assert.True(raised); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private FuncControlTemplate<TestWindowBase> CreateTemplate() |
||||
|
{ |
||||
|
return new FuncControlTemplate<TestWindowBase>(x => |
||||
|
new ContentPresenter |
||||
|
{ |
||||
|
Name = "PART_ContentPresenter", |
||||
|
[!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private class TestWindowBase : WindowBase |
||||
|
{ |
||||
|
public bool IsClosed { get; private set; } |
||||
|
|
||||
|
public TestWindowBase(IWindowBaseImpl impl) |
||||
|
: base(impl) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override void HandleApplicationExiting() |
||||
|
{ |
||||
|
base.HandleApplicationExiting(); |
||||
|
IsClosed = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue