From 9a1348b37b87a30048ddb197c5c2f3d6545bfe6e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 24 Sep 2017 00:37:11 +0300 Subject: [PATCH] Initial remote XAML previewer implementation --- Avalonia.sln | 92 +++++++++- Avalonia.sln.DotSettings | 1 + samples/ControlCatalog.NetCore/Program.cs | 22 ++- samples/Previewer/App.xaml | 6 + samples/Previewer/App.xaml.cs | 14 ++ samples/Previewer/Center.cs | 19 ++ samples/Previewer/MainWindow.xaml | 12 ++ samples/Previewer/MainWindow.xaml.cs | 86 +++++++++ samples/Previewer/Previewer.csproj | 27 +++ samples/Previewer/Program.cs | 13 ++ samples/RemoteTest/RemoteTest.csproj | 2 +- src/Avalonia.Controls/Design.cs | 2 +- .../InternalPlatformThreadingInterface.cs} | 14 +- src/Avalonia.Controls/Remote/RemoteWidget.cs | 2 +- .../Remote/Server/RemoteServerTopLevelImpl.cs | 10 +- .../DesignWindowLoader.cs | 76 ++++++++ .../DesignerAssist.cs | 60 +----- .../Avalonia.DotNetFrameworkRuntime.csproj | 4 +- .../BsonStreamTransport.cs | 19 +- .../DesignMessages.cs | 17 ++ src/Avalonia.Remote.Protocol/EventStash.cs | 20 +- src/Avalonia.Remote.Protocol/ITransport.cs | 4 +- .../TcpTransportBase.cs | 16 +- .../TransportConnectionWrapper.cs | 12 +- .../ViewportMessages.cs | 7 + .../Avalonia.LinuxFramebuffer.csproj | 24 +-- .../LinuxFramebufferPlatform.cs | 7 +- src/Linux/Avalonia.LinuxFramebuffer/Mice.cs | 2 +- .../Avalonia.Designer.HostApp.csproj | 25 +++ .../DetachableTransportConnection.cs | 35 ++++ .../PreviewerWindowImpl.cs | 91 +++++++++ .../PreviewerWindowingPlatform.cs | 66 +++++++ .../Avalonia.Designer.HostApp/Program.cs | 173 ++++++++++++++++++ src/tools/Avalonia.Designer.HostApp/Stubs.cs | 136 ++++++++++++++ 34 files changed, 977 insertions(+), 139 deletions(-) create mode 100644 samples/Previewer/App.xaml create mode 100644 samples/Previewer/App.xaml.cs create mode 100644 samples/Previewer/Center.cs create mode 100644 samples/Previewer/MainWindow.xaml create mode 100644 samples/Previewer/MainWindow.xaml.cs create mode 100644 samples/Previewer/Previewer.csproj create mode 100644 samples/Previewer/Program.cs rename src/{Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs => Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs} (89%) create mode 100644 src/Avalonia.DesignerSupport/DesignWindowLoader.cs create mode 100644 src/Avalonia.Remote.Protocol/DesignMessages.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj create mode 100644 src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/Program.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/Stubs.cs diff --git a/Avalonia.sln b/Avalonia.sln index 56c489fd0b..2f2b175fa3 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 @@ -189,6 +189,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Remote.Protocol", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteTest", "samples\RemoteTest\RemoteTest.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{4ED8B739-6F4E-4CD4-B993-545E6B5CE637}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer.HostApp", "src\tools\Avalonia.Designer.HostApp\Avalonia.Designer.HostApp.csproj", "{050CC912-FF49-4A8B-B534-9544017446DD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Previewer", "samples\Previewer\Previewer.csproj", "{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -2598,6 +2604,86 @@ Global {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Mono.Build.0 = Release|Any CPU {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|x86.ActiveCfg = Release|Any CPU {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|x86.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.ActiveCfg = Ad-Hoc|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.Build.0 = Ad-Hoc|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.Build.0 = Ad-Hoc|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.Build.0 = AppStore|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.ActiveCfg = AppStore|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.Build.0 = AppStore|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.ActiveCfg = AppStore|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.Build.0 = AppStore|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.ActiveCfg = AppStore|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.Build.0 = AppStore|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.ActiveCfg = Debug|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.Build.0 = Debug|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.ActiveCfg = Debug|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.Build.0 = Debug|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.ActiveCfg = Debug|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.Build.0 = Debug|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.ActiveCfg = Release|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.Build.0 = Release|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.ActiveCfg = Release|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.Build.0 = Release|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.ActiveCfg = Release|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.Build.0 = Release|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.ActiveCfg = Ad-Hoc|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.Build.0 = Ad-Hoc|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.Build.0 = Ad-Hoc|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.Build.0 = AppStore|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.ActiveCfg = AppStore|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.Build.0 = AppStore|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.ActiveCfg = AppStore|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.Build.0 = AppStore|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.ActiveCfg = AppStore|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.Build.0 = AppStore|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.ActiveCfg = Debug|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.Build.0 = Debug|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.ActiveCfg = Debug|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.Build.0 = Debug|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.ActiveCfg = Debug|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.Build.0 = Debug|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Any CPU.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.ActiveCfg = Release|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.Build.0 = Release|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.ActiveCfg = Release|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.Build.0 = Release|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.ActiveCfg = Release|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2656,8 +2742,12 @@ Global {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888} {E1582370-37B3-403C-917F-8209551B1634} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1E8CA5AA-707A-4C57-A682-D265A49E10C3} + {050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection EndGlobal diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings index 1fd6f8d092..d61208c358 100644 --- a/Avalonia.sln.DotSettings +++ b/Avalonia.sln.DotSettings @@ -1,4 +1,5 @@  + True ExplicitlyExcluded ExplicitlyExcluded ExplicitlyExcluded diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 7c67a239e6..7e7fb7d2bf 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Avalonia; +using Avalonia.Controls; namespace ControlCatalog.NetCore { @@ -8,17 +9,22 @@ namespace ControlCatalog.NetCore { static void Main(string[] args) { - if (args.Contains("--fbdev")) AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => - { - tl.Content = new MainView(); - System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); - }); + if (args.Contains("--fbdev")) + AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => + { + tl.Content = new MainView(); + System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); + }); else - AppBuilder.Configure() - .UsePlatformDetect() - .Start(); + BuildAvaloniaApp().Start(); } + /// + /// This method is needed for IDE previewer infrastructure + /// + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure().UsePlatformDetect(); + static void ConsoleSilencer() { Console.CursorVisible = false; diff --git a/samples/Previewer/App.xaml b/samples/Previewer/App.xaml new file mode 100644 index 0000000000..6bae1955af --- /dev/null +++ b/samples/Previewer/App.xaml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs new file mode 100644 index 0000000000..fffa987a27 --- /dev/null +++ b/samples/Previewer/App.xaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Markup.Xaml; + +namespace Previewer +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + } + +} \ No newline at end of file diff --git a/samples/Previewer/Center.cs b/samples/Previewer/Center.cs new file mode 100644 index 0000000000..7a28827d61 --- /dev/null +++ b/samples/Previewer/Center.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; + +namespace Previewer +{ + public class Center : Decorator + { + protected override Size ArrangeOverride(Size finalSize) + { + if (Child != null) + { + var desired = Child.DesiredSize; + Child.Arrange(new Rect((finalSize.Width - desired.Width) / 2, (finalSize.Height - desired.Height) / 2, + desired.Width, desired.Height)); + } + return finalSize; + } + } +} \ No newline at end of file diff --git a/samples/Previewer/MainWindow.xaml b/samples/Previewer/MainWindow.xaml new file mode 100644 index 0000000000..eb612303f2 --- /dev/null +++ b/samples/Previewer/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Previewer/MainWindow.xaml.cs b/samples/Previewer/MainWindow.xaml.cs new file mode 100644 index 0000000000..c72b1f7e55 --- /dev/null +++ b/samples/Previewer/MainWindow.xaml.cs @@ -0,0 +1,86 @@ +using System; +using System.Net; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Remote; +using Avalonia.Markup.Xaml; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Designer; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; + +namespace Previewer +{ + public class MainWindow : Window + { + private const string InitialXaml = @" + Hello world! + + "; + private IAvaloniaRemoteTransportConnection _connection; + private Control _errorsContainer; + private TextBlock _errors; + private RemoteWidget _remote; + + + public MainWindow() + { + this.InitializeComponent(); + var tb = this.FindControl("Xaml"); + tb.Text = InitialXaml; + var scroll = this.FindControl("Remote"); + var rem = new Center(); + scroll.Content = rem; + _errorsContainer = this.FindControl("ErrorsContainer"); + _errors = this.FindControl("Errors"); + tb.GetObservable(TextBox.TextProperty).Subscribe(text => _connection?.Send(new UpdateXamlMessage + { + Xaml = text + })); + new BsonTcpTransport().Listen(IPAddress.Loopback, 25000, t => + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (_connection != null) + { + _connection.Dispose(); + _connection.OnMessage -= OnMessage; + } + _connection = t; + rem.Child = _remote = new RemoteWidget(t); + t.Send(new UpdateXamlMessage + { + Xaml = tb.Text + }); + + t.OnMessage += OnMessage; + }); + }); + Title = "Listening on 127.0.0.1:25000"; + } + + private void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (transport != _connection) + return; + if (obj is UpdateXamlResultMessage result) + { + _errorsContainer.IsVisible = result.Error != null; + _errors.Text = result.Error ?? ""; + } + if (obj is RequestViewportResizeMessage resize) + { + _remote.Width = Math.Min(4096, Math.Max(resize.Width, 1)); + _remote.Height = Math.Min(4096, Math.Max(resize.Height, 1)); + } + }); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj new file mode 100644 index 0000000000..13e2418d67 --- /dev/null +++ b/samples/Previewer/Previewer.csproj @@ -0,0 +1,27 @@ + + + Exe + netcoreapp2.0 + + + + %(Filename) + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Previewer/Program.cs b/samples/Previewer/Program.cs new file mode 100644 index 0000000000..48363e27f2 --- /dev/null +++ b/samples/Previewer/Program.cs @@ -0,0 +1,13 @@ +using System; +using Avalonia; + +namespace Previewer +{ + class Program + { + static void Main(string[] args) + { + AppBuilder.Configure().UsePlatformDetect().Start(); + } + } +} \ No newline at end of file diff --git a/samples/RemoteTest/RemoteTest.csproj b/samples/RemoteTest/RemoteTest.csproj index 8682916503..2487c66e41 100644 --- a/samples/RemoteTest/RemoteTest.csproj +++ b/samples/RemoteTest/RemoteTest.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp1.1 + netcoreapp2.0 diff --git a/src/Avalonia.Controls/Design.cs b/src/Avalonia.Controls/Design.cs index a0b0d5d0ca..ce52891749 100644 --- a/src/Avalonia.Controls/Design.cs +++ b/src/Avalonia.Controls/Design.cs @@ -64,7 +64,7 @@ namespace Avalonia.Controls return rv; } - internal static void ApplyDesignerProperties(Control target, Control source) + public static void ApplyDesignModeProperties(Control target, Control source) { if (source.IsSet(WidthProperty)) target.Width = source.GetValue(WidthProperty); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs similarity index 89% rename from src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs rename to src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs index 9231649754..eebde51555 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs +++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs @@ -1,19 +1,16 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; -using System.Text; using System.Threading; using System.Threading.Tasks; using Avalonia.Platform; using Avalonia.Rendering; -namespace Avalonia.LinuxFramebuffer +namespace Avalonia.Controls.Platform { - class PlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop + public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop { - public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); - - public PlatformThreadingInterface() + public InternalPlatformThreadingInterface() { TlsCurrentThreadIsLoopThread = true; StartTimer(new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs())); @@ -36,7 +33,7 @@ namespace Avalonia.LinuxFramebuffer while (true) { Action item; - lock(_actions) + lock (_actions) if (_actions.Count == 0) break; else @@ -66,6 +63,7 @@ namespace Avalonia.LinuxFramebuffer _timer = timer; _handle = GCHandle.Alloc(_timer); } + public void Dispose() { _handle.Free(); @@ -109,4 +107,4 @@ namespace Avalonia.LinuxFramebuffer public event EventHandler Tick; } -} +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs index f4bcb37d48..c05aeaf970 100644 --- a/src/Avalonia.Controls/Remote/RemoteWidget.cs +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls.Remote public RemoteWidget(IAvaloniaRemoteTransportConnection connection) { _connection = connection; - _connection.OnMessage += msg => Dispatcher.UIThread.InvokeAsync(() => OnMessage(msg)); + _connection.OnMessage += (t, msg) => Dispatcher.UIThread.InvokeAsync(() => OnMessage(msg)); _connection.Send(new ClientSupportedPixelFormatsMessage { Formats = new[] diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index be97f7caaa..c2e6a200f9 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -36,7 +36,7 @@ namespace Avalonia.Controls.Remote.Server _transport.OnMessage += OnMessage; } - private void OnMessage(object obj) + protected virtual void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) { lock (_lock) { @@ -88,6 +88,12 @@ namespace Avalonia.Controls.Remote.Server } } + protected void SetDpi(Vector dpi) + { + _dpi = dpi; + RenderIfNeeded(); + } + protected virtual Size Measure(Size constaint) { var l = (ILayoutable) InputRoot; @@ -131,7 +137,7 @@ namespace Avalonia.Controls.Remote.Server return _framebuffer; } - void RenderIfNeeded() + protected void RenderIfNeeded() { lock (_lock) { diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs new file mode 100644 index 0000000000..9ad2a216b4 --- /dev/null +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; + +namespace Avalonia.DesignerSupport +{ + public class DesignWindowLoader + { + public static Window LoadDesignerWindow(string xaml, string assemblyPath) + { + Window window; + Control control; + using (PlatformManager.DesignerMode()) + { + var loader = new AvaloniaXamlLoader(); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)); + + + + Uri baseUri = null; + if (assemblyPath != null) + { + //Fabricate fake Uri + baseUri = + new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(assemblyPath)); + } + + var loaded = loader.Load(stream, null, baseUri); + var styles = loaded as Styles; + if (styles != null) + { + var substitute = Design.GetPreviewWith(styles) ?? + styles.Select(Design.GetPreviewWith).FirstOrDefault(s => s != null); + if (substitute != null) + { + substitute.Styles.AddRange(styles); + control = substitute; + } + else + control = new StackPanel + { + Children = + { + new TextBlock {Text = "Styles can't be previewed without Design.PreviewWith. Add"}, + new TextBlock {Text = ""}, + new TextBlock {Text = " "}, + new TextBlock {Text = ""}, + new TextBlock {Text = "before setters in your first Style"} + } + }; + } + if (loaded is Application) + control = new TextBlock {Text = "Application can't be previewed in design view"}; + else + control = (Control) loaded; + + window = control as Window; + if (window == null) + { + window = new Window() {Content = (Control)control}; + } + + if (!window.IsSet(Window.SizeToContentProperty)) + window.SizeToContent = SizeToContent.WidthAndHeight; + } + window.Show(); + Design.ApplyDesignModeProperties(window, control); + return window; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.DesignerSupport/DesignerAssist.cs b/src/Avalonia.DesignerSupport/DesignerAssist.cs index 2f5fc79147..65c4c14d83 100644 --- a/src/Avalonia.DesignerSupport/DesignerAssist.cs +++ b/src/Avalonia.DesignerSupport/DesignerAssist.cs @@ -86,67 +86,11 @@ namespace Avalonia.DesignerSupport private static void UpdateXaml2(Dictionary dic) { var xamlInfo = new DesignerApiXamlFileInfo(dic); - Window window; - Control control; - - using (PlatformManager.DesignerMode()) - { - var loader = new AvaloniaXamlLoader(); - var stream = new MemoryStream(Encoding.UTF8.GetBytes(xamlInfo.Xaml)); - - - - Uri baseUri = null; - if (xamlInfo.AssemblyPath != null) - { - //Fabricate fake Uri - baseUri = - new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(xamlInfo.AssemblyPath)); - } - - var loaded = loader.Load(stream, null, baseUri); - var styles = loaded as Styles; - if (styles != null) - { - var substitute = Design.GetPreviewWith(styles) ?? - styles.Select(Design.GetPreviewWith).FirstOrDefault(s => s != null); - if (substitute != null) - { - substitute.Styles.AddRange(styles); - control = substitute; - } - else - control = new StackPanel - { - Children = - { - new TextBlock {Text = "Styles can't be previewed without Design.PreviewWith. Add"}, - new TextBlock {Text = ""}, - new TextBlock {Text = " "}, - new TextBlock {Text = ""}, - new TextBlock {Text = "before setters in your first Style"} - } - }; - } - if (loaded is Application) - control = new TextBlock {Text = "Application can't be previewed in design view"}; - else - control = (Control) loaded; - - window = control as Window; - if (window == null) - { - window = new Window() {Content = (Control)control}; - } - - if (!window.IsSet(Window.SizeToContentProperty)) - window.SizeToContent = SizeToContent.WidthAndHeight; - } + Window window = DesignWindowLoader.LoadDesignerWindow(xamlInfo.Xaml, xamlInfo.AssemblyPath); s_currentWindow?.Close(); s_currentWindow = window; - window.Show(); - Design.ApplyDesignerProperties(window, control); + // ReSharper disable once PossibleNullReferenceException // Always not null at this point Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle); diff --git a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj index 181f5e3a1e..32171e8495 100644 --- a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj +++ b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj @@ -10,7 +10,7 @@ Properties\SharedAssemblyInfo.cs - + @@ -18,4 +18,4 @@ - \ No newline at end of file + diff --git a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs index 8c983c385d..82765f98f6 100644 --- a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs +++ b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs @@ -8,7 +8,7 @@ using Newtonsoft.Json.Bson; namespace Avalonia.Remote.Protocol { - public class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection + class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection { private readonly IMessageTypeResolver _resolver; private readonly Stream _inputStream; @@ -79,17 +79,8 @@ namespace Avalonia.Remote.Protocol var guid = new Guid(guidBytes); var buffer = new byte[length]; await ReadExact(buffer).ConfigureAwait(false); - if (Environment.GetEnvironmentVariable("WTF") == "WTF") - { - - using (var f = System.IO.File.Create("/tmp/wtf2.bin")) - { - f.Write(infoBlock, 0, infoBlock.Length); - f.Write(buffer, 0, buffer.Length); - } - } var message = Serializer.Deserialize(new BsonReader(new MemoryStream(buffer)), _resolver.GetByGuid(guid)); - OnMessage?.Invoke(message); + OnMessage?.Invoke(this, message); } } catch (Exception e) @@ -150,11 +141,11 @@ namespace Avalonia.Remote.Protocol var cancel = e as OperationCanceledException; if (cancel?.CancellationToken == _cancel) return; - OnException?.Invoke(e); + OnException?.Invoke(this, e); } - public event Action OnMessage; - public event Action OnException; + public event Action OnMessage; + public event Action OnException; } } \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/DesignMessages.cs b/src/Avalonia.Remote.Protocol/DesignMessages.cs new file mode 100644 index 0000000000..d6e898f320 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/DesignMessages.cs @@ -0,0 +1,17 @@ +namespace Avalonia.Remote.Protocol.Designer +{ + [AvaloniaRemoteMessageGuid("9AEC9A2E-6315-4066-B4BA-E9A9EFD0F8CC")] + public class UpdateXamlMessage + { + public string Xaml { get; set; } + public string AssemblyPath { get; set; } + } + + [AvaloniaRemoteMessageGuid("B7A70093-0C5D-47FD-9261-22086D43A2E2")] + public class UpdateXamlResultMessage + { + public string Error { get; set; } + } + + +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/EventStash.cs b/src/Avalonia.Remote.Protocol/EventStash.cs index 06840c8253..1fe561d473 100644 --- a/src/Avalonia.Remote.Protocol/EventStash.cs +++ b/src/Avalonia.Remote.Protocol/EventStash.cs @@ -3,18 +3,20 @@ using System.Collections.Generic; namespace Avalonia.Remote.Protocol { - public class EventStash + class EventStash { + private readonly IAvaloniaRemoteTransportConnection _transport; private readonly Action _exceptionHandler; private List _stash; - private Action _delegate; + private Action _delegate; - public EventStash(Action exceptionHandler = null) + public EventStash(IAvaloniaRemoteTransportConnection transport, Action exceptionHandler = null) { + _transport = transport; _exceptionHandler = exceptionHandler; } - public void Add(Action handler) + public void Add(Action handler) { List stash; lock (this) @@ -37,25 +39,25 @@ namespace Avalonia.Remote.Protocol if (_exceptionHandler != null) try { - _delegate?.Invoke(m); + _delegate?.Invoke(_transport, m); } catch (Exception e) { _exceptionHandler(e); } else - _delegate?.Invoke(m); + _delegate?.Invoke(_transport, m); } } - public void Remove(Action handler) + public void Remove(Action handler) { lock (this) _delegate -= handler; } - public void Fire(T ev) + public void Fire(IAvaloniaRemoteTransportConnection transport, T ev) { if (_delegate == null) { @@ -66,7 +68,7 @@ namespace Avalonia.Remote.Protocol } } else - _delegate?.Invoke(ev); + _delegate?.Invoke(_transport, ev); } } } \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/ITransport.cs b/src/Avalonia.Remote.Protocol/ITransport.cs index d4e5427cb4..afc7d86845 100644 --- a/src/Avalonia.Remote.Protocol/ITransport.cs +++ b/src/Avalonia.Remote.Protocol/ITransport.cs @@ -9,7 +9,7 @@ namespace Avalonia.Remote.Protocol public interface IAvaloniaRemoteTransportConnection : IDisposable { Task Send(object data); - event Action OnMessage; - event Action OnException; + event Action OnMessage; + event Action OnException; } } diff --git a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs index 4278badaae..af62f2da59 100644 --- a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs +++ b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs @@ -50,17 +50,11 @@ namespace Avalonia.Remote.Protocol AcceptNew(); Task.Run(async () => { - try - { - var tcs = new TaskCompletionSource(); - var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0)); - cb(t); - await tcs.Task; - } - finally - { - cl.Dispose(); - } + var tcs = new TaskCompletionSource(); + var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0)); + cb(t); + await tcs.Task; + }); } diff --git a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs index b01b6d7303..1e821b7c24 100644 --- a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs +++ b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs @@ -8,7 +8,7 @@ namespace Avalonia.Remote.Protocol { private readonly IAvaloniaRemoteTransportConnection _conn; private EventStash _onMessage; - private EventStash _onException = new EventStash(); + private EventStash _onException; private Queue _sendQueue = new Queue(); private object _lock =new object(); @@ -17,7 +17,8 @@ namespace Avalonia.Remote.Protocol public TransportConnectionWrapper(IAvaloniaRemoteTransportConnection conn) { _conn = conn; - _onMessage = new EventStash(_onException.Fire); + _onException = new EventStash(this); + _onMessage = new EventStash(this, e => _onException.Fire(this, e)); _conn.OnException +=_onException.Fire; conn.OnMessage += _onMessage.Fire; @@ -58,8 +59,7 @@ namespace Avalonia.Remote.Protocol { wi.Tcs.TrySetException(e); } - } - + } } public Task Send(object data) @@ -86,13 +86,13 @@ namespace Avalonia.Remote.Protocol return tcs.Task; } - public event Action OnMessage + public event Action OnMessage { add => _onMessage.Add(value); remove => _onMessage.Remove(value); } - public event Action OnException + public event Action OnException { add => _onException.Add(value); remove => _onException.Remove(value); diff --git a/src/Avalonia.Remote.Protocol/ViewportMessages.cs b/src/Avalonia.Remote.Protocol/ViewportMessages.cs index 82a7688f37..f71e893d26 100644 --- a/src/Avalonia.Remote.Protocol/ViewportMessages.cs +++ b/src/Avalonia.Remote.Protocol/ViewportMessages.cs @@ -30,6 +30,13 @@ namespace Avalonia.Remote.Protocol.Viewport public double DpiY { get; set; } } + [AvaloniaRemoteMessageGuid("9B47B3D8-61DF-4C38-ACD4-8C1BB72554AC")] + public class RequestViewportResizeMessage + { + public double Width { get; set; } + public double Height { get; set; } + } + [AvaloniaRemoteMessageGuid("63481025-7016-43FE-BADC-F2FD0F88609E")] public class ClientSupportedPixelFormatsMessage { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj index c38cb0f114..466b47f7a0 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj +++ b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj @@ -1,14 +1,14 @@  - - netstandard2.0 - true - - - - - - - - - + + netstandard2.0 + true + + + + + + + + + \ No newline at end of file diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index bc1684cc15..e733beae27 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -12,6 +12,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; using Avalonia; +using Avalonia.Controls.Platform; namespace Avalonia.LinuxFramebuffer { @@ -22,6 +23,7 @@ namespace Avalonia.LinuxFramebuffer public static MouseDevice MouseDevice = new MouseDevice(); private static readonly Stopwatch St = Stopwatch.StartNew(); internal static uint Timestamp => (uint)St.ElapsedTicks; + public static InternalPlatformThreadingInterface Threading; public static FramebufferToplevelImpl TopLevel; LinuxFramebufferPlatform(string fbdev = null) { @@ -31,12 +33,13 @@ namespace Avalonia.LinuxFramebuffer void Initialize() { + Threading = new InternalPlatformThreadingInterface(); AvaloniaLocator.CurrentMutable .Bind().ToTransient() .Bind().ToConstant(KeyboardDevice) .Bind().ToSingleton() - .Bind().ToConstant(PlatformThreadingInterface.Instance) - .Bind().ToConstant(PlatformThreadingInterface.Instance); + .Bind().ToConstant(Threading) + .Bind().ToConstant(Threading); } internal static TopLevel Initialize(T builder, string fbdev = null) where T : AppBuilderBase, new() diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs index d1cf9eefb6..7e85796c0c 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs @@ -55,7 +55,7 @@ namespace Avalonia.LinuxFramebuffer if (!ev.HasValue) break; - PlatformThreadingInterface.Instance.Send(() => ProcessEvent(dev, ev.Value)); + LinuxFramebufferPlatform.Threading.Send(() => ProcessEvent(dev, ev.Value)); } } } diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj new file mode 100644 index 0000000000..f0f3b12cc5 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -0,0 +1,25 @@ + + + Exe + netcoreapp2.0 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs b/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs new file mode 100644 index 0000000000..9cd87a2c59 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading.Tasks; +using Avalonia.Remote.Protocol; + +namespace Avalonia.Designer.HostApp +{ + class DetachableTransportConnection : IAvaloniaRemoteTransportConnection + { + private IAvaloniaRemoteTransportConnection _inner; + + public DetachableTransportConnection(IAvaloniaRemoteTransportConnection inner) + { + _inner = inner; + _inner.OnMessage += FireOnMessage; + } + + public void Dispose() + { + if (_inner != null) + _inner.OnMessage -= FireOnMessage; + _inner = null; + } + + public void FireOnMessage(IAvaloniaRemoteTransportConnection transport, object obj) => OnMessage?.Invoke(transport, obj); + + public Task Send(object data) + { + return _inner?.Send(data); + } + + public event Action OnMessage; + + public event Action OnException; + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs b/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs new file mode 100644 index 0000000000..5ffaa6459a --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs @@ -0,0 +1,91 @@ +using System; +using System.Reactive.Disposables; +using Avalonia.Controls; +using Avalonia.Controls.Remote.Server; +using Avalonia.Platform; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; + +namespace Avalonia.Designer.HostApp +{ + public class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl, IEmbeddableWindowImpl + { + private readonly IAvaloniaRemoteTransportConnection _transport; + + public PreviewerWindowImpl(IAvaloniaRemoteTransportConnection transport) : base(transport) + { + _transport = transport; + ClientSize = new Size(1, 1); + } + + public void Show() + { + } + + public void Hide() + { + } + + public void BeginMoveDrag() + { + } + + public void BeginResizeDrag(WindowEdge edge) + { + } + + public Point Position { get; set; } + public Action PositionChanged { get; set; } + public Action Deactivated { get; set; } + public Action Activated { get; set; } + public IPlatformHandle Handle { get; } + public WindowState WindowState { get; set; } + public Size MaxClientSize { get; } = new Size(4096, 4096); + public event Action LostFocus; + + protected override void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) + { + // In previewer mode we completely ignore client-side viewport size + if (obj is ClientViewportAllocatedMessage alloc) + { + Dispatcher.UIThread.InvokeAsync(() => SetDpi(new Vector(alloc.DpiX, alloc.DpiY))); + return; + } + base.OnMessage(transport, obj); + } + + public void Resize(Size clientSize) + { + _transport.Send(new RequestViewportResizeMessage + { + Width = clientSize.Width, + Height = clientSize.Height + }); + ClientSize = clientSize; + RenderIfNeeded(); + } + + public void Activate() + { + } + + public void SetTitle(string title) + { + } + + public IDisposable ShowDialog() + { + return Disposable.Empty; + } + + public void SetSystemDecorations(bool enabled) + { + } + + public void SetIcon(IWindowIconImpl icon) + { + } + + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs b/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs new file mode 100644 index 0000000000..b33f871139 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Platform; +using Avalonia.Remote.Protocol; +using Avalonia.Rendering; + +namespace Avalonia.Designer.HostApp +{ + class PreviewerWindowingPlatform : IWindowingPlatform, IPlatformSettings + { + static readonly IKeyboardDevice Keyboard = new KeyboardDevice(); + private static IAvaloniaRemoteTransportConnection s_transport; + private static DetachableTransportConnection s_lastWindowTransport; + private static PreviewerWindowImpl s_lastWindow; + public static List PreFlightMessages = new List(); + + public IWindowImpl CreateWindow() => new WindowStub(); + + public IEmbeddableWindowImpl CreateEmbeddableWindow() + { + if (s_lastWindow != null) + { + s_lastWindowTransport.Dispose(); + try + { + s_lastWindow.Dispose(); + } + catch + { + //Ignore + } + } + s_lastWindow = + new PreviewerWindowImpl(s_lastWindowTransport = new DetachableTransportConnection(s_transport)); + foreach (var pf in PreFlightMessages) + s_lastWindowTransport.FireOnMessage(s_lastWindowTransport, pf); + return s_lastWindow; + } + + public IPopupImpl CreatePopup() => new WindowStub(); + + public static void Initialize(IAvaloniaRemoteTransportConnection transport) + { + s_transport = transport; + var instance = new PreviewerWindowingPlatform(); + var threading = new InternalPlatformThreadingInterface(); + AvaloniaLocator.CurrentMutable + .Bind().ToSingleton() + .Bind().ToSingleton() + .Bind().ToConstant(Keyboard) + .Bind().ToConstant(instance) + .Bind().ToConstant(threading) + .Bind().ToConstant(threading) + .Bind().ToSingleton() + .Bind().ToConstant(instance) + .Bind().ToSingleton(); + + } + + public Size DoubleClickSize { get; } = new Size(2, 2); + public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/Program.cs b/src/tools/Avalonia.Designer.HostApp/Program.cs new file mode 100644 index 0000000000..88dadf479e --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/Program.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.DesignerSupport; +using Avalonia.Input; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Designer; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; + +namespace Avalonia.Designer.HostApp +{ + class Program + { + + private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats; + private static ClientViewportAllocatedMessage s_viewportAllocatedMessage; + private static IAvaloniaRemoteTransportConnection s_transport; + class CommandLineArgs + { + public string AppPath { get; set; } + public Uri Transport { get; set; } + } + + static Exception Die(string error) + { + if (error != null) + { + Console.Error.WriteLine(error); + Console.Error.Flush(); + } + Environment.Exit(1); + return new Exception("APPEXIT"); + } + + static Exception PrintUsage() + { + Console.Error.WriteLine("Usage: --transport transport_spec app"); + Console.Error.WriteLine(); + Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ MyApp.exe"); + Console.Error.Flush(); + return Die(null); + } + + static CommandLineArgs ParseCommandLineArgs(string[] args) + { + var rv = new CommandLineArgs(); + Action next = null; + try + { + foreach (var arg in args) + { + if (next != null) + { + next(arg); + next = null; + } + else if (arg == "--transport") + next = a => rv.Transport = new Uri(a, UriKind.Absolute); + else if (rv.AppPath == null) + rv.AppPath = arg; + else + PrintUsage(); + + } + if (rv.AppPath == null || rv.Transport == null) + PrintUsage(); + } + catch + { + PrintUsage(); + } + return rv; + } + + static IAvaloniaRemoteTransportConnection CreateTransport(Uri transport) + { + if (transport.Scheme == "tcp-bson") + { + return new BsonTcpTransport().Connect(IPAddress.Parse(transport.Host), transport.Port).Result; + } + PrintUsage(); + return null; + } + + interface IAppInitializer + { + Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj); + } + + class AppInitializer : IAppInitializer where T : AppBuilderBase, new() + { + public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj) + { + var builder = (AppBuilderBase) obj; + builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport)); + builder.SetupWithoutStarting(); + return builder.Instance; + } + } + + private const string BuilderMethodName = "BuildAvaloniaApp"; + + class NeverClose : ICloseable + { + public event EventHandler Closed; + } + + static void Main(string[] cmdline) + { + var args = ParseCommandLineArgs(cmdline); + var transport = CreateTransport(args.Transport); + var asm = Assembly.LoadFile(System.IO.Path.GetFullPath(args.AppPath)); + var entryPoint = asm.EntryPoint; + if (entryPoint == null) + throw Die($"Assembly {args.AppPath} doesn't have an entry point"); + var builderMethod = entryPoint.DeclaringType.GetMethod(BuilderMethodName, + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (builderMethod == null) + throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}"); + + var appBuilder = builderMethod.Invoke(null, null); + var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType())); + var app = initializer.GetConfiguredApp(transport, appBuilder); + s_transport = transport; + transport.OnMessage += OnTransportMessage; + transport.OnException += (t, e) => Die(e.ToString()); + app.Run(new NeverClose()); + } + + + private static void RebuildPreFlight() + { + PreviewerWindowingPlatform.PreFlightMessages = new List + { + s_supportedPixelFormats, + s_viewportAllocatedMessage + }; + } + + private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.InvokeAsync(() => + { + if (obj is ClientSupportedPixelFormatsMessage formats) + { + s_supportedPixelFormats = formats; + RebuildPreFlight(); + } + if (obj is ClientViewportAllocatedMessage viewport) + { + s_viewportAllocatedMessage = viewport; + RebuildPreFlight(); + } + if (obj is UpdateXamlMessage xaml) + { + try + { + DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath); + s_transport.Send(new UpdateXamlResultMessage()); + } + catch (Exception e) + { + s_transport.Send(new UpdateXamlResultMessage + { + Error = e.ToString() + }); + } + } + }); + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/Stubs.cs b/src/tools/Avalonia.Designer.HostApp/Stubs.cs new file mode 100644 index 0000000000..a4160e7da4 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/Stubs.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reactive.Disposables; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Input.Raw; +using Avalonia.Platform; +using Avalonia.Rendering; + +namespace Avalonia.Designer.HostApp +{ + /// + /// Popups are no-op + /// + + class WindowStub : IPopupImpl, IWindowImpl + { + public Action Deactivated { get; set; } + public Action Activated { get; set; } + public IPlatformHandle Handle { get; } + public Size MaxClientSize { get; } + public Size ClientSize { get; } + public double Scaling { get; } + public IEnumerable Surfaces { get; } + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Action Closed { get; set; } + public IMouseDevice MouseDevice { get; } = new MouseDevice(); + public Point Position { get; set; } + public Action PositionChanged { get; set; } + public WindowState WindowState { get; set; } + public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root); + public void Dispose() + { + } + public void Invalidate(Rect rect) + { + } + + public void SetInputRoot(IInputRoot inputRoot) + { + } + + public Point PointToClient(Point point) => point; + + public Point PointToScreen(Point point) => point; + + public void SetCursor(IPlatformHandle cursor) + { + } + + public void Show() + { + } + + public void Hide() + { + } + + public void BeginMoveDrag() + { + } + + public void BeginResizeDrag(WindowEdge edge) + { + } + + public void Activate() + { + } + + public void Resize(Size clientSize) + { + } + + public void SetTitle(string title) + { + } + + public IDisposable ShowDialog() => Disposable.Empty; + + public void SetSystemDecorations(bool enabled) + { + } + + public void SetIcon(IWindowIconImpl icon) + { + } + } + + class ClipboardStub : IClipboard + { + public Task GetTextAsync() => Task.FromResult(""); + + public Task SetTextAsync(string text) => Task.CompletedTask; + + public Task ClearAsync() => Task.CompletedTask; + } + + class CursorFactoryStub : IStandardCursorFactory + { + public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB"); + } + + class IconLoaderStub : IPlatformIconLoader + { + class IconStub : IWindowIconImpl + { + public void Save(Stream outputStream) + { + + } + } + + public IWindowIconImpl LoadIcon(string fileName) => new IconStub(); + + public IWindowIconImpl LoadIcon(Stream stream) => new IconStub(); + + public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) => new IconStub(); + } + + class SystemDialogsStub : ISystemDialogImpl + { + public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) => + Task.FromResult((string[]) null); + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) => + Task.FromResult((string) null); + } +} \ No newline at end of file