// -----------------------------------------------------------------------
//
// Copyright 2014 MIT Licence. See licence.md for more information.
//
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Perspex.Input;
using Perspex.Input.Raw;
using Perspex.Layout;
using Perspex.Platform;
using Perspex.Rendering;
using Perspex.Styling;
using Perspex.Threading;
using Perspex.Controls.Primitives;
using Perspex.Interactivity;
using Splat;
///
/// 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 abstract class TopLevel : ContentControl, IInputRoot, ILayoutRoot, IRenderRoot, ICloseable
{
///
/// Defines the property.
///
public static readonly PerspexProperty ClientSizeProperty =
PerspexProperty.Register("ClientSize");
///
/// Defines the property.
///
public static readonly PerspexProperty IsActiveProperty =
PerspexProperty.Register("IsActive");
///
/// The dispatcher for the window.
///
private Dispatcher dispatcher;
///
/// The render manager for the window.s
///
private IRenderManager renderManager;
///
/// The window renderer.
///
private IRenderer renderer;
///
/// The input manager for the window.
///
private IInputManager inputManager;
///
/// The access key handler for the window.
///
private IAccessKeyHandler accessKeyHandler;
///
/// The access keyboard navigation handler for the window.
///
private IKeyboardNavigationHandler keyboardNavigationHandler;
///
/// Whether an auto-size operation is in progress.
///
private bool autoSizing;
///
/// Initializes static members of the class.
///
static TopLevel()
{
TopLevel.AffectsMeasure(TopLevel.ClientSizeProperty);
}
///
/// Initializes a new instance of the class.
///
/// The platform-specific window implementation.
public TopLevel(ITopLevelImpl impl)
: this(impl, Locator.Current)
{
}
///
/// Initializes a new instance of the class.
///
/// The platform-specific window implementation.
///
/// The dependency resolver to use. If null the default dependency resolver will be used.
///
public TopLevel(ITopLevelImpl impl, IDependencyResolver dependencyResolver)
{
dependencyResolver = dependencyResolver ?? Locator.Current;
IPlatformRenderInterface renderInterface = dependencyResolver.GetService();
this.PlatformImpl = impl;
this.accessKeyHandler = dependencyResolver.GetService();
this.inputManager = dependencyResolver.GetService();
this.keyboardNavigationHandler = dependencyResolver.GetService();
this.LayoutManager = dependencyResolver.GetService();
this.renderManager = dependencyResolver.GetService();
if (renderInterface == null)
{
throw new InvalidOperationException(
"Could not create an interface to the rendering subsystem: maybe no rendering subsystem was initialized?");
}
if (this.PlatformImpl == null)
{
throw new InvalidOperationException(
"Could not create window implementation: maybe no windowing subsystem was initialized?");
}
if (this.inputManager == null)
{
throw new InvalidOperationException(
"Could not create input manager: maybe Application.RegisterServices() wasn't called?");
}
if (this.LayoutManager == null)
{
throw new InvalidOperationException(
"Could not create layout manager: maybe Application.RegisterServices() wasn't called?");
}
if (this.renderManager == null)
{
throw new InvalidOperationException(
"Could not create render manager: maybe Application.RegisterServices() wasn't called?");
}
if (this.accessKeyHandler != null)
{
this.accessKeyHandler.SetOwner(this);
}
if (this.keyboardNavigationHandler != null)
{
this.keyboardNavigationHandler.SetOwner(this);
}
this.PlatformImpl.SetOwner(this);
this.PlatformImpl.Activated = this.HandleActivated;
this.PlatformImpl.Deactivated = this.HandleDeactivated;
this.PlatformImpl.Closed = this.HandleClosed;
this.PlatformImpl.Input = this.HandleInput;
this.PlatformImpl.Paint = this.HandlePaint;
this.PlatformImpl.Resized = this.HandleResized;
Size clientSize = this.ClientSize = this.PlatformImpl.ClientSize;
this.dispatcher = Dispatcher.UIThread;
this.renderer = renderInterface.CreateRenderer(this.PlatformImpl.Handle, clientSize.Width, clientSize.Height);
this.LayoutManager.Root = this;
this.LayoutManager.LayoutNeeded.Subscribe(_ => this.HandleLayoutNeeded());
this.LayoutManager.LayoutCompleted.Subscribe(_ => this.HandleLayoutCompleted());
this.renderManager.RenderNeeded.Subscribe(_ => this.HandleRenderNeeded());
IStyler styler = dependencyResolver.GetService();
styler.ApplyStyles(this);
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => this.PlatformImpl.ClientSize = x);
}
///
/// 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;
///
/// Gets or sets the client size of the window.
///
public Size ClientSize
{
get { return this.GetValue(ClientSizeProperty); }
private set { this.SetValue(ClientSizeProperty, value); }
}
///
/// Gets a value that indicates whether the window is active.
///
public bool IsActive
{
get { return this.GetValue(IsActiveProperty); }
private set { this.SetValue(IsActiveProperty, value); }
}
///
/// Gets the layout manager for the window.
///
public ILayoutManager LayoutManager
{
get;
private set;
}
///
/// Gets the platform-specific window implementation.
///
public ITopLevelImpl PlatformImpl
{
get;
}
///
/// Gets the window renderer.
///
IRenderer IRenderRoot.Renderer
{
get { return this.renderer; }
}
///
/// Gets the window render manager.
///
IRenderManager IRenderRoot.RenderManager
{
get { return this.renderManager; }
}
///
/// Gets the access key handler for the window.
///
IAccessKeyHandler IInputRoot.AccessKeyHandler
{
get { return this.accessKeyHandler; }
}
///
/// Gets or sets the keyboard navigation handler for the window.
///
IKeyboardNavigationHandler IInputRoot.KeyboardNavigationHandler
{
get { return this.keyboardNavigationHandler; }
}
///
/// Gets or sets a value indicating whether access keys are shown in the window.
///
bool IInputRoot.ShowAccessKeys
{
get { return this.GetValue(AccessText.ShowAccessKeyProperty); }
set { this.SetValue(AccessText.ShowAccessKeyProperty, value); }
}
///
/// Translates a point from window coordinates into screen coordinates.
///
/// The point.
/// The point in screen coordinates.
Point IRenderRoot.TranslatePointToScreen(Point p)
{
return this.PlatformImpl.PointToScreen(p);
}
///
/// Activates the window.
///
public void Activate()
{
this.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()
{
this.autoSizing = true;
return Disposable.Create(() => this.autoSizing = false);
}
///
/// Carries out the arrange pass of the window.
///
/// The final window size.
/// The parameter unchanged.
protected override Size ArrangeOverride(Size finalSize)
{
using (this.BeginAutoSizing())
{
this.PlatformImpl.ClientSize = finalSize;
}
return base.ArrangeOverride(finalSize);
}
///
/// Handles an activated notification from .
///
private void HandleActivated()
{
if (this.Activated != null)
{
this.Activated(this, EventArgs.Empty);
}
var scope = this as IFocusScope;
if (scope != null)
{
FocusManager.Instance.SetFocusScope(scope);
}
this.IsActive = true;
}
///
/// Handles a closed notification from .
///
private void HandleClosed()
{
if (this.Closed != null)
{
this.Closed(this, EventArgs.Empty);
}
}
///
/// Handles a deactivated notification from .
///
private void HandleDeactivated()
{
this.IsActive = false;
if (this.Deactivated != null)
{
this.Deactivated(this, EventArgs.Empty);
}
}
///
/// Handles input from .
///
/// The event args.
private void HandleInput(RawInputEventArgs e)
{
this.inputManager.Process(e);
}
///
/// Handles a layout request from .
///
private void HandleLayoutNeeded()
{
this.dispatcher.InvokeAsync(this.LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render);
}
///
/// Handles a layout completion request from .
///
private void HandleLayoutCompleted()
{
this.renderManager.InvalidateRender(this);
}
///
/// Handles a render request from .
///
private void HandleRenderNeeded()
{
this.dispatcher.InvokeAsync(
() => this.PlatformImpl.Invalidate(new Rect(this.ClientSize)),
DispatcherPriority.Render);
}
///
/// Handles a paint request from .
///
/// The rectangle to paint.
/// An optional platform-specific handle.
private void HandlePaint(Rect rect, IPlatformHandle handle)
{
this.renderer.Render(this, handle);
this.renderManager.RenderFinished();
}
///
/// Handles a resize notification from .
///
/// The new client size.
private void HandleResized(Size clientSize)
{
if (!this.autoSizing)
{
this.Width = clientSize.Width;
this.Height = clientSize.Height;
}
this.ClientSize = clientSize;
this.renderer.Resize((int)clientSize.Width, (int)clientSize.Height);
this.LayoutManager.ExecuteLayoutPass();
this.PlatformImpl.Invalidate(new Rect(clientSize));
}
}
}