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"> |
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" /> |
|||
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" /> |
|||
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" /> |
|||
<ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> |
|||
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" /> |
|||
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> |
|||
</ItemGroup> |
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" /> |
|||
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" /> |
|||
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" /> |
|||
<ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> |
|||
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" /> |
|||
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> |
|||
</ItemGroup> |
|||
</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