diff --git a/Avalonia.sln b/Avalonia.sln
index 3c2fc7437b..4e7b4cc318 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -221,8 +221,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "samples\Sandbox\Sandbox.csproj", "{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroComGenerator", "src\tools\MicroComGenerator\MicroComGenerator.csproj", "{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniMvvm", "samples\MiniMvvm\MiniMvvm.csproj", "{BC594FD5-4AF2-409E-A1E6-04123F54D7C5}"
@@ -2027,30 +2025,6 @@ Global
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhone.Build.0 = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.Build.0 = Release|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.ActiveCfg = Release|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.Build.0 = Release|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@@ -2253,7 +2227,6 @@ Global
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098}
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 903146cdd7..497402fe4b 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -58,8 +58,10 @@ jobs:
displayName: 'Generate avalonia-native'
inputs:
script: |
- export PATH="`pwd`/sdk:$PATH"
- cd src/tools/MicroComGenerator; dotnet run -f net6.0 -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h
+ export COREHOST_TRACE=0
+ export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
+ export DOTNET_CLI_TELEMETRY_OPTOUT=1
+ ./build.sh --target GenerateCppHeaders --configuration Release
- task: Xcode@5
inputs:
diff --git a/build/MicroCom.targets b/build/MicroCom.targets
deleted file mode 100644
index 029d7f95f5..0000000000
--- a/build/MicroCom.targets
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
- false
- all
- true
- TargetFramework=net6.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <_AvaloniaPatchComInterop>true
-
-
-
diff --git a/nukebuild/MicroComGen.cs b/nukebuild/MicroComGen.cs
index 06c8acbf23..b1e546cb97 100644
--- a/nukebuild/MicroComGen.cs
+++ b/nukebuild/MicroComGen.cs
@@ -1,14 +1,14 @@
using System.IO;
-using MicroComGenerator;
+using MicroCom.CodeGenerator;
using Nuke.Common;
partial class Build : NukeBuild
{
Target GenerateCppHeaders => _ => _.Executes(() =>
{
- var text = File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl");
- var ast = AstParser.Parse(text);
+ var file = MicroComCodeGenerator.Parse(
+ File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"));
File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h",
- CppGen.GenerateCpp(ast));
+ file.GenerateCppHeader());
});
}
\ No newline at end of file
diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj
index b28d3eb700..52b60b7d0f 100644
--- a/nukebuild/_build.csproj
+++ b/nukebuild/_build.csproj
@@ -15,7 +15,7 @@
-
+
@@ -37,10 +37,6 @@
-
- MicroComGenerator\%(Filename)%(Extension)
-
-
diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index 57f22e7a05..6a940a54f1 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/src/Android/Avalonia.Android/AndroidPlatform.cs
@@ -33,8 +33,16 @@ namespace Avalonia.Android
{
public static readonly AndroidPlatform Instance = new AndroidPlatform();
public static AndroidPlatformOptions Options { get; private set; }
- public Size DoubleClickSize => new Size(4, 4);
- public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200);
+
+ ///
+ public Size TouchDoubleClickSize => new Size(4, 4);
+
+ ///
+ public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(200);
+
+ public Size DoubleClickSize => TouchDoubleClickSize;
+
+ public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500);
public static void Initialize(Type appType, AndroidPlatformOptions options)
{
diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt
new file mode 100644
index 0000000000..4701a83175
--- /dev/null
+++ b/src/Avalonia.Base/ApiCompatBaseline.txt
@@ -0,0 +1,3 @@
+Compat issues with assembly Avalonia.Base:
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Threading.IDispatcher.Post(System.Action, T, Avalonia.Threading.DispatcherPriority)' is present in the implementation but not in the contract.
+Total Issues: 1
diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs
index ffc013268d..908f431776 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.cs
@@ -81,7 +81,7 @@ namespace Avalonia.Threading
_ = action ?? throw new ArgumentNullException(nameof(action));
return _jobRunner.InvokeAsync(action, priority);
}
-
+
///
public Task InvokeAsync(Func function, DispatcherPriority priority = DispatcherPriority.Normal)
{
@@ -110,6 +110,13 @@ namespace Avalonia.Threading
_jobRunner.Post(action, priority);
}
+ ///
+ public void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal)
+ {
+ _ = action ?? throw new ArgumentNullException(nameof(action));
+ _jobRunner.Post(action, arg, priority);
+ }
+
///
/// This is needed for platform backends that don't have internal priority system (e. g. win32)
/// To ensure that there are no jobs with higher priority
diff --git a/src/Avalonia.Base/Threading/IDispatcher.cs b/src/Avalonia.Base/Threading/IDispatcher.cs
index 084204dfcd..cd5add70d4 100644
--- a/src/Avalonia.Base/Threading/IDispatcher.cs
+++ b/src/Avalonia.Base/Threading/IDispatcher.cs
@@ -26,6 +26,15 @@ namespace Avalonia.Threading
/// The priority with which to invoke the method.
void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
+ ///
+ /// Posts an action that will be invoked on the dispatcher thread.
+ ///
+ /// type of argument
+ /// The method to call.
+ /// The argument of method to call.
+ /// The priority with which to invoke the method.
+ void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal);
+
///
/// Invokes a action on the dispatcher thread.
///
diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs
index eb7c5e1af3..f2aef0414c 100644
--- a/src/Avalonia.Base/Threading/JobRunner.cs
+++ b/src/Avalonia.Base/Threading/JobRunner.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Threading
{
private IPlatformThreadingInterface? _platform;
- private readonly Queue[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1)
+ private readonly Queue[] _queues = Enumerable.Range(0, (int)DispatcherPriority.MaxValue + 1)
.Select(_ => new Queue()).ToArray();
public JobRunner(IPlatformThreadingInterface? platform)
@@ -59,7 +59,7 @@ namespace Avalonia.Threading
/// A task that can be used to track the method's execution.
public Task InvokeAsync(Func function, DispatcherPriority priority)
{
- var job = new Job(function, priority);
+ var job = new JobWithResult(function, priority);
AddJob(job);
return job.Task;
}
@@ -75,6 +75,17 @@ namespace Avalonia.Threading
AddJob(new Job(action, priority, true));
}
+ ///
+ /// Post action that will be invoked on main thread
+ ///
+ /// The method to call.
+ /// The parameter of method to call.
+ /// The priority with which to invoke the method.
+ internal void Post(Action action, T parameter, DispatcherPriority priority)
+ {
+ AddJob(new Job(action, parameter, priority, true));
+ }
+
///
/// Allows unit tests to change the platform threading interface.
///
@@ -86,7 +97,7 @@ namespace Avalonia.Threading
private void AddJob(IJob job)
{
bool needWake;
- var queue = _queues[(int) job.Priority];
+ var queue = _queues[(int)job.Priority];
lock (queue)
{
needWake = queue.Count == 0;
@@ -98,7 +109,7 @@ namespace Avalonia.Threading
private IJob? GetNextJob(DispatcherPriority minimumPriority)
{
- for (int c = (int) DispatcherPriority.MaxValue; c >= (int) minimumPriority; c--)
+ for (int c = (int)DispatcherPriority.MaxValue; c >= (int)minimumPriority; c--)
{
var q = _queues[c];
lock (q)
@@ -109,14 +120,14 @@ namespace Avalonia.Threading
}
return null;
}
-
+
private interface IJob
{
///
/// Gets the job priority.
///
DispatcherPriority Priority { get; }
-
+
///
/// Runs the job.
///
@@ -177,11 +188,61 @@ namespace Avalonia.Threading
}
}
}
-
+
///
- /// A job to run.
+ /// A typed job to run.
+ ///
+ /// Type of job parameter
+ private sealed class Job : IJob
+ {
+ private readonly Action _action;
+ private readonly T _parameter;
+ private readonly TaskCompletionSource? _taskCompletionSource;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The method to call.
+ /// The parameter of method to call.
+ /// The job priority.
+ /// Do not wrap exception in TaskCompletionSource
+
+ public Job(Action action, T parameter, DispatcherPriority priority, bool throwOnUiThread)
+ {
+ _action = action;
+ _parameter = parameter;
+ Priority = priority;
+ _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource();
+ }
+
+ ///
+ public DispatcherPriority Priority { get; }
+
+ ///
+ void IJob.Run()
+ {
+ if (_taskCompletionSource == null)
+ {
+ _action(_parameter);
+ return;
+ }
+ try
+ {
+ _action(_parameter);
+ _taskCompletionSource.SetResult(default);
+ }
+ catch (Exception e)
+ {
+ _taskCompletionSource.SetException(e);
+ }
+ }
+ }
+
+ ///
+ /// A job to run thath return value.
///
- private sealed class Job : IJob
+ /// Type of job result
+ private sealed class JobWithResult : IJob
{
private readonly Func _function;
private readonly TaskCompletionSource _taskCompletionSource;
@@ -191,7 +252,7 @@ namespace Avalonia.Threading
///
/// The method to call.
/// The job priority.
- public Job(Func function, DispatcherPriority priority)
+ public JobWithResult(Func function, DispatcherPriority priority)
{
_function = function;
Priority = priority;
@@ -200,7 +261,7 @@ namespace Avalonia.Threading
///
public DispatcherPriority Priority { get; }
-
+
///
/// The task.
///
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 32428bea53..20d8a94c1a 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -1006,13 +1006,28 @@ namespace Avalonia.Controls
if (text != null && clickInfo.Properties.IsLeftButtonPressed && !(clickInfo.Pointer?.Captured is Border))
{
var point = e.GetPosition(_presenter);
- var index = CaretIndex = _presenter.GetCaretIndex(point);
+ var index = _presenter.GetCaretIndex(point);
+ var clickToSelect = index != CaretIndex && e.KeyModifiers.HasFlag(KeyModifiers.Shift);
+
+ if (!clickToSelect)
+ {
+ CaretIndex = index;
+ }
+
#pragma warning disable CS0618 // Type or member is obsolete
switch (e.ClickCount)
#pragma warning restore CS0618 // Type or member is obsolete
{
case 1:
- SelectionStart = SelectionEnd = index;
+ if (clickToSelect)
+ {
+ SelectionStart = Math.Min(index, CaretIndex);
+ SelectionEnd = Math.Max(index, CaretIndex);
+ }
+ else
+ {
+ SelectionStart = SelectionEnd = index;
+ }
break;
case 2:
if (!StringUtils.IsStartOfWord(text, index))
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
index ada63f5326..5d8b166b5a 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
+++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
@@ -64,5 +64,9 @@ namespace Avalonia.DesignerSupport.Remote
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
+
+ public Size TouchDoubleClickSize => new Size(16, 16);
+
+ public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs
new file mode 100644
index 0000000000..b41755f239
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Reflection;
+using Avalonia.Controls;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Diagnostics
+{
+ static class Convetions
+ {
+ public static string DefaultScreenshotsRoot =>
+ System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create),
+ "Screenshots");
+
+ public static IScreenshotHandler DefaultScreenshotHandler { get; } =
+ new Screenshots.FilePickerHandler();
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
index c07e794b9e..46ee8e686c 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
@@ -1,4 +1,5 @@
-using Avalonia.Input;
+using System;
+using Avalonia.Input;
namespace Avalonia.Diagnostics
{
@@ -33,5 +34,12 @@ namespace Avalonia.Diagnostics
/// Gets or sets a value indicating whether DevTools should be displayed implemented interfaces on Control details. The default value is true.
///
public bool ShowImplementedInterfaces { get; set; } = true;
+
+ ///
+ /// Allow to customizze SreenshotHandler
+ ///
+ /// Default handler is
+ public IScreenshotHandler ScreenshotHandler { get; set; }
+ = Convetions.DefaultScreenshotHandler;
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs
new file mode 100644
index 0000000000..3503ab56b3
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs
@@ -0,0 +1,17 @@
+using System.Threading.Tasks;
+using Avalonia.Controls;
+
+namespace Avalonia.Diagnostics
+{
+ ///
+ /// Allowed to define custom handler for Shreeshot
+ ///
+ public interface IScreenshotHandler
+ {
+ ///
+ /// Handle the Screenshot
+ ///
+ ///
+ Task Take(IControl control);
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs
new file mode 100644
index 0000000000..c111b045d3
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs
@@ -0,0 +1,29 @@
+using System.Threading.Tasks;
+using Avalonia.Controls;
+
+namespace Avalonia.Diagnostics.Screenshots
+{
+ ///
+ /// Base class for render Screenshto to stream
+ ///
+ public abstract class BaseRenderToStreamHandler : IScreenshotHandler
+ {
+
+ ///
+ /// Get stream
+ ///
+ ///
+ /// stream to render the control
+ protected abstract Task GetStream(IControl control);
+
+ public async Task Take(IControl control)
+ {
+ using var output = await GetStream(control);
+ if (output is { })
+ {
+ control.RenderTo(output);
+ await output.FlushAsync();
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
new file mode 100644
index 0000000000..fb57058ae9
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
@@ -0,0 +1,85 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Lifetimes = Avalonia.Controls.ApplicationLifetimes;
+
+namespace Avalonia.Diagnostics.Screenshots
+{
+ ///
+ /// Show a FileSavePicker to select where save screenshot
+ ///
+ public sealed class FilePickerHandler : BaseRenderToStreamHandler
+ {
+ ///
+ /// Instance FilePickerHandler
+ ///
+ public FilePickerHandler()
+ {
+
+ }
+ ///
+ /// Instance FilePickerHandler with specificated parameter
+ ///
+ /// SaveFilePicker Title
+ ///
+ public FilePickerHandler(string? title
+ , string? screenshotRoot = default
+ )
+ {
+ if (title is { })
+ Title = title;
+ if (screenshotRoot is { })
+ ScreenshotsRoot = screenshotRoot;
+ }
+ ///
+ /// Get the root folder where screeshots well be stored.
+ /// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots.
+ ///
+ public string ScreenshotsRoot { get; }
+ = Convetions.DefaultScreenshotsRoot;
+
+ ///
+ /// SaveFilePicker Title
+ ///
+ public string Title { get; } = "Save Screenshot to ...";
+
+ Window GetWindow(IControl control)
+ {
+ var window = control.VisualRoot as Window;
+ var app = Application.Current;
+ if (app?.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ window = desktop.Windows.FirstOrDefault(w => w is Views.MainWindow);
+ }
+ return window!;
+ }
+
+ protected async override Task GetStream(IControl control)
+ {
+ Stream? output = default;
+ var result = await new SaveFileDialog()
+ {
+ Title = Title,
+ Filters = new() { new FileDialogFilter() { Name = "PNG", Extensions = new() { "png" } } },
+ Directory = ScreenshotsRoot,
+ }.ShowAsync(GetWindow(control));
+ if (!string.IsNullOrWhiteSpace(result))
+ {
+ var foldler = Path.GetDirectoryName(result);
+ // Directory information for path, or null if path denotes a root directory or is
+ // null. Returns System.String.Empty if path does not contain directory information.
+ if (!string.IsNullOrWhiteSpace(foldler))
+ {
+ if (!Directory.Exists(foldler))
+ {
+ Directory.CreateDirectory(foldler);
+ }
+ output = new FileStream(result, FileMode.Create);
+ }
+ }
+ return output;
+ }
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
index d04016f7f9..e08c5bc8dd 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
@@ -1,8 +1,11 @@
using System;
using System.ComponentModel;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Diagnostics.Models;
using Avalonia.Input;
+using Avalonia.Metadata;
using Avalonia.Threading;
using System.Reactive.Linq;
using System.Linq;
@@ -26,8 +29,10 @@ namespace Avalonia.Diagnostics.ViewModels
private bool _freezePopups;
private string? _pointerOverElementName;
private IInputRoot? _pointerOverRoot;
+ private IScreenshotHandler? _screenshotHandler;
private bool _showPropertyType;
private bool _showImplementedInterfaces;
+
public MainViewModel(AvaloniaObject root)
{
_root = root;
@@ -286,9 +291,38 @@ namespace Avalonia.Diagnostics.ViewModels
}
public int? StartupScreenIndex { get; private set; } = default;
+
+ [DependsOn(nameof(TreePageViewModel.SelectedNode))]
+ [DependsOn(nameof(Content))]
+ bool CanShot(object? parameter)
+ {
+ return Content is TreePageViewModel tree
+ && tree.SelectedNode != null
+ && tree.SelectedNode.Visual is VisualTree.IVisual visual
+ && visual.VisualRoot != null;
+ }
+
+ async void Shot(object? parameter)
+ {
+ if ((Content as TreePageViewModel)?.SelectedNode?.Visual is IControl control
+ && _screenshotHandler is { }
+ )
+ {
+ try
+ {
+ await _screenshotHandler.Take(control);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine(ex.Message);
+ //TODO: Notify error
+ }
+ }
+ }
public void SetOptions(DevToolsOptions options)
{
+ _screenshotHandler = options.ScreenshotHandler;
StartupScreenIndex = options.StartupScreenIndex;
ShowImplementedInterfaces = options.ShowImplementedInterfaces;
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
index 2af569c92f..35c9e27f1a 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
@@ -6,6 +6,36 @@