diff --git a/Avalonia.sln b/Avalonia.sln index 3ab8048857..ad14483b40 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -176,6 +176,7 @@ Global tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{48840edd-24bf-495d-911e-2eb12ae75d3b}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{7863ea94-f0fb-4386-bf8c-e5bfa761560a}*SharedItemsImports = 4 + src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 4 src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 4 src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4 diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs index e39ba2c121..efbc71b808 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs @@ -10,6 +10,7 @@ using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; +using Avalonia.Controls; using Avalonia.Platform; namespace Avalonia.Android.Platform.SkiaPlatform @@ -18,24 +19,25 @@ namespace Avalonia.Android.Platform.SkiaPlatform { private Point _position; private bool _isAdded; + Action IWindowBaseImpl.Activated { get; set; } + public Action PositionChanged { get; set; } + public Action Deactivated { get; set; } + public PopupImpl() : base(ActivityTracker.Current, true) { } private Size _clientSize = new Size(1, 1); - public override Size ClientSize + + public void Resize(Size value) { - get { return base.ClientSize; } - set - { - if(View == null) - return; - _clientSize = value; - UpdateParams(); - } + if (View == null) + return; + _clientSize = value; + UpdateParams(); } - public override Point Position + public Point Position { get { return _position; } set @@ -87,5 +89,22 @@ namespace Avalonia.Android.Platform.SkiaPlatform Hide(); base.Dispose(); } + + + public void Activate() + { + } + + public void BeginMoveDrag() + { + //Not supported + } + + public void BeginResizeDrag(WindowEdge edge) + { + //Not supported + } + + } } \ No newline at end of file diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index d34981b964..75772be171 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -67,8 +67,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform public Action Closed { get; set; } - public Action Deactivated { get; set; } - public Action Input { get; set; } public Size MaxClientSize { get; protected set; } @@ -79,29 +77,17 @@ namespace Avalonia.Android.Platform.SkiaPlatform public Action ScalingChanged { get; set; } - public Action PositionChanged { get; set; } - public View View => _view; - Action ITopLevelImpl.Activated { get; set; } - - IPlatformHandle ITopLevelImpl.Handle => _view; + public IPlatformHandle Handle => _view; public IEnumerable Surfaces => new object[] {this}; - - public void Activate() - { - } - + public virtual void Hide() { _view.Visibility = ViewStates.Invisible; } - - public void SetSystemDecorations(bool enabled) - { - } - + public void Invalidate(Rect rect) { if (_view.Holder?.Surface?.IsValid == true) _view.Invalidate(); @@ -126,40 +112,19 @@ namespace Avalonia.Android.Platform.SkiaPlatform { InputRoot = inputRoot; } - - public void SetTitle(string title) - { - } - + public virtual void Show() { _view.Visibility = ViewStates.Visible; } - public void BeginMoveDrag() - { - //Not supported - } - - public void BeginResizeDrag(WindowEdge edge) - { - //Not supported - } - - public virtual Point Position { get; set; } - public double Scaling => 1; void Draw() { Paint?.Invoke(new Rect(new Point(0, 0), ClientSize)); } - - public void SetIcon(IWindowIconImpl icon) - { - // No window icons for mobile platforms - } - + public virtual void Dispose() { _view.Dispose(); diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index c05e44b357..6119103e6d 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -57,6 +57,7 @@ + @@ -65,6 +66,7 @@ + diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs index fc58e751f4..4688017187 100644 --- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs +++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs @@ -11,12 +11,11 @@ namespace Avalonia.Controls.Embedding { public EmbeddableControlRoot(IEmbeddableWindowImpl impl) : base(impl) { - PlatformImpl.Show(); + } public EmbeddableControlRoot() : base(PlatformManager.CreateEmbeddableWindow()) { - PlatformImpl.Show(); } public new IEmbeddableWindowImpl PlatformImpl => (IEmbeddableWindowImpl) base.PlatformImpl; @@ -25,7 +24,6 @@ namespace Avalonia.Controls.Embedding { EnsureInitialized(); ApplyTemplate(); - PlatformImpl.Show(); LayoutManager.Instance.ExecuteInitialLayoutPass(this); } diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs index 2185fd982b..e919275d4f 100644 --- a/src/Avalonia.Controls/Menu.cs +++ b/src/Avalonia.Controls/Menu.cs @@ -103,9 +103,11 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTree(e); - var topLevel = e.Root as TopLevel; + var topLevel = (TopLevel)e.Root; + var window = e.Root as Window; - topLevel.Deactivated += Deactivated; + if (window != null) + window.Deactivated += Deactivated; var pointerPress = topLevel.AddHandler( PointerPressedEvent, @@ -114,7 +116,11 @@ namespace Avalonia.Controls _subscription = new CompositeDisposable( pointerPress, - Disposable.Create(() => topLevel.Deactivated -= Deactivated), + Disposable.Create(() => + { + if (window != null) + window.Deactivated -= Deactivated; + }), InputManager.Instance.Process.Subscribe(ListenForNonClientClick)); var inputRoot = e.Root as IInputRoot; diff --git a/src/Avalonia.Controls/Platform/IEmbeddableWindowImpl.cs b/src/Avalonia.Controls/Platform/IEmbeddableWindowImpl.cs index 45b15e5f45..3f974f3748 100644 --- a/src/Avalonia.Controls/Platform/IEmbeddableWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IEmbeddableWindowImpl.cs @@ -8,7 +8,7 @@ namespace Avalonia.Platform /// /// Defines a platform-specific embeddable window implementation. /// - public interface IEmbeddableWindowImpl : IWindowImpl + public interface IEmbeddableWindowImpl : ITopLevelImpl { event Action LostFocus; } diff --git a/src/Avalonia.Controls/Platform/IPopupImpl.cs b/src/Avalonia.Controls/Platform/IPopupImpl.cs index 34b3b8b557..1b606f550b 100644 --- a/src/Avalonia.Controls/Platform/IPopupImpl.cs +++ b/src/Avalonia.Controls/Platform/IPopupImpl.cs @@ -6,7 +6,7 @@ namespace Avalonia.Platform /// /// Defines a platform-specific popup window implementation. /// - public interface IPopupImpl : ITopLevelImpl + public interface IPopupImpl : IWindowBaseImpl { } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 77884acf73..16f436fd45 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; @@ -19,25 +18,15 @@ namespace Avalonia.Platform public interface ITopLevelImpl : IDisposable { /// - /// Gets or sets the client size of the window. + /// Gets the client size of the toplevel. /// - Size ClientSize { get; set; } + Size ClientSize { get; } /// - /// Gets the maximum size of a window on the system. - /// - Size MaxClientSize { get; } - - /// - /// Gets the scaling factor for the window. + /// Gets the scaling factor for the toplevel. /// double Scaling { get; } - /// - /// Gets the platform window handle. - /// - IPlatformHandle Handle { get; } - /// /// The list of native platform's surfaces that can be consumed by rendering subsystems. /// @@ -51,57 +40,32 @@ namespace Avalonia.Platform IEnumerable Surfaces { get; } /// - /// Gets or sets a method called when the window is activated (receives focus). - /// - Action Activated { get; set; } - - /// - /// Gets or sets a method called when the window is closed. - /// - Action Closed { get; set; } - - /// - /// Gets or sets a method called when the window is deactivated (loses focus). - /// - Action Deactivated { get; set; } - - /// - /// Gets or sets a method called when the window receives input. + /// Gets or sets a method called when the toplevel receives input. /// Action Input { get; set; } /// - /// Gets or sets a method called when the window requires painting. + /// Gets or sets a method called when the toplevel requires painting. /// Action Paint { get; set; } /// - /// Gets or sets a method called when the window is resized. + /// Gets or sets a method called when the toplevel is resized. /// Action Resized { get; set; } /// - /// Gets or sets a method called when the window's scaling changes. + /// Gets or sets a method called when the toplevel's scaling changes. /// Action ScalingChanged { get; set; } /// - /// Gets or sets a method called when the window's position changes. - /// - Action PositionChanged { get; set; } - - /// - /// Activates the window. - /// - void Activate(); - - /// - /// Invalidates a rect on the window. + /// Invalidates a rect on the toplevel. /// void Invalidate(Rect rect); /// - /// Sets the for the window. + /// Sets the for the toplevel. /// void SetInputRoot(IInputRoot inputRoot); @@ -120,32 +84,14 @@ namespace Avalonia.Platform Point PointToScreen(Point point); /// - /// Sets the cursor associated with the window. + /// Sets the cursor associated with the toplevel. /// /// The cursor. Use null for default cursor void SetCursor(IPlatformHandle cursor); /// - /// Shows the toplevel. - /// - void Show(); - - /// - /// Hides the window. - /// - void Hide(); - - /// - /// Starts moving a window with left button being held. Should be called from left mouse button press event handler. - /// - void BeginMoveDrag(); - - /// - /// 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 + /// Gets or sets a method called when the underlying implementation is destroyed. /// - void BeginResizeDrag(WindowEdge edge); - - Point Position { get; set; } + Action Closed { get; set; } } } diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs new file mode 100644 index 0000000000..a5c7c60843 --- /dev/null +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -0,0 +1,69 @@ +using System; +using Avalonia.Controls; + +namespace Avalonia.Platform +{ + public interface IWindowBaseImpl : ITopLevelImpl + { + /// + /// Shows the toplevel. + /// + void Show(); + + /// + /// Hides the window. + /// + void Hide(); + + /// + /// Starts moving a window with left button being held. Should be called from left mouse button press event handler. + /// + void BeginMoveDrag(); + + /// + /// 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 + /// + void BeginResizeDrag(WindowEdge edge); + + /// + /// Gets position of the window relatively to the screen + /// + Point Position { get; set; } + + /// + /// Gets or sets a method called when the window's position changes. + /// + Action PositionChanged { get; set; } + + /// + /// Activates the window. + /// + void Activate(); + + /// + /// Gets or sets a method called when the window is deactivated (loses focus). + /// + Action Deactivated { get; set; } + + /// + /// Gets or sets a method called when the window is activated (receives focus). + /// + Action Activated { get; set; } + + /// + /// Gets the platform window handle. + /// + IPlatformHandle Handle { get; } + + /// + /// Gets the maximum size of a window on the system. + /// + Size MaxClientSize { get; } + + /// + /// Sets the client size of the toplevel. + /// + void Resize(Size clientSize); + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 609e9834cb..69b946346e 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -9,7 +9,7 @@ namespace Avalonia.Platform /// /// Defines a platform-specific window implementation. /// - public interface IWindowImpl : ITopLevelImpl + public interface IWindowImpl : IWindowBaseImpl { /// /// Gets or sets the minimized/maximized state of the window. diff --git a/src/Avalonia.Controls/Platform/PlatformManager.cs b/src/Avalonia.Controls/Platform/PlatformManager.cs index 8f069a3ab2..fa01b9e839 100644 --- a/src/Avalonia.Controls/Platform/PlatformManager.cs +++ b/src/Avalonia.Controls/Platform/PlatformManager.cs @@ -31,7 +31,7 @@ namespace Avalonia.Controls.Platform throw new Exception("Could not CreateWindow(): IWindowingPlatform is not registered."); } - return s_designerMode ? platform.CreateEmbeddableWindow() : platform.CreateWindow(); + return s_designerMode ? (IWindowImpl)platform.CreateEmbeddableWindow() : platform.CreateWindow(); } public static IEmbeddableWindowImpl CreateEmbeddableWindow() diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index 228ad65ffa..96190720ae 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -97,7 +97,9 @@ namespace Avalonia.Controls.Presenters /// public override Size MeasureOverride(Size availableSize) { - var window = Owner.GetVisualRoot() as TopLevel; + var visualRoot = Owner.GetVisualRoot(); + var maxAvailableSize = (visualRoot as WindowBase)?.PlatformImpl?.MaxClientSize + ?? (visualRoot as TopLevel)?.ClientSize; // If infinity is passed as the available size and we're virtualized then we need to // fill the available space, but to do that we *don't* want to materialize all our @@ -107,9 +109,9 @@ namespace Avalonia.Controls.Presenters { if (availableSize.Height == double.PositiveInfinity) { - if (window != null) + if (maxAvailableSize.HasValue) { - availableSize = availableSize.WithHeight(window.PlatformImpl.MaxClientSize.Height); + availableSize = availableSize.WithHeight(maxAvailableSize.Value.Height); } } @@ -119,9 +121,9 @@ namespace Avalonia.Controls.Presenters { if (availableSize.Width == double.PositiveInfinity) { - if (window != null) + if (maxAvailableSize.HasValue) { - availableSize = availableSize.WithWidth(window.PlatformImpl.MaxClientSize.Width); + availableSize = availableSize.WithWidth(maxAvailableSize.Value.Width); } } diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 63f3fb647b..bb2a61c024 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -213,7 +213,9 @@ namespace Avalonia.Controls.Primitives if (_topLevel != null) { - _topLevel.Deactivated += TopLevelDeactivated; + var window = _topLevel as Window; + if (window != null) + window.Deactivated += WindowDeactivated; _topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel); _nonClientListener = InputManager.Instance.Process.Subscribe(ListenForNonClientClick); } @@ -239,7 +241,9 @@ namespace Avalonia.Controls.Primitives if (_topLevel != null) { _topLevel.RemoveHandler(PointerPressedEvent, PointerPressedOutside); - _topLevel.Deactivated -= TopLevelDeactivated; + var window = _topLevel as Window; + if (window != null) + window.Deactivated -= WindowDeactivated; _nonClientListener?.Dispose(); _nonClientListener = null; } @@ -381,7 +385,7 @@ namespace Avalonia.Controls.Primitives } } - private void TopLevelDeactivated(object sender, EventArgs e) + private void WindowDeactivated(object sender, EventArgs e) { if (!StaysOpen) { diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index f67bbef5e1..5e5177b29d 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls.Primitives /// /// The root window of a . /// - public class PopupRoot : TopLevel, IInteractive, IHostedVisualTreeRoot, IDisposable + public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable { private IDisposable _presenterSubscription; diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 1853d67019..4de0256a7d 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -18,12 +18,12 @@ using Avalonia.VisualTree; namespace Avalonia.Controls { /// - /// Base class for top-level windows. + /// Base class for top-level widgets. /// /// - /// This class acts as a base for top level windows such as and - /// . It handles scheduling layout, styling and rendering as well as - /// tracking the window and state. + /// This class acts as a base for top level widget. + /// It handles scheduling layout, styling and rendering as well as + /// tracking the widget's . /// public abstract class TopLevel : ContentControl, IInputRoot, ILayoutRoot, IRenderRoot, ICloseable, IStyleRoot { @@ -33,12 +33,6 @@ namespace Avalonia.Controls public static readonly DirectProperty ClientSizeProperty = AvaloniaProperty.RegisterDirect(nameof(ClientSize), o => o.ClientSize); - /// - /// Defines the property. - /// - public static readonly DirectProperty IsActiveProperty = - AvaloniaProperty.RegisterDirect(nameof(IsActive), o => o.IsActive); - /// /// Defines the property. /// @@ -51,7 +45,6 @@ namespace Avalonia.Controls private readonly IApplicationLifecycle _applicationLifecycle; private readonly IPlatformRenderInterface _renderInterface; private Size _clientSize; - private bool _isActive; /// /// Initializes static members of the class. @@ -100,21 +93,20 @@ namespace Avalonia.Controls Renderer = rendererFactory?.CreateRenderer(this, renderLoop); PlatformImpl.SetInputRoot(this); - PlatformImpl.Activated = HandleActivated; - PlatformImpl.Deactivated = HandleDeactivated; + PlatformImpl.Closed = HandleClosed; PlatformImpl.Input = HandleInput; PlatformImpl.Paint = Renderer != null ? (Action)Renderer.Render : null; PlatformImpl.Resized = HandleResized; PlatformImpl.ScalingChanged = HandleScalingChanged; - PlatformImpl.PositionChanged = HandlePositionChanged; + _keyboardNavigationHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this); styler?.ApplyStyles(this); ClientSize = PlatformImpl.ClientSize; - this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.ClientSize = x); + this.GetObservable(PointerOverElementProperty) .Select( x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty()) @@ -126,51 +118,18 @@ namespace Avalonia.Controls } } - /// - /// Fired when the window is activated. - /// - public event EventHandler Activated; - /// /// Fired when the window is closed. /// public event EventHandler Closed; - /// - /// Fired when the window is deactivated. - /// - public event EventHandler Deactivated; - - /// - /// Fired when the window position is changed. - /// - public event EventHandler PositionChanged; - /// /// Gets or sets the client size of the window. /// public Size ClientSize { get { return _clientSize; } - private set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); } - } - - /// - /// Gets a value that indicates whether the window is active. - /// - public bool IsActive - { - get { return _isActive; } - private set { SetAndRaise(IsActiveProperty, ref _isActive, value); } - } - - /// - /// Gets or sets the window position in screen coordinates. - /// - public Point Position - { - get { return PlatformImpl.Position; } - set { PlatformImpl.Position = value; } + protected set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); } } /// @@ -225,15 +184,6 @@ namespace Avalonia.Controls get { return AvaloniaLocator.Current.GetService(); } } - /// - /// Whether an auto-size operation is in progress. - /// - protected bool AutoSizing - { - get; - private set; - } - /// IRenderTarget IRenderRoot.CreateRenderTarget() { @@ -258,43 +208,6 @@ namespace Avalonia.Controls return PlatformImpl.PointToScreen(p); } - /// - /// Activates the window. - /// - public void Activate() - { - PlatformImpl.Activate(); - } - - /// - /// Begins an auto-resize operation. - /// - /// A disposable used to finish the operation. - /// - /// When an auto-resize operation is in progress any resize events received will not be - /// cause the new size to be written to the and - /// properties. - /// - protected IDisposable BeginAutoSizing() - { - AutoSizing = true; - return Disposable.Create(() => AutoSizing = false); - } - - /// - /// Carries out the arrange pass of the window. - /// - /// The final window size. - /// The parameter unchanged. - protected override Size ArrangeOverride(Size finalSize) - { - using (BeginAutoSizing()) - { - PlatformImpl.ClientSize = finalSize; - } - - return base.ArrangeOverride(PlatformImpl.ClientSize); - } /// /// Handles a resize notification from . @@ -302,13 +215,9 @@ namespace Avalonia.Controls /// The new client size. protected virtual void HandleResized(Size clientSize) { - if (!AutoSizing) - { - Width = clientSize.Width; - Height = clientSize.Height; - } - ClientSize = clientSize; + Width = clientSize.Width; + Height = clientSize.Height; LayoutManager.Instance.ExecuteLayoutPass(); PlatformImpl.Invalidate(new Rect(clientSize)); } @@ -358,23 +267,6 @@ namespace Avalonia.Controls return result; } - /// - /// Handles an activated notification from . - /// - private void HandleActivated() - { - Activated?.Invoke(this, EventArgs.Empty); - - var scope = this as IFocusScope; - - if (scope != null) - { - FocusManager.Instance.SetFocusScope(scope); - } - - IsActive = true; - } - /// /// Handles a closed notification from . /// @@ -398,16 +290,6 @@ namespace Avalonia.Controls { } - /// - /// Handles a deactivated notification from . - /// - private void HandleDeactivated() - { - IsActive = false; - - Deactivated?.Invoke(this, EventArgs.Empty); - } - /// /// Handles input from . /// @@ -416,26 +298,5 @@ namespace Avalonia.Controls { _inputManager.ProcessInput(e); } - - /// - /// Handles a window position change notification from - /// . - /// - /// The window position. - private void HandlePositionChanged(Point pos) - { - PositionChanged?.Invoke(this, new PointEventArgs(pos)); - } - - /// - /// Starts moving a window with left button being held. Should be called from left mouse button press event handler - /// - public void BeginMoveDrag() => PlatformImpl.BeginMoveDrag(); - - /// - /// 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 - /// - public void BeginResizeDrag(WindowEdge edge) => PlatformImpl.BeginResizeDrag(edge); } } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 40c52a748d..992279d981 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -43,7 +43,7 @@ namespace Avalonia.Controls /// /// A top-level window. /// - public class Window : TopLevel, IStyleable, IFocusScope, ILayoutRoot, INameScope + public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope { private static IList s_windows = new List(); diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs new file mode 100644 index 0000000000..ac6eea8641 --- /dev/null +++ b/src/Avalonia.Controls/WindowBase.cs @@ -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 +{ + /// + /// Base class for top-level windows. + /// + /// + /// This class acts as a base for top level windows such as and + /// . It handles scheduling layout, styling and rendering as well as + /// tracking the window and state. + /// + public class WindowBase : TopLevel + { + /// + /// Defines the property. + /// + public static readonly DirectProperty IsActiveProperty = + AvaloniaProperty.RegisterDirect(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)); + } + + /// + /// Fired when the window is activated. + /// + public event EventHandler Activated; + + /// + /// Fired when the window is deactivated. + /// + public event EventHandler Deactivated; + + /// + /// Fired when the window position is changed. + /// + public event EventHandler PositionChanged; + + public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl; + + + /// + /// Gets a value that indicates whether the window is active. + /// + public bool IsActive + { + get { return _isActive; } + private set { SetAndRaise(IsActiveProperty, ref _isActive, value); } + } + + /// + /// Gets or sets the window position in screen coordinates. + /// + public Point Position + { + get { return PlatformImpl.Position; } + set { PlatformImpl.Position = value; } + } + + /// + /// Whether an auto-size operation is in progress. + /// + protected bool AutoSizing + { + get; + private set; + } + + /// + /// Activates the window. + /// + public void Activate() + { + PlatformImpl.Activate(); + } + + + /// + /// Begins an auto-resize operation. + /// + /// A disposable used to finish the operation. + /// + /// When an auto-resize operation is in progress any resize events received will not be + /// cause the new size to be written to the and + /// properties. + /// + protected IDisposable BeginAutoSizing() + { + AutoSizing = true; + return Disposable.Create(() => AutoSizing = false); + } + + /// + /// Carries out the arrange pass of the window. + /// + /// The final window size. + /// The parameter unchanged. + protected override Size ArrangeOverride(Size finalSize) + { + using (BeginAutoSizing()) + { + PlatformImpl.Resize(finalSize); + } + + return base.ArrangeOverride(PlatformImpl.ClientSize); + } + + /// + /// Handles a resize notification from . + /// + /// The new client size. + protected override void HandleResized(Size clientSize) + { + if (!AutoSizing) + { + Width = clientSize.Width; + Height = clientSize.Height; + } + ClientSize = clientSize; + LayoutManager.Instance.ExecuteLayoutPass(); + PlatformImpl.Invalidate(new Rect(clientSize)); + + } + + /// + /// Handles a window position change notification from + /// . + /// + /// The window position. + private void HandlePositionChanged(Point pos) + { + PositionChanged?.Invoke(this, new PointEventArgs(pos)); + } + + /// + /// Handles an activated notification from . + /// + private void HandleActivated() + { + Activated?.Invoke(this, EventArgs.Empty); + + var scope = this as IFocusScope; + + if (scope != null) + { + FocusManager.Instance.SetFocusScope(scope); + } + + IsActive = true; + } + + /// + /// Handles a deactivated notification from . + /// + private void HandleDeactivated() + { + IsActive = false; + + Deactivated?.Invoke(this, EventArgs.Empty); + } + + /// + /// Starts moving a window with left button being held. Should be called from left mouse button press event handler + /// + public void BeginMoveDrag() => PlatformImpl.BeginMoveDrag(); + + /// + /// 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 + /// + public void BeginResizeDrag(WindowEdge edge) => PlatformImpl.BeginResizeDrag(edge); + } +} diff --git a/src/Avalonia.DesignerSupport/DesignerAssist.cs b/src/Avalonia.DesignerSupport/DesignerAssist.cs index 95e7345227..c9ae89354c 100644 --- a/src/Avalonia.DesignerSupport/DesignerAssist.cs +++ b/src/Avalonia.DesignerSupport/DesignerAssist.cs @@ -75,8 +75,7 @@ namespace Avalonia.DesignerSupport private static void SetScalingFactor(double factor) { PlatformManager.SetDesignerScalingFactor(factor); - if (s_currentWindow != null) - s_currentWindow.PlatformImpl.ClientSize = s_currentWindow.ClientSize; + s_currentWindow?.PlatformImpl.Resize(s_currentWindow.ClientSize); } static Window s_currentWindow; diff --git a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj index 07a9ab56ed..d7e4400a99 100644 --- a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj +++ b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj @@ -61,7 +61,7 @@ - + diff --git a/src/Gtk/Avalonia.Gtk/EmbeddableImpl.cs b/src/Gtk/Avalonia.Gtk/EmbeddableImpl.cs index a5a34cd47b..4e8085a057 100644 --- a/src/Gtk/Avalonia.Gtk/EmbeddableImpl.cs +++ b/src/Gtk/Avalonia.Gtk/EmbeddableImpl.cs @@ -12,7 +12,7 @@ using WindowEdge = Avalonia.Controls.WindowEdge; namespace Avalonia.Gtk { - class EmbeddableImpl : WindowImplBase, IEmbeddableWindowImpl + class EmbeddableImpl : TopLevelImpl, IEmbeddableWindowImpl { #pragma warning disable CS0067 // Method not used public event Action LostFocus; @@ -36,37 +36,7 @@ namespace Avalonia.Gtk public override Size ClientSize { get { return new Size(Widget.Allocation.Width, Widget.Allocation.Height); } - set {} - } - - - //Stubs are needed for future GTK designer embedding support - public override void SetTitle(string title) - { - } - - public override IDisposable ShowDialog() => Disposable.Create(() => { }); - - public override void SetSystemDecorations(bool enabled) - { - } - - public override void SetIcon(IWindowIconImpl icon) - { - } - - public override void BeginMoveDrag() - { - } - - public override void BeginResizeDrag(WindowEdge edge) - { - } - - public override Point Position - { - get { return new Point(); } - set {} } + } } diff --git a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs index 00a4727769..0c9ed44274 100644 --- a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs +++ b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs @@ -5,10 +5,10 @@ namespace Avalonia.Gtk { class FramebufferManager : IFramebufferPlatformSurface, IDisposable { - private readonly WindowImplBase _window; + private readonly TopLevelImpl _window; private SurfaceFramebuffer _fb; - public FramebufferManager(WindowImplBase window) + public FramebufferManager(TopLevelImpl window) { _window = window; } diff --git a/src/Gtk/Avalonia.Gtk/SystemDialogImpl.cs b/src/Gtk/Avalonia.Gtk/SystemDialogImpl.cs index 962621856a..05dc1bf02d 100644 --- a/src/Gtk/Avalonia.Gtk/SystemDialogImpl.cs +++ b/src/Gtk/Avalonia.Gtk/SystemDialogImpl.cs @@ -15,7 +15,7 @@ namespace Avalonia.Gtk public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) { var tcs = new TaskCompletionSource(); - var dlg = new global::Gtk.FileChooserDialog(dialog.Title, ((WindowImplBase)parent)?.Widget.Toplevel as Window, + var dlg = new global::Gtk.FileChooserDialog(dialog.Title, ((TopLevelImpl)parent)?.Widget.Toplevel as Window, dialog is OpenFileDialog ? FileChooserAction.Open : FileChooserAction.Save, @@ -57,7 +57,7 @@ namespace Avalonia.Gtk public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) { var tcs = new TaskCompletionSource(); - var dlg = new global::Gtk.FileChooserDialog(dialog.Title, ((WindowImplBase)parent)?.Widget.Toplevel as Window, + var dlg = new global::Gtk.FileChooserDialog(dialog.Title, ((TopLevelImpl)parent)?.Widget.Toplevel as Window, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Select Folder", ResponseType.Accept) diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/TopLevelImpl.cs similarity index 79% rename from src/Gtk/Avalonia.Gtk/WindowImplBase.cs rename to src/Gtk/Avalonia.Gtk/TopLevelImpl.cs index 4d7552aa72..a1bb3f847e 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/TopLevelImpl.cs @@ -15,10 +15,10 @@ namespace Avalonia.Gtk { using Gtk = global::Gtk; - public abstract class WindowImplBase : IWindowImpl + public abstract class TopLevelImpl : ITopLevelImpl { private IInputRoot _inputRoot; - protected Gtk.Widget _window; + private Gtk.Widget _widget; private FramebufferManager _framebuffer; private Gtk.IMContext _imContext; @@ -27,44 +27,43 @@ namespace Avalonia.Gtk private static readonly Gdk.Cursor DefaultCursor = new Gdk.Cursor(CursorType.LeftPtr); - protected WindowImplBase(Gtk.Widget window) + protected TopLevelImpl(Gtk.Widget window) { - _window = window; + _widget = window; _framebuffer = new FramebufferManager(this); Init(); } void Init() { - Handle = _window as IPlatformHandle; - _window.Events = EventMask.AllEventsMask; + Handle = _widget as IPlatformHandle; + _widget.Events = EventMask.AllEventsMask; _imContext = new Gtk.IMMulticontext(); _imContext.Commit += ImContext_Commit; - _window.Realized += OnRealized; - _window.Realize(); - _window.ButtonPressEvent += OnButtonPressEvent; - _window.ButtonReleaseEvent += OnButtonReleaseEvent; - _window.ScrollEvent += OnScrollEvent; - _window.Destroyed += OnDestroyed; - _window.KeyPressEvent += OnKeyPressEvent; - _window.KeyReleaseEvent += OnKeyReleaseEvent; - _window.ExposeEvent += OnExposeEvent; - _window.MotionNotifyEvent += OnMotionNotifyEvent; + _widget.Realized += OnRealized; + _widget.Realize(); + _widget.ButtonPressEvent += OnButtonPressEvent; + _widget.ButtonReleaseEvent += OnButtonReleaseEvent; + _widget.ScrollEvent += OnScrollEvent; + _widget.Destroyed += OnDestroyed; + _widget.KeyPressEvent += OnKeyPressEvent; + _widget.KeyReleaseEvent += OnKeyReleaseEvent; + _widget.ExposeEvent += OnExposeEvent; + _widget.MotionNotifyEvent += OnMotionNotifyEvent; } public IPlatformHandle Handle { get; private set; } - public Gtk.Widget Widget => _window; + public Gtk.Widget Widget => _widget; public Gdk.Drawable CurrentDrawable { get; private set; } void OnRealized (object sender, EventArgs eventArgs) { - _imContext.ClientWindow = _window.GdkWindow; + _imContext.ClientWindow = _widget.GdkWindow; } - public abstract Size ClientSize { get; set; } - + public abstract Size ClientSize { get; } public Size MaxClientSize { @@ -72,7 +71,7 @@ namespace Avalonia.Gtk { // TODO: This should take into account things such as taskbar and window border // thickness etc. - return new Size(_window.Screen.Width, _window.Screen.Height); + return new Size(_widget.Screen.Width, _widget.Screen.Height); } } @@ -80,7 +79,7 @@ namespace Avalonia.Gtk { get { - switch (_window.GdkWindow.State) + switch (_widget.GdkWindow.State) { case Gdk.WindowState.Iconified: return Controls.WindowState.Minimized; @@ -96,14 +95,14 @@ namespace Avalonia.Gtk switch (value) { case Controls.WindowState.Minimized: - _window.GdkWindow.Iconify(); + _widget.GdkWindow.Iconify(); break; case Controls.WindowState.Maximized: - _window.GdkWindow.Maximize(); + _widget.GdkWindow.Maximize(); break; case Controls.WindowState.Normal: - _window.GdkWindow.Deiconify(); - _window.GdkWindow.Unmaximize(); + _widget.GdkWindow.Deiconify(); + _widget.GdkWindow.Unmaximize(); break; } } @@ -141,15 +140,15 @@ namespace Avalonia.Gtk public void Invalidate(Rect rect) { - if (_window?.GdkWindow != null) - _window.GdkWindow.InvalidateRect( + if (_widget?.GdkWindow != null) + _widget.GdkWindow.InvalidateRect( new Rectangle((int) rect.X, (int) rect.Y, (int) rect.Width, (int) rect.Height), true); } public Point PointToClient(Point point) { int x, y; - _window.GdkWindow.GetDeskrelativeOrigin(out x, out y); + _widget.GdkWindow.GetDeskrelativeOrigin(out x, out y); return new Point(point.X - x, point.Y - y); } @@ -157,7 +156,7 @@ namespace Avalonia.Gtk public Point PointToScreen(Point point) { int x, y; - _window.GdkWindow.GetDeskrelativeOrigin(out x, out y); + _widget.GdkWindow.GetDeskrelativeOrigin(out x, out y); return new Point(point.X + x, point.Y + y); } @@ -166,28 +165,15 @@ namespace Avalonia.Gtk _inputRoot = inputRoot; } - public abstract void SetTitle(string title); - public abstract IDisposable ShowDialog(); - public abstract void SetSystemDecorations(bool enabled); - public abstract void SetIcon(IWindowIconImpl icon); - public void SetCursor(IPlatformHandle cursor) { - _window.GdkWindow.Cursor = cursor != null ? new Gdk.Cursor(cursor.Handle) : DefaultCursor; + _widget.GdkWindow.Cursor = cursor != null ? new Gdk.Cursor(cursor.Handle) : DefaultCursor; } - public void Show() => _window.Show(); - - public void Hide() => _window.Hide(); - public abstract void BeginMoveDrag(); - public abstract void BeginResizeDrag(WindowEdge edge); - public abstract Point Position { get; set; } + public void Show() => _widget.Show(); - void ITopLevelImpl.Activate() - { - _window.Activate(); - } + public void Hide() => _widget.Hide(); private static InputModifiers GetModifierKeys(ModifierType state) { @@ -317,9 +303,9 @@ namespace Avalonia.Gtk public void Dispose() { _framebuffer.Dispose(); - _window.Hide(); - _window.Dispose(); - _window = null; + _widget.Hide(); + _widget.Dispose(); + _widget = null; } } } diff --git a/src/Gtk/Avalonia.Gtk/WindowImpl.cs b/src/Gtk/Avalonia.Gtk/WindowImpl.cs index eca7c24136..d8555b4e05 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImpl.cs @@ -6,7 +6,7 @@ using Gdk; namespace Avalonia.Gtk { using Gtk = global::Gtk; - public class WindowImpl : WindowImplBase + public class WindowImpl : TopLevelImpl, IWindowImpl { private Gtk.Window _window; private Gtk.Window Window => _window ?? (_window = (Gtk.Window) Widget); @@ -61,24 +61,29 @@ namespace Avalonia.Gtk Window.GetSize(out width, out height); return new Size(width, height); } + } - set - { - Window.Resize((int)value.Width, (int)value.Height); - } + public void Resize(Size value) + { + Window.Resize((int)value.Width, (int)value.Height); } - public override void SetTitle(string title) + public void SetTitle(string title) { Window.Title = title; } + void IWindowBaseImpl.Activate() + { + _window.Activate(); + } + void OnFocusActivated(object sender, EventArgs eventArgs) { Activated(); } - public override void BeginMoveDrag() + public void BeginMoveDrag() { int x, y; ModifierType mod; @@ -86,7 +91,7 @@ namespace Avalonia.Gtk Window.BeginMoveDrag(1, x, y, 0); } - public override void BeginResizeDrag(Controls.WindowEdge edge) + public void BeginResizeDrag(Controls.WindowEdge edge) { int x, y; ModifierType mod; @@ -94,7 +99,7 @@ namespace Avalonia.Gtk Window.BeginResizeDrag((Gdk.WindowEdge)(int)edge, 1, x, y, 0); } - public override Point Position + public Point Position { get { @@ -108,7 +113,7 @@ namespace Avalonia.Gtk } } - public override IDisposable ShowDialog() + public IDisposable ShowDialog() { Window.Modal = true; Window.Show(); @@ -116,9 +121,9 @@ namespace Avalonia.Gtk return Disposable.Empty; } - public override void SetSystemDecorations(bool enabled) => Window.Decorated = enabled; + public void SetSystemDecorations(bool enabled) => Window.Decorated = enabled; - public override void SetIcon(IWindowIconImpl icon) + public void SetIcon(IWindowIconImpl icon) { Window.Icon = ((IconImpl)icon).Pixbuf; } diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index 785baeba8a..958af437a6 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -66,7 +66,7 @@ - + diff --git a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs index 1b2317ed6a..e52f0efb81 100644 --- a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs +++ b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs @@ -9,8 +9,8 @@ namespace Avalonia.Gtk3 { class FramebufferManager : IFramebufferPlatformSurface, IDisposable { - private readonly TopLevelImpl _window; - public FramebufferManager(TopLevelImpl window) + private readonly WindowBaseImpl _window; + public FramebufferManager(WindowBaseImpl window) { _window = window; } diff --git a/src/Gtk/Avalonia.Gtk3/PopupImpl.cs b/src/Gtk/Avalonia.Gtk3/PopupImpl.cs index 5d23148b76..8fd1c28ea4 100644 --- a/src/Gtk/Avalonia.Gtk3/PopupImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/PopupImpl.cs @@ -9,7 +9,7 @@ using Avalonia.Platform; namespace Avalonia.Gtk3 { - class PopupImpl : TopLevelImpl, IPopupImpl + class PopupImpl : WindowBaseImpl, IPopupImpl { static GtkWindow CreateWindow() { diff --git a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs b/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs index 04c224ff5b..efdadc2379 100644 --- a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs +++ b/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs @@ -77,14 +77,14 @@ namespace Avalonia.Gtk3 public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) { - return ShowDialog(dialog.Title, ((TopLevelImpl) parent)?.GtkWidget, + return ShowDialog(dialog.Title, ((WindowBaseImpl) parent)?.GtkWidget, dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save, (dialog as OpenFileDialog)?.AllowMultiple ?? false, dialog.InitialFileName); } public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) { - var res = await ShowDialog(dialog.Title, ((TopLevelImpl) parent)?.GtkWidget, + var res = await ShowDialog(dialog.Title, ((WindowBaseImpl) parent)?.GtkWidget, GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory); return res?.FirstOrDefault(); } diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs similarity index 97% rename from src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs rename to src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 0063c13910..478580e65e 100644 --- a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -12,7 +12,7 @@ using Avalonia.Platform; namespace Avalonia.Gtk3 { - abstract class TopLevelImpl : ITopLevelImpl, IPlatformHandle + abstract class WindowBaseImpl : IWindowBaseImpl, IPlatformHandle { public readonly GtkWindow GtkWidget; private IInputRoot _inputRoot; @@ -25,7 +25,7 @@ namespace Avalonia.Gtk3 private uint _lastKbdEvent; private uint _lastSmoothScrollEvent; - public TopLevelImpl(GtkWindow gtkWidget) + public WindowBaseImpl(GtkWindow gtkWidget) { GtkWidget = gtkWidget; @@ -318,12 +318,13 @@ namespace Avalonia.Gtk3 Native.GtkWindowGetSize(GtkWidget, out w, out h); return new Size(w, h); } - set - { - if (GtkWidget.IsClosed) - return; - Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height); - } + } + + public void Resize(Size value) + { + if (GtkWidget.IsClosed) + return; + Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height); } public Point Position diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index 631944872f..f083185a84 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -5,7 +5,7 @@ using Avalonia.Platform; namespace Avalonia.Gtk3 { - class WindowImpl : TopLevelImpl, IWindowImpl + class WindowImpl : WindowBaseImpl, IWindowImpl { public WindowImpl() : base(Native.GtkWindowNew(GtkWindowType.TopLevel)) { diff --git a/src/Windows/Avalonia.Win32/Embedding/WinFormsAvaloniaControlHost.cs b/src/Windows/Avalonia.Win32/Embedding/WinFormsAvaloniaControlHost.cs index 3b52090493..bdee85c91e 100644 --- a/src/Windows/Avalonia.Win32/Embedding/WinFormsAvaloniaControlHost.cs +++ b/src/Windows/Avalonia.Win32/Embedding/WinFormsAvaloniaControlHost.cs @@ -15,10 +15,12 @@ namespace Avalonia.Win32.Embedding { private readonly EmbeddableControlRoot _root = new EmbeddableControlRoot(); + private IntPtr WindowHandle => ((WindowImpl) _root.PlatformImpl).Handle.Handle; + public WinFormsAvaloniaControlHost() { SetStyle(ControlStyles.AllPaintingInWmPaint, true); - UnmanagedMethods.SetParent(_root.PlatformImpl.Handle.Handle, Handle); + UnmanagedMethods.SetParent(WindowHandle, Handle); _root.Prepare(); if (_root.IsFocused) FocusManager.Instance.Focus(null); @@ -59,20 +61,20 @@ namespace Avalonia.Win32.Embedding private void RootGotFocus(object sender, Interactivity.RoutedEventArgs e) { - UnmanagedMethods.SetFocus(_root.PlatformImpl.Handle.Handle); + UnmanagedMethods.SetFocus(WindowHandle); } protected override void OnGotFocus(EventArgs e) { if (_root != null) - UnmanagedMethods.SetFocus(_root.PlatformImpl.Handle.Handle); + UnmanagedMethods.SetFocus(WindowHandle); } void FixPosition() { if (_root != null && Width > 0 && Height > 0) - UnmanagedMethods.MoveWindow(_root.PlatformImpl.Handle.Handle, 0, 0, Width, Height, true); + UnmanagedMethods.MoveWindow(WindowHandle, 0, 0, Width, Height, true); } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 2aa9ea7d0a..e668fd964a 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -187,7 +187,9 @@ namespace Avalonia.Win32 #if NETSTANDARD throw new NotSupportedException(); #else - return new EmbeddedWindowImpl(); + var embedded = new EmbeddedWindowImpl(); + embedded.Show(); + return embedded; #endif } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index d9b200169f..0000b5c0a9 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -89,23 +89,23 @@ namespace Avalonia.Win32 UnmanagedMethods.GetClientRect(_hwnd, out rect); return new Size(rect.right, rect.bottom) / Scaling; } + } - set + public void Resize(Size value) + { + if (value != ClientSize) { - if (value != ClientSize) - { - value *= Scaling; - value += BorderThickness; - - UnmanagedMethods.SetWindowPos( - _hwnd, - IntPtr.Zero, - 0, - 0, - (int)value.Width, - (int)value.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_RESIZE); - } + value *= Scaling; + value += BorderThickness; + + UnmanagedMethods.SetWindowPos( + _hwnd, + IntPtr.Zero, + 0, + 0, + (int)value.Width, + (int)value.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_RESIZE); } } diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs index 96ab2786f7..7949e331fe 100644 --- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs +++ b/src/iOS/Avalonia.iOS/TopLevelImpl.cs @@ -32,6 +32,7 @@ namespace Avalonia.iOS { _keyboardHelper = new KeyboardEventsHelper(this); AutoresizingMask = UIViewAutoresizing.All; + _keyboardHelper.ActivateAutoShowKeybord(); } [Export("hasText")] @@ -44,37 +45,21 @@ namespace Avalonia.iOS public void DeleteBackward() => _keyboardHelper.DeleteBackward(); public override bool CanBecomeFirstResponder => _keyboardHelper.CanBecomeFirstResponder(); - - public Action Activated { get; set; } + public Action Closed { get; set; } - public Action Deactivated { get; set; } public Action Input { get; set; } public Action Paint { get; set; } public Action Resized { get; set; } public Action ScalingChanged { get; set; } - public Action PositionChanged { get; set; } public IPlatformHandle Handle => null; public double Scaling => UIScreen.MainScreen.Scale; - public WindowState WindowState - { - get { return WindowState.Normal; } - set { } - } - + public override void LayoutSubviews() => Resized?.Invoke(ClientSize); - public Size ClientSize - { - get { return Bounds.Size.ToAvalonia(); } - set { InvokeOnMainThread(() => Resized?.Invoke(ClientSize)); } - } - - public void Activate() - { - } + public Size ClientSize => Bounds.Size.ToAvalonia(); public override void Draw(CGRect rect) { @@ -93,42 +78,9 @@ namespace Avalonia.iOS { //Not supported } - - public void Show() - { - _keyboardHelper.ActivateAutoShowKeybord(); - } - - public void BeginMoveDrag() - { - //Not supported - } - - public void BeginResizeDrag(WindowEdge edge) - { - //Not supported - } - - public Point Position - { - get { return _position; } - set - { - _position = value; - PositionChanged?.Invoke(_position); - } - } - - public Size MaxClientSize => Bounds.Size.ToAvalonia(); - + public IEnumerable Surfaces => new object[] { this }; - - - public void Hide() - { - //Not supported - } - + public override void TouchesEnded(NSSet touches, UIEvent evt) { var touch = touches.AnyObject as UITouch; @@ -182,11 +134,7 @@ namespace Avalonia.iOS _touchLastPoint = location; } } - - public void SetIcon(IWindowIconImpl icon) - { - } - + public ILockedFramebuffer Lock() => new EmulatedFramebuffer(this); } } diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj index aea972ec42..c581cccf2f 100644 --- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj +++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj @@ -101,6 +101,7 @@ + diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 0e783e3f8d..e19729706d 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -86,7 +86,6 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(TestServices.StyledWindow)) { var impl = new Mock(); - impl.SetupProperty(x => x.ClientSize); impl.SetupProperty(x => x.Resized); impl.SetupGet(x => x.Scaling).Returns(1); @@ -106,28 +105,7 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Impl_ClientSize_Should_Be_Set_After_Layout_Pass() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var impl = Mock.Of(x => x.Scaling == 1); - - var target = new TestTopLevel(impl) - { - Template = CreateTemplate(), - Content = new TextBlock - { - Width = 321, - Height = 432, - } - }; - - LayoutManager.Instance.ExecuteInitialLayoutPass(target); - - Mock.Get(impl).VerifySet(x => x.ClientSize = new Size(321, 432)); - } - } + [Fact] public void Width_And_Height_Should_Not_Be_Set_After_Layout_Pass() @@ -163,38 +141,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Activate_Should_Call_Impl_Activate() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var impl = new Mock(); - var target = new TestTopLevel(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(); - impl.SetupAllProperties(); - - bool raised = false; - var target = new TestTopLevel(impl.Object); - target.Activated += (s, e) => raised = true; - - impl.Object.Activated(); - - Assert.True(raised); - } - } - [Fact] public void Impl_Close_Should_Call_Raise_Closed_Event() { @@ -213,24 +159,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Impl_Deactivate_Should_Call_Raise_Activated_Event() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var impl = new Mock(); - impl.SetupAllProperties(); - - bool raised = false; - var target = new TestTopLevel(impl.Object); - target.Deactivated += (s, e) => raised = true; - - impl.Object.Deactivated(); - - Assert.True(raised); - } - } - [Fact] public void Impl_Input_Should_Pass_Input_To_InputManager() { diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs new file mode 100644 index 0000000000..d4b850d66e --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -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(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(); + 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(); + 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(); + 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 CreateTemplate() + { + return new FuncControlTemplate(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; + } + } + } +} diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs index b3e9b981a5..9922b624e5 100644 --- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs @@ -139,7 +139,10 @@ namespace Avalonia.Layout.UnitTests var renderInterface = fixture.Create(); var windowImpl = new Mock(); - windowImpl.SetupProperty(x => x.ClientSize); + Size clientSize = default(Size); + + windowImpl.SetupGet(x => x.ClientSize).Returns(() => clientSize); + windowImpl.Setup(x => x.Resize(It.IsAny())).Callback(s => clientSize = s); windowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1024, 1024)); windowImpl.SetupGet(x => x.Scaling).Returns(1);