From 97388703b3e986baf1d4ce1a7cc80f96cb70a714 Mon Sep 17 00:00:00 2001 From: Jason Jarvis Date: Wed, 13 Apr 2016 21:44:12 -0700 Subject: [PATCH] Adding various other runtime application configuration extensions to improve boilerplate main startup. Updated most Sample applications to use simpler bootstrapping extensions. --- samples/BindingTest/App.xaml.cs | 24 +- samples/BindingTest/MainWindow.xaml.cs | 5 +- samples/ControlCatalog/App.xaml.cs | 52 +- samples/ControlCatalog/ControlCatalog.csproj | 1 + samples/ControlCatalog/MainWindow.xaml.cs | 7 +- samples/ControlCatalog/Program.cs | 127 ++++ samples/TestApplication/Program.cs | 29 +- samples/TestApplicationShared/App.cs | 46 +- samples/XamlTestApplication/App.cs | 23 +- samples/XamlTestApplication/Program.cs | 44 +- samples/XamlTestApplicationPcl/XamlTestApp.cs | 15 +- .../Perspex.Markup.Xaml/PerspexXamlLoader.cs | 12 + src/Perspex.Controls/Window.cs | 631 +++++++++--------- src/Perspex.Diagnostics/DevTools.xaml.cs | 13 +- 14 files changed, 573 insertions(+), 456 deletions(-) create mode 100644 samples/ControlCatalog/Program.cs diff --git a/samples/BindingTest/App.xaml.cs b/samples/BindingTest/App.xaml.cs index 5ffac249e9..38b9aea430 100644 --- a/samples/BindingTest/App.xaml.cs +++ b/samples/BindingTest/App.xaml.cs @@ -13,30 +13,20 @@ namespace BindingTest public App() { RegisterServices(); - InitializeSubsystems((int)Environment.OSVersion.Platform); - InitializeComponent(); - InitializeLogging(); - } - - public static void AttachDevTools(Window window) - { - DevTools.Attach(window); } private static void Main() { - var app = new App(); - var window = new MainWindow(); - window.Show(); - app.Run(window); - } + InitializeLogging(); - private void InitializeComponent() - { - PerspexXamlLoader.Load(this); + new App() + .UseWin32Subsystem() + .UseDirect2D() + .LoadFromXaml() + .RunWithMainWindow(); } - private void InitializeLogging() + private static void InitializeLogging() { #if DEBUG SerilogLogger.Initialize(new LoggerConfiguration() diff --git a/samples/BindingTest/MainWindow.xaml.cs b/samples/BindingTest/MainWindow.xaml.cs index a8e64485cb..61b1e0ffb8 100644 --- a/samples/BindingTest/MainWindow.xaml.cs +++ b/samples/BindingTest/MainWindow.xaml.cs @@ -1,4 +1,5 @@ using BindingTest.ViewModels; +using Perspex; using Perspex.Controls; using Perspex.Markup.Xaml; @@ -10,12 +11,12 @@ namespace BindingTest { this.InitializeComponent(); this.DataContext = new MainWindowViewModel(); - App.AttachDevTools(this); + this.AttachDevTools(); } private void InitializeComponent() { - PerspexXamlLoader.Load(this); + this.LoadFromXaml(); } } } diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 2930fa746a..f5cc993e2c 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -9,58 +9,14 @@ using Serilog; namespace ControlCatalog { + // Eventually we should move this into a PCL library so we can access + // from mobile platforms + // class App : Application { public App() { RegisterServices(); - InitializeSubsystems(GetPlatformId()); - InitializeLogging(); - InitializeComponent(); } - - public static void AttachDevTools(Window window) - { -#if DEBUG - DevTools.Attach(window); -#endif - } - - static void Main(string[] args) - { - var app = new App(); - var window = new MainWindow(); - window.Show(); - app.Run(window); - } - - private void InitializeComponent() - { - PerspexXamlLoader.Load(this); - } - - private void InitializeLogging() - { -#if DEBUG - SerilogLogger.Initialize(new LoggerConfiguration() - .MinimumLevel.Warning() - .WriteTo.Trace(outputTemplate: "{Area}: {Message}") - .CreateLogger()); -#endif - } - - private int GetPlatformId() - { - var args = Environment.GetCommandLineArgs(); - - if (args.Contains("--gtk")) - { - return (int)PlatformID.Unix; - } - else - { - return (int)Environment.OSVersion.Platform; - } - } - } + } } diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 087998c805..36238efc7c 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -103,6 +103,7 @@ SliderPage.xaml + diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 5a5b7d4303..67b88dc71f 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -1,4 +1,5 @@ -using Perspex.Controls; +using Perspex; +using Perspex.Controls; using Perspex.Markup.Xaml; namespace ControlCatalog @@ -8,12 +9,12 @@ namespace ControlCatalog public MainWindow() { this.InitializeComponent(); - App.AttachDevTools(this); + this.AttachDevTools(); } private void InitializeComponent() { - PerspexXamlLoader.Load(this); + this.LoadFromXaml(); } } } diff --git a/samples/ControlCatalog/Program.cs b/samples/ControlCatalog/Program.cs new file mode 100644 index 0000000000..0627e77019 --- /dev/null +++ b/samples/ControlCatalog/Program.cs @@ -0,0 +1,127 @@ +using Perspex.Logging.Serilog; +using Serilog; +using System; +using System.Linq; +using Perspex; + +namespace ControlCatalog +{ + internal class Program + { + static void Main(string[] args) + { + InitializeLogging(); + + new App() + .ConfigureRenderSystem(args) + .LoadFromXaml() + .RunWithMainWindow(); + } + + // This will be made into a runtime configuration extension soon! + private static void InitializeLogging() + { +#if DEBUG + SerilogLogger.Initialize(new LoggerConfiguration() + .MinimumLevel.Warning() + .WriteTo.Trace(outputTemplate: "{Area}: {Message}") + .CreateLogger()); +#endif + } + + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Experimental: Would like to move this into a shared location once I figure out the best place for it + // considering all common libraries are PCL and do not have access to Environment.OSVersion.Platform + // nor do they have access to the platform specific render/subsystem extensions. + // + // Perhaps via DI we register each system with a priority/rank + // + public static class RenderSystemExtensions + { + [Flags] + enum RenderSystem + { + None = 0, + GTK = 1, + Skia = 2, + Direct2D = 4 + }; + + /// + /// Default (Optimal) render system for a particular platform + /// + /// + private static RenderSystem DefaultRenderSystem() + { + switch (Environment.OSVersion.Platform) + { + case PlatformID.MacOSX: + return RenderSystem.GTK; + + case PlatformID.Unix: + return RenderSystem.GTK; + + case PlatformID.Win32Windows: + return RenderSystem.Direct2D; + } + + return RenderSystem.None; + } + + /// + /// Returns an array of avalidable rendering systems in priority order + /// + /// + private static RenderSystem[] AvailableRenderSystems() + { + switch (Environment.OSVersion.Platform) + { + case PlatformID.MacOSX: + return new RenderSystem[] { RenderSystem.GTK, RenderSystem.Skia }; + + case PlatformID.Unix: + return new RenderSystem[] { RenderSystem.GTK, RenderSystem.Skia }; + + case PlatformID.Win32Windows: + return new RenderSystem[] { RenderSystem.Direct2D, RenderSystem.Skia, RenderSystem.GTK }; + } + + return new RenderSystem[0]; + } + + /// + /// Selects the optimal render system for desktop platforms. Supports cmd line overrides + /// + /// + /// + public static AppT ConfigureRenderSystem(this AppT app, string[] args) where AppT : Application + { + // So this all works great under Windows where it can support + // ALL configurations. But on OSX/Unix we cannot use Direct2D + // + if (args.Contains("--gtk") || DefaultRenderSystem() == RenderSystem.GTK) + { + app.UseGtkSubsystem(); + app.UseCairo(); + } + else + { + app.UseWin32Subsystem(); + + // not available until we do the SkiaSharp merge + //if (args.Contains("--skia") || DefaultRenderSystem() == RenderSystem.Skia) + //{ + // app.UseSkia(); + //} + //else + { + app.UseDirect2D(); + } + } + + return app; + } + } +} diff --git a/samples/TestApplication/Program.cs b/samples/TestApplication/Program.cs index 7aa433bfa8..f7169290a7 100644 --- a/samples/TestApplication/Program.cs +++ b/samples/TestApplication/Program.cs @@ -31,11 +31,30 @@ namespace TestApplication // The version of ReactiveUI currently included is for WPF and so expects a WPF // dispatcher. This makes sure it's initialized. System.Windows.Threading.Dispatcher foo = System.Windows.Threading.Dispatcher.CurrentDispatcher; - new App(); - MainWindow.RootNamespace = "TestApplication"; - var wnd = MainWindow.Create(); - DevTools.Attach(wnd); - Application.Current.Run(wnd); + + var app = new App(); + + if (args.Contains("--gtk")) + { + app.UseGtkSubsystem(); + app.UseCairo(); + } + else + { + app.UseWin32Subsystem(); + + // not available until we do the SkiaSharp merge + //if (args.Contains("--skia")) + //{ + // app.UseSkia(); + //} + //else + { + app.UseDirect2D(); + } + } + + app.Run(); } } } diff --git a/samples/TestApplicationShared/App.cs b/samples/TestApplicationShared/App.cs index e4211ffb82..83987d26df 100644 --- a/samples/TestApplicationShared/App.cs +++ b/samples/TestApplicationShared/App.cs @@ -8,6 +8,9 @@ using Perspex.Controls.Templates; using Perspex.Markup.Xaml; using Perspex.Styling; using Perspex.Themes.Default; +using Perspex.Diagnostics; +using Perspex.Platform; +using Perspex.Shared.PlatformSupport; namespace TestApplication { @@ -15,22 +18,35 @@ namespace TestApplication { public App() { + // TODO: I believe this has to happen before we select sub systems. Can we + // move this safely into Application itself? Is there anything in here + // that is platform specific?? + // RegisterServices(); - InitializeSubsystems((int)Environment.OSVersion.Platform); - Styles.Add(new DefaultTheme()); + } - var loader = new PerspexXamlLoader(); - var baseLight = (IStyle)loader.Load( - new Uri("resm:Perspex.Themes.Default.Accents.BaseLight.xaml?assembly=Perspex.Themes.Default")); - Styles.Add(baseLight); + public void Run() + { + Styles.Add(new DefaultTheme()); - Styles.Add(new SampleTabStyle()); - DataTemplates = new DataTemplates - { - new FuncTreeDataTemplate( - x => new TextBlock {Text = x.Name}, - x => x.Children), - }; - } - } + var loader = new PerspexXamlLoader(); + var baseLight = (IStyle)loader.Load( + new Uri("resm:Perspex.Themes.Default.Accents.BaseLight.xaml?assembly=Perspex.Themes.Default")); + Styles.Add(baseLight); + + Styles.Add(new SampleTabStyle()); + DataTemplates = new DataTemplates + { + new FuncTreeDataTemplate( + x => new TextBlock {Text = x.Name}, + x => x.Children), + }; + + MainWindow.RootNamespace = "TestApplication"; + var wnd = MainWindow.Create(); + DevTools.Attach(wnd); + + Run(wnd); + } + } } diff --git a/samples/XamlTestApplication/App.cs b/samples/XamlTestApplication/App.cs index 01ac91299c..8841a02c46 100644 --- a/samples/XamlTestApplication/App.cs +++ b/samples/XamlTestApplication/App.cs @@ -7,26 +7,5 @@ using Serilog; namespace XamlTestApplication { - public class App : XamlTestApp - { - public App() - { - InitializeLogging(); - } - - protected override void RegisterPlatform() - { - InitializeSubsystems((int)Environment.OSVersion.Platform); - } - - private void InitializeLogging() - { -#if DEBUG - SerilogLogger.Initialize(new LoggerConfiguration() - .MinimumLevel.Warning() - .WriteTo.Trace(outputTemplate: "{Area}: {Message}") - .CreateLogger()); -#endif - } - } + // No longer needed! } diff --git a/samples/XamlTestApplication/Program.cs b/samples/XamlTestApplication/Program.cs index 76d1aa7efb..339cd1ae30 100644 --- a/samples/XamlTestApplication/Program.cs +++ b/samples/XamlTestApplication/Program.cs @@ -1,39 +1,39 @@ // Copyright (c) The Perspex 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.Linq; using System.Diagnostics; using System.Windows.Threading; using Perspex; -using Perspex.Collections; -using Perspex.Controls; -using Perspex.Controls.Templates; -using ReactiveUI; -using XamlTestApplication.Views; +using Serilog; +using Perspex.Logging.Serilog; namespace XamlTestApplication { internal class Program { - private static void Main() + private static void Main(string[] args) { - var sw = new Stopwatch(); - sw.Start(); - + // this sucks. Can we fix this? Do we even need it anymore? var foo = Dispatcher.CurrentDispatcher; - App application = new App - { - - }; - - var window = new MainWindow(); - window.Show(); + InitializeLogging(); - sw.Stop(); - Debug.WriteLine($"Startup: {sw.ElapsedMilliseconds}ms"); - - Application.Current.Run(window); + new XamlTestApp() + .UseWin32Subsystem() + .UseDirect2D() + .LoadFromXaml() + .RunWithMainWindow(); } - } + + private static void InitializeLogging() + { +#if DEBUG + SerilogLogger.Initialize(new LoggerConfiguration() + .MinimumLevel.Warning() + .WriteTo.Trace(outputTemplate: "{Area}: {Message}") + .CreateLogger()); +#endif + } + } } \ No newline at end of file diff --git a/samples/XamlTestApplicationPcl/XamlTestApp.cs b/samples/XamlTestApplicationPcl/XamlTestApp.cs index a4d4ac2e0e..936b35699b 100644 --- a/samples/XamlTestApplicationPcl/XamlTestApp.cs +++ b/samples/XamlTestApplicationPcl/XamlTestApp.cs @@ -1,23 +1,14 @@ using Perspex; using Perspex.Markup.Xaml; +using XamlTestApplication.Views; namespace XamlTestApplication { - public abstract class XamlTestApp : Application + public class XamlTestApp : Application { - protected abstract void RegisterPlatform(); - public XamlTestApp() { RegisterServices(); - RegisterPlatform(); - InitializeComponent(); - } - - private void InitializeComponent() - { - var loader = new PerspexXamlLoader(); - loader.Load(typeof(XamlTestApp), this); } - } + } } diff --git a/src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs b/src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs index 389e646215..1bbff1c886 100644 --- a/src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs +++ b/src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs @@ -187,3 +187,15 @@ namespace Perspex.Markup.Xaml } } } + +namespace Perspex +{ + public static class XamlObjectExtensions + { + public static ObjectT LoadFromXaml(this ObjectT obj) + { + Markup.Xaml.PerspexXamlLoader.Load(obj); + return obj; + } + } +} diff --git a/src/Perspex.Controls/Window.cs b/src/Perspex.Controls/Window.cs index 7692f22e01..cf6a3c7190 100644 --- a/src/Perspex.Controls/Window.cs +++ b/src/Perspex.Controls/Window.cs @@ -14,314 +14,327 @@ using System.Collections.Generic; namespace Perspex.Controls { - /// - /// Determines how a will size itself to fit its content. - /// - public enum SizeToContent - { - /// - /// The window will not automatically size itself to fit its content. - /// - Manual, - - /// - /// The window will size itself horizontally to fit its content. - /// - Width, - - /// - /// The window will size itself vertically to fit its content. - /// - Height, - - /// - /// The window will size itself horizontally and vertically to fit its content. - /// - WidthAndHeight, - } - - /// - /// A top-level window. - /// - public class Window : TopLevel, IStyleable, IFocusScope, ILayoutRoot, INameScope - { - private static IList s_windows = new List(); - - /// - /// Retrieves an enumeration of all Windows in the currently running application. - /// - public static IList OpenWindows => s_windows; - - /// - /// Defines the property. - /// - public static readonly StyledProperty SizeToContentProperty = - PerspexProperty.Register(nameof(SizeToContent)); - - /// - /// Enables of disables system window decorations (title bar, buttons, etc) - /// - public static readonly StyledProperty HasSystemDecorationsProperty = - PerspexProperty.Register(nameof(HasSystemDecorations), true); - - /// - /// Defines the property. - /// - public static readonly StyledProperty TitleProperty = - PerspexProperty.Register(nameof(Title), "Window"); - - private readonly NameScope _nameScope = new NameScope(); - private object _dialogResult; - private readonly Size _maxPlatformClientSize; - - /// - /// Initializes static members of the class. - /// - static Window() - { - BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White); - TitleProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl.SetTitle((string)e.NewValue)); - HasSystemDecorationsProperty.Changed.AddClassHandler( + /// + /// Determines how a will size itself to fit its content. + /// + public enum SizeToContent + { + /// + /// The window will not automatically size itself to fit its content. + /// + Manual, + + /// + /// The window will size itself horizontally to fit its content. + /// + Width, + + /// + /// The window will size itself vertically to fit its content. + /// + Height, + + /// + /// The window will size itself horizontally and vertically to fit its content. + /// + WidthAndHeight, + } + + /// + /// A top-level window. + /// + public class Window : TopLevel, IStyleable, IFocusScope, ILayoutRoot, INameScope + { + private static IList s_windows = new List(); + + /// + /// Retrieves an enumeration of all Windows in the currently running application. + /// + public static IList OpenWindows => s_windows; + + /// + /// Defines the property. + /// + public static readonly StyledProperty SizeToContentProperty = + PerspexProperty.Register(nameof(SizeToContent)); + + /// + /// Enables of disables system window decorations (title bar, buttons, etc) + /// + public static readonly StyledProperty HasSystemDecorationsProperty = + PerspexProperty.Register(nameof(HasSystemDecorations), true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty TitleProperty = + PerspexProperty.Register(nameof(Title), "Window"); + + private readonly NameScope _nameScope = new NameScope(); + private object _dialogResult; + private readonly Size _maxPlatformClientSize; + + /// + /// Initializes static members of the class. + /// + static Window() + { + BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White); + TitleProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl.SetTitle((string)e.NewValue)); + HasSystemDecorationsProperty.Changed.AddClassHandler( (s, e) => s.PlatformImpl.SetSystemDecorations((bool) e.NewValue)); - } - - /// - /// Initializes a new instance of the class. - /// - public Window() - : base(PlatformManager.CreateWindow()) - { - _maxPlatformClientSize = this.PlatformImpl.MaxClientSize; - } - - /// - event EventHandler INameScope.Registered - { - add { _nameScope.Registered += value; } - remove { _nameScope.Registered -= value; } - } - - /// - event EventHandler INameScope.Unregistered - { - add { _nameScope.Unregistered += value; } - remove { _nameScope.Unregistered -= value; } - } - - /// - /// Gets the platform-specific window implementation. - /// - public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl; - - /// - /// Gets or sets a value indicating how the window will size itself to fit its content. - /// - public SizeToContent SizeToContent - { - get { return GetValue(SizeToContentProperty); } - set { SetValue(SizeToContentProperty, value); } - } - - /// - /// Gets or sets the title of the window. - /// - public string Title - { - get { return GetValue(TitleProperty); } - set { SetValue(TitleProperty, value); } - } - - /// - /// Enables of disables system window decorations (title bar, buttons, etc) - /// - /// - public bool HasSystemDecorations - { - get { return GetValue(HasSystemDecorationsProperty); } - set { SetValue(HasSystemDecorationsProperty, value); } - } - - /// - /// Gets or sets the minimized/maximized state of the window. - /// - public WindowState WindowState - { - get { return this.PlatformImpl.WindowState; } - set { this.PlatformImpl.WindowState = value; } - } - - /// - Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize; - - /// - Type IStyleable.StyleKey => typeof(Window); - - /// - /// Closes the window. - /// - public void Close() - { - s_windows.Remove(this); - PlatformImpl.Dispose(); - } - - protected override void HandleApplicationExiting() - { - base.HandleApplicationExiting(); - Close(); - } - - /// - /// Closes a dialog window with the specified result. - /// - /// The dialog result. - /// - /// When the window is shown with the method, the - /// resulting task will produce the value when the window - /// is closed. - /// - public void Close(object dialogResult) - { - _dialogResult = dialogResult; - Close(); - } - - /// - /// Hides the window but does not close it. - /// - public void Hide() - { - using (BeginAutoSizing()) - { - PlatformImpl.Hide(); - } - } - - /// - /// Shows the window. - /// - public void Show() - { - s_windows.Add(this); - - EnsureInitialized(); - LayoutManager.Instance.ExecuteInitialLayoutPass(this); - - using (BeginAutoSizing()) - { - PlatformImpl.Show(); - } - } - - /// - /// Shows the window as a dialog. - /// - /// - /// A task that can be used to track the lifetime of the dialog. - /// - public Task ShowDialog() - { - return ShowDialog(); - } - - /// - /// Shows the window as a dialog. - /// - /// - /// The type of the result produced by the dialog. - /// - /// . - /// A task that can be used to retrive the result of the dialog when it closes. - /// - public Task ShowDialog() - { - s_windows.Add(this); - - EnsureInitialized(); - LayoutManager.Instance.ExecuteInitialLayoutPass(this); - - using (BeginAutoSizing()) - { - var modal = PlatformImpl.ShowDialog(); - var result = new TaskCompletionSource(); - - Observable.FromEventPattern(this, nameof(Closed)) - .Take(1) - .Subscribe(_ => - { - modal.Dispose(); - result.SetResult((TResult)_dialogResult); - }); - - return result.Task; - } - } - - /// - void INameScope.Register(string name, object element) - { - _nameScope.Register(name, element); - } - - /// - object INameScope.Find(string name) - { - return _nameScope.Find(name); - } - - /// - void INameScope.Unregister(string name) - { - _nameScope.Unregister(name); - } - - /// - protected override Size MeasureOverride(Size availableSize) - { - var sizeToContent = SizeToContent; - var size = ClientSize; - var desired = base.MeasureOverride(availableSize.Constrain(_maxPlatformClientSize)); - - switch (sizeToContent) - { - case SizeToContent.Width: - size = new Size(desired.Width, ClientSize.Height); - break; - case SizeToContent.Height: - size = new Size(ClientSize.Width, desired.Height); - break; - case SizeToContent.WidthAndHeight: - size = new Size(desired.Width, desired.Height); - break; - case SizeToContent.Manual: - size = ClientSize; - break; - default: - throw new InvalidOperationException("Invalid value for SizeToContent."); - } - - return size; - } - - /// - protected override void HandleResized(Size clientSize) - { - if (!AutoSizing) - { - SizeToContent = SizeToContent.Manual; - } - - base.HandleResized(clientSize); - } - - private void EnsureInitialized() - { - if (!this.IsInitialized) - { - var init = (ISupportInitialize)this; - init.BeginInit(); - init.EndInit(); - } - } - } + } + + /// + /// Initializes a new instance of the class. + /// + public Window() + : base(PlatformManager.CreateWindow()) + { + _maxPlatformClientSize = this.PlatformImpl.MaxClientSize; + } + + /// + event EventHandler INameScope.Registered + { + add { _nameScope.Registered += value; } + remove { _nameScope.Registered -= value; } + } + + /// + event EventHandler INameScope.Unregistered + { + add { _nameScope.Unregistered += value; } + remove { _nameScope.Unregistered -= value; } + } + + /// + /// Gets the platform-specific window implementation. + /// + public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl; + + /// + /// Gets or sets a value indicating how the window will size itself to fit its content. + /// + public SizeToContent SizeToContent + { + get { return GetValue(SizeToContentProperty); } + set { SetValue(SizeToContentProperty, value); } + } + + /// + /// Gets or sets the title of the window. + /// + public string Title + { + get { return GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// Enables of disables system window decorations (title bar, buttons, etc) + /// + /// + public bool HasSystemDecorations + { + get { return GetValue(HasSystemDecorationsProperty); } + set { SetValue(HasSystemDecorationsProperty, value); } + } + + /// + /// Gets or sets the minimized/maximized state of the window. + /// + public WindowState WindowState + { + get { return this.PlatformImpl.WindowState; } + set { this.PlatformImpl.WindowState = value; } + } + + /// + Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize; + + /// + Type IStyleable.StyleKey => typeof(Window); + + /// + /// Closes the window. + /// + public void Close() + { + s_windows.Remove(this); + PlatformImpl.Dispose(); + } + + protected override void HandleApplicationExiting() + { + base.HandleApplicationExiting(); + Close(); + } + + /// + /// Closes a dialog window with the specified result. + /// + /// The dialog result. + /// + /// When the window is shown with the method, the + /// resulting task will produce the value when the window + /// is closed. + /// + public void Close(object dialogResult) + { + _dialogResult = dialogResult; + Close(); + } + + /// + /// Hides the window but does not close it. + /// + public void Hide() + { + using (BeginAutoSizing()) + { + PlatformImpl.Hide(); + } + } + + /// + /// Shows the window. + /// + public void Show() + { + s_windows.Add(this); + + EnsureInitialized(); + LayoutManager.Instance.ExecuteInitialLayoutPass(this); + + using (BeginAutoSizing()) + { + PlatformImpl.Show(); + } + } + + /// + /// Shows the window as a dialog. + /// + /// + /// A task that can be used to track the lifetime of the dialog. + /// + public Task ShowDialog() + { + return ShowDialog(); + } + + /// + /// Shows the window as a dialog. + /// + /// + /// The type of the result produced by the dialog. + /// + /// . + /// A task that can be used to retrive the result of the dialog when it closes. + /// + public Task ShowDialog() + { + s_windows.Add(this); + + EnsureInitialized(); + LayoutManager.Instance.ExecuteInitialLayoutPass(this); + + using (BeginAutoSizing()) + { + var modal = PlatformImpl.ShowDialog(); + var result = new TaskCompletionSource(); + + Observable.FromEventPattern(this, nameof(Closed)) + .Take(1) + .Subscribe(_ => + { + modal.Dispose(); + result.SetResult((TResult)_dialogResult); + }); + + return result.Task; + } + } + + /// + void INameScope.Register(string name, object element) + { + _nameScope.Register(name, element); + } + + /// + object INameScope.Find(string name) + { + return _nameScope.Find(name); + } + + /// + void INameScope.Unregister(string name) + { + _nameScope.Unregister(name); + } + + /// + protected override Size MeasureOverride(Size availableSize) + { + var sizeToContent = SizeToContent; + var size = ClientSize; + var desired = base.MeasureOverride(availableSize.Constrain(_maxPlatformClientSize)); + + switch (sizeToContent) + { + case SizeToContent.Width: + size = new Size(desired.Width, ClientSize.Height); + break; + case SizeToContent.Height: + size = new Size(ClientSize.Width, desired.Height); + break; + case SizeToContent.WidthAndHeight: + size = new Size(desired.Width, desired.Height); + break; + case SizeToContent.Manual: + size = ClientSize; + break; + default: + throw new InvalidOperationException("Invalid value for SizeToContent."); + } + + return size; + } + + /// + protected override void HandleResized(Size clientSize) + { + if (!AutoSizing) + { + SizeToContent = SizeToContent.Manual; + } + + base.HandleResized(clientSize); + } + + private void EnsureInitialized() + { + if (!this.IsInitialized) + { + var init = (ISupportInitialize)this; + init.BeginInit(); + init.EndInit(); + } + } + } +} + +namespace Perspex +{ + public static class WindowApplicationExtensions + { + public static void RunWithMainWindow(this Application app) where WindowT : Perspex.Controls.Window, new() + { + var window = new WindowT(); + window.Show(); + app.Run(window); + } + } } diff --git a/src/Perspex.Diagnostics/DevTools.xaml.cs b/src/Perspex.Diagnostics/DevTools.xaml.cs index a6e4e83533..5d4d962f15 100644 --- a/src/Perspex.Diagnostics/DevTools.xaml.cs +++ b/src/Perspex.Diagnostics/DevTools.xaml.cs @@ -13,9 +13,20 @@ using Perspex.Markup.Xaml; using Perspex.VisualTree; using ReactiveUI; +namespace Perspex +{ + public static class WindowExtensions + { + public static void AttachDevTools(this Window window) + { + Perspex.Diagnostics.DevTools.Attach(window); + } + } +} + namespace Perspex.Diagnostics { - public class DevTools : UserControl + public class DevTools : UserControl { private static Dictionary s_open = new Dictionary(); private IDisposable _keySubscription;