From 32b45de1081611df7aad99f1740ac38c8fbe67de Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 28 Sep 2020 17:25:21 +0200 Subject: [PATCH 01/31] [DevTools] Allowed to take a screenshot of the currently selected control. --- .../Diagnostics/ViewModels/MainViewModel.cs | 129 +++++++++++++++--- .../Diagnostics/Views/MainView.xaml | 2 + .../Diagnostics/Views/MainWindow.xaml | 4 +- .../Diagnostics/VisualExtensions.cs | 63 +++++++++ 4 files changed, 179 insertions(+), 19 deletions(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index bf7d0e232a5..0d3f5842cd2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -1,8 +1,13 @@ using System; using System.ComponentModel; +using System.Reactive.Linq; +using System.Reflection; +using System.Threading.Tasks; using Avalonia.Controls; +using Avalonia.Controls.Shapes; using Avalonia.Diagnostics.Models; using Avalonia.Input; +using Avalonia.Metadata; using Avalonia.Threading; namespace Avalonia.Diagnostics.ViewModels @@ -21,6 +26,7 @@ internal class MainViewModel : ViewModelBase, IDisposable private bool _shouldVisualizeMarginPadding = true; private bool _shouldVisualizeDirtyRects; private bool _showFpsOverlay; + private IDisposable _selectedNodeChanged; public MainViewModel(TopLevel root) { @@ -42,7 +48,7 @@ public bool ShouldVisualizeMarginPadding get => _shouldVisualizeMarginPadding; set => RaiseAndSetIfChanged(ref _shouldVisualizeMarginPadding, value); } - + public bool ShouldVisualizeDirtyRects { get => _shouldVisualizeDirtyRects; @@ -85,27 +91,43 @@ public ViewModelBase Content get { return _content; } private set { - if (_content is TreePageViewModel oldTree && - value is TreePageViewModel newTree && - oldTree?.SelectedNode?.Visual is IControl control) + TreePageViewModel oldTree = _content as TreePageViewModel; + TreePageViewModel newTree = value as TreePageViewModel; + if (oldTree != null) { - // HACK: We want to select the currently selected control in the new tree, but - // to select nested nodes in TreeView, currently the TreeView has to be able to - // expand the parent nodes. Because at this point the TreeView isn't visible, - // this will fail unless we schedule the selection to run after layout. - DispatcherTimer.RunOnce( - () => - { - try + _selectedNodeChanged?.Dispose(); + _selectedNodeChanged = null; + } + + if (newTree != null) + { + if (oldTree != null && + oldTree?.SelectedNode?.Visual is IControl control) + { + // HACK: We want to select the currently selected control in the new tree, but + // to select nested nodes in TreeView, currently the TreeView has to be able to + // expand the parent nodes. Because at this point the TreeView isn't visible, + // this will fail unless we schedule the selection to run after layout. + DispatcherTimer.RunOnce( + () => { - newTree.SelectControl(control); - } - catch { } - }, - TimeSpan.FromMilliseconds(0), - DispatcherPriority.ApplicationIdle); + try + { + newTree.SelectControl(control); + } + catch { } + }, + TimeSpan.FromMilliseconds(0), + DispatcherPriority.ApplicationIdle); + } + _selectedNodeChanged = Observable + .FromEventPattern( + x => newTree.PropertyChanged += x, + x => newTree.PropertyChanged -= x + ).Subscribe(arg => RaisePropertyChanged(nameof(TreePageViewModel.SelectedNode))); } + RaiseAndSetIfChanged(ref _content, value); } } @@ -167,6 +189,7 @@ public void Dispose() { KeyboardDevice.Instance.PropertyChanged -= KeyboardPropertyChanged; _pointerOverSubscription.Dispose(); + _selectedNodeChanged?.Dispose(); _logicalTree.Dispose(); _visualTree.Dispose(); _root.Renderer.DrawDirtyRects = false; @@ -185,5 +208,75 @@ private void KeyboardPropertyChanged(object sender, PropertyChangedEventArgs e) UpdateFocusedControl(); } } + + [DependsOn(nameof(TreePageViewModel.SelectedNode))] + [DependsOn(nameof(Content))] + bool CanShot(object paramter) + { + return Content is TreePageViewModel tree + && tree.SelectedNode != null + && tree.SelectedNode.Visual != null + && tree.SelectedNode.Visual.VisualRoot != null; + } + + void Shot(object parameter) + { + // This is a workaround because MethodToCommand does not support the asynchronous method. + Task.Factory.StartNew(arg => + { + if (arg is IControl control) + { + try + { + var folder = GetScreenShotDirectory(_root); + if (System.IO.Directory.Exists(folder) == false) + { + System.IO.Directory.CreateDirectory(folder); + } + var filePath = System.IO.Path.Combine(folder + , $"{DateTime.Now:yyyyMMddhhmmssfff}.png"); + + var output = new System.IO.FileStream(filePath, System.IO.FileMode.Create); + Dispatcher.UIThread.Post(() => + { + control.RenderTo(output); + output.Dispose(); + } + ); + + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + //TODO: Notify error + } + + } + + + }, (Content as TreePageViewModel)?.SelectedNode?.Visual); + } + + /// + /// Return the path of the screenshot folder according to the rules indicated in issue GH-4743 + /// + /// + /// + string GetScreenShotDirectory(TopLevel root) + { + var rootType = root.GetType(); + var windowName = rootType.Name; + var assembly = Assembly.GetExecutingAssembly(); + var appName = Application.Current?.Name + ?? assembly.GetCustomAttribute()?.Product + ?? assembly.GetName().Name; + var appVerions = assembly.GetCustomAttribute().InformationalVersion + ?? assembly.GetCustomAttribute().Version; + return System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create) + , "ScreenShots" + , appName + , appVerions + , windowName); + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 8c4db33f913..516ad8628c2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -65,6 +65,8 @@ BorderBrush="{DynamicResource ThemeControlMidBrush}" BorderThickness="0,1,0,0"> + Hold F8 to take a screenshot. + Hold Ctrl+Shift over a control to inspect. Focused: diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index 7dd4ed0832b..ed621949e49 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -16,6 +16,8 @@ - + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs new file mode 100644 index 00000000000..7902c58b4fa --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -0,0 +1,63 @@ +using System.IO; +using Avalonia.Controls; +using Avalonia.Media.Imaging; +using Avalonia.VisualTree; + +namespace Avalonia.Diagnostics +{ + static class VisualExtensions + { + /// + /// Rendered control to stream + /// + /// the control I want to render in the stream + /// destination destina + /// (optional) dpi quality default 96 + public static void RenderTo(this IControl source, Stream destination, double dpi = 96) + { + var rect = source.Bounds; + var pixelSize = new PixelSize((int)rect.Width, (int)rect.Height); + var dpiVector = new Vector(dpi, dpi); + + // get Visual root + var root = (source.VisualRoot + ?? source.GetVisualRoot()) + as IControl; + + // Backup current vaules + var oldgeometry = root.Clip; + var oldClipToBounds = root.ClipToBounds; + var oldRenderTransformOrigin = root.RenderTransformOrigin; + var oldrenderTransform = root.RenderTransform; + + try + { + // Translate coordinate clinet to root + var top = source.TranslatePoint(new Point(0,0), root); + var bottomRight = source.TranslatePoint(rect.BottomRight, root); + + // Set clip region + var clipRegion = new Media.RectangleGeometry(new Rect(top.Value, bottomRight.Value)); + root.ClipToBounds = true; + root.Clip = clipRegion; + // Traslate origin + root.RenderTransformOrigin = new RelativePoint(top.Value, RelativeUnit.Absolute); + root.RenderTransform = new Media.TranslateTransform(-top.Value.X, -top.Value.Y); + using (var bitmap = new RenderTargetBitmap(pixelSize, dpiVector)) + { + bitmap.Render(root); + bitmap.Save(destination); + } + + } + finally + { + // Restore current vaules + root.ClipToBounds = oldClipToBounds; + root.Clip = oldgeometry; + root.RenderTransformOrigin = oldRenderTransformOrigin; + root.RenderTransform = oldrenderTransform; + } + } + } +} From f99526fccc22b942e58293b75deafe87ff8621f1 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 11 Jun 2021 12:36:06 +0200 Subject: [PATCH 02/31] feat(DevTools): Allowed to customize screenshot root folder. --- src/Avalonia.Diagnostics/Diagnostics/Convetions.cs | 11 +++++++++++ src/Avalonia.Diagnostics/Diagnostics/DevTools.cs | 5 ++++- .../Diagnostics/DevToolsOptions.cs | 8 ++++++++ .../Diagnostics/ViewModels/MainViewModel.cs | 12 ++++++++++-- .../Diagnostics/Views/MainWindow.xaml.cs | 3 +++ 5 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Convetions.cs diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs new file mode 100644 index 00000000000..cf3158a82c0 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs @@ -0,0 +1,11 @@ +using System; + +namespace Avalonia.Diagnostics +{ + static class Convetions + { + public static string DefaultScreenShotRoot => + System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create), + "ScreenShot"); + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 0e36c8f9cb0..acbd9172b19 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -10,7 +10,8 @@ namespace Avalonia.Diagnostics { public static class DevTools { - private static readonly Dictionary s_open = new Dictionary(); + private static readonly Dictionary s_open = + new Dictionary(); public static IDisposable Attach(TopLevel root, KeyGesture gesture) { @@ -53,6 +54,8 @@ public static IDisposable Open(TopLevel root, DevToolsOptions options) Height = options.Size.Height, }; + window.SetOptions(options); + window.Closed += DevToolsClosed; s_open.Add(root, window); diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index f9978f3b7ef..af827e0dc2a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -22,5 +22,13 @@ public class DevToolsOptions /// Gets or sets the initial size of the DevTools window. The default value is 1280x720. /// public Size Size { get; set; } = new Size(1280, 720); + + + /// + /// Get or sets the root folder where screeshot well be stored. + /// The default root folder is MyPictures/ScreenShot. + /// + public string? ScreenshotRoot { get; set; } + } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 733a0b0893f..7ea775d025c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -27,6 +27,7 @@ internal class MainViewModel : ViewModelBase, IDisposable private bool _shouldVisualizeDirtyRects; private bool _showFpsOverlay; private IDisposable _selectedNodeChanged; + private string _screenshotRoot; #nullable disable // Remove "nullable disable" after MemberNotNull will work on our CI. @@ -282,6 +283,13 @@ void Shot(object? parameter) }, (Content as TreePageViewModel)?.SelectedNode?.Visual); } + public void SetOptions(DevToolsOptions options) + { + _screenshotRoot = string.IsNullOrWhiteSpace(options.ScreenshotRoot) + ? Convetions.DefaultScreenShotRoot + : options.ScreenshotRoot!; + } + /// /// Return the path of the screenshot folder according to the rules indicated in issue GH-4743 /// @@ -297,11 +305,11 @@ string GetScreenShotDirectory(TopLevel root) ?? assembly.GetName().Name; var appVerions = assembly.GetCustomAttribute().InformationalVersion ?? assembly.GetCustomAttribute().Version; - return System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create) - , "ScreenShots" + return System.IO.Path.Combine(_screenshotRoot , appName , appVerions , windowName); + ; } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index bbb8e765513..c0b82c4fff2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -115,5 +115,8 @@ private void RawKeyDown(RawKeyEventArgs e) } private void RootClosed(object? sender, EventArgs e) => Close(); + + public void SetOptions(DevToolsOptions options) => + (DataContext as MainViewModel)?.SetOptions(options); } } From 861fdb2c1de5028d16358b13c15824a9b9903cd7 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 11 Jun 2021 14:46:07 +0200 Subject: [PATCH 03/31] feat(DevTools): Allowed screenshot filename by convention --- .../Diagnostics/Convetions.cs | 37 ++++++++++++++++++- .../Diagnostics/DevToolsOptions.cs | 8 +++- .../Diagnostics/ViewModels/MainViewModel.cs | 36 ++++-------------- 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs index cf3158a82c0..24dc70396b3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs @@ -1,11 +1,46 @@ using System; +using System.Reflection; +using Avalonia.Controls; +using Avalonia.VisualTree; namespace Avalonia.Diagnostics { static class Convetions { - public static string DefaultScreenShotRoot => + public static string DefaultScreenshotRoot => System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create), "ScreenShot"); + + /// + /// Return the path of the screenshot folder according to the rules indicated in issue GH-4743 + /// + /// + /// full file path + public static Func DefaultScreenshotFileNameConvention = (control,screenshotRoot) => + { + IVisual root = control.VisualRoot is null + ? control + : control.VisualRoot ?? control.GetVisualRoot(); + var rootType = root.GetType(); + var windowName = rootType.Name; + if (root is IControl rc && !string.IsNullOrWhiteSpace(rc.Name)) + { + windowName = rc.Name; + } + + var assembly = Assembly.GetExecutingAssembly(); + var appName = Application.Current?.Name + ?? assembly.GetCustomAttribute()?.Product + ?? assembly.GetName().Name; + var appVerions = assembly.GetCustomAttribute().InformationalVersion + ?? assembly.GetCustomAttribute().Version; + var folder = System.IO.Path.Combine(screenshotRoot + , appName + , appVerions + , windowName); + + return System.IO.Path.Combine(folder + , $"{DateTime.Now:yyyyMMddhhmmssfff}.png"); + }; } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index af827e0dc2a..ec2ac4e10cd 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 { @@ -30,5 +31,10 @@ public class DevToolsOptions /// public string? ScreenshotRoot { get; set; } + /// + /// Get or sets conventin for screenshot fileName. + ///For known default screen shot file name convection see GH-4743. + /// + public Func? ScreenshotFileNameConvention { get; set; } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 7ea775d025c..aaa01979986 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -28,6 +28,7 @@ internal class MainViewModel : ViewModelBase, IDisposable private bool _showFpsOverlay; private IDisposable _selectedNodeChanged; private string _screenshotRoot; + private Func _getScreenshotFileName; #nullable disable // Remove "nullable disable" after MemberNotNull will work on our CI. @@ -256,13 +257,12 @@ void Shot(object? parameter) { try { - var folder = GetScreenShotDirectory(_root); + var filePath = _getScreenshotFileName(control, _screenshotRoot); + var folder = System.IO.Path.GetDirectoryName(filePath); if (System.IO.Directory.Exists(folder) == false) { System.IO.Directory.CreateDirectory(folder); } - var filePath = System.IO.Path.Combine(folder - , $"{DateTime.Now:yyyyMMddhhmmssfff}.png"); var output = new System.IO.FileStream(filePath, System.IO.FileMode.Create); Dispatcher.UIThread.Post(() => @@ -276,8 +276,8 @@ void Shot(object? parameter) catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); - //TODO: Notify error - } + //TODO: Notify error + } } }, (Content as TreePageViewModel)?.SelectedNode?.Visual); @@ -286,31 +286,11 @@ void Shot(object? parameter) public void SetOptions(DevToolsOptions options) { _screenshotRoot = string.IsNullOrWhiteSpace(options.ScreenshotRoot) - ? Convetions.DefaultScreenShotRoot + ? Convetions.DefaultScreenshotRoot : options.ScreenshotRoot!; - } - /// - /// Return the path of the screenshot folder according to the rules indicated in issue GH-4743 - /// - /// - /// - string GetScreenShotDirectory(TopLevel root) - { - var rootType = root.GetType(); - var windowName = rootType.Name; - var assembly = Assembly.GetExecutingAssembly(); - var appName = Application.Current?.Name - ?? assembly.GetCustomAttribute()?.Product - ?? assembly.GetName().Name; - var appVerions = assembly.GetCustomAttribute().InformationalVersion - ?? assembly.GetCustomAttribute().Version; - return System.IO.Path.Combine(_screenshotRoot - , appName - , appVerions - , windowName); - ; + _getScreenshotFileName = options.ScreenshotFileNameConvention + ?? Convetions.DefaultScreenshotFileNameConvention; } } - } From 1e2fd8744680a31b855131f12ba85a1ab2fbb691 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 11 Jun 2021 18:41:39 +0200 Subject: [PATCH 04/31] fixes(DevTools): Mark Shot as async and catch eventualy RenderTo excepion --- .../Diagnostics/ViewModels/MainViewModel.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index aaa01979986..31923a856cf 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -248,12 +248,11 @@ bool CanShot(object? paramter) && tree.SelectedNode.Visual.VisualRoot != null; } - void Shot(object? parameter) + async void Shot(object? parameter) { - // This is a workaround because MethodToCommand does not support the asynchronous method. - Task.Factory.StartNew(arg => + await Task.Run(() => { - if (arg is IControl control) + if ((Content as TreePageViewModel)?.SelectedNode?.Visual is IControl control) { try { @@ -263,24 +262,29 @@ void Shot(object? parameter) { System.IO.Directory.CreateDirectory(folder); } - var output = new System.IO.FileStream(filePath, System.IO.FileMode.Create); Dispatcher.UIThread.Post(() => { - control.RenderTo(output); - output.Dispose(); + try + { + control.RenderTo(output); + output.Dispose(); + } + catch (Exception re) + { + //TODO: Notify error + System.Diagnostics.Debug.WriteLine(re.Message); + } } ); - } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); //TODO: Notify error } - } - }, (Content as TreePageViewModel)?.SelectedNode?.Visual); + }); } public void SetOptions(DevToolsOptions options) From 7c85b99ef911265379696a1fd2f9aad403b7a553 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 11 Jun 2021 18:43:23 +0200 Subject: [PATCH 05/31] fixes: allignment of code comment --- src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index ec2ac4e10cd..937a07a04f1 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -33,7 +33,7 @@ public class DevToolsOptions /// /// Get or sets conventin for screenshot fileName. - ///For known default screen shot file name convection see GH-4743. + /// For known default screen shot file name convection see GH-4743. /// public Func? ScreenshotFileNameConvention { get; set; } } From 5c2e066f7db20a3969ed252008a0fbe9d8f0678c Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 11 Jun 2021 18:55:14 +0200 Subject: [PATCH 06/31] fixes: CS0173 when build on linux and macOS --- src/Avalonia.Diagnostics/Diagnostics/Convetions.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs index 24dc70396b3..d87752f8915 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs @@ -18,9 +18,15 @@ static class Convetions /// full file path public static Func DefaultScreenshotFileNameConvention = (control,screenshotRoot) => { - IVisual root = control.VisualRoot is null - ? control - : control.VisualRoot ?? control.GetVisualRoot(); + 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)) From af518718341aa0e962b5b86da0391857f48e0ad1 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 12 Jun 2021 10:58:53 +0200 Subject: [PATCH 07/31] Refactoring Shot method --- .../Diagnostics/ViewModels/MainViewModel.cs | 50 +++++++------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 31923a856cf..f004bcadfc6 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -250,41 +250,27 @@ bool CanShot(object? paramter) async void Shot(object? parameter) { - await Task.Run(() => + if ((Content as TreePageViewModel)?.SelectedNode?.Visual is IControl control) + { + try { - if ((Content as TreePageViewModel)?.SelectedNode?.Visual is IControl control) + var filePath = _getScreenshotFileName(control, _screenshotRoot); + var folder = System.IO.Path.GetDirectoryName(filePath); + if (System.IO.Directory.Exists(folder) == false) { - try - { - var filePath = _getScreenshotFileName(control, _screenshotRoot); - var folder = System.IO.Path.GetDirectoryName(filePath); - if (System.IO.Directory.Exists(folder) == false) - { - System.IO.Directory.CreateDirectory(folder); - } - var output = new System.IO.FileStream(filePath, System.IO.FileMode.Create); - Dispatcher.UIThread.Post(() => - { - try - { - control.RenderTo(output); - output.Dispose(); - } - catch (Exception re) - { - //TODO: Notify error - System.Diagnostics.Debug.WriteLine(re.Message); - } - } - ); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine(ex.Message); - //TODO: Notify error - } + await Task.Run(new Action(() => System.IO.Directory.CreateDirectory(folder))); } - }); + using var output = new System.IO.FileStream(filePath, System.IO.FileMode.Create); + await Dispatcher.UIThread.InvokeAsync(() => control.RenderTo(output)); + await output.FlushAsync(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + //TODO: Notify error + } + } + } public void SetOptions(DevToolsOptions options) From 25356a92a879ef3e113eb5f06a6ee4db1537de0b Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 12 Jun 2021 11:48:50 +0200 Subject: [PATCH 08/31] fixes: typo in DefaultScreenshotRoot --- src/Avalonia.Diagnostics/Diagnostics/Convetions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs index d87752f8915..45bf361d53d 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs @@ -9,7 +9,7 @@ static class Convetions { public static string DefaultScreenshotRoot => System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create), - "ScreenShot"); + "Screenshot"); /// /// Return the path of the screenshot folder according to the rules indicated in issue GH-4743 From dcdbe6ecb0627dcacfe58410c5063aceafec3c3d Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 12 Jun 2021 11:49:57 +0200 Subject: [PATCH 09/31] fixes: assembly detection --- src/Avalonia.Diagnostics/Diagnostics/Convetions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs index 45bf361d53d..cb8c304ae33 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs @@ -34,7 +34,7 @@ static class Convetions windowName = rc.Name; } - var assembly = Assembly.GetExecutingAssembly(); + var assembly = Assembly.GetEntryAssembly(); var appName = Application.Current?.Name ?? assembly.GetCustomAttribute()?.Product ?? assembly.GetName().Name; From 9cb6c5dd8a85a560e208edcfc946a98eb35812fc Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 12 Jun 2021 11:50:34 +0200 Subject: [PATCH 10/31] fixes: code comment --- src/Avalonia.Diagnostics/Diagnostics/Convetions.cs | 2 -- src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs index cb8c304ae33..f4baf94801f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs @@ -14,8 +14,6 @@ static class Convetions /// /// Return the path of the screenshot folder according to the rules indicated in issue GH-4743 /// - /// - /// full file path public static Func DefaultScreenshotFileNameConvention = (control,screenshotRoot) => { IVisual root; diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs index 7902c58b4fa..7fa6eda5f59 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -40,7 +40,7 @@ public static void RenderTo(this IControl source, Stream destination, double dpi var clipRegion = new Media.RectangleGeometry(new Rect(top.Value, bottomRight.Value)); root.ClipToBounds = true; root.Clip = clipRegion; - // Traslate origin + // Translate origin root.RenderTransformOrigin = new RelativePoint(top.Value, RelativeUnit.Absolute); root.RenderTransform = new Media.TranslateTransform(-top.Value.X, -top.Value.Y); using (var bitmap = new RenderTargetBitmap(pixelSize, dpiVector)) From 0c1f6ccf2429288b6d560fbe2114333b20f5d17b Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 12 Jun 2021 11:51:08 +0200 Subject: [PATCH 11/31] fixes: nullable warnings --- src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs index 7fa6eda5f59..56c00ccc2a6 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -22,7 +22,7 @@ public static void RenderTo(this IControl source, Stream destination, double dpi // get Visual root var root = (source.VisualRoot ?? source.GetVisualRoot()) - as IControl; + as IControl ?? source; // Backup current vaules var oldgeometry = root.Clip; @@ -33,8 +33,8 @@ public static void RenderTo(this IControl source, Stream destination, double dpi try { // Translate coordinate clinet to root - var top = source.TranslatePoint(new Point(0,0), root); - var bottomRight = source.TranslatePoint(rect.BottomRight, root); + var top = source.TranslatePoint(new Point(0,0), root)!; + var bottomRight = source.TranslatePoint(rect.BottomRight, root)!; // Set clip region var clipRegion = new Media.RectangleGeometry(new Rect(top.Value, bottomRight.Value)); From d683a9de9e90cd2003933c0fa12b81440cccdb46 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 12 Jun 2021 12:46:36 +0200 Subject: [PATCH 12/31] fixes: merge issue --- .../Diagnostics/ViewModels/MainViewModel.cs | 50 +++++++------------ 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index f004bcadfc6..4084c8ea4f7 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -97,43 +97,27 @@ public ViewModelBase Content // [MemberNotNull(nameof(_content))] private set { - TreePageViewModel oldTree = _content as TreePageViewModel; - TreePageViewModel newTree = value as TreePageViewModel; - if (oldTree != null) + if (_content is TreePageViewModel oldTree && + value is TreePageViewModel newTree && + oldTree?.SelectedNode?.Visual is IControl control) { - _selectedNodeChanged?.Dispose(); - _selectedNodeChanged = null; - } - - if (newTree != null) - { - if (oldTree != null && - oldTree?.SelectedNode?.Visual is IControl control) - { - // HACK: We want to select the currently selected control in the new tree, but - // to select nested nodes in TreeView, currently the TreeView has to be able to - // expand the parent nodes. Because at this point the TreeView isn't visible, - // this will fail unless we schedule the selection to run after layout. - DispatcherTimer.RunOnce( - () => + // HACK: We want to select the currently selected control in the new tree, but + // to select nested nodes in TreeView, currently the TreeView has to be able to + // expand the parent nodes. Because at this point the TreeView isn't visible, + // this will fail unless we schedule the selection to run after layout. + DispatcherTimer.RunOnce( + () => + { + try { - try - { - newTree.SelectControl(control); - } - catch { } - }, - TimeSpan.FromMilliseconds(0), - DispatcherPriority.ApplicationIdle); - } - _selectedNodeChanged = Observable - .FromEventPattern( - x => newTree.PropertyChanged += x, - x => newTree.PropertyChanged -= x - ).Subscribe(arg => RaisePropertyChanged(nameof(TreePageViewModel.SelectedNode))); + newTree.SelectControl(control); + } + catch { } + }, + TimeSpan.FromMilliseconds(0), + DispatcherPriority.ApplicationIdle); } - RaiseAndSetIfChanged(ref _content, value); } } From de5a03313bdcd69fe165ba7b2a00b8a3edc5b1bd Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 12 Jun 2021 12:51:20 +0200 Subject: [PATCH 13/31] fixes: removed unnecessary using --- .../Diagnostics/ViewModels/MainViewModel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 4084c8ea4f7..c6301808c9a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -1,10 +1,8 @@ using System; using System.ComponentModel; using System.Reactive.Linq; -using System.Reflection; using System.Threading.Tasks; using Avalonia.Controls; -using Avalonia.Controls.Shapes; using Avalonia.Diagnostics.Models; using Avalonia.Input; using Avalonia.Metadata; From b715db17c716fecb77c18c325eec317ed89c894c Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sun, 13 Jun 2021 19:14:11 +0200 Subject: [PATCH 14/31] fixes: removed Dispatcher.UIThread.InvokeAsync --- .../Diagnostics/ViewModels/MainViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index c6301808c9a..71fdccab51a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -243,7 +243,7 @@ async void Shot(object? parameter) await Task.Run(new Action(() => System.IO.Directory.CreateDirectory(folder))); } using var output = new System.IO.FileStream(filePath, System.IO.FileMode.Create); - await Dispatcher.UIThread.InvokeAsync(() => control.RenderTo(output)); + control.RenderTo(output); await output.FlushAsync(); } catch (Exception ex) From a82b86105272a4f5f9c03ac4942beb539a643a9a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sun, 13 Jun 2021 19:31:02 +0200 Subject: [PATCH 15/31] fixes: unexpected to the user results after call RenderTo --- .../Diagnostics/VisualExtensions.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs index 56c00ccc2a6..91fe418509b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -1,5 +1,7 @@ -using System.IO; +using System; +using System.IO; using Avalonia.Controls; +using Avalonia.Data; using Avalonia.Media.Imaging; using Avalonia.VisualTree; @@ -24,11 +26,11 @@ public static void RenderTo(this IControl source, Stream destination, double dpi ?? source.GetVisualRoot()) as IControl ?? source; - // Backup current vaules - var oldgeometry = root.Clip; - var oldClipToBounds = root.ClipToBounds; - var oldRenderTransformOrigin = root.RenderTransformOrigin; - var oldrenderTransform = root.RenderTransform; + + IDisposable? clipSetter = default; + IDisposable? clipToBoundsSetter = default; + IDisposable? renderTransformOriginSetter = default; + IDisposable? renderTransformSetter = default; try { @@ -38,11 +40,18 @@ public static void RenderTo(this IControl source, Stream destination, double dpi // Set clip region var clipRegion = new Media.RectangleGeometry(new Rect(top.Value, bottomRight.Value)); - root.ClipToBounds = true; - root.Clip = clipRegion; + clipToBoundsSetter = root.SetValue(Visual.ClipToBoundsProperty, true, BindingPriority.Animation); + clipSetter = root.SetValue(Visual.ClipProperty,clipRegion, BindingPriority.Animation); + // Translate origin - root.RenderTransformOrigin = new RelativePoint(top.Value, RelativeUnit.Absolute); - root.RenderTransform = new Media.TranslateTransform(-top.Value.X, -top.Value.Y); + renderTransformOriginSetter = root.SetValue(Visual.RenderTransformOriginProperty, + new RelativePoint(top.Value, RelativeUnit.Absolute), + BindingPriority.Animation); + + renderTransformSetter = root.SetValue(Visual.RenderTransformProperty, + new Media.TranslateTransform(-top.Value.X, -top.Value.Y), + BindingPriority.Animation); + using (var bitmap = new RenderTargetBitmap(pixelSize, dpiVector)) { bitmap.Render(root); @@ -53,10 +62,10 @@ public static void RenderTo(this IControl source, Stream destination, double dpi finally { // Restore current vaules - root.ClipToBounds = oldClipToBounds; - root.Clip = oldgeometry; - root.RenderTransformOrigin = oldRenderTransformOrigin; - root.RenderTransform = oldrenderTransform; + clipSetter?.Dispose(); + clipToBoundsSetter?.Dispose(); + renderTransformOriginSetter?.Dispose(); + renderTransformSetter?.Dispose(); } } } From f00a10124424986815eb7de876634176e2bc1c60 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 15 Jun 2021 10:55:42 +0200 Subject: [PATCH 16/31] fixes: screenshot Clip --- .../Diagnostics/VisualExtensions.cs | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs index 91fe418509b..9e25d51339e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -17,43 +17,45 @@ static class VisualExtensions /// (optional) dpi quality default 96 public static void RenderTo(this IControl source, Stream destination, double dpi = 96) { - var rect = source.Bounds; + if (source.TransformedBounds == null) + { + return; + } + var rect = source.TransformedBounds.Value.Clip; + var top = rect.TopLeft; var pixelSize = new PixelSize((int)rect.Width, (int)rect.Height); var dpiVector = new Vector(dpi, dpi); // get Visual root - var root = (source.VisualRoot + var root = (source.VisualRoot ?? source.GetVisualRoot()) as IControl ?? source; - + IDisposable? clipSetter = default; IDisposable? clipToBoundsSetter = default; IDisposable? renderTransformOriginSetter = default; IDisposable? renderTransformSetter = default; - + // Save current TransformerBounds + var currentTransformerBounds = source.TransformedBounds.Value; try - { - // Translate coordinate clinet to root - var top = source.TranslatePoint(new Point(0,0), root)!; - var bottomRight = source.TranslatePoint(rect.BottomRight, root)!; - + { // Set clip region - var clipRegion = new Media.RectangleGeometry(new Rect(top.Value, bottomRight.Value)); + var clipRegion = new Media.RectangleGeometry(rect); clipToBoundsSetter = root.SetValue(Visual.ClipToBoundsProperty, true, BindingPriority.Animation); - clipSetter = root.SetValue(Visual.ClipProperty,clipRegion, BindingPriority.Animation); + clipSetter = root.SetValue(Visual.ClipProperty, clipRegion, BindingPriority.Animation); // Translate origin renderTransformOriginSetter = root.SetValue(Visual.RenderTransformOriginProperty, - new RelativePoint(top.Value, RelativeUnit.Absolute), + new RelativePoint(top, RelativeUnit.Absolute), BindingPriority.Animation); renderTransformSetter = root.SetValue(Visual.RenderTransformProperty, - new Media.TranslateTransform(-top.Value.X, -top.Value.Y), + new Media.TranslateTransform(-top.X, -top.Y), BindingPriority.Animation); using (var bitmap = new RenderTargetBitmap(pixelSize, dpiVector)) - { + { bitmap.Render(root); bitmap.Save(destination); } @@ -61,11 +63,18 @@ public static void RenderTo(this IControl source, Stream destination, double dpi } finally { - // Restore current vaules + // Restore values before trasformation + renderTransformSetter?.Dispose(); + renderTransformOriginSetter?.Dispose(); clipSetter?.Dispose(); clipToBoundsSetter?.Dispose(); - renderTransformOriginSetter?.Dispose(); - renderTransformSetter?.Dispose(); + /* Restore TransformerBounds + * this is workaraund for issue. When i call bitmap.Render(root), + * ImmediateRenderer set TransformedBounds.Clip to empty rect + * (see https://github.com/AvaloniaUI/Avalonia/blob/151a7a010d477436b37dde0eb89deb1f0df42c7d/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs#L303-L308). + * If you remake a screenshot of same control, the clip bounds of screenshot is not correct. + */ + source.TransformedBounds = currentTransformerBounds; } } } From 6173eca121f19aa69194fa11bfabfa8a8e25592a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 15 Jun 2021 11:04:34 +0200 Subject: [PATCH 17/31] fixes: typo --- src/Avalonia.Diagnostics/Diagnostics/Convetions.cs | 4 ++-- src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs | 6 +++--- .../Diagnostics/ViewModels/MainViewModel.cs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs index f4baf94801f..65b3607ea55 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs @@ -7,9 +7,9 @@ namespace Avalonia.Diagnostics { static class Convetions { - public static string DefaultScreenshotRoot => + public static string DefaultScreenshotsRoot => System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create), - "Screenshot"); + "Screenshots"); /// /// Return the path of the screenshot folder according to the rules indicated in issue GH-4743 diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index 937a07a04f1..00fced2cb9f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -26,10 +26,10 @@ public class DevToolsOptions /// - /// Get or sets the root folder where screeshot well be stored. - /// The default root folder is MyPictures/ScreenShot. + /// Get or sets the root folder where screeshots well be stored. + /// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots. /// - public string? ScreenshotRoot { get; set; } + public string? ScreenshotsRoot { get; set; } /// /// Get or sets conventin for screenshot fileName. diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 71fdccab51a..bfd8509c4e7 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -257,9 +257,9 @@ async void Shot(object? parameter) public void SetOptions(DevToolsOptions options) { - _screenshotRoot = string.IsNullOrWhiteSpace(options.ScreenshotRoot) - ? Convetions.DefaultScreenshotRoot - : options.ScreenshotRoot!; + _screenshotRoot = string.IsNullOrWhiteSpace(options.ScreenshotsRoot) + ? Convetions.DefaultScreenshotsRoot + : options.ScreenshotsRoot!; _getScreenshotFileName = options.ScreenshotFileNameConvention ?? Convetions.DefaultScreenshotFileNameConvention; From a6fe880ba99d81209c931737c076de4d5e7989e7 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 18 Jun 2021 17:17:48 +0200 Subject: [PATCH 18/31] fixes: remove workaraund --- .../Diagnostics/VisualExtensions.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs index 9e25d51339e..ac90abc5bce 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -36,8 +36,6 @@ public static void RenderTo(this IControl source, Stream destination, double dpi IDisposable? clipToBoundsSetter = default; IDisposable? renderTransformOriginSetter = default; IDisposable? renderTransformSetter = default; - // Save current TransformerBounds - var currentTransformerBounds = source.TransformedBounds.Value; try { // Set clip region @@ -68,13 +66,7 @@ public static void RenderTo(this IControl source, Stream destination, double dpi renderTransformOriginSetter?.Dispose(); clipSetter?.Dispose(); clipToBoundsSetter?.Dispose(); - /* Restore TransformerBounds - * this is workaraund for issue. When i call bitmap.Render(root), - * ImmediateRenderer set TransformedBounds.Clip to empty rect - * (see https://github.com/AvaloniaUI/Avalonia/blob/151a7a010d477436b37dde0eb89deb1f0df42c7d/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs#L303-L308). - * If you remake a screenshot of same control, the clip bounds of screenshot is not correct. - */ - source.TransformedBounds = currentTransformerBounds; + source?.InvalidateVisual(); } } } From 5cbb702b86f257753ca871a80e3b6b257da65ceb Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 25 Dec 2021 02:29:58 -0500 Subject: [PATCH 19/31] Apply suggestions from code review --- .../Diagnostics/ViewModels/MainViewModel.cs | 3 +-- .../Diagnostics/VisualExtensions.cs | 10 ++++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 35a32cbe21e..115ff348f3a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -231,7 +231,7 @@ public void RequestTreeNavigateTo(IControl control, bool isVisualTree) [DependsOn(nameof(TreePageViewModel.SelectedNode))] [DependsOn(nameof(Content))] - bool CanShot(object? paramter) + bool CanShot(object? parameter) { return Content is TreePageViewModel tree && tree.SelectedNode != null @@ -261,7 +261,6 @@ async void Shot(object? parameter) //TODO: Notify error } } - } public void SetOptions(DevToolsOptions options) diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs index ac90abc5bce..1585524df47 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -10,11 +10,11 @@ namespace Avalonia.Diagnostics static class VisualExtensions { /// - /// Rendered control to stream + /// Render control to the destination stream. /// - /// the control I want to render in the stream - /// destination destina - /// (optional) dpi quality default 96 + /// Control to be rendered. + /// Destination stream. + /// Dpi quality. public static void RenderTo(this IControl source, Stream destination, double dpi = 96) { if (source.TransformedBounds == null) @@ -31,7 +31,6 @@ public static void RenderTo(this IControl source, Stream destination, double dpi ?? source.GetVisualRoot()) as IControl ?? source; - IDisposable? clipSetter = default; IDisposable? clipToBoundsSetter = default; IDisposable? renderTransformOriginSetter = default; @@ -57,7 +56,6 @@ public static void RenderTo(this IControl source, Stream destination, double dpi bitmap.Render(root); bitmap.Save(destination); } - } finally { From 8b810f252f0c10a629cbd773a8c88607cdff1838 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 27 Dec 2021 10:30:19 +0100 Subject: [PATCH 20/31] feat(DevTools): Add Shot Button --- .../Diagnostics/Views/ControlDetailsView.xaml | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 7f924af1447..c65a6ae83f2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -28,10 +28,38 @@ - + Date: Mon, 27 Dec 2021 11:00:30 +0100 Subject: [PATCH 21/31] feat(DevTools): Custom Screenshot handler --- .../Diagnostics/Convetions.cs | 12 ++++-- .../Diagnostics/DevToolsOptions.cs | 13 ++----- .../Diagnostics/IScreenshotHandler.cs | 17 ++++++++ .../Diagnostics/Screenshots/FileHandler.cs | 39 +++++++++++++++++++ .../Diagnostics/ViewModels/MainViewModel.cs | 25 +++--------- .../Diagnostics/VisualExtensions.cs | 2 +- 6 files changed, 74 insertions(+), 34 deletions(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileHandler.cs diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs index 65b3607ea55..457853e44e0 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs @@ -32,19 +32,23 @@ static class Convetions windowName = rc.Name; } - var assembly = Assembly.GetEntryAssembly(); + var assembly = Assembly.GetEntryAssembly()!; var appName = Application.Current?.Name ?? assembly.GetCustomAttribute()?.Product ?? assembly.GetName().Name; - var appVerions = assembly.GetCustomAttribute().InformationalVersion - ?? assembly.GetCustomAttribute().Version; + var appVerions = assembly.GetCustomAttribute()?.InformationalVersion + ?? assembly?.GetCustomAttribute()?.Version + ?? "0.0.0.0"; var folder = System.IO.Path.Combine(screenshotRoot - , appName + , appName! , appVerions , windowName); return System.IO.Path.Combine(folder , $"{DateTime.Now:yyyyMMddhhmmssfff}.png"); }; + + public static IScreenshotHandler DefaultScreenshotHandler { get; } = + new Screenshots.FileHandler(); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index 2cbb001a5d1..3cfe8e0e0a4 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -31,15 +31,10 @@ public class DevToolsOptions public int? StartupScreenIndex { get; set; } /// - /// Get or sets the root folder where screeshots well be stored. - /// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots. + /// Allow to customizze SreenshotHandler /// - public string? ScreenshotsRoot { get; set; } - - /// - /// Get or sets conventin for screenshot fileName. - /// For known default screen shot file name convection see GH-4743. - /// - public Func? ScreenshotFileNameConvention { get; set; } + /// 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 00000000000..3503ab56b33 --- /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/FileHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileHandler.cs new file mode 100644 index 00000000000..c1c86db6c51 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileHandler.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using Avalonia.Controls; + +namespace Avalonia.Diagnostics.Screenshots +{ + /// + /// Take a Screenshot on file + /// + public sealed class FileHandler : IScreenshotHandler + { + /// + /// Get or sets the root folder where screeshots well be stored. + /// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots. + /// + public string ScreenshotsRoot { get; set; } + = Convetions.DefaultScreenshotsRoot; + + /// + /// Get or sets conventin for screenshot fileName. + /// For known default screen shot file name convection see GH-4743. + /// + public Func ScreenshotFileNameConvention { get; set; } + = Convetions.DefaultScreenshotFileNameConvention; + + public async Task Take(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!))); + } + using var output = new System.IO.FileStream(filePath, System.IO.FileMode.Create); + control.RenderTo(output); + await output.FlushAsync(); + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index b0d87fece03..bfa0840d5e2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -29,8 +29,7 @@ internal class MainViewModel : ViewModelBase, IDisposable private bool _freezePopups; private string? _pointerOverElementName; private IInputRoot? _pointerOverRoot; - private string? _screenshotRoot; - private Func? _getScreenshotFileName; + private IScreenshotHandler? _screenshotHandler; public MainViewModel(AvaloniaObject root) { @@ -277,22 +276,13 @@ bool CanShot(object? parameter) async void Shot(object? parameter) { - if ((Content as TreePageViewModel)?.SelectedNode?.Visual is IControl control - && _screenshotRoot is { } - && _getScreenshotFileName is { } + if ((Content as TreePageViewModel)?.SelectedNode?.Visual is IControl control + && _screenshotHandler is { } ) { try { - var filePath = _getScreenshotFileName(control, _screenshotRoot); - var folder = System.IO.Path.GetDirectoryName(filePath); - if (System.IO.Directory.Exists(folder) == false) - { - await Task.Run(new Action(() => System.IO.Directory.CreateDirectory(folder!))); - } - using var output = new System.IO.FileStream(filePath, System.IO.FileMode.Create); - control.RenderTo(output); - await output.FlushAsync(); + await _screenshotHandler.Take(control); } catch (Exception ex) { @@ -304,12 +294,7 @@ async void Shot(object? parameter) public void SetOptions(DevToolsOptions options) { - _screenshotRoot = string.IsNullOrWhiteSpace(options.ScreenshotsRoot) - ? Convetions.DefaultScreenshotsRoot - : options.ScreenshotsRoot!; - - _getScreenshotFileName = options.ScreenshotFileNameConvention - ?? Convetions.DefaultScreenshotFileNameConvention; + _screenshotHandler = options.ScreenshotHandler; StartupScreenIndex = options.StartupScreenIndex; } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs index 1585524df47..b9a3b9e7583 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -7,7 +7,7 @@ namespace Avalonia.Diagnostics { - static class VisualExtensions + public static class VisualExtensions { /// /// Render control to the destination stream. From 7540112bf952a445917a9535daa4108eb2ece780 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 27 Dec 2021 16:52:46 +0100 Subject: [PATCH 22/31] fixes(DevTools): Renamed Screenshots.FileHandler to Screenshots.FileConvetionHandler --- src/Avalonia.Diagnostics/Diagnostics/Convetions.cs | 2 +- src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs | 2 +- .../Screenshots/{FileHandler.cs => FileConvetionHandler.cs} | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/Avalonia.Diagnostics/Diagnostics/Screenshots/{FileHandler.cs => FileConvetionHandler.cs} (92%) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs index 457853e44e0..bdd888fee53 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs @@ -49,6 +49,6 @@ static class Convetions }; public static IScreenshotHandler DefaultScreenshotHandler { get; } = - new Screenshots.FileHandler(); + new Screenshots.FileConvetionHandler(); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index 3cfe8e0e0a4..f2424037147 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -33,7 +33,7 @@ public class DevToolsOptions /// /// Allow to customizze SreenshotHandler /// - /// Default handler is + /// Default handler is public IScreenshotHandler ScreenshotHandler { get; set; } = Convetions.DefaultScreenshotHandler; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileConvetionHandler.cs similarity index 92% rename from src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileHandler.cs rename to src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileConvetionHandler.cs index c1c86db6c51..d4050456eb3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileHandler.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileConvetionHandler.cs @@ -5,9 +5,9 @@ namespace Avalonia.Diagnostics.Screenshots { /// - /// Take a Screenshot on file + /// Take a Screenshot on file by convention /// - public sealed class FileHandler : IScreenshotHandler + public sealed class FileConvetionHandler : IScreenshotHandler { /// /// Get or sets the root folder where screeshots well be stored. From 1007ab23b9fe3f95326bb79715d103620a61c298 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 27 Dec 2021 17:33:32 +0100 Subject: [PATCH 23/31] feat(DevTools): Screenshots.BaseRenderToStreamHandler --- .../Screenshots/BaseRenderToStreamHandler.cs | 29 +++++++++++++++++++ .../Screenshots/FileConvetionHandler.cs | 8 ++--- 2 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs new file mode 100644 index 00000000000..c111b045d3b --- /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/FileConvetionHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileConvetionHandler.cs index d4050456eb3..8860303fa2a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileConvetionHandler.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileConvetionHandler.cs @@ -7,7 +7,7 @@ namespace Avalonia.Diagnostics.Screenshots /// /// Take a Screenshot on file by convention /// - public sealed class FileConvetionHandler : IScreenshotHandler + public sealed class FileConvetionHandler : BaseRenderToStreamHandler { /// /// Get or sets the root folder where screeshots well be stored. @@ -23,7 +23,7 @@ public sealed class FileConvetionHandler : IScreenshotHandler public Func ScreenshotFileNameConvention { get; set; } = Convetions.DefaultScreenshotFileNameConvention; - public async Task Take(IControl control) + protected override async Task GetStream(IControl control) { var filePath = ScreenshotFileNameConvention(control, ScreenshotsRoot); var folder = System.IO.Path.GetDirectoryName(filePath); @@ -31,9 +31,7 @@ public async Task Take(IControl control) { await Task.Run(new Action(() => System.IO.Directory.CreateDirectory(folder!))); } - using var output = new System.IO.FileStream(filePath, System.IO.FileMode.Create); - control.RenderTo(output); - await output.FlushAsync(); + return new System.IO.FileStream(filePath, System.IO.FileMode.Create); } } } From a7c3562392613616d73d8ca50f2cf66f151b5712 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 27 Dec 2021 17:39:50 +0100 Subject: [PATCH 24/31] feat(DevTools): Screenshots.FilePickerHandler --- .../Avalonia.Diagnostics.csproj | 1 + .../Diagnostics/Convetions.cs | 2 +- .../Screenshots/FilePickerHandler.cs | 62 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index 1fc3604f70a..1c00928217b 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs index bdd888fee53..7d237604987 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs @@ -49,6 +49,6 @@ static class Convetions }; public static IScreenshotHandler DefaultScreenshotHandler { get; } = - new Screenshots.FileConvetionHandler(); + new Screenshots.FilePickerHandler(); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs new file mode 100644 index 00000000000..b293f17d0cd --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs @@ -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 +{ + /// + /// Show a FileSavePicker to select where save screenshot + /// + public sealed class FilePickerHandler : BaseRenderToStreamHandler + { + /// + /// Get or sets the root folder where screeshots well be stored. + /// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots. + /// + public string ScreenshotsRoot { get; set; } + = Convetions.DefaultScreenshotsRoot; + + /// + /// Get or sets conventin for screenshot fileName. + /// For known default screen shot file name convection see GH-4743. + /// + public Func ScreenshotFileNameConvention { get; set; } + = Convetions.DefaultScreenshotFileNameConvention; + + /// + /// SaveFilePicker Title + /// + 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 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; + } + } +} From 5d246e7ba978f48b1f504b240307812d51cd38bc Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 28 Dec 2021 09:29:15 +0100 Subject: [PATCH 25/31] fixes(DevTools): marked VisualExtensions as internal --- src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs index b9a3b9e7583..a80ed2d4ef3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -7,7 +7,7 @@ namespace Avalonia.Diagnostics { - public static class VisualExtensions + internal static class VisualExtensions { /// /// Render control to the destination stream. @@ -36,7 +36,7 @@ public static void RenderTo(this IControl source, Stream destination, double dpi IDisposable? renderTransformOriginSetter = default; IDisposable? renderTransformSetter = default; try - { + { // Set clip region var clipRegion = new Media.RectangleGeometry(rect); clipToBoundsSetter = root.SetValue(Visual.ClipToBoundsProperty, true, BindingPriority.Animation); @@ -52,7 +52,7 @@ public static void RenderTo(this IControl source, Stream destination, double dpi BindingPriority.Animation); using (var bitmap = new RenderTargetBitmap(pixelSize, dpiVector)) - { + { bitmap.Render(root); bitmap.Save(destination); } From d7826e9cb28445b4853006fe082256aa8e56fe1d Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 28 Dec 2021 10:12:40 +0100 Subject: [PATCH 26/31] fixes(DevTools): Strip out FileConvetionHandler --- .../Screenshots/FileConvetionHandler.cs | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileConvetionHandler.cs diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileConvetionHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileConvetionHandler.cs deleted file mode 100644 index 8860303fa2a..00000000000 --- a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FileConvetionHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Threading.Tasks; -using Avalonia.Controls; - -namespace Avalonia.Diagnostics.Screenshots -{ - /// - /// Take a Screenshot on file by convention - /// - public sealed class FileConvetionHandler : BaseRenderToStreamHandler - { - /// - /// Get or sets the root folder where screeshots well be stored. - /// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots. - /// - public string ScreenshotsRoot { get; set; } - = Convetions.DefaultScreenshotsRoot; - - /// - /// Get or sets conventin for screenshot fileName. - /// For known default screen shot file name convection see GH-4743. - /// - public Func ScreenshotFileNameConvention { get; set; } - = Convetions.DefaultScreenshotFileNameConvention; - - protected override async Task 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); - } - } -} From 771238b716d085099d364e05a81a289a1e297b98 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 28 Dec 2021 10:15:17 +0100 Subject: [PATCH 27/31] fixes(DevTools): FilePickerHandler promote Title and ScreenshotsRoot properties to ctor --- .../Diagnostics/Convetions.cs | 37 --------------- .../Screenshots/FilePickerHandler.cs | 47 ++++++++++++++----- 2 files changed, 35 insertions(+), 49 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs index 7d237604987..b41755f2391 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs @@ -11,43 +11,6 @@ static class Convetions System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create), "Screenshots"); - /// - /// Return the path of the screenshot folder according to the rules indicated in issue GH-4743 - /// - public static Func DefaultScreenshotFileNameConvention = (control,screenshotRoot) => - { - 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()?.Product - ?? assembly.GetName().Name; - var appVerions = assembly.GetCustomAttribute()?.InformationalVersion - ?? assembly?.GetCustomAttribute()?.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(); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs index b293f17d0cd..fb57058ae95 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs @@ -13,23 +13,37 @@ namespace Avalonia.Diagnostics.Screenshots public sealed class FilePickerHandler : BaseRenderToStreamHandler { /// - /// Get or sets the root folder where screeshots well be stored. - /// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots. + /// Instance FilePickerHandler /// - public string ScreenshotsRoot { get; set; } - = Convetions.DefaultScreenshotsRoot; + 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 or sets conventin for screenshot fileName. - /// For known default screen shot file name convection see GH-4743. + /// Get the root folder where screeshots well be stored. + /// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots. /// - public Func ScreenshotFileNameConvention { get; set; } - = Convetions.DefaultScreenshotFileNameConvention; + public string ScreenshotsRoot { get; } + = Convetions.DefaultScreenshotsRoot; /// /// SaveFilePicker Title /// - public string Title { get; set; } = "Save Screenshot to ..."; + public string Title { get; } = "Save Screenshot to ..."; Window GetWindow(IControl control) { @@ -50,11 +64,20 @@ Window GetWindow(IControl control) Title = Title, Filters = new() { new FileDialogFilter() { Name = "PNG", Extensions = new() { "png" } } }, Directory = ScreenshotsRoot, - InitialFileName = ScreenshotFileNameConvention(control, ScreenshotsRoot) }.ShowAsync(GetWindow(control)); - if (result is { }) + if (!string.IsNullOrWhiteSpace(result)) { - output = new FileStream(result, FileMode.Create); + 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; } From 3591daf41329c31db305f1fc8400230acaa2bcb9 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 28 Dec 2021 10:45:59 +0100 Subject: [PATCH 28/31] fixes(DevTools): Remove screenshot HotKey description form status bar --- src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 63179f75505..381fc68cf34 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -77,8 +77,6 @@ BorderThickness="0,1,0,0"> - Hold F8 to take a screenshot. - Hold Ctrl+Shift over a control to inspect. Focused: From 4414a43923f0134214275c6ed018dc10f92bda20 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 28 Dec 2021 10:48:05 +0100 Subject: [PATCH 29/31] fixes(DevTools): Xml Comment --- src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index f2424037147..33166f1147a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -33,7 +33,7 @@ public class DevToolsOptions /// /// Allow to customizze SreenshotHandler /// - /// Default handler is + /// Default handler is public IScreenshotHandler ScreenshotHandler { get; set; } = Convetions.DefaultScreenshotHandler; } From 08236dffb030a05e9697ff94f8ea7454b2b2d3d8 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 28 Dec 2021 10:55:48 +0100 Subject: [PATCH 30/31] fixes(DevTools): Moved Screenshot command to File menu --- .../Diagnostics/Views/ControlDetailsView.xaml | 30 +------------------ .../Diagnostics/Views/MainView.xaml | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index d8412850634..d7acbbd577f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -29,38 +29,10 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 98a85642209a1eb44357bbecb0f38e5fb5f4f32a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 29 Dec 2021 11:34:01 +0100 Subject: [PATCH 31/31] fixes(DevTools): Removed reference to Avalonia.Dialogs --- src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index 1c00928217b..1fc3604f70a 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -14,7 +14,6 @@ -