Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DevTools] Allowed to take a screenshot of the currently selected control. #4762

Merged
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
32b45de
[DevTools] Allowed to take a screenshot of the currently selected con…
workgroupengineering Sep 28, 2020
5db991c
Merge branch 'master' into features/DevTools/Screenshot
workgroupengineering Jun 11, 2021
f99526f
feat(DevTools): Allowed to customize screenshot root folder.
workgroupengineering Jun 11, 2021
861fdb2
feat(DevTools): Allowed screenshot filename by convention
workgroupengineering Jun 11, 2021
1e2fd87
fixes(DevTools): Mark Shot as async and catch eventualy RenderTo exce…
workgroupengineering Jun 11, 2021
7c85b99
fixes: allignment of code comment
workgroupengineering Jun 11, 2021
5c2e066
fixes: CS0173 when build on linux and macOS
workgroupengineering Jun 11, 2021
af51871
Refactoring Shot method
workgroupengineering Jun 12, 2021
b5f30aa
Merge branch 'master' into features/DevTools/Screenshot
workgroupengineering Jun 12, 2021
25356a9
fixes: typo in DefaultScreenshotRoot
workgroupengineering Jun 12, 2021
dcdbe6e
fixes: assembly detection
workgroupengineering Jun 12, 2021
9cb6c5d
fixes: code comment
workgroupengineering Jun 12, 2021
0c1f6cc
fixes: nullable warnings
workgroupengineering Jun 12, 2021
d683a9d
fixes: merge issue
workgroupengineering Jun 12, 2021
de5a033
fixes: removed unnecessary using
workgroupengineering Jun 12, 2021
b715db1
fixes: removed Dispatcher.UIThread.InvokeAsync
spaccabit Jun 13, 2021
a82b861
fixes: unexpected to the user results after call RenderTo
spaccabit Jun 13, 2021
f00a101
fixes: screenshot Clip
workgroupengineering Jun 15, 2021
73b43d2
Merge branch 'master' into features/DevTools/Screenshot
workgroupengineering Jun 15, 2021
6173eca
fixes: typo
workgroupengineering Jun 15, 2021
e0549a1
Merge branch 'master' into features/DevTools/Screenshot
workgroupengineering Jun 16, 2021
f6e4b29
Merge branch 'master' into features/DevTools/Screenshot
workgroupengineering Jun 16, 2021
a6fe880
fixes: remove workaraund
workgroupengineering Jun 18, 2021
89f9d8d
Merge branch 'master' into features/DevTools/Screenshot
workgroupengineering Jun 18, 2021
5d6efb3
Merge branch 'master' into features/DevTools/Screenshot
workgroupengineering Jul 10, 2021
cd09d9a
Merge branch 'master' into features/DevTools/Screenshot
jmacato Aug 1, 2021
6836f95
Merge remote-tracking branch 'upstream/master' into features/DevTools…
workgroupengineering Aug 23, 2021
5cbb702
Apply suggestions from code review
maxkatz6 Dec 25, 2021
84e511c
fixes(DevTools): merge conflict with master branch
workgroupengineering Dec 27, 2021
8b810f2
feat(DevTools): Add Shot Button
workgroupengineering Dec 27, 2021
0422187
feat(DevTools): Custom Screenshot handler
workgroupengineering Dec 27, 2021
7540112
fixes(DevTools): Renamed Screenshots.FileHandler to Screenshots.FileC…
workgroupengineering Dec 27, 2021
1007ab2
feat(DevTools): Screenshots.BaseRenderToStreamHandler
workgroupengineering Dec 27, 2021
a7c3562
feat(DevTools): Screenshots.FilePickerHandler
workgroupengineering Dec 27, 2021
c36944a
fixes(DevTools): merge conflict with master branch
workgroupengineering Dec 27, 2021
5d246e7
fixes(DevTools): marked VisualExtensions as internal
workgroupengineering Dec 28, 2021
d7826e9
fixes(DevTools): Strip out FileConvetionHandler
workgroupengineering Dec 28, 2021
771238b
fixes(DevTools): FilePickerHandler promote Title and ScreenshotsRoot …
workgroupengineering Dec 28, 2021
3591daf
fixes(DevTools): Remove screenshot HotKey description form status bar
workgroupengineering Dec 28, 2021
4414a43
fixes(DevTools): Xml Comment
workgroupengineering Dec 28, 2021
08236df
fixes(DevTools): Moved Screenshot command to File menu
workgroupengineering Dec 28, 2021
98a8564
fixes(DevTools): Removed reference to Avalonia.Dialogs
workgroupengineering Dec 29, 2021
a5a3ddb
Merge branch 'master' into features/DevTools/Screenshot
maxkatz6 Dec 29, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
workgroupengineering marked this conversation as resolved.
Show resolved Hide resolved
<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" />
Expand Down
54 changes: 54 additions & 0 deletions src/Avalonia.Diagnostics/Diagnostics/Convetions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.VisualTree;

namespace Avalonia.Diagnostics
{
static class Convetions
{
public static string DefaultScreenshotsRoot =>
workgroupengineering marked this conversation as resolved.
Show resolved Hide resolved
System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create),
"Screenshots");

/// <summary>
/// Return the path of the screenshot folder according to the rules indicated in issue <see href="https://github.com/AvaloniaUI/Avalonia/issues/4743">GH-4743</see>
/// </summary>
public static Func<IControl, string,string> DefaultScreenshotFileNameConvention = (control,screenshotRoot) =>
workgroupengineering marked this conversation as resolved.
Show resolved Hide resolved
{
IVisual root;
if ((control.VisualRoot ?? control.GetVisualRoot()) is IVisual vr)
{
root = vr;
}
else
{
root = control;
}
var rootType = root.GetType();
var windowName = rootType.Name;
if (root is IControl rc && !string.IsNullOrWhiteSpace(rc.Name))
{
windowName = rc.Name;
}

var assembly = Assembly.GetEntryAssembly()!;
var appName = Application.Current?.Name
?? assembly.GetCustomAttribute<AssemblyProductAttribute>()?.Product
?? assembly.GetName().Name;
var appVerions = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion
?? assembly?.GetCustomAttribute<AssemblyVersionAttribute>()?.Version
?? "0.0.0.0";
var folder = System.IO.Path.Combine(screenshotRoot
, appName!
, appVerions
, windowName);

return System.IO.Path.Combine(folder
, $"{DateTime.Now:yyyyMMddhhmmssfff}.png");
};

public static IScreenshotHandler DefaultScreenshotHandler { get; } =
new Screenshots.FilePickerHandler();
}
}
10 changes: 9 additions & 1 deletion src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Avalonia.Input;
using System;
using Avalonia.Input;

namespace Avalonia.Diagnostics
{
Expand Down Expand Up @@ -28,5 +29,12 @@ public class DevToolsOptions
/// Get or set the startup screen index where the DevTools window will be displayed.
/// </summary>
public int? StartupScreenIndex { get; set; }

/// <summary>
/// Allow to customizze SreenshotHandler
/// </summary>
/// <remarks>Default handler is <see cref="Screenshots.FileConvetionHandler"/></remarks>
public IScreenshotHandler ScreenshotHandler { get; set; }
= Convetions.DefaultScreenshotHandler;
}
}
17 changes: 17 additions & 0 deletions src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Threading.Tasks;
using Avalonia.Controls;

namespace Avalonia.Diagnostics
{
/// <summary>
/// Allowed to define custom handler for Shreeshot
/// </summary>
public interface IScreenshotHandler
{
/// <summary>
/// Handle the Screenshot
/// </summary>
/// <returns></returns>
Task Take(IControl control);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Avalonia.Controls;

namespace Avalonia.Diagnostics.Screenshots
{
/// <summary>
/// Base class for render Screenshto to stream
/// </summary>
public abstract class BaseRenderToStreamHandler : IScreenshotHandler
{

/// <summary>
/// Get stream
/// </summary>
/// <param name="control"></param>
/// <returns>stream to render the control</returns>
protected abstract Task<System.IO.Stream?> GetStream(IControl control);

public async Task Take(IControl control)
{
using var output = await GetStream(control);
if (output is { })
{
control.RenderTo(output);
await output.FlushAsync();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls;

namespace Avalonia.Diagnostics.Screenshots
{
/// <summary>
/// Take a Screenshot on file by convention
/// </summary>
public sealed class FileConvetionHandler : BaseRenderToStreamHandler
workgroupengineering marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Get or sets the root folder where screeshots well be stored.
/// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots.
/// </summary>
public string ScreenshotsRoot { get; set; }
= Convetions.DefaultScreenshotsRoot;

/// <summary>
/// Get or sets conventin for screenshot fileName.
/// For known default screen shot file name convection see <see href="https://github.com/AvaloniaUI/Avalonia/issues/4743">GH-4743</see>.
/// </summary>
public Func<IControl, string, string> ScreenshotFileNameConvention { get; set; }
= Convetions.DefaultScreenshotFileNameConvention;

protected override async Task<System.IO.Stream?> GetStream(IControl control)
{
var filePath = ScreenshotFileNameConvention(control, ScreenshotsRoot);
var folder = System.IO.Path.GetDirectoryName(filePath);
if (System.IO.Directory.Exists(folder) == false)
{
await Task.Run(new Action(() => System.IO.Directory.CreateDirectory(folder!)));
}
return new System.IO.FileStream(filePath, System.IO.FileMode.Create);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Lifetimes = Avalonia.Controls.ApplicationLifetimes;

namespace Avalonia.Diagnostics.Screenshots
{
/// <summary>
/// Show a FileSavePicker to select where save screenshot
/// </summary>
public sealed class FilePickerHandler : BaseRenderToStreamHandler
{
/// <summary>
/// Get or sets the root folder where screeshots well be stored.
/// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots.
/// </summary>
public string ScreenshotsRoot { get; set; }
workgroupengineering marked this conversation as resolved.
Show resolved Hide resolved
= Convetions.DefaultScreenshotsRoot;

/// <summary>
/// Get or sets conventin for screenshot fileName.
/// For known default screen shot file name convection see <see href="https://github.com/AvaloniaUI/Avalonia/issues/4743">GH-4743</see>.
/// </summary>
public Func<IControl, string, string> ScreenshotFileNameConvention { get; set; }
= Convetions.DefaultScreenshotFileNameConvention;

/// <summary>
/// SaveFilePicker Title
/// </summary>
public string Title { get; set; } = "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<Stream?> GetStream(IControl control)
{
Stream? output = default;
var result = await new SaveFileDialog()
{
Title = Title,
Filters = new() { new FileDialogFilter() { Name = "PNG", Extensions = new() { "png" } } },
Directory = ScreenshotsRoot,
InitialFileName = ScreenshotFileNameConvention(control, ScreenshotsRoot)
}.ShowAsync(GetWindow(control));
if (result is { })
{
output = new FileStream(result, FileMode.Create);
}
return output;
}
}
}
34 changes: 34 additions & 0 deletions src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -26,7 +29,9 @@ internal class MainViewModel : ViewModelBase, IDisposable
private bool _freezePopups;
private string? _pointerOverElementName;
private IInputRoot? _pointerOverRoot;
private IScreenshotHandler? _screenshotHandler;
private bool _showPropertyType;

public MainViewModel(AvaloniaObject root)
{
_root = root;
Expand Down Expand Up @@ -285,9 +290,38 @@ public void RequestTreeNavigateTo(IControl control, bool isVisualTree)
}

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);
maxkatz6 marked this conversation as resolved.
Show resolved Hide resolved
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
//TODO: Notify error
}
}
}

public void SetOptions(DevToolsOptions options)
{
_screenshotHandler = options.ScreenshotHandler;
StartupScreenIndex = options.StartupScreenIndex;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,38 @@

<Grid Grid.Column="0" RowDefinitions="Auto,Auto,*">

<Grid ColumnDefinitions="Auto, *" RowDefinitions="Auto, Auto">
<Grid ColumnDefinitions="Auto, *,Auto" RowDefinitions="Auto, Auto">
<Button Grid.Column="0" Grid.RowSpan="2" Content="^" Command="{Binding ApplyParentProperty}" />
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding SelectedEntityName}" FontWeight="Bold" />
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding SelectedEntityType}" FontStyle="Italic" />
<Button Command="{Binding $parent[local:MainView].DataContext.Shot}" Grid.Column="2" Grid.RowSpan="2">
workgroupengineering marked this conversation as resolved.
Show resolved Hide resolved
<Image>
<DrawingImage>
<DrawingGroup>
<GeometryDrawing Geometry="F1 M 13.4533,17.56C 13.4533,19.8827 15.344,21.772 17.6653,21.772L 17.6653,21.772C 19.988,21.772 21.8773,19.8827 21.8773,17.56L 21.8773,17.56C 21.8773,15.2373 19.988,13.348 17.6653,13.348L 17.6653,13.348C 15.344,13.348 13.4533,15.2373 13.4533,17.56 Z ">
<GeometryDrawing.Brush>
<RadialGradientBrush Center="0.245696,0.288009" GradientOrigin="0.245696,0.288009" Radius="0.499952">
<RadialGradientBrush.GradientStops>
<GradientStop Color="#FF878A8C" Offset="0" />
<GradientStop Color="#FF544A4C" Offset="0.991379" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Geometry="F1 M 13.332,6.22803L 10.2227,9.72668L 8.49866,9.72668L 8.49866,7.56136L 5.33333,7.56136L 5.33333,9.72668L 3.33333,9.72668L 3.33333,24.3947L 13.1213,24.3947C 14.424,25.264 15.9853,25.772 17.6653,25.772L 17.6653,25.772C 19.344,25.772 20.9067,25.264 22.2094,24.3947L 28.6667,24.3947L 28.6667,9.72668L 24.944,9.72668L 21.8333,6.22803M 12.12,17.56C 12.12,14.5013 14.608,12.0147 17.6653,12.0147L 17.6653,12.0147C 20.7227,12.0147 23.2107,14.5013 23.2107,17.56L 23.2107,17.56C 23.2107,20.6174 20.7227,23.104 17.6653,23.104L 17.6653,23.104C 14.608,23.104 12.12,20.6174 12.12,17.56 Z ">
<GeometryDrawing.Brush>
<RadialGradientBrush Center="0.196943,0.216757" GradientOrigin="0.196943,0.216757" Radius="0.44654">
<RadialGradientBrush.GradientStops>
<GradientStop Color="#FF87898C" Offset="0" />
<GradientStop Color="#FF544A4C" Offset="1" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage>
</Image>
</Button>
</Grid>

<controls:FilterTextBox Grid.Row="1"
Expand Down
2 changes: 2 additions & 0 deletions src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
BorderThickness="0,1,0,0">
<Grid ColumnDefinitions="*, Auto">
<StackPanel Grid.Column="0" Spacing="4" Orientation="Horizontal">
<TextBlock>Hold F8 to take a screenshot.</TextBlock>
<Separator Width="8"/>
<TextBlock>Hold Ctrl+Shift over a control to inspect.</TextBlock>
<Separator Width="8" />
<TextBlock>Focused:</TextBlock>
Expand Down
4 changes: 3 additions & 1 deletion src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
<StyleInclude Source="avares://Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.axaml" />
<StyleInclude Source="avares://Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml" />
</Window.Styles>

<Window.KeyBindings>
<KeyBinding Gesture="F8" Command="{Binding Shot}"/>
</Window.KeyBindings>
<views:MainView/>
</Window>
Loading