committed by
GitHub
57 changed files with 4110 additions and 123 deletions
@ -0,0 +1,6 @@ |
|||||
|
<Application xmlns="https://github.com/avaloniaui"> |
||||
|
<Application.Styles> |
||||
|
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/> |
||||
|
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/> |
||||
|
</Application.Styles> |
||||
|
</Application> |
||||
@ -0,0 +1,14 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace Previewer |
||||
|
{ |
||||
|
public class App : Application |
||||
|
{ |
||||
|
public override void Initialize() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
<Window xmlns="https://github.com/avaloniaui" Width="600" Height="500" |
||||
|
Title="Previewer"> |
||||
|
<Grid RowDefinitions="0.5*,200"> |
||||
|
<ScrollViewer Name="Remote"/> |
||||
|
|
||||
|
<ScrollViewer Name="ErrorsContainer" Background="#ffe0e0"> |
||||
|
<TextBlock Name="Errors"/> |
||||
|
</ScrollViewer> |
||||
|
<TextBox Grid.Row="1" AcceptsReturn="True" Name="Xaml"/> |
||||
|
</Grid> |
||||
|
|
||||
|
</Window> |
||||
@ -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 = @"<Window xmlns=""https://github.com/avaloniaui"" Width=""600"" Height=""500"">
|
||||
|
<TextBlock>Hello world!</TextBlock> |
||||
|
|
||||
|
</Window>";
|
||||
|
private IAvaloniaRemoteTransportConnection _connection; |
||||
|
private Control _errorsContainer; |
||||
|
private TextBlock _errors; |
||||
|
private RemoteWidget _remote; |
||||
|
|
||||
|
|
||||
|
public MainWindow() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
var tb = this.FindControl<TextBox>("Xaml"); |
||||
|
tb.Text = InitialXaml; |
||||
|
var scroll = this.FindControl<ScrollViewer>("Remote"); |
||||
|
var rem = new Center(); |
||||
|
scroll.Content = rem; |
||||
|
_errorsContainer = this.FindControl<Control>("ErrorsContainer"); |
||||
|
_errors = this.FindControl<TextBlock>("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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFramework>netcoreapp2.0</TargetFramework> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<Compile Update="**\*.xaml.cs"> |
||||
|
<DependentUpon>%(Filename)</DependentUpon> |
||||
|
</Compile> |
||||
|
<EmbeddedResource Include="**\*.xaml" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System; |
||||
|
using Avalonia; |
||||
|
|
||||
|
namespace Previewer |
||||
|
{ |
||||
|
class Program |
||||
|
{ |
||||
|
static void Main(string[] args) |
||||
|
{ |
||||
|
AppBuilder.Configure<App>().UsePlatformDetect().Start<MainWindow>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
using System; |
||||
|
using System.Net; |
||||
|
using System.Net.Sockets; |
||||
|
using System.Threading; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Remote; |
||||
|
using Avalonia.Remote.Protocol; |
||||
|
using Avalonia.Threading; |
||||
|
using ControlCatalog; |
||||
|
|
||||
|
namespace RemoteTest |
||||
|
{ |
||||
|
class Program |
||||
|
{ |
||||
|
static void Main(string[] args) |
||||
|
{ |
||||
|
AppBuilder.Configure<App>().UsePlatformDetect().SetupWithoutStarting(); |
||||
|
|
||||
|
var l = new TcpListener(IPAddress.Loopback, 0); |
||||
|
l.Start(); |
||||
|
var port = ((IPEndPoint) l.LocalEndpoint).Port; |
||||
|
l.Stop(); |
||||
|
|
||||
|
var transport = new BsonTcpTransport(); |
||||
|
transport.Listen(IPAddress.Loopback, port, sc => |
||||
|
{ |
||||
|
Dispatcher.UIThread.InvokeAsync(() => |
||||
|
{ |
||||
|
new RemoteServer(sc).Content = new MainView(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
var cts = new CancellationTokenSource(); |
||||
|
transport.Connect(IPAddress.Loopback, port).ContinueWith(t => |
||||
|
{ |
||||
|
Dispatcher.UIThread.InvokeAsync(() => |
||||
|
{ |
||||
|
var window = new Window() |
||||
|
{ |
||||
|
Content = new RemoteWidget(t.Result) |
||||
|
}; |
||||
|
window.Closed += delegate { cts.Cancel(); }; |
||||
|
window.Show(); |
||||
|
}); |
||||
|
}); |
||||
|
Dispatcher.UIThread.MainLoop(cts.Token); |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFramework>netcoreapp2.0</TargetFramework> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" /> |
||||
|
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,63 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Layout; |
||||
|
using Avalonia.Styling; |
||||
|
|
||||
|
namespace Avalonia.Controls.Embedding.Offscreen |
||||
|
{ |
||||
|
class OffscreenTopLevel : TopLevel, IStyleable |
||||
|
{ |
||||
|
public OffscreenTopLevelImplBase Impl { get; } |
||||
|
|
||||
|
public OffscreenTopLevel(OffscreenTopLevelImplBase impl) : base(impl) |
||||
|
{ |
||||
|
Impl = impl; |
||||
|
Prepare(); |
||||
|
} |
||||
|
|
||||
|
public void Prepare() |
||||
|
{ |
||||
|
EnsureInitialized(); |
||||
|
ApplyTemplate(); |
||||
|
LayoutManager.Instance.ExecuteInitialLayoutPass(this); |
||||
|
} |
||||
|
|
||||
|
private void EnsureInitialized() |
||||
|
{ |
||||
|
if (!this.IsInitialized) |
||||
|
{ |
||||
|
var init = (ISupportInitialize)this; |
||||
|
init.BeginInit(); |
||||
|
init.EndInit(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private readonly NameScope _nameScope = new NameScope(); |
||||
|
public event EventHandler<NameScopeEventArgs> Registered |
||||
|
{ |
||||
|
add { _nameScope.Registered += value; } |
||||
|
remove { _nameScope.Registered -= value; } |
||||
|
} |
||||
|
|
||||
|
public event EventHandler<NameScopeEventArgs> Unregistered |
||||
|
{ |
||||
|
add { _nameScope.Unregistered += value; } |
||||
|
remove { _nameScope.Unregistered -= value; } |
||||
|
} |
||||
|
|
||||
|
public void Register(string name, object element) => _nameScope.Register(name, element); |
||||
|
|
||||
|
public object Find(string name) => _nameScope.Find(name); |
||||
|
|
||||
|
public void Unregister(string name) => _nameScope.Unregister(name); |
||||
|
|
||||
|
Type IStyleable.StyleKey => typeof(EmbeddableControlRoot); |
||||
|
public void Dispose() |
||||
|
{ |
||||
|
PlatformImpl.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering; |
||||
|
|
||||
|
namespace Avalonia.Controls.Embedding.Offscreen |
||||
|
{ |
||||
|
public abstract class OffscreenTopLevelImplBase : ITopLevelImpl |
||||
|
{ |
||||
|
private double _scaling = 1; |
||||
|
private Size _clientSize; |
||||
|
public IInputRoot InputRoot { get; private set; } |
||||
|
|
||||
|
public virtual void Dispose() |
||||
|
{ |
||||
|
//No-op
|
||||
|
} |
||||
|
|
||||
|
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root); |
||||
|
|
||||
|
public abstract void Invalidate(Rect rect); |
||||
|
public abstract IEnumerable<object> Surfaces { get; } |
||||
|
|
||||
|
public Size ClientSize |
||||
|
{ |
||||
|
get { return _clientSize; } |
||||
|
set |
||||
|
{ |
||||
|
_clientSize = value; |
||||
|
Resized?.Invoke(value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public double Scaling |
||||
|
{ |
||||
|
get { return _scaling; } |
||||
|
set |
||||
|
{ |
||||
|
_scaling = value; |
||||
|
ScalingChanged?.Invoke(value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Action<RawInputEventArgs> Input { get; set; } |
||||
|
public Action<Rect> Paint { get; set; } |
||||
|
public Action<Size> Resized { get; set; } |
||||
|
public Action<double> ScalingChanged { get; set; } |
||||
|
public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot; |
||||
|
|
||||
|
public virtual Point PointToClient(Point point) => point; |
||||
|
|
||||
|
public virtual Point PointToScreen(Point point) => point; |
||||
|
|
||||
|
public virtual void SetCursor(IPlatformHandle cursor) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public Action Closed { get; set; } |
||||
|
public abstract IMouseDevice MouseDevice { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls.Embedding; |
||||
|
using Avalonia.Controls.Remote.Server; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Remote.Protocol; |
||||
|
|
||||
|
namespace Avalonia.Controls.Remote |
||||
|
{ |
||||
|
public class RemoteServer |
||||
|
{ |
||||
|
private EmbeddableControlRoot _topLevel; |
||||
|
|
||||
|
class EmbeddableRemoteServerTopLevelImpl : RemoteServerTopLevelImpl, IEmbeddableWindowImpl |
||||
|
{ |
||||
|
public EmbeddableRemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport) : base(transport) |
||||
|
{ |
||||
|
} |
||||
|
#pragma warning disable 67
|
||||
|
public event Action LostFocus; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public RemoteServer(IAvaloniaRemoteTransportConnection transport) |
||||
|
{ |
||||
|
_topLevel = new EmbeddableControlRoot(new EmbeddableRemoteServerTopLevelImpl(transport)); |
||||
|
_topLevel.Prepare(); |
||||
|
//TODO: Somehow react on closed connection?
|
||||
|
} |
||||
|
|
||||
|
public object Content |
||||
|
{ |
||||
|
get => _topLevel.Content; |
||||
|
set => _topLevel.Content = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Media.Imaging; |
||||
|
using Avalonia.Remote.Protocol; |
||||
|
using Avalonia.Remote.Protocol.Viewport; |
||||
|
using Avalonia.Threading; |
||||
|
using PixelFormat = Avalonia.Platform.PixelFormat; |
||||
|
|
||||
|
namespace Avalonia.Controls.Remote |
||||
|
{ |
||||
|
public class RemoteWidget : Control |
||||
|
{ |
||||
|
private readonly IAvaloniaRemoteTransportConnection _connection; |
||||
|
private FrameMessage _lastFrame; |
||||
|
private WritableBitmap _bitmap; |
||||
|
public RemoteWidget(IAvaloniaRemoteTransportConnection connection) |
||||
|
{ |
||||
|
_connection = connection; |
||||
|
_connection.OnMessage += (t, msg) => Dispatcher.UIThread.InvokeAsync(() => OnMessage(msg)); |
||||
|
_connection.Send(new ClientSupportedPixelFormatsMessage |
||||
|
{ |
||||
|
Formats = new[] |
||||
|
{ |
||||
|
Avalonia.Remote.Protocol.Viewport.PixelFormat.Bgra8888, |
||||
|
Avalonia.Remote.Protocol.Viewport.PixelFormat.Rgba8888, |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void OnMessage(object msg) |
||||
|
{ |
||||
|
if (msg is FrameMessage frame) |
||||
|
{ |
||||
|
_connection.Send(new FrameReceivedMessage |
||||
|
{ |
||||
|
SequenceId = frame.SequenceId |
||||
|
}); |
||||
|
_lastFrame = frame; |
||||
|
InvalidateVisual(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
protected override void ArrangeCore(Rect finalRect) |
||||
|
{ |
||||
|
_connection.Send(new ClientViewportAllocatedMessage |
||||
|
{ |
||||
|
Width = finalRect.Width, |
||||
|
Height = finalRect.Height, |
||||
|
DpiX = 96, |
||||
|
DpiY = 96 //TODO: Somehow detect the actual DPI
|
||||
|
}); |
||||
|
base.ArrangeCore(finalRect); |
||||
|
} |
||||
|
|
||||
|
public override void Render(DrawingContext context) |
||||
|
{ |
||||
|
if (_lastFrame != null) |
||||
|
{ |
||||
|
var fmt = (PixelFormat) _lastFrame.Format; |
||||
|
if (_bitmap == null || _bitmap.PixelWidth != _lastFrame.Width || |
||||
|
_bitmap.PixelHeight != _lastFrame.Height) |
||||
|
_bitmap = new WritableBitmap(_lastFrame.Width, _lastFrame.Height, fmt); |
||||
|
using (var l = _bitmap.Lock()) |
||||
|
{ |
||||
|
var lineLen = (fmt == PixelFormat.Rgb565 ? 2 : 4) * _lastFrame.Width; |
||||
|
for (var y = 0; y < _lastFrame.Height; y++) |
||||
|
Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride, |
||||
|
new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen); |
||||
|
} |
||||
|
context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelWidth, _bitmap.PixelHeight), |
||||
|
new Rect(Bounds.Size)); |
||||
|
} |
||||
|
base.Render(context); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,176 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls.Embedding.Offscreen; |
||||
|
using Avalonia.Controls.Platform.Surfaces; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Layout; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Remote.Protocol; |
||||
|
using Avalonia.Remote.Protocol.Viewport; |
||||
|
using Avalonia.Threading; |
||||
|
using PixelFormat = Avalonia.Platform.PixelFormat; |
||||
|
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; |
||||
|
|
||||
|
namespace Avalonia.Controls.Remote.Server |
||||
|
{ |
||||
|
public class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface |
||||
|
{ |
||||
|
private readonly IAvaloniaRemoteTransportConnection _transport; |
||||
|
private LockedFramebuffer _framebuffer; |
||||
|
private object _lock = new object(); |
||||
|
private long _lastSentFrame = -1; |
||||
|
private long _lastReceivedFrame = -1; |
||||
|
private long _nextFrameNumber = 1; |
||||
|
private ClientViewportAllocatedMessage _pendingAllocation; |
||||
|
private bool _invalidated; |
||||
|
private Vector _dpi = new Vector(96, 96); |
||||
|
private ProtocolPixelFormat[] _supportedFormats; |
||||
|
|
||||
|
public RemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport) |
||||
|
{ |
||||
|
_transport = transport; |
||||
|
_transport.OnMessage += OnMessage; |
||||
|
} |
||||
|
|
||||
|
protected virtual void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
if (obj is FrameReceivedMessage lastFrame) |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
_lastReceivedFrame = lastFrame.SequenceId; |
||||
|
} |
||||
|
Dispatcher.UIThread.InvokeAsync(RenderIfNeeded); |
||||
|
} |
||||
|
if (obj is ClientSupportedPixelFormatsMessage supportedFormats) |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
_supportedFormats = supportedFormats.Formats; |
||||
|
Dispatcher.UIThread.InvokeAsync(RenderIfNeeded); |
||||
|
} |
||||
|
if (obj is MeasureViewportMessage measure) |
||||
|
Dispatcher.UIThread.InvokeAsync(() => |
||||
|
{ |
||||
|
var m = Measure(new Size(measure.Width, measure.Height)); |
||||
|
_transport.Send(new MeasureViewportMessage |
||||
|
{ |
||||
|
Width = m.Width, |
||||
|
Height = m.Height |
||||
|
}); |
||||
|
}); |
||||
|
if (obj is ClientViewportAllocatedMessage allocated) |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
if (_pendingAllocation == null) |
||||
|
Dispatcher.UIThread.InvokeAsync(() => |
||||
|
{ |
||||
|
ClientViewportAllocatedMessage allocation; |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
allocation = _pendingAllocation; |
||||
|
_pendingAllocation = null; |
||||
|
} |
||||
|
_dpi = new Vector(allocation.DpiX, allocation.DpiY); |
||||
|
ClientSize = new Size(allocation.Width, allocation.Height); |
||||
|
RenderIfNeeded(); |
||||
|
}); |
||||
|
|
||||
|
_pendingAllocation = allocated; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected void SetDpi(Vector dpi) |
||||
|
{ |
||||
|
_dpi = dpi; |
||||
|
RenderIfNeeded(); |
||||
|
} |
||||
|
|
||||
|
protected virtual Size Measure(Size constaint) |
||||
|
{ |
||||
|
var l = (ILayoutable) InputRoot; |
||||
|
l.Measure(constaint); |
||||
|
return l.DesiredSize; |
||||
|
} |
||||
|
|
||||
|
public override IEnumerable<object> Surfaces => new[] { this }; |
||||
|
|
||||
|
FrameMessage RenderFrame(int width, int height, ProtocolPixelFormat? format) |
||||
|
{ |
||||
|
var fmt = format ?? ProtocolPixelFormat.Rgba8888; |
||||
|
var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4; |
||||
|
var data = new byte[width * height * bpp]; |
||||
|
var handle = GCHandle.Alloc(data, GCHandleType.Pinned); |
||||
|
try |
||||
|
{ |
||||
|
_framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), width, height, width * bpp, _dpi, (PixelFormat)fmt, |
||||
|
null); |
||||
|
Paint?.Invoke(new Rect(0, 0, width, height)); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
_framebuffer = null; |
||||
|
handle.Free(); |
||||
|
} |
||||
|
return new FrameMessage |
||||
|
{ |
||||
|
Data = data, |
||||
|
Format = (ProtocolPixelFormat) format, |
||||
|
Width = width, |
||||
|
Height = height, |
||||
|
Stride = width * bpp, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public ILockedFramebuffer Lock() |
||||
|
{ |
||||
|
if (_framebuffer == null) |
||||
|
throw new InvalidOperationException("Paint was not requested, wait for Paint event"); |
||||
|
return _framebuffer; |
||||
|
} |
||||
|
|
||||
|
protected void RenderIfNeeded() |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
if (_lastReceivedFrame != _lastSentFrame || !_invalidated || _supportedFormats == null) |
||||
|
return; |
||||
|
|
||||
|
} |
||||
|
if (ClientSize.Width < 1 || ClientSize.Height < 1) |
||||
|
return; |
||||
|
var format = ProtocolPixelFormat.Rgba8888; |
||||
|
foreach(var fmt in _supportedFormats) |
||||
|
if (fmt <= ProtocolPixelFormat.MaxValue) |
||||
|
{ |
||||
|
format = fmt; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
var frame = RenderFrame((int) ClientSize.Width, (int) ClientSize.Height, format); |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
_lastSentFrame = _nextFrameNumber++; |
||||
|
frame.SequenceId = _lastSentFrame; |
||||
|
_invalidated = false; |
||||
|
} |
||||
|
_transport.Send(frame); |
||||
|
} |
||||
|
|
||||
|
public override void Invalidate(Rect rect) |
||||
|
{ |
||||
|
_invalidated = true; |
||||
|
Dispatcher.UIThread.InvokeAsync(RenderIfNeeded); |
||||
|
} |
||||
|
|
||||
|
public override IMouseDevice MouseDevice { get; } = new MouseDevice(); |
||||
|
} |
||||
|
} |
||||
@ -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 = "<Design.PreviewWith>"}, |
||||
|
new TextBlock {Text = " <Border Padding=20><!-- YOUR CONTROL FOR PREVIEW HERE--></Border>"}, |
||||
|
new TextBlock {Text = "<Design.PreviewWith>"}, |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Remote.Protocol; |
||||
|
|
||||
|
namespace Avalonia.DesignerSupport.Remote |
||||
|
{ |
||||
|
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<IAvaloniaRemoteTransportConnection, object> OnMessage; |
||||
|
|
||||
|
public event Action<IAvaloniaRemoteTransportConnection, Exception> OnException; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,96 @@ |
|||||
|
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.DesignerSupport.Remote |
||||
|
{ |
||||
|
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<Point> 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 IScreenImpl Screen { get; } = new ScreenStub(); |
||||
|
|
||||
|
public void Activate() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void SetTitle(string title) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public IDisposable ShowDialog() |
||||
|
{ |
||||
|
return Disposable.Empty; |
||||
|
} |
||||
|
|
||||
|
public void SetSystemDecorations(bool enabled) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void SetIcon(IWindowIconImpl icon) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void ShowTaskbarIcon(bool value) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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.DesignerSupport.Remote |
||||
|
{ |
||||
|
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<object> PreFlightMessages = new List<object>(); |
||||
|
|
||||
|
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<IClipboard>().ToSingleton<ClipboardStub>() |
||||
|
.Bind<IStandardCursorFactory>().ToSingleton<CursorFactoryStub>() |
||||
|
.Bind<IKeyboardDevice>().ToConstant(Keyboard) |
||||
|
.Bind<IPlatformSettings>().ToConstant(instance) |
||||
|
.Bind<IPlatformThreadingInterface>().ToConstant(threading) |
||||
|
.Bind<IRenderLoop>().ToConstant(threading) |
||||
|
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsStub>() |
||||
|
.Bind<IWindowingPlatform>().ToConstant(instance) |
||||
|
.Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>(); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public Size DoubleClickSize { get; } = new Size(2, 2); |
||||
|
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,172 @@ |
|||||
|
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.DesignerSupport.Remote |
||||
|
{ |
||||
|
public class RemoteDesignerEntryPoint |
||||
|
{ |
||||
|
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<string> 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<T> : IAppInitializer where T : AppBuilderBase<T>, new() |
||||
|
{ |
||||
|
public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj) |
||||
|
{ |
||||
|
var builder = (AppBuilderBase<T>) obj; |
||||
|
builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport)); |
||||
|
builder.SetupWithoutStarting(); |
||||
|
return builder.Instance; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private const string BuilderMethodName = "BuildAvaloniaApp"; |
||||
|
|
||||
|
class NeverClose : ICloseable |
||||
|
{ |
||||
|
public event EventHandler Closed; |
||||
|
} |
||||
|
|
||||
|
public 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<object> |
||||
|
{ |
||||
|
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() |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,146 @@ |
|||||
|
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.DesignerSupport.Remote |
||||
|
{ |
||||
|
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<object> Surfaces { get; } |
||||
|
public Action<RawInputEventArgs> Input { get; set; } |
||||
|
public Action<Rect> Paint { get; set; } |
||||
|
public Action<Size> Resized { get; set; } |
||||
|
public Action<double> ScalingChanged { get; set; } |
||||
|
public Action Closed { get; set; } |
||||
|
public IMouseDevice MouseDevice { get; } = new MouseDevice(); |
||||
|
public Point Position { get; set; } |
||||
|
public Action<Point> 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 IScreenImpl Screen { get; } = new ScreenStub(); |
||||
|
|
||||
|
public void SetTitle(string title) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public IDisposable ShowDialog() => Disposable.Empty; |
||||
|
|
||||
|
public void SetSystemDecorations(bool enabled) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void SetIcon(IWindowIconImpl icon) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void ShowTaskbarIcon(bool value) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class ClipboardStub : IClipboard |
||||
|
{ |
||||
|
public Task<string> 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<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) => |
||||
|
Task.FromResult((string[]) null); |
||||
|
|
||||
|
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) => |
||||
|
Task.FromResult((string) null); |
||||
|
} |
||||
|
|
||||
|
class ScreenStub : IScreenImpl |
||||
|
{ |
||||
|
public int ScreenCount => 1; |
||||
|
|
||||
|
public Screen[] AllScreens { get; } = |
||||
|
{new Screen(new Rect(0, 0, 4000, 4000), new Rect(0, 0, 4000, 4000), true)}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<DefineConstants>AVALONIA_REMOTE_PROTOCOL;$(DefineConstants)</DefineConstants> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<Compile Include="..\Avalonia.Input\Key.cs" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol |
||||
|
{ |
||||
|
[AttributeUsage(AttributeTargets.Class)] |
||||
|
public class AvaloniaRemoteMessageGuidAttribute : Attribute |
||||
|
{ |
||||
|
public Guid Guid { get; } |
||||
|
|
||||
|
public AvaloniaRemoteMessageGuidAttribute(string guid) |
||||
|
{ |
||||
|
Guid = Guid.Parse(guid); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,150 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Metsys.Bson; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol |
||||
|
{ |
||||
|
class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection |
||||
|
{ |
||||
|
private readonly IMessageTypeResolver _resolver; |
||||
|
private readonly Stream _inputStream; |
||||
|
private readonly Stream _outputStream; |
||||
|
private readonly Action _disposeCallback; |
||||
|
private readonly CancellationToken _cancel; |
||||
|
private readonly CancellationTokenSource _cancelSource; |
||||
|
private readonly MemoryStream _outputBlock = new MemoryStream(); |
||||
|
private readonly object _lock = new object(); |
||||
|
private bool _writeOperationPending; |
||||
|
private bool _readingAlreadyStarted; |
||||
|
private bool _writerIsBroken; |
||||
|
private static readonly byte[] ZeroLength = new byte[4]; |
||||
|
|
||||
|
public BsonStreamTransportConnection(IMessageTypeResolver resolver, Stream inputStream, Stream outputStream, Action disposeCallback) |
||||
|
{ |
||||
|
_resolver = resolver; |
||||
|
_inputStream = inputStream; |
||||
|
_outputStream = outputStream; |
||||
|
_disposeCallback = disposeCallback; |
||||
|
_cancelSource = new CancellationTokenSource(); |
||||
|
_cancel = _cancelSource.Token; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_cancelSource.Cancel(); |
||||
|
_disposeCallback?.Invoke(); |
||||
|
} |
||||
|
|
||||
|
public void StartReading() |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
if(_readingAlreadyStarted) |
||||
|
throw new InvalidOperationException("Reading has already started"); |
||||
|
_readingAlreadyStarted = true; |
||||
|
Task.Run(Reader, _cancel); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async Task ReadExact(byte[] buffer) |
||||
|
{ |
||||
|
int read = 0; |
||||
|
while (read != buffer.Length) |
||||
|
{ |
||||
|
var readNow = await _inputStream.ReadAsync(buffer, read, buffer.Length - read, _cancel) |
||||
|
.ConfigureAwait(false); |
||||
|
if (readNow == 0) |
||||
|
throw new EndOfStreamException(); |
||||
|
read += readNow; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async Task Reader() |
||||
|
{ |
||||
|
Task.Yield(); |
||||
|
try |
||||
|
{ |
||||
|
while (true) |
||||
|
{ |
||||
|
var infoBlock = new byte[20]; |
||||
|
await ReadExact(infoBlock).ConfigureAwait(false); |
||||
|
var length = BitConverter.ToInt32(infoBlock, 0); |
||||
|
var guidBytes = new byte[16]; |
||||
|
Buffer.BlockCopy(infoBlock, 4, guidBytes, 0, 16); |
||||
|
var guid = new Guid(guidBytes); |
||||
|
var buffer = new byte[length]; |
||||
|
await ReadExact(buffer).ConfigureAwait(false); |
||||
|
var message = Deserializer.Deserialize(new BinaryReader(new MemoryStream(buffer)), |
||||
|
_resolver.GetByGuid(guid)); |
||||
|
OnMessage?.Invoke(this, message); |
||||
|
} |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
FireException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public async Task Send(object data) |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
if(_writerIsBroken) //Ignore further calls, since there is no point of writing to "broken" stream
|
||||
|
return; |
||||
|
if (_writeOperationPending) |
||||
|
throw new InvalidOperationException("Previous send operation was not finished"); |
||||
|
_writeOperationPending = true; |
||||
|
} |
||||
|
try |
||||
|
{ |
||||
|
var guid = _resolver.GetGuid(data.GetType()).ToByteArray(); |
||||
|
_outputBlock.Seek(0, SeekOrigin.Begin); |
||||
|
_outputBlock.SetLength(0); |
||||
|
_outputBlock.Write(ZeroLength, 0, 4); |
||||
|
_outputBlock.Write(guid, 0, guid.Length); |
||||
|
var serialized = Serializer.Serialize(data); |
||||
|
_outputBlock.Write(serialized, 0, serialized.Length); |
||||
|
_outputBlock.Seek(0, SeekOrigin.Begin); |
||||
|
var length = BitConverter.GetBytes((int)_outputBlock.Length - 20); |
||||
|
_outputBlock.Write(length, 0, length.Length); |
||||
|
_outputBlock.Seek(0, SeekOrigin.Begin); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await _outputBlock.CopyToAsync(_outputStream, 0x1000, _cancel).ConfigureAwait(false); |
||||
|
} |
||||
|
catch (Exception e) //We are only catching "network"-related exceptions here
|
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
_writerIsBroken = true; |
||||
|
} |
||||
|
FireException(e); |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
_writeOperationPending = false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void FireException(Exception e) |
||||
|
{ |
||||
|
var cancel = e as OperationCanceledException; |
||||
|
if (cancel?.CancellationToken == _cancel) |
||||
|
return; |
||||
|
OnException?.Invoke(this, e); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public event Action<IAvaloniaRemoteTransportConnection, object> OnMessage; |
||||
|
public event Action<IAvaloniaRemoteTransportConnection, Exception> OnException; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Reflection; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol |
||||
|
{ |
||||
|
public class BsonTcpTransport : TcpTransportBase |
||||
|
{ |
||||
|
public BsonTcpTransport(IMessageTypeResolver resolver) : base(resolver) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public BsonTcpTransport() : this(new DefaultMessageTypeResolver(typeof(BsonTcpTransport).GetTypeInfo().Assembly)) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
protected override IAvaloniaRemoteTransportConnection CreateTransport(IMessageTypeResolver resolver, |
||||
|
Stream stream, Action dispose) |
||||
|
{ |
||||
|
var t = new BsonStreamTransportConnection(resolver, stream, stream, dispose); |
||||
|
var wrap = new TransportConnectionWrapper(t); |
||||
|
t.StartReading(); |
||||
|
return wrap; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol |
||||
|
{ |
||||
|
public class DefaultMessageTypeResolver : IMessageTypeResolver |
||||
|
{ |
||||
|
private readonly Dictionary<Guid, Type> _guidsToTypes = new Dictionary<Guid, Type>(); |
||||
|
private readonly Dictionary<Type, Guid> _typesToGuids = new Dictionary<Type, Guid>(); |
||||
|
public DefaultMessageTypeResolver(params Assembly[] assemblies) |
||||
|
{ |
||||
|
foreach (var asm in |
||||
|
(assemblies ?? new Assembly[0]).Concat(new[] |
||||
|
{typeof(AvaloniaRemoteMessageGuidAttribute).GetTypeInfo().Assembly})) |
||||
|
{ |
||||
|
foreach (var t in asm.ExportedTypes) |
||||
|
{ |
||||
|
var attr = t.GetTypeInfo().GetCustomAttribute<AvaloniaRemoteMessageGuidAttribute>(); |
||||
|
if (attr != null) |
||||
|
{ |
||||
|
_guidsToTypes[attr.Guid] = t; |
||||
|
_typesToGuids[t] = attr.Guid; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Type GetByGuid(Guid id) => _guidsToTypes[id]; |
||||
|
public Guid GetGuid(Type type) => _typesToGuids[type]; |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol |
||||
|
{ |
||||
|
class EventStash<T> |
||||
|
{ |
||||
|
private readonly IAvaloniaRemoteTransportConnection _transport; |
||||
|
private readonly Action<Exception> _exceptionHandler; |
||||
|
private List<T> _stash; |
||||
|
private Action<IAvaloniaRemoteTransportConnection, T> _delegate; |
||||
|
|
||||
|
public EventStash(IAvaloniaRemoteTransportConnection transport, Action<Exception> exceptionHandler = null) |
||||
|
{ |
||||
|
_transport = transport; |
||||
|
_exceptionHandler = exceptionHandler; |
||||
|
} |
||||
|
|
||||
|
public void Add(Action<IAvaloniaRemoteTransportConnection, T> handler) |
||||
|
{ |
||||
|
List<T> stash; |
||||
|
lock (this) |
||||
|
{ |
||||
|
var needsReplay = _delegate == null; |
||||
|
_delegate += handler; |
||||
|
if(!needsReplay) |
||||
|
return; |
||||
|
|
||||
|
lock (this) |
||||
|
{ |
||||
|
stash = _stash; |
||||
|
if(_stash == null) |
||||
|
return; |
||||
|
_stash = null; |
||||
|
} |
||||
|
} |
||||
|
foreach (var m in stash) |
||||
|
{ |
||||
|
if (_exceptionHandler != null) |
||||
|
try |
||||
|
{ |
||||
|
_delegate?.Invoke(_transport, m); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
_exceptionHandler(e); |
||||
|
} |
||||
|
else |
||||
|
_delegate?.Invoke(_transport, m); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public void Remove(Action<IAvaloniaRemoteTransportConnection, T> handler) |
||||
|
{ |
||||
|
lock (this) |
||||
|
_delegate -= handler; |
||||
|
} |
||||
|
|
||||
|
public void Fire(IAvaloniaRemoteTransportConnection transport, T ev) |
||||
|
{ |
||||
|
if (_delegate == null) |
||||
|
{ |
||||
|
lock (this) |
||||
|
{ |
||||
|
_stash = _stash ?? new List<T>(); |
||||
|
_stash.Add(ev); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
_delegate?.Invoke(_transport, ev); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol |
||||
|
{ |
||||
|
public interface IMessageTypeResolver |
||||
|
{ |
||||
|
Type GetByGuid(Guid id); |
||||
|
Guid GetGuid(Type type); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol |
||||
|
{ |
||||
|
public interface IAvaloniaRemoteTransportConnection : IDisposable |
||||
|
{ |
||||
|
Task Send(object data); |
||||
|
event Action<IAvaloniaRemoteTransportConnection, object> OnMessage; |
||||
|
event Action<IAvaloniaRemoteTransportConnection, Exception> OnException; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
/* |
||||
|
We are keeping copies of core events here, so they can be used |
||||
|
without referencing Avalonia itself, e. g. from projects that |
||||
|
are using WPF, GTK#, etc |
||||
|
*/ |
||||
|
namespace Avalonia.Remote.Protocol.Input |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Keep this in sync with InputModifiers in the main library
|
||||
|
/// </summary>
|
||||
|
[Flags] |
||||
|
public enum InputModifiers |
||||
|
{ |
||||
|
Alt, |
||||
|
Control, |
||||
|
Shift, |
||||
|
Windows, |
||||
|
LeftMouseButton, |
||||
|
RightMouseButton, |
||||
|
MiddleMouseButton |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Keep this in sync with InputModifiers in the main library
|
||||
|
/// </summary>
|
||||
|
public enum MouseButton |
||||
|
{ |
||||
|
None, |
||||
|
Left, |
||||
|
Right, |
||||
|
Middle |
||||
|
} |
||||
|
|
||||
|
public abstract class InputEventMessageBase |
||||
|
{ |
||||
|
public InputModifiers[] Modifiers { get; set; } |
||||
|
} |
||||
|
|
||||
|
public abstract class PointerEventMessageBase : InputEventMessageBase |
||||
|
{ |
||||
|
public double X { get; set; } |
||||
|
public double Y { get; set; } |
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("6228F0B9-99F2-4F62-A621-414DA2881648")] |
||||
|
public class PointerMovedEventMessage : PointerEventMessageBase |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("7E9E2818-F93F-411A-800E-6B1AEB11DA46")] |
||||
|
public class PointerPressedEventMessage : PointerEventMessageBase |
||||
|
{ |
||||
|
public MouseButton Button { get; set; } |
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("4ADC84EE-E7C8-4BCF-986C-DE3A2F78EDE4")] |
||||
|
public class PointerReleasedEventMessage : PointerEventMessageBase |
||||
|
{ |
||||
|
public MouseButton Button { get; set; } |
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("79301A05-F02D-4B90-BB39-472563B504AE")] |
||||
|
public class ScrollEventMessage : PointerEventMessageBase |
||||
|
{ |
||||
|
public double DeltaX { get; set; } |
||||
|
public double DeltaY { get; set; } |
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("1C3B691E-3D54-4237-BFB0-9FEA83BC1DB8")] |
||||
|
public class KeyEventMessage : InputEventMessageBase |
||||
|
{ |
||||
|
public bool IsDown { get; set; } |
||||
|
public Key Key { get; set; } |
||||
|
} |
||||
|
|
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,78 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Net; |
||||
|
using System.Net.Sockets; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol |
||||
|
{ |
||||
|
public abstract class TcpTransportBase |
||||
|
{ |
||||
|
private readonly IMessageTypeResolver _resolver; |
||||
|
|
||||
|
public TcpTransportBase(IMessageTypeResolver resolver) |
||||
|
{ |
||||
|
_resolver = resolver; |
||||
|
} |
||||
|
|
||||
|
protected abstract IAvaloniaRemoteTransportConnection CreateTransport(IMessageTypeResolver resolver, |
||||
|
Stream stream, Action disposeCallback); |
||||
|
|
||||
|
class DisposableServer : IDisposable |
||||
|
{ |
||||
|
private readonly TcpListener _l; |
||||
|
|
||||
|
public DisposableServer(TcpListener l) |
||||
|
{ |
||||
|
_l = l; |
||||
|
} |
||||
|
public void Dispose() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
_l.Stop(); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//Ignore
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IDisposable Listen(IPAddress address, int port, Action<IAvaloniaRemoteTransportConnection> cb) |
||||
|
{ |
||||
|
var server = new TcpListener(address, port); |
||||
|
async void AcceptNew() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var cl = await server.AcceptTcpClientAsync(); |
||||
|
AcceptNew(); |
||||
|
Task.Run(async () => |
||||
|
{ |
||||
|
var tcs = new TaskCompletionSource<int>(); |
||||
|
var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0)); |
||||
|
cb(t); |
||||
|
await tcs.Task; |
||||
|
|
||||
|
|
||||
|
}); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//Ignore and stop
|
||||
|
} |
||||
|
} |
||||
|
server.Start(); |
||||
|
AcceptNew(); |
||||
|
return new DisposableServer(server); |
||||
|
} |
||||
|
|
||||
|
public async Task<IAvaloniaRemoteTransportConnection> Connect(IPAddress address, int port) |
||||
|
{ |
||||
|
var c = new TcpClient(); |
||||
|
await c.ConnectAsync(address, port); |
||||
|
return CreateTransport(_resolver, c.GetStream(), c.Dispose); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,101 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol |
||||
|
{ |
||||
|
public class TransportConnectionWrapper : IAvaloniaRemoteTransportConnection |
||||
|
{ |
||||
|
private readonly IAvaloniaRemoteTransportConnection _conn; |
||||
|
private EventStash<object> _onMessage; |
||||
|
private EventStash<Exception> _onException; |
||||
|
|
||||
|
private Queue<SendOperation> _sendQueue = new Queue<SendOperation>(); |
||||
|
private object _lock =new object(); |
||||
|
private TaskCompletionSource<int> _signal; |
||||
|
private bool _workerIsAlive; |
||||
|
public TransportConnectionWrapper(IAvaloniaRemoteTransportConnection conn) |
||||
|
{ |
||||
|
_conn = conn; |
||||
|
_onException = new EventStash<Exception>(this); |
||||
|
_onMessage = new EventStash<object>(this, e => _onException.Fire(this, e)); |
||||
|
_conn.OnException +=_onException.Fire; |
||||
|
conn.OnMessage += _onMessage.Fire; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
class SendOperation |
||||
|
{ |
||||
|
public object Message { get; set; } |
||||
|
public TaskCompletionSource<int> Tcs { get; set; } |
||||
|
} |
||||
|
|
||||
|
public void Dispose() => _conn.Dispose(); |
||||
|
|
||||
|
async void Worker() |
||||
|
{ |
||||
|
while (true) |
||||
|
{ |
||||
|
SendOperation wi = null; |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
if (_sendQueue.Count != 0) |
||||
|
wi = _sendQueue.Dequeue(); |
||||
|
} |
||||
|
if (wi == null) |
||||
|
{ |
||||
|
var signal = new TaskCompletionSource<int>(); |
||||
|
lock (_lock) |
||||
|
_signal = signal; |
||||
|
await signal.Task.ConfigureAwait(false); |
||||
|
continue; |
||||
|
} |
||||
|
try |
||||
|
{ |
||||
|
await _conn.Send(wi.Message).ConfigureAwait(false); |
||||
|
wi.Tcs.TrySetResult(0); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
wi.Tcs.TrySetException(e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task Send(object data) |
||||
|
{ |
||||
|
var tcs = new TaskCompletionSource<int>(); |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
if (!_workerIsAlive) |
||||
|
{ |
||||
|
_workerIsAlive = true; |
||||
|
Worker(); |
||||
|
} |
||||
|
_sendQueue.Enqueue(new SendOperation |
||||
|
{ |
||||
|
Message = data, |
||||
|
Tcs = tcs |
||||
|
}); |
||||
|
if (_signal != null) |
||||
|
{ |
||||
|
_signal.SetResult(0); |
||||
|
_signal = null; |
||||
|
} |
||||
|
} |
||||
|
return tcs.Task; |
||||
|
} |
||||
|
|
||||
|
public event Action<IAvaloniaRemoteTransportConnection, object> OnMessage |
||||
|
{ |
||||
|
add => _onMessage.Add(value); |
||||
|
remove => _onMessage.Remove(value); |
||||
|
} |
||||
|
|
||||
|
public event Action<IAvaloniaRemoteTransportConnection, Exception> OnException |
||||
|
{ |
||||
|
add => _onException.Add(value); |
||||
|
remove => _onException.Remove(value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol.Viewport |
||||
|
{ |
||||
|
public enum PixelFormat |
||||
|
{ |
||||
|
Rgb565, |
||||
|
Rgba8888, |
||||
|
Bgra8888, |
||||
|
MaxValue = Bgra8888 |
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("6E3C5310-E2B1-4C3D-8688-01183AA48C5B")] |
||||
|
public class MeasureViewportMessage |
||||
|
{ |
||||
|
public double Width { get; set; } |
||||
|
public double Height { get; set; } |
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("BD7A8DE6-3DB8-4A13-8583-D6D4AB189A31")] |
||||
|
public class ClientViewportAllocatedMessage |
||||
|
{ |
||||
|
public double Width { get; set; } |
||||
|
public double Height { get; set; } |
||||
|
public double DpiX { get; set; } |
||||
|
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 |
||||
|
{ |
||||
|
public PixelFormat[] Formats { get; set; } |
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("68014F8A-289D-4851-8D34-5367EDA7F827")] |
||||
|
public class FrameReceivedMessage |
||||
|
{ |
||||
|
public long SequenceId { get; set; } |
||||
|
} |
||||
|
|
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("F58313EE-FE69-4536-819D-F52EDF201A0E")] |
||||
|
public class FrameMessage |
||||
|
{ |
||||
|
public long SequenceId { get; set; } |
||||
|
public PixelFormat Format { get; set; } |
||||
|
public byte[] Data { get; set; } |
||||
|
public int Width { get; set; } |
||||
|
public int Height { get; set; } |
||||
|
public int Stride { get; set; } |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Platform |
||||
|
{ |
||||
|
public class LockedFramebuffer : ILockedFramebuffer |
||||
|
{ |
||||
|
private readonly Action _onDispose; |
||||
|
|
||||
|
public LockedFramebuffer(IntPtr address, int width, int height, int rowBytes, Vector dpi, PixelFormat format, |
||||
|
Action onDispose) |
||||
|
{ |
||||
|
_onDispose = onDispose; |
||||
|
Address = address; |
||||
|
Width = width; |
||||
|
Height = height; |
||||
|
RowBytes = rowBytes; |
||||
|
Dpi = dpi; |
||||
|
Format = format; |
||||
|
} |
||||
|
|
||||
|
public IntPtr Address { get; } |
||||
|
public int Width { get; } |
||||
|
public int Height { get; } |
||||
|
public int RowBytes { get; } |
||||
|
public Vector Dpi { get; } |
||||
|
public PixelFormat Format { get; } |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_onDispose?.Invoke(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,14 +1,14 @@ |
|||||
<Project Sdk="Microsoft.NET.Sdk"> |
<Project Sdk="Microsoft.NET.Sdk"> |
||||
<PropertyGroup> |
<PropertyGroup> |
||||
<TargetFramework>netstandard2.0</TargetFramework> |
<TargetFramework>netstandard2.0</TargetFramework> |
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
||||
</PropertyGroup> |
</PropertyGroup> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" /> |
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" /> |
||||
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" /> |
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" /> |
||||
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" /> |
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" /> |
||||
<ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> |
<ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> |
||||
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" /> |
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" /> |
||||
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> |
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
@ -0,0 +1,111 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
||||
|
<PropertyGroup> |
||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
||||
|
<ProjectGuid>{4ADA61C8-D191-428D-9066-EF4F0D86520F}</ProjectGuid> |
||||
|
<OutputType>WinExe</OutputType> |
||||
|
<AppDesignerFolder>Properties</AppDesignerFolder> |
||||
|
<RootNamespace>Avalonia.Designer.HostApp</RootNamespace> |
||||
|
<AssemblyName>Avalonia.Designer.HostApp</AssemblyName> |
||||
|
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> |
||||
|
<FileAlignment>512</FileAlignment> |
||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> |
||||
|
<TargetFrameworkProfile /> |
||||
|
<RestoreProjectStyle>PackageReference</RestoreProjectStyle> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
||||
|
<PlatformTarget>AnyCPU</PlatformTarget> |
||||
|
<DebugSymbols>true</DebugSymbols> |
||||
|
<DebugType>full</DebugType> |
||||
|
<Optimize>false</Optimize> |
||||
|
<OutputPath>bin\Debug\</OutputPath> |
||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants> |
||||
|
<ErrorReport>prompt</ErrorReport> |
||||
|
<WarningLevel>4</WarningLevel> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
||||
|
<PlatformTarget>x86</PlatformTarget> |
||||
|
<DebugType>pdbonly</DebugType> |
||||
|
<Optimize>true</Optimize> |
||||
|
<OutputPath>bin\Release\</OutputPath> |
||||
|
<DefineConstants>TRACE</DefineConstants> |
||||
|
<ErrorReport>prompt</ErrorReport> |
||||
|
<WarningLevel>4</WarningLevel> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup> |
||||
|
<StartupObject /> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<Reference Include="System" /> |
||||
|
<Reference Include="System.Core" /> |
||||
|
<Reference Include="System.Xml.Linq" /> |
||||
|
<Reference Include="System.Data.DataSetExtensions" /> |
||||
|
<Reference Include="Microsoft.CSharp" /> |
||||
|
<Reference Include="System.Data" /> |
||||
|
<Reference Include="System.Xml" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<Compile Include="Program.cs" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj"> |
||||
|
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project> |
||||
|
<Name>Avalonia.DesignerSupport</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj"> |
||||
|
<Project>{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}</Project> |
||||
|
<Name>Avalonia.DotNetFrameworkRuntime</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj"> |
||||
|
<Project>{3E53A01A-B331-47F3-B828-4A5717E77A24}</Project> |
||||
|
<Name>Avalonia.Markup.Xaml</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Markup\Avalonia.Markup\Avalonia.Markup.csproj"> |
||||
|
<Project>{6417E941-21BC-467B-A771-0DE389353CE6}</Project> |
||||
|
<Name>Avalonia.Markup</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj"> |
||||
|
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project> |
||||
|
<Name>Avalonia.Animation</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj"> |
||||
|
<Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project> |
||||
|
<Name>Avalonia.Base</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj"> |
||||
|
<Project>{D2221C82-4A25-4583-9B43-D791E3F6820C}</Project> |
||||
|
<Name>Avalonia.Controls</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj"> |
||||
|
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project> |
||||
|
<Name>Avalonia.Input</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj"> |
||||
|
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project> |
||||
|
<Name>Avalonia.Interactivity</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj"> |
||||
|
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project> |
||||
|
<Name>Avalonia.Layout</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj"> |
||||
|
<Project>{B61B66A3-B82D-4875-8001-89D3394FE0C9}</Project> |
||||
|
<Name>Avalonia.Logging.Serilog</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj"> |
||||
|
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project> |
||||
|
<Name>Avalonia.Visuals</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj"> |
||||
|
<Project>{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}</Project> |
||||
|
<Name>Avalonia.Styling</Name> |
||||
|
</ProjectReference> |
||||
|
<ProjectReference Include="..\..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj"> |
||||
|
<Project>{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}</Project> |
||||
|
<Name>Avalonia.Themes.Default</Name> |
||||
|
</ProjectReference> |
||||
|
</ItemGroup> |
||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
||||
|
</Project> |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Designer.HostApp.NetFX |
||||
|
{ |
||||
|
class Program |
||||
|
{ |
||||
|
public static void Main(string[] args) |
||||
|
=> Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(args); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"profiles": { |
||||
|
"Avalonia.Designer.HostApp.NetFX": { |
||||
|
"commandName": "Project", |
||||
|
"commandLineArgs": "--transport tcp-bson://127.0.0.1:25000/ bin/Debug/net461/ControlCatalog.Desktop.exe" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFrameworks>netcoreapp2.0</TargetFrameworks> |
||||
|
|
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" /> |
||||
|
<ProjectReference Include="..\..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" /> |
||||
|
<ProjectReference Include="..\..\Markup\Avalonia.Markup\Avalonia.Markup.csproj" /> |
||||
|
<ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj" /> |
||||
|
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" /> |
||||
|
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" /> |
||||
|
<ProjectReference Include="..\..\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> |
||||
|
<ProjectReference Include="..\..\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.csproj" /> |
||||
|
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" /> |
||||
|
<ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> |
||||
|
<ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj" /> |
||||
|
<ProjectReference Include="..\..\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" /> |
||||
|
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" /> |
||||
|
<ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" /> |
||||
|
<ProjectReference Include="..\..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\samples\ControlCatalog.NetCore\ControlCatalog.NetCore.csproj" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,10 @@ |
|||||
|
|
||||
|
|
||||
|
namespace Avalonia.Designer.HostApp |
||||
|
{ |
||||
|
class Program |
||||
|
{ |
||||
|
public static void Main(string[] args) |
||||
|
=> Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(args); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue