From 5bfbfb97d3689acd353136b05136f8d825010ef6 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Sun, 14 Feb 2021 11:36:06 +0300 Subject: [PATCH 001/129] Use ControlCatalog.NetCore as an artifact instead of ControlCatalog.Desktop --- nukebuild/Build.cs | 17 +++++++++++------ nukebuild/BuildParameters.cs | 7 ++----- .../ControlCatalog.NetCore.csproj | 6 +++++- samples/ControlCatalog/ControlCatalog.csproj | 2 +- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 8e331edab4..2c6ec70bd6 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -305,14 +305,19 @@ partial class Build : NukeBuild .Executes(() => { var data = Parameters; + var pathToProjectSource = RootDirectory / "samples" / "ControlCatalog.NetCore"; + var pathToPublish = pathToProjectSource / "bin" / data.Configuration / "publish"; + + DotNetPublish(c => c + .SetProject(pathToProjectSource / "ControlCatalog.NetCore.csproj") + .EnableNoBuild() + .SetConfiguration(data.Configuration) + .AddProperty("PackageVersion", data.Version) + .AddProperty("PublishDir", pathToPublish)); + Zip(data.ZipCoreArtifacts, data.BinRoot); Zip(data.ZipNuGetArtifacts, data.NugetRoot); - Zip(data.ZipTargetControlCatalogDesktopDir, - GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dll").Concat( - GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.config")).Concat( - GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.so")).Concat( - GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dylib")).Concat( - GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.exe"))); + Zip(data.ZipTargetControlCatalogNetCoreDir, pathToPublish); }); Target CreateIntermediateNugetPackages => _ => _ diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs index c76019d9eb..a92c988fbd 100644 --- a/nukebuild/BuildParameters.cs +++ b/nukebuild/BuildParameters.cs @@ -58,8 +58,7 @@ public partial class Build public string FileZipSuffix { get; } public AbsolutePath ZipCoreArtifacts { get; } public AbsolutePath ZipNuGetArtifacts { get; } - public AbsolutePath ZipSourceControlCatalogDesktopDir { get; } - public AbsolutePath ZipTargetControlCatalogDesktopDir { get; } + public AbsolutePath ZipTargetControlCatalogNetCoreDir { get; } public BuildParameters(Build b) @@ -129,9 +128,7 @@ public partial class Build FileZipSuffix = Version + ".zip"; ZipCoreArtifacts = ZipRoot / ("Avalonia-" + FileZipSuffix); ZipNuGetArtifacts = ZipRoot / ("Avalonia-NuGet-" + FileZipSuffix); - ZipSourceControlCatalogDesktopDir = - RootDirectory / ("samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461"); - ZipTargetControlCatalogDesktopDir = ZipRoot / ("ControlCatalog.Desktop-" + FileZipSuffix); + ZipTargetControlCatalogNetCoreDir = ZipRoot / ("ControlCatalog.NetCore-" + FileZipSuffix); } string GetVersion() diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index d5aedf7783..3c2d2ee359 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -1,7 +1,7 @@  - Exe + WinExe netcoreapp3.1 true @@ -15,6 +15,10 @@ + + + en + diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 1aa926a2a6..53ad213d92 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -27,6 +27,6 @@ - + From d7f3c24365a9cb73649363892ea0296e89b1a9c1 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 25 Feb 2021 13:24:20 +0100 Subject: [PATCH 002/129] Allow for controlling behavior of CaptionButtons. --- .../Chrome/CaptionButtons.cs | 73 ++++++++++++------- 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index cd60130c5b..d41a95b5a0 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -14,17 +14,21 @@ namespace Avalonia.Controls.Chrome public class CaptionButtons : TemplatedControl { private CompositeDisposable? _disposables; - private Window? _hostWindow; - public void Attach(Window hostWindow) + /// + /// Currently attached window. + /// + protected Window? HostWindow { get; private set; } + + public virtual void Attach(Window hostWindow) { if (_disposables == null) { - _hostWindow = hostWindow; + HostWindow = hostWindow; _disposables = new CompositeDisposable { - _hostWindow.GetObservable(Window.WindowStateProperty) + HostWindow.GetObservable(Window.WindowStateProperty) .Subscribe(x => { PseudoClasses.Set(":minimized", x == WindowState.Minimized); @@ -36,14 +40,45 @@ namespace Avalonia.Controls.Chrome } } - public void Detach() + public virtual void Detach() { if (_disposables != null) { _disposables.Dispose(); _disposables = null; - _hostWindow = null; + HostWindow = null; + } + } + + protected virtual void OnClose() + { + HostWindow?.Close(); + } + + protected virtual void OnRestore() + { + if (HostWindow != null) + { + HostWindow.WindowState = HostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; + } + } + + protected virtual void OnMinimize() + { + if (HostWindow != null) + { + HostWindow.WindowState = WindowState.Minimized; + } + } + + private void OnToggleFullScreen() + { + if (HostWindow != null) + { + HostWindow.WindowState = HostWindow.WindowState == WindowState.FullScreen + ? WindowState.Normal + : WindowState.FullScreen; } } @@ -56,31 +91,13 @@ namespace Avalonia.Controls.Chrome var minimiseButton = e.NameScope.Get("PART_MinimiseButton"); var fullScreenButton = e.NameScope.Get("PART_FullScreenButton"); - closeButton.PointerReleased += (sender, e) => _hostWindow?.Close(); + closeButton.PointerReleased += (sender, e) => OnClose(); - restoreButton.PointerReleased += (sender, e) => - { - if (_hostWindow != null) - { - _hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; - } - }; + restoreButton.PointerReleased += (sender, e) => OnRestore(); - minimiseButton.PointerReleased += (sender, e) => - { - if (_hostWindow != null) - { - _hostWindow.WindowState = WindowState.Minimized; - } - }; + minimiseButton.PointerReleased += (sender, e) => OnMinimize(); - fullScreenButton.PointerReleased += (sender, e) => - { - if (_hostWindow != null) - { - _hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen; - } - }; + fullScreenButton.PointerReleased += (sender, e) => OnToggleFullScreen(); } } } From 8a79141c24647376e392790607170ba3b4a31aa5 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 25 Feb 2021 14:56:51 +0100 Subject: [PATCH 003/129] Fix access modifer. --- src/Avalonia.Controls/Chrome/CaptionButtons.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index d41a95b5a0..be15d3d444 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -72,7 +72,7 @@ namespace Avalonia.Controls.Chrome } } - private void OnToggleFullScreen() + protected virtual void OnToggleFullScreen() { if (HostWindow != null) { From cf8117d9ecfe53687879ee4c6af1223f78d8319d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 5 Mar 2021 17:58:08 +0000 Subject: [PATCH 004/129] change default extend chrome hint --- src/Avalonia.Controls/ApiCompatBaseline.txt | 3 ++- src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs | 2 +- src/Avalonia.Native/avn.idl | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index e5adc8c6ed..f55f440db9 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -1,6 +1,7 @@ Compat issues with assembly Avalonia.Controls: MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. +EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -Total Issues: 4 +Total Issues: 5 diff --git a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs index de3f58886b..bb3c0288eb 100644 --- a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs +++ b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs @@ -16,7 +16,7 @@ namespace Avalonia.Platform /// /// The default for the platform. /// - Default = SystemChrome, + Default = PreferSystemChrome, /// /// Use SystemChrome diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 57a0c32067..476e64bd2d 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -397,7 +397,7 @@ enum AvnExtendClientAreaChromeHints AvnSystemChrome = 0x01, AvnPreferSystemChrome = 0x02, AvnOSXThickTitleBar = 0x08, - AvnDefaultChrome = AvnSystemChrome, + AvnDefaultChrome = AvnPreferSystemChrome, } [uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)] From d1f44dcdf4d89abf20bba753494a3ac23728fd5e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Mar 2021 11:57:59 +0100 Subject: [PATCH 005/129] Added DevToolsOptions. With options to: - Set the key gesture - Show as child window - Set the initial size Co-Authored-By: workgroupengineering --- .../DevToolsExtensions.cs | 12 ++++++++- .../Diagnostics/DevTools.cs | 24 ++++++++++++----- .../Diagnostics/DevToolsOptions.cs | 26 +++++++++++++++++++ 3 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs diff --git a/src/Avalonia.Diagnostics/DevToolsExtensions.cs b/src/Avalonia.Diagnostics/DevToolsExtensions.cs index 4bc2ca313f..a432c94a5d 100644 --- a/src/Avalonia.Diagnostics/DevToolsExtensions.cs +++ b/src/Avalonia.Diagnostics/DevToolsExtensions.cs @@ -15,7 +15,7 @@ namespace Avalonia /// The window to attach DevTools to. public static void AttachDevTools(this TopLevel root) { - DevTools.Attach(root, new KeyGesture(Key.F12)); + DevTools.Attach(root, new DevToolsOptions()); } /// @@ -27,5 +27,15 @@ namespace Avalonia { DevTools.Attach(root, gesture); } + + /// + /// Attaches DevTools to a window, to be opened with the specified options. + /// + /// The window to attach DevTools to. + /// additional settint of DevTools + public static void AttachDevTools(this TopLevel root, DevToolsOptions options) + { + DevTools.Attach(root, options); + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 4899be2955..7942d22962 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -6,6 +6,8 @@ using Avalonia.Diagnostics.Views; using Avalonia.Input; using Avalonia.Interactivity; +#nullable enable + namespace Avalonia.Diagnostics { public static class DevTools @@ -13,12 +15,20 @@ namespace Avalonia.Diagnostics private static readonly Dictionary s_open = new Dictionary(); public static IDisposable Attach(TopLevel root, KeyGesture gesture) + { + return Attach(root, new DevToolsOptions() + { + Gesture = gesture, + }); + } + + public static IDisposable Attach(TopLevel root, DevToolsOptions options) { void PreviewKeyDown(object sender, KeyEventArgs e) { - if (gesture.Matches(e)) + if (options.Gesture.Matches(e)) { - Open(root); + Open(root, options); } } @@ -28,7 +38,9 @@ namespace Avalonia.Diagnostics RoutingStrategies.Tunnel); } - public static IDisposable Open(TopLevel root) + public static IDisposable Open(TopLevel root) => Open(root, new DevToolsOptions()); + + public static IDisposable Open(TopLevel root, DevToolsOptions options) { if (s_open.TryGetValue(root, out var window)) { @@ -38,15 +50,15 @@ namespace Avalonia.Diagnostics { window = new MainWindow { - Width = 1024, - Height = 512, Root = root, + Width = options.Size.Width, + Height = options.Size.Height, }; window.Closed += DevToolsClosed; s_open.Add(root, window); - if (root is Window inspectedWindow) + if (options.ShowAsChildWindow && root is Window inspectedWindow) { window.Show(inspectedWindow); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs new file mode 100644 index 0000000000..ee46192207 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -0,0 +1,26 @@ +using Avalonia.Input; + +namespace Avalonia.Diagnostics +{ + /// + /// Describes options used to customize DevTools. + /// + public class DevToolsOptions + { + /// + /// Gets or sets the key gesture used to open DevTools. + /// + public KeyGesture Gesture { get; set; } = new KeyGesture(Key.F12); + + /// + /// Gets or sets a value indicating whether DevTools should be displayed as a child window + /// of the window being inspected. The default value is true. + /// + public bool ShowAsChildWindow { get; set; } = true; + + /// + /// Gets or sets the initial size of the DevTools window. The default value is 1024x512. + /// + public Size Size { get; set; } = new Size(1024, 512); + } +} From f71d6b64c1085a6b0d1aaaf221e1c56248eaf1dc Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 10:30:46 +0800 Subject: [PATCH 006/129] add new Path measurement API on IGeometryImpl.cs --- .../Platform/IGeometryImpl.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs index 7490ad912a..5c39afa846 100644 --- a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs @@ -11,6 +11,12 @@ namespace Avalonia.Platform /// Gets the geometry's bounding rectangle. /// Rect Bounds { get; } + + /// + /// Gets the geometry's total length as if all its contours are placed + /// in a straight line. + /// + double ContourLength { get; } /// /// Gets the geometry's bounding rectangle with the specified pen. @@ -47,5 +53,25 @@ namespace Avalonia.Platform /// The transform. /// The cloned geometry. ITransformedGeometryImpl WithTransform(Matrix transform); + + /// + /// Attempts to get the corresponding point from the + /// specified distance + /// + /// The contour distance to get from. + /// The point in the specified distance. + /// If there's valid point at the specified distance. + bool TryGetPointAtDistance(double distance, out Point point); + + /// + /// Attempts to get the corresponding point and + /// tangent from the specified distance along the + /// contour of the geometry. + /// + /// The contour distance to get from. + /// The point in the specified distance. + /// The tangent in the specified distance. + /// If there's valid point and tangent at the specified distance. + bool TryGetPositionAndTangentAtDistance (double distance, out Point position, out Point tangent); } } From a71910f92866ea1577b4ff0d36dac77fc4f18d5e Mon Sep 17 00:00:00 2001 From: amwx Date: Wed, 17 Mar 2021 21:43:45 -0500 Subject: [PATCH 007/129] Initial Flyout Impl --- src/Avalonia.Controls/Flyouts/Flyout.cs | 65 ++++ src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 315 ++++++++++++++++++ .../Flyouts/FlyoutPlacementMode.cs | 80 +++++ .../Flyouts/FlyoutPresenter.cs | 10 + .../Controls/FlyoutPresenter.xaml | 39 +++ 5 files changed, 509 insertions(+) create mode 100644 src/Avalonia.Controls/Flyouts/Flyout.cs create mode 100644 src/Avalonia.Controls/Flyouts/FlyoutBase.cs create mode 100644 src/Avalonia.Controls/Flyouts/FlyoutPlacementMode.cs create mode 100644 src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs create mode 100644 src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml diff --git a/src/Avalonia.Controls/Flyouts/Flyout.cs b/src/Avalonia.Controls/Flyouts/Flyout.cs new file mode 100644 index 0000000000..723a5e84f8 --- /dev/null +++ b/src/Avalonia.Controls/Flyouts/Flyout.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text; +using Avalonia.Controls.Primitives; +using Avalonia.Styling; + +#nullable enable + +namespace Avalonia.Controls +{ + public class Flyout : FlyoutBase + { + public static readonly StyledProperty ContentProperty = + AvaloniaProperty.Register(nameof(Content)); + + public Styles? FlyoutPresenterStyle + { + get + { + if (_styles == null) + { + _styles = new Styles(); + _styles.CollectionChanged += OnFlyoutPresenterStylesChanged; + } + + return _styles; + } + } + + private Styles? _styles; + private bool _stylesDirty; + + public object Content + { + get => GetValue(ContentProperty); + set => SetValue(ContentProperty, value); + } + + protected override Control CreatePresenter() + { + return new FlyoutPresenter + { + [!ContentControl.ContentProperty] = this[!ContentProperty] + }; + } + + protected override void OnOpened() + { + if (_styles != null && _stylesDirty) + { + // Presenter for flyout generally shouldn't be public, so + // we should be ok to just reset the styles + _popup.Child.Styles.Clear(); + _popup.Child.Styles.Add(_styles); + } + base.OnOpened(); + } + + private void OnFlyoutPresenterStylesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + _stylesDirty = true; + } + } +} diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs new file mode 100644 index 0000000000..64ab07a658 --- /dev/null +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -0,0 +1,315 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using Avalonia.Layout; + +#nullable enable + +namespace Avalonia.Controls.Primitives +{ + public abstract class FlyoutBase : AvaloniaObject + { + private static readonly DirectProperty IsOpenProperty = + AvaloniaProperty.RegisterDirect(nameof(IsOpen), + x => x.IsOpen); + + public static readonly DirectProperty TargetProperty = + AvaloniaProperty.RegisterDirect(nameof(Target), x => x.Target); + + public static readonly DirectProperty PlacementProperty = + AvaloniaProperty.RegisterDirect(nameof(Placement), + x => x.Placement, (x, v) => x.Placement = v); + + public static readonly AttachedProperty AttachedFlyoutProperty = + AvaloniaProperty.RegisterAttached("AttachedFlyout", null); + + private bool _isOpen; + private Control? _target; + protected Popup? _popup; + + public bool IsOpen + { + get => _isOpen; + private set => SetAndRaise(IsOpenProperty, ref _isOpen, value); + } + + public FlyoutPlacementMode Placement + { + get => GetValue(PlacementProperty); + set => SetValue(PlacementProperty, value); + } + + public Control? Target + { + get => _target; + private set => SetAndRaise(TargetProperty, ref _target, value); + } + + public event EventHandler? Closed; + public event EventHandler? Closing; + public event EventHandler? Opened; + public event EventHandler? Opening; + + public static FlyoutBase? GetAttachedFlyout(Control element) + { + return element.GetValue(AttachedFlyoutProperty); + } + + public static void SetAttachedFlyout(Control element, FlyoutBase? value) + { + element.SetValue(AttachedFlyoutProperty, value); + } + + public static void ShowAttachedFlyout(Control flyoutOwner) + { + var flyout = GetAttachedFlyout(flyoutOwner); + flyout?.ShowAt(flyoutOwner); + } + + public void ShowAt(Control placementTarget) + { + ShowAtCore(placementTarget); + } + + public void ShowAt(Control placementTarget, bool showAtPointer) + { + ShowAtCore(placementTarget, showAtPointer); + } + + public void Hide(bool canCancel = true) + { + if (!IsOpen) + { + return; + } + + if (canCancel) + { + bool cancel = false; + + var closing = new CancelEventArgs(); + Closing?.Invoke(this, closing); + if (cancel || closing.Cancel) + { + return; + } + } + + IsOpen = _popup.IsOpen = false; + + OnClosed(); + } + + protected virtual void ShowAtCore(Control placementTarget, bool showAtPointer = false) + { + if (placementTarget == null) + throw new ArgumentNullException("placementTarget cannot be null"); + + if (_popup == null) + { + InitPopup(); + } + + if (IsOpen) + { + if (placementTarget == Target) + { + return; + } + else // Close before opening a new one + { + Hide(false); + } + } + + if (_popup.Parent != null && _popup.Parent != placementTarget) + { + ((ISetLogicalParent)_popup).SetParent(null); + } + + _popup.PlacementTarget = Target = placementTarget; + + ((ISetLogicalParent)_popup).SetParent(placementTarget); + + if (_popup.Child == null) + { + _popup.Child = CreatePresenter(); + } + + OnOpening(); + IsOpen = _popup.IsOpen = true; + PositionPopup(showAtPointer); + OnOpened(); + } + + protected virtual void OnOpening() + { + Opening?.Invoke(this, null); + } + + protected virtual void OnOpened() + { + Opened?.Invoke(this, null); + } + + protected virtual void OnClosing(CancelEventArgs args) + { + Closing?.Invoke(this, args); + } + + protected virtual void OnClosed() + { + Closed?.Invoke(this, null); + } + + protected abstract Control CreatePresenter(); + + private void InitPopup() + { + _popup = new Popup(); + _popup.WindowManagerAddShadowHint = false; + _popup.IsLightDismissEnabled = true; + + _popup.Opened += OnPopupOpened; + _popup.Closed += OnPopupClosed; + } + + private void OnPopupOpened(object sender, EventArgs e) + { + IsOpen = true; + OnOpened(); + } + + private void OnPopupClosed(object sender, EventArgs e) + { + Hide(); + } + + private void PositionPopup(bool showAtPointer) + { + Size sz; + if(_popup.DesiredSize == Size.Empty) + { + sz = LayoutHelper.MeasureChild(_popup, Size.Infinity, new Thickness()); + } + else + { + sz = _popup.DesiredSize; + } + + if (showAtPointer) + { + _popup.PlacementMode = PlacementMode.Pointer; + } + else + { + _popup.PlacementMode = PlacementMode.AnchorAndGravity; + _popup.PlacementConstraintAdjustment = + PopupPositioning.PopupPositionerConstraintAdjustment.SlideX | + PopupPositioning.PopupPositionerConstraintAdjustment.SlideY; + } + + + var trgtBnds = Target?.Bounds ?? Rect.Empty; + + switch (Placement) + { + case FlyoutPlacementMode.Top: //Above & centered + _popup.PlacementRect = new Rect(-sz.Width / 2, 0, sz.Width, 1); + _popup.PlacementGravity = PopupPositioning.PopupGravity.Top; + + break; + + case FlyoutPlacementMode.TopEdgeAlignedLeft: + _popup.PlacementRect = new Rect(0, 0, 0, 0); + _popup.PlacementGravity = PopupPositioning.PopupGravity.TopRight; + + break; + + case FlyoutPlacementMode.TopEdgeAlignedRight: + _popup.PlacementRect = new Rect(trgtBnds.Width - 1, 0, 10, 1); + _popup.PlacementGravity = PopupPositioning.PopupGravity.TopLeft; + + break; + + case FlyoutPlacementMode.RightEdgeAlignedTop: + _popup.PlacementRect = new Rect(trgtBnds.Width - 1, 0, 1, 1); + _popup.PlacementGravity = PopupPositioning.PopupGravity.BottomRight; + _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Right; + + break; + + case FlyoutPlacementMode.Right: //Right & centered + _popup.PlacementRect = new Rect(trgtBnds.Width - 1, 0, 1, trgtBnds.Height); + _popup.PlacementGravity = PopupPositioning.PopupGravity.Right; + _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Right; + + break; + + case FlyoutPlacementMode.RightEdgeAlignedBottom: + _popup.PlacementRect = new Rect(trgtBnds.Width - 1, trgtBnds.Height - 1, 1, 1); + _popup.PlacementGravity = PopupPositioning.PopupGravity.TopRight; + _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Right; + + break; + + case FlyoutPlacementMode.Bottom: //Below & centered + _popup.PlacementRect = new Rect(0, trgtBnds.Height - 1, trgtBnds.Width, 1); + _popup.PlacementGravity = PopupPositioning.PopupGravity.Bottom; + _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Bottom; + + break; + + case FlyoutPlacementMode.BottomEdgeAlignedLeft: + _popup.PlacementRect = new Rect(0, trgtBnds.Height - 1, 1, 1); + _popup.PlacementGravity = PopupPositioning.PopupGravity.BottomRight; + _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Bottom; + + break; + + case FlyoutPlacementMode.BottomEdgeAlignedRight: + _popup.PlacementRect = new Rect(trgtBnds.Width - 1, trgtBnds.Height - 1, 1, 1); + _popup.PlacementGravity = PopupPositioning.PopupGravity.BottomLeft; + _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Bottom; + + break; + + case FlyoutPlacementMode.LeftEdgeAlignedTop: + _popup.PlacementRect = new Rect(0, 0, 1, 1); + _popup.PlacementGravity = PopupPositioning.PopupGravity.BottomLeft; + _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Left; + + break; + + case FlyoutPlacementMode.Left: //Left & centered + _popup.PlacementRect = new Rect(0, 0, 1, trgtBnds.Height); + _popup.PlacementGravity = PopupPositioning.PopupGravity.Left; + _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Left; + + break; + + case FlyoutPlacementMode.LeftEdgeAlignedBottom: + _popup.PlacementRect = new Rect(0, trgtBnds.Height - 1, 1, 1); + _popup.PlacementGravity = PopupPositioning.PopupGravity.TopLeft; + _popup.PlacementAnchor = PopupPositioning.PopupAnchor.BottomLeft; + + break; + + case FlyoutPlacementMode.Full: + //Not sure how the get this to work + //Popup should display at max size in the middle of the VisualRoot/Window of the Target + throw new NotSupportedException("FlyoutPlacementMode.Full is not supported at this time"); + //break; + + //includes Auto (not sure what determines that)... + default: + //This is just FlyoutPlacementMode.Top behavior (above & centered) + _popup.PlacementRect = new Rect(-sz.Width / 2, 0, sz.Width, 1); + _popup.PlacementGravity = PopupPositioning.PopupGravity.Top; + + break; + } + } + } +} diff --git a/src/Avalonia.Controls/Flyouts/FlyoutPlacementMode.cs b/src/Avalonia.Controls/Flyouts/FlyoutPlacementMode.cs new file mode 100644 index 0000000000..15987daa50 --- /dev/null +++ b/src/Avalonia.Controls/Flyouts/FlyoutPlacementMode.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Controls +{ + public enum FlyoutPlacementMode + { + /// + /// Preferred location is above the target element + /// + Top = 0, + + /// + /// Preferred location is below the target element + /// + Bottom = 1, + + /// + /// Preferred location is to the left of the target element + /// + Left = 2, + + /// + /// Preferred location is to the right of the target element + /// + Right = 3, + + /// + /// Preferred location is centered on the screen + /// + Full = 4, + + /// + /// Preferred location is above the target element, with the left edge of the flyout + /// aligned with the left edge of the target element + /// + TopEdgeAlignedLeft = 5, + + /// + /// Preferred location is above the target element, with the right edge of flyout aligned with right edge of the target element. + /// + TopEdgeAlignedRight = 6, + + /// + /// Preferred location is below the target element, with the left edge of flyout aligned with left edge of the target element. + /// + BottomEdgeAlignedLeft = 7, + + /// + /// Preferred location is below the target element, with the right edge of flyout aligned with right edge of the target element. + /// + BottomEdgeAlignedRight = 8, + + /// + /// Preferred location is to the left of the target element, with the top edge of flyout aligned with top edge of the target element. + /// + LeftEdgeAlignedTop = 9, + + /// + /// Preferred location is to the left of the target element, with the bottom edge of flyout aligned with bottom edge of the target element. + /// + LeftEdgeAlignedBottom = 10, + + /// + /// Preferred location is to the right of the target element, with the top edge of flyout aligned with top edge of the target element. + /// + RightEdgeAlignedTop = 11, + + /// + /// Preferred location is to the right of the target element, with the bottom edge of flyout aligned with bottom edge of the target element. + /// + RightEdgeAlignedBottom = 12, + + /// + /// Preferred location is determined automatically. + /// + Auto = 13 + } +} diff --git a/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs b/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs new file mode 100644 index 0000000000..80477d6969 --- /dev/null +++ b/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Controls +{ + public class FlyoutPresenter : ContentControl + { + } +} diff --git a/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml b/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml new file mode 100644 index 0000000000..2be49d007b --- /dev/null +++ b/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml @@ -0,0 +1,39 @@ + + + + From 5b270a31421ca57f269facf7114343edcc7759e5 Mon Sep 17 00:00:00 2001 From: amwx Date: Wed, 17 Mar 2021 21:49:22 -0500 Subject: [PATCH 008/129] Add Flyout property to Button --- src/Avalonia.Controls/Button.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index c779e4b0cb..121087d262 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Windows.Input; using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; @@ -78,9 +79,14 @@ namespace Avalonia.Controls public static readonly StyledProperty IsPressedProperty = AvaloniaProperty.Register(nameof(IsPressed)); + public static readonly DirectProperty FlyoutProperty = + AvaloniaProperty.RegisterDirect(nameof(Flyout), + x => x.Flyout, (x, v) => x.Flyout = v); + private ICommand _command; private bool _commandCanExecute = true; private KeyGesture _hotkey; + private FlyoutBase _flyout; /// /// Initializes static members of the class. @@ -169,6 +175,12 @@ namespace Avalonia.Controls private set { SetValue(IsPressedProperty, value); } } + public FlyoutBase Flyout + { + get => _flyout; + set => SetAndRaise(FlyoutProperty, ref _flyout, value); + } + protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; /// @@ -278,6 +290,8 @@ namespace Avalonia.Controls /// protected virtual void OnClick() { + OpenFlyout(); + var e = new RoutedEventArgs(ClickEvent); RaiseEvent(e); @@ -288,6 +302,11 @@ namespace Avalonia.Controls } } + protected virtual void OpenFlyout() + { + _flyout?.ShowAt(this); + } + /// protected override void OnPointerPressed(PointerPressedEventArgs e) { From ceb170e472cebb4ec1f60243deb9a91b1ee39722 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 11:01:02 +0800 Subject: [PATCH 009/129] add apicompat for the new api --- src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt | 1 + src/Avalonia.DesignerSupport/ApiCompatBaseline.txt | 1 + src/Avalonia.Desktop/ApiCompatBaseline.txt | 1 + src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt | 1 + src/Avalonia.Diagnostics/ApiCompatBaseline.txt | 1 + src/Avalonia.Dialogs/ApiCompatBaseline.txt | 1 + src/Avalonia.Interactivity/ApiCompatBaseline.txt | 1 + src/Avalonia.Layout/ApiCompatBaseline.txt | 1 + src/Avalonia.Themes.Default/ApiCompatBaseline.txt | 1 + src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt | 1 + src/Avalonia.Visuals/ApiCompatBaseline.txt | 6 ++++++ src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt | 1 + src/Markup/Avalonia.Markup/ApiCompatBaseline.txt | 1 + 13 files changed, 18 insertions(+) create mode 100644 src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt create mode 100644 src/Avalonia.DesignerSupport/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Desktop/ApiCompatBaseline.txt create mode 100644 src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Diagnostics/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Dialogs/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Interactivity/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Layout/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Themes.Default/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Visuals/ApiCompatBaseline.txt create mode 100644 src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt create mode 100644 src/Markup/Avalonia.Markup/ApiCompatBaseline.txt diff --git a/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt b/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Avalonia.DesignerSupport/ApiCompatBaseline.txt b/src/Avalonia.DesignerSupport/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.DesignerSupport/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Avalonia.Desktop/ApiCompatBaseline.txt b/src/Avalonia.Desktop/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.Desktop/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt b/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Avalonia.Diagnostics/ApiCompatBaseline.txt b/src/Avalonia.Diagnostics/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.Diagnostics/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Avalonia.Dialogs/ApiCompatBaseline.txt b/src/Avalonia.Dialogs/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.Dialogs/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Avalonia.Interactivity/ApiCompatBaseline.txt b/src/Avalonia.Interactivity/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.Interactivity/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Avalonia.Layout/ApiCompatBaseline.txt b/src/Avalonia.Layout/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.Layout/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Avalonia.Themes.Default/ApiCompatBaseline.txt b/src/Avalonia.Themes.Default/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.Themes.Default/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt b/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt new file mode 100644 index 0000000000..f51f26974e --- /dev/null +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -0,0 +1,6 @@ +Compat issues with assembly Avalonia.Visuals: +InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength.get()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract. +Total Issues: 4 diff --git a/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt b/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Markup/Avalonia.Markup/ApiCompatBaseline.txt b/src/Markup/Avalonia.Markup/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Markup/Avalonia.Markup/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 From d7f149914d8ca307e4859fa4c4f3f5bad8173693 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 11:17:57 +0800 Subject: [PATCH 010/129] change api again --- src/Avalonia.Visuals/Platform/IGeometryImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs index 5c39afa846..c61e6c6112 100644 --- a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs @@ -72,6 +72,6 @@ namespace Avalonia.Platform /// The point in the specified distance. /// The tangent in the specified distance. /// If there's valid point and tangent at the specified distance. - bool TryGetPositionAndTangentAtDistance (double distance, out Point position, out Point tangent); + bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent); } } From c6581cf02023c99b21e64cd973d2490576563136 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 11:22:55 +0800 Subject: [PATCH 011/129] api compat update --- src/Avalonia.Animation/ApiCompatBaseline.txt | 1 + src/Avalonia.Base/ApiCompatBaseline.txt | 1 + src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt | 1 + 3 files changed, 3 insertions(+) create mode 100644 src/Avalonia.Animation/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Base/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt diff --git a/src/Avalonia.Animation/ApiCompatBaseline.txt b/src/Avalonia.Animation/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.Animation/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.Base/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 diff --git a/src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt b/src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fcc74cf864 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt @@ -0,0 +1 @@ +Total Issues: 0 From 60d111b074fa51c61d0f83d284deadd5adf9a869 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 11:24:12 +0800 Subject: [PATCH 012/129] add skia implementation --- src/Skia/Avalonia.Skia/GeometryImpl.cs | 43 +++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 879b18742e..e8b5795f60 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -11,9 +11,20 @@ namespace Avalonia.Skia internal abstract class GeometryImpl : IGeometryImpl { private PathCache _pathCache; - + /// public abstract Rect Bounds { get; } + + /// + public double ContourLength + { + get + { + if (EffectivePath is null) return 0; + return new SKPathMeasure(EffectivePath).Length; + } + } + public abstract SKPath EffectivePath { get; } /// @@ -103,6 +114,36 @@ namespace Avalonia.Skia { return new TransformedGeometryImpl(this, transform); } + + /// + public bool TryGetPointAtDistance(double distance, out Point point) + { + if (EffectivePath is null) + { + point = new Point(); + return false; + } + + var res = new SKPathMeasure(EffectivePath).GetPosition((float)distance, out var skPoint); + point = new Point(skPoint.X, skPoint.Y); + return res; + } + + /// + public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent) + { + if (EffectivePath is null) + { + point = new Point(); + tangent = new Point(); + return false; + } + + var res = new SKPathMeasure(EffectivePath).GetPositionAndTangent((float)distance, out var skPoint, out var skTangent); + point = new Point(skPoint.X, skPoint.Y); + tangent = new Point(skTangent.X, skTangent.Y); + return res; + } /// /// Invalidate all caches. Call after chaining path contents. From 38b322b02b5aa2b62d86038ba97bc2484a2e987c Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 11:24:22 +0800 Subject: [PATCH 013/129] add partial d2d implementation --- .../Avalonia.Direct2D1/Media/GeometryImpl.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs index d04e2b3110..81c21c8648 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs @@ -1,3 +1,4 @@ +using Avalonia.Logging; using Avalonia.Platform; using SharpDX.Direct2D1; @@ -8,6 +9,8 @@ namespace Avalonia.Direct2D1.Media /// public abstract class GeometryImpl : IGeometryImpl { + private const float ContourApproximation = 0.0001f; + public GeometryImpl(Geometry geometry) { Geometry = geometry; @@ -16,6 +19,9 @@ namespace Avalonia.Direct2D1.Media /// public Rect Bounds => Geometry.GetWidenedBounds(0).ToAvalonia(); + /// + public double ContourLength => Geometry.ComputeLength(null, ContourApproximation); + public Geometry Geometry { get; } /// @@ -57,6 +63,24 @@ namespace Avalonia.Direct2D1.Media transform.ToDirect2D()), this); } + + /// + public bool TryGetPointAtDistance(double distance, out Point point) + { + Geometry.ComputePointAtLength((float)distance, ContourApproximation, out var tangentVector); + point = new Point(tangentVector.X, tangentVector.Y); + return true; + } + + /// + public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent) + { + // Direct2D doesnt have this sadly. + Logger.TryGet(LogEventLevel.Warning, LogArea.Visual)?.Log(this, "TryGetPointAndTangentAtDistance is not available in Direct2D."); + point = new Point(); + tangent = new Point(); + return false; + } protected virtual Geometry GetSourceGeometry() => Geometry; } From 386a318abe2cdc4811832d6f68f56e232217b06b Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 11:24:37 +0800 Subject: [PATCH 014/129] add stubs for headless/mock --- .../HeadlessPlatformRenderInterface.cs | 16 ++++++++++++++++ .../Avalonia.UnitTests/MockStreamGeometryImpl.cs | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 6a78f4c6e7..ce5a8a2e86 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -104,6 +104,9 @@ namespace Avalonia.Headless } public Rect Bounds { get; set; } + + public double ContourLength { get; } = 0; + public virtual bool FillContains(Point point) => Bounds.Contains(point); public Rect GetRenderBounds(IPen pen) @@ -126,6 +129,19 @@ namespace Avalonia.Headless public ITransformedGeometryImpl WithTransform(Matrix transform) => new HeadlessTransformedGeometryStub(this, transform); + + public bool TryGetPointAtDistance(double distance, out Point point) + { + point = new Point(); + return false; + } + + public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent) + { + point = new Point(); + tangent = new Point(); + return false; + } } class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl diff --git a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs index 4fa3fbf523..4e66eaeb24 100644 --- a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs +++ b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs @@ -30,6 +30,8 @@ namespace Avalonia.UnitTests public IGeometryImpl SourceGeometry { get; } public Rect Bounds => _context.CalculateBounds(); + + public double ContourLength { get; } public Matrix Transform { get; } @@ -69,6 +71,19 @@ namespace Avalonia.UnitTests return new MockStreamGeometryImpl(transform, _context); } + public bool TryGetPointAtDistance(double distance, out Point point) + { + point = new Point(); + return false; + } + + public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent) + { + point = new Point(); + tangent = new Point(); + return false; + } + class MockStreamGeometryContext : IStreamGeometryContextImpl { private List points = new List(); From ce655d715cb197ba3bb62bb0e56cc8ebf7eac7fe Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 18 Mar 2021 11:43:17 +0800 Subject: [PATCH 015/129] Apply suggestions from code review --- src/Avalonia.DesignerSupport/ApiCompatBaseline.txt | 2 +- src/Avalonia.Desktop/ApiCompatBaseline.txt | 2 +- src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt | 2 +- src/Avalonia.Diagnostics/ApiCompatBaseline.txt | 2 +- src/Avalonia.Dialogs/ApiCompatBaseline.txt | 2 +- src/Avalonia.Interactivity/ApiCompatBaseline.txt | 2 +- src/Avalonia.Layout/ApiCompatBaseline.txt | 2 +- src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt | 2 +- src/Avalonia.Themes.Default/ApiCompatBaseline.txt | 2 +- src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt | 2 +- src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt | 2 +- src/Markup/Avalonia.Markup/ApiCompatBaseline.txt | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.DesignerSupport/ApiCompatBaseline.txt b/src/Avalonia.DesignerSupport/ApiCompatBaseline.txt index fcc74cf864..8b13789179 100644 --- a/src/Avalonia.DesignerSupport/ApiCompatBaseline.txt +++ b/src/Avalonia.DesignerSupport/ApiCompatBaseline.txt @@ -1 +1 @@ -Total Issues: 0 + diff --git a/src/Avalonia.Desktop/ApiCompatBaseline.txt b/src/Avalonia.Desktop/ApiCompatBaseline.txt index fcc74cf864..8b13789179 100644 --- a/src/Avalonia.Desktop/ApiCompatBaseline.txt +++ b/src/Avalonia.Desktop/ApiCompatBaseline.txt @@ -1 +1 @@ -Total Issues: 0 + diff --git a/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt b/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt index fcc74cf864..8b13789179 100644 --- a/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt +++ b/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt @@ -1 +1 @@ -Total Issues: 0 + diff --git a/src/Avalonia.Diagnostics/ApiCompatBaseline.txt b/src/Avalonia.Diagnostics/ApiCompatBaseline.txt index fcc74cf864..8b13789179 100644 --- a/src/Avalonia.Diagnostics/ApiCompatBaseline.txt +++ b/src/Avalonia.Diagnostics/ApiCompatBaseline.txt @@ -1 +1 @@ -Total Issues: 0 + diff --git a/src/Avalonia.Dialogs/ApiCompatBaseline.txt b/src/Avalonia.Dialogs/ApiCompatBaseline.txt index fcc74cf864..8b13789179 100644 --- a/src/Avalonia.Dialogs/ApiCompatBaseline.txt +++ b/src/Avalonia.Dialogs/ApiCompatBaseline.txt @@ -1 +1 @@ -Total Issues: 0 + diff --git a/src/Avalonia.Interactivity/ApiCompatBaseline.txt b/src/Avalonia.Interactivity/ApiCompatBaseline.txt index fcc74cf864..8b13789179 100644 --- a/src/Avalonia.Interactivity/ApiCompatBaseline.txt +++ b/src/Avalonia.Interactivity/ApiCompatBaseline.txt @@ -1 +1 @@ -Total Issues: 0 + diff --git a/src/Avalonia.Layout/ApiCompatBaseline.txt b/src/Avalonia.Layout/ApiCompatBaseline.txt index fcc74cf864..8b13789179 100644 --- a/src/Avalonia.Layout/ApiCompatBaseline.txt +++ b/src/Avalonia.Layout/ApiCompatBaseline.txt @@ -1 +1 @@ -Total Issues: 0 + diff --git a/src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt b/src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt index fcc74cf864..8b13789179 100644 --- a/src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt +++ b/src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt @@ -1 +1 @@ -Total Issues: 0 + diff --git a/src/Avalonia.Themes.Default/ApiCompatBaseline.txt b/src/Avalonia.Themes.Default/ApiCompatBaseline.txt index fcc74cf864..8b13789179 100644 --- a/src/Avalonia.Themes.Default/ApiCompatBaseline.txt +++ b/src/Avalonia.Themes.Default/ApiCompatBaseline.txt @@ -1 +1 @@ -Total Issues: 0 + diff --git a/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt b/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt index fcc74cf864..8b13789179 100644 --- a/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt +++ b/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt @@ -1 +1 @@ -Total Issues: 0 + diff --git a/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt b/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt index fcc74cf864..8b13789179 100644 --- a/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt +++ b/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt @@ -1 +1 @@ -Total Issues: 0 + diff --git a/src/Markup/Avalonia.Markup/ApiCompatBaseline.txt b/src/Markup/Avalonia.Markup/ApiCompatBaseline.txt index fcc74cf864..8b13789179 100644 --- a/src/Markup/Avalonia.Markup/ApiCompatBaseline.txt +++ b/src/Markup/Avalonia.Markup/ApiCompatBaseline.txt @@ -1 +1 @@ -Total Issues: 0 + From a1583ef3e87b38cf9a169d0daa15cc8eb9178477 Mon Sep 17 00:00:00 2001 From: amwx Date: Thu, 18 Mar 2021 00:13:07 -0500 Subject: [PATCH 016/129] Couple fixes --- src/Avalonia.Controls/Flyouts/Flyout.cs | 2 + src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 43 ++++++++------------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Controls/Flyouts/Flyout.cs b/src/Avalonia.Controls/Flyouts/Flyout.cs index 723a5e84f8..ed16628605 100644 --- a/src/Avalonia.Controls/Flyouts/Flyout.cs +++ b/src/Avalonia.Controls/Flyouts/Flyout.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Text; using Avalonia.Controls.Primitives; +using Avalonia.Metadata; using Avalonia.Styling; #nullable enable @@ -31,6 +32,7 @@ namespace Avalonia.Controls private Styles? _styles; private bool _stylesDirty; + [Content] public object Content { get => GetValue(ContentProperty); diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 64ab07a658..ca267dc5e9 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -17,9 +17,8 @@ namespace Avalonia.Controls.Primitives public static readonly DirectProperty TargetProperty = AvaloniaProperty.RegisterDirect(nameof(Target), x => x.Target); - public static readonly DirectProperty PlacementProperty = - AvaloniaProperty.RegisterDirect(nameof(Placement), - x => x.Placement, (x, v) => x.Placement = v); + public static readonly StyledProperty PlacementProperty = + AvaloniaProperty.Register(nameof(Placement)); public static readonly AttachedProperty AttachedFlyoutProperty = AvaloniaProperty.RegisterAttached("AttachedFlyout", null); @@ -128,9 +127,11 @@ namespace Avalonia.Controls.Primitives ((ISetLogicalParent)_popup).SetParent(null); } - _popup.PlacementTarget = Target = placementTarget; - - ((ISetLogicalParent)_popup).SetParent(placementTarget); + if (_popup.PlacementTarget != placementTarget) + { + _popup.PlacementTarget = Target = placementTarget; + ((ISetLogicalParent)_popup).SetParent(placementTarget); + } if (_popup.Child == null) { @@ -138,8 +139,8 @@ namespace Avalonia.Controls.Primitives } OnOpening(); - IsOpen = _popup.IsOpen = true; PositionPopup(showAtPointer); + IsOpen = _popup.IsOpen = true; OnOpened(); } @@ -189,13 +190,14 @@ namespace Avalonia.Controls.Primitives private void PositionPopup(bool showAtPointer) { Size sz; - if(_popup.DesiredSize == Size.Empty) + if(_popup.Child.DesiredSize == Size.Empty) { - sz = LayoutHelper.MeasureChild(_popup, Size.Infinity, new Thickness()); + // Popup may not have been shown yet. Measure content + sz = LayoutHelper.MeasureChild(_popup.Child, Size.Infinity, new Thickness()); } else { - sz = _popup.DesiredSize; + sz = _popup.Child.DesiredSize; } if (showAtPointer) @@ -210,90 +212,78 @@ namespace Avalonia.Controls.Primitives PopupPositioning.PopupPositionerConstraintAdjustment.SlideY; } - var trgtBnds = Target?.Bounds ?? Rect.Empty; switch (Placement) { case FlyoutPlacementMode.Top: //Above & centered - _popup.PlacementRect = new Rect(-sz.Width / 2, 0, sz.Width, 1); + _popup.PlacementRect = new Rect(0, 0, trgtBnds.Width-1, 1); _popup.PlacementGravity = PopupPositioning.PopupGravity.Top; - + _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Top; break; case FlyoutPlacementMode.TopEdgeAlignedLeft: _popup.PlacementRect = new Rect(0, 0, 0, 0); - _popup.PlacementGravity = PopupPositioning.PopupGravity.TopRight; - + _popup.PlacementGravity = PopupPositioning.PopupGravity.TopRight; break; case FlyoutPlacementMode.TopEdgeAlignedRight: _popup.PlacementRect = new Rect(trgtBnds.Width - 1, 0, 10, 1); - _popup.PlacementGravity = PopupPositioning.PopupGravity.TopLeft; - + _popup.PlacementGravity = PopupPositioning.PopupGravity.TopLeft; break; case FlyoutPlacementMode.RightEdgeAlignedTop: _popup.PlacementRect = new Rect(trgtBnds.Width - 1, 0, 1, 1); _popup.PlacementGravity = PopupPositioning.PopupGravity.BottomRight; _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Right; - break; case FlyoutPlacementMode.Right: //Right & centered _popup.PlacementRect = new Rect(trgtBnds.Width - 1, 0, 1, trgtBnds.Height); _popup.PlacementGravity = PopupPositioning.PopupGravity.Right; _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Right; - break; case FlyoutPlacementMode.RightEdgeAlignedBottom: _popup.PlacementRect = new Rect(trgtBnds.Width - 1, trgtBnds.Height - 1, 1, 1); _popup.PlacementGravity = PopupPositioning.PopupGravity.TopRight; _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Right; - break; case FlyoutPlacementMode.Bottom: //Below & centered _popup.PlacementRect = new Rect(0, trgtBnds.Height - 1, trgtBnds.Width, 1); _popup.PlacementGravity = PopupPositioning.PopupGravity.Bottom; _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Bottom; - break; case FlyoutPlacementMode.BottomEdgeAlignedLeft: _popup.PlacementRect = new Rect(0, trgtBnds.Height - 1, 1, 1); _popup.PlacementGravity = PopupPositioning.PopupGravity.BottomRight; _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Bottom; - break; case FlyoutPlacementMode.BottomEdgeAlignedRight: _popup.PlacementRect = new Rect(trgtBnds.Width - 1, trgtBnds.Height - 1, 1, 1); _popup.PlacementGravity = PopupPositioning.PopupGravity.BottomLeft; _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Bottom; - break; case FlyoutPlacementMode.LeftEdgeAlignedTop: _popup.PlacementRect = new Rect(0, 0, 1, 1); _popup.PlacementGravity = PopupPositioning.PopupGravity.BottomLeft; _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Left; - break; case FlyoutPlacementMode.Left: //Left & centered _popup.PlacementRect = new Rect(0, 0, 1, trgtBnds.Height); _popup.PlacementGravity = PopupPositioning.PopupGravity.Left; _popup.PlacementAnchor = PopupPositioning.PopupAnchor.Left; - break; case FlyoutPlacementMode.LeftEdgeAlignedBottom: _popup.PlacementRect = new Rect(0, trgtBnds.Height - 1, 1, 1); _popup.PlacementGravity = PopupPositioning.PopupGravity.TopLeft; _popup.PlacementAnchor = PopupPositioning.PopupAnchor.BottomLeft; - break; case FlyoutPlacementMode.Full: @@ -307,7 +297,6 @@ namespace Avalonia.Controls.Primitives //This is just FlyoutPlacementMode.Top behavior (above & centered) _popup.PlacementRect = new Rect(-sz.Width / 2, 0, sz.Width, 1); _popup.PlacementGravity = PopupPositioning.PopupGravity.Top; - break; } } From 518d4022c85d33a8a12d08430c1b5cf2a6d6e80f Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 18 Mar 2021 11:44:01 +0800 Subject: [PATCH 017/129] Delete ApiCompatBaseline.txt --- src/Avalonia.Animation/ApiCompatBaseline.txt | 1 - src/Avalonia.Base/ApiCompatBaseline.txt | 1 - src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt | 1 - src/Avalonia.DesignerSupport/ApiCompatBaseline.txt | 1 - src/Avalonia.Desktop/ApiCompatBaseline.txt | 1 - src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt | 1 - src/Avalonia.Diagnostics/ApiCompatBaseline.txt | 1 - src/Avalonia.Dialogs/ApiCompatBaseline.txt | 1 - src/Avalonia.Interactivity/ApiCompatBaseline.txt | 1 - src/Avalonia.Layout/ApiCompatBaseline.txt | 1 - src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt | 1 - src/Avalonia.Themes.Default/ApiCompatBaseline.txt | 1 - src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt | 1 - src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt | 1 - src/Markup/Avalonia.Markup/ApiCompatBaseline.txt | 1 - 15 files changed, 15 deletions(-) delete mode 100644 src/Avalonia.Animation/ApiCompatBaseline.txt delete mode 100644 src/Avalonia.Base/ApiCompatBaseline.txt delete mode 100644 src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt delete mode 100644 src/Avalonia.DesignerSupport/ApiCompatBaseline.txt delete mode 100644 src/Avalonia.Desktop/ApiCompatBaseline.txt delete mode 100644 src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt delete mode 100644 src/Avalonia.Diagnostics/ApiCompatBaseline.txt delete mode 100644 src/Avalonia.Dialogs/ApiCompatBaseline.txt delete mode 100644 src/Avalonia.Interactivity/ApiCompatBaseline.txt delete mode 100644 src/Avalonia.Layout/ApiCompatBaseline.txt delete mode 100644 src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt delete mode 100644 src/Avalonia.Themes.Default/ApiCompatBaseline.txt delete mode 100644 src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt delete mode 100644 src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt delete mode 100644 src/Markup/Avalonia.Markup/ApiCompatBaseline.txt diff --git a/src/Avalonia.Animation/ApiCompatBaseline.txt b/src/Avalonia.Animation/ApiCompatBaseline.txt deleted file mode 100644 index fcc74cf864..0000000000 --- a/src/Avalonia.Animation/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ -Total Issues: 0 diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt deleted file mode 100644 index fcc74cf864..0000000000 --- a/src/Avalonia.Base/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ -Total Issues: 0 diff --git a/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt b/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt deleted file mode 100644 index fcc74cf864..0000000000 --- a/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ -Total Issues: 0 diff --git a/src/Avalonia.DesignerSupport/ApiCompatBaseline.txt b/src/Avalonia.DesignerSupport/ApiCompatBaseline.txt deleted file mode 100644 index 8b13789179..0000000000 --- a/src/Avalonia.DesignerSupport/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Avalonia.Desktop/ApiCompatBaseline.txt b/src/Avalonia.Desktop/ApiCompatBaseline.txt deleted file mode 100644 index 8b13789179..0000000000 --- a/src/Avalonia.Desktop/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt b/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt deleted file mode 100644 index 8b13789179..0000000000 --- a/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Avalonia.Diagnostics/ApiCompatBaseline.txt b/src/Avalonia.Diagnostics/ApiCompatBaseline.txt deleted file mode 100644 index 8b13789179..0000000000 --- a/src/Avalonia.Diagnostics/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Avalonia.Dialogs/ApiCompatBaseline.txt b/src/Avalonia.Dialogs/ApiCompatBaseline.txt deleted file mode 100644 index 8b13789179..0000000000 --- a/src/Avalonia.Dialogs/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Avalonia.Interactivity/ApiCompatBaseline.txt b/src/Avalonia.Interactivity/ApiCompatBaseline.txt deleted file mode 100644 index 8b13789179..0000000000 --- a/src/Avalonia.Interactivity/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Avalonia.Layout/ApiCompatBaseline.txt b/src/Avalonia.Layout/ApiCompatBaseline.txt deleted file mode 100644 index 8b13789179..0000000000 --- a/src/Avalonia.Layout/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt b/src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt deleted file mode 100644 index 8b13789179..0000000000 --- a/src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Avalonia.Themes.Default/ApiCompatBaseline.txt b/src/Avalonia.Themes.Default/ApiCompatBaseline.txt deleted file mode 100644 index 8b13789179..0000000000 --- a/src/Avalonia.Themes.Default/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt b/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt deleted file mode 100644 index 8b13789179..0000000000 --- a/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt b/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt deleted file mode 100644 index 8b13789179..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Markup/Avalonia.Markup/ApiCompatBaseline.txt b/src/Markup/Avalonia.Markup/ApiCompatBaseline.txt deleted file mode 100644 index 8b13789179..0000000000 --- a/src/Markup/Avalonia.Markup/ApiCompatBaseline.txt +++ /dev/null @@ -1 +0,0 @@ - From 88fef2fdd5f2ba6dc01d639de40bc65e571bc56b Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 15:57:08 +0800 Subject: [PATCH 018/129] Add Bitmap Blending Mode API --- .../HeadlessPlatformRenderInterface.cs | 2 +- src/Avalonia.Visuals/ApiCompatBaseline.txt | 5 +- .../Media/Imaging/BitmapBlendingMode.cs | 57 +++++++++++++++++++ .../Platform/IDrawingContextImpl.cs | 3 +- .../SceneGraph/DeferredDrawingContextImpl.cs | 6 +- .../Rendering/SceneGraph/ImageNode.cs | 28 ++++++--- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 5 +- src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs | 33 +++++++++++ .../Media/DrawingContextImpl.cs | 36 +++++++++++- 9 files changed, 156 insertions(+), 19 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index ce5a8a2e86..a00dc1f618 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -393,7 +393,7 @@ namespace Avalonia.Headless { } - public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default, BitmapBlendingMode bitmapBlendingMode = BitmapBlendingMode.SourceOver) { } diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index f51f26974e..e781cb1d3e 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -1,6 +1,9 @@ Compat issues with assembly Avalonia.Visuals: +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawBitmap(Avalonia.Utilities.IRef, System.Double, Avalonia.Rect, Avalonia.Rect, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the contract but not in the implementation. +MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawBitmap(Avalonia.Utilities.IRef, System.Double, Avalonia.Rect, Avalonia.Rect, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawBitmap(Avalonia.Utilities.IRef, System.Double, Avalonia.Rect, Avalonia.Rect, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode, Avalonia.Visuals.Media.Imaging.BitmapBlendingMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract. -Total Issues: 4 +Total Issues: 7 diff --git a/src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs b/src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs new file mode 100644 index 0000000000..473b43dab3 --- /dev/null +++ b/src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs @@ -0,0 +1,57 @@ +namespace Avalonia.Visuals.Media.Imaging +{ + /// + /// Controls the way the bitmaps are drawn together. + /// + public enum BitmapBlendingMode + { + /// + /// Source is placed over the destination. + /// + SourceOver, + /// + /// Only the source will be present. + /// + Source, + /// + /// Only the destination will be present. + /// + Destination, + /// + /// Destination is placed over the source. + /// + DestinationOver, + /// + /// The source that overlaps the destination, replaces the destination. + /// + SourceIn, + /// + /// Destination which overlaps the source, replaces the source. + /// + DestinationIn, + /// + /// Source is placed, where it falls outside of the destination. + /// + SourceOut, + /// + /// Destination is placed, where it falls outside of the source. + /// + DestinationOut, + /// + /// Source which overlaps the destination, replaces the destination. + /// + SourceAtop, + /// + /// Destination which overlaps the source replaces the source. + /// + DestinationAtop, + /// + /// The non-overlapping regions of source and destination are combined. + /// + Xor, + /// + /// Display the sum of the source image and destination image. + /// + Plus, + } +} diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index d6e88a7507..e9a1a2a6ae 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -30,7 +30,8 @@ namespace Avalonia.Platform /// The rect in the image to draw. /// The rect in the output to draw to. /// The bitmap interpolation mode. - void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default); + /// The bitmap blending mode. + void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default, BitmapBlendingMode bitmapBlendMode = BitmapBlendingMode.SourceOver); /// /// Draws a bitmap image. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 28f426266d..1705bc6b1c 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -112,13 +112,13 @@ namespace Avalonia.Rendering.SceneGraph } /// - public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode, BitmapBlendingMode bitmapBlendingMode) { var next = NextDrawAs(); - if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)) + if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode, bitmapBlendingMode)) { - Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)); + Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode, bitmapBlendingMode)); } else { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index c9052c6ef2..c50601120f 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -18,7 +18,7 @@ namespace Avalonia.Rendering.SceneGraph /// The source rect. /// The destination rect. /// The bitmap interpolation mode. - public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) + public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode, BitmapBlendingMode bitmapBlendingMode) : base(destRect, transform) { Transform = transform; @@ -27,6 +27,7 @@ namespace Avalonia.Rendering.SceneGraph SourceRect = sourceRect; DestRect = destRect; BitmapInterpolationMode = bitmapInterpolationMode; + BitmapBlendingMode = bitmapBlendingMode; SourceVersion = Source.Item.Version; } @@ -67,6 +68,14 @@ namespace Avalonia.Rendering.SceneGraph /// The scaling mode. /// public BitmapInterpolationMode BitmapInterpolationMode { get; } + + /// + /// The bitmap blending mode. + /// + /// + /// The blending mode. + /// + public BitmapBlendingMode BitmapBlendingMode { get; } /// /// Determines if this draw operation equals another. @@ -82,22 +91,23 @@ namespace Avalonia.Rendering.SceneGraph /// The properties of the other draw operation are passed in as arguments to prevent /// allocation of a not-yet-constructed draw operation object. /// - public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) + public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode, BitmapBlendingMode bitmapBlendingMode) { return transform == Transform && - Equals(source.Item, Source.Item) && - source.Item.Version == SourceVersion && - opacity == Opacity && - sourceRect == SourceRect && - destRect == DestRect && - bitmapInterpolationMode == BitmapInterpolationMode; + Equals(source.Item, Source.Item) && + source.Item.Version == SourceVersion && + opacity == Opacity && + sourceRect == SourceRect && + destRect == DestRect && + bitmapInterpolationMode == BitmapInterpolationMode && + bitmapBlendingMode == BitmapBlendingMode; } /// public override void Render(IDrawingContextImpl context) { context.Transform = Transform; - context.DrawBitmap(Source, Opacity, SourceRect, DestRect, BitmapInterpolationMode); + context.DrawBitmap(Source, Opacity, SourceRect, DestRect, BitmapInterpolationMode, BitmapBlendingMode); } /// diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 2a79a4bb50..692d40a653 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -132,7 +132,7 @@ namespace Avalonia.Skia } /// - public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode, BitmapBlendingMode bitmapBlendingMode) { var drawableImage = (IDrawableBitmapImpl)source.Item; var s = sourceRect.ToSKRect(); @@ -145,6 +145,7 @@ namespace Avalonia.Skia }) { paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality(); + paint.BlendMode = bitmapBlendingMode.ToSKBlendMode(); drawableImage.Draw(this, s, d, paint); } @@ -154,7 +155,7 @@ namespace Avalonia.Skia public void DrawBitmap(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { PushOpacityMask(opacityMask, opacityMaskRect); - DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default); + DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default, BitmapBlendingMode.SourceOver); PopOpacityMask(); } diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs index bb3dbbfadc..75b4231640 100644 --- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs +++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs @@ -25,6 +25,39 @@ namespace Avalonia.Skia } } + public static SKBlendMode ToSKBlendMode(this BitmapBlendingMode blendingMode) + { + switch (blendingMode) + { + case BitmapBlendingMode.SourceOver: + return SKBlendMode.SrcOver; + case BitmapBlendingMode.Source: + return SKBlendMode.Src; + case BitmapBlendingMode.SourceIn: + return SKBlendMode.SrcIn; + case BitmapBlendingMode.SourceOut: + return SKBlendMode.SrcOut; + case BitmapBlendingMode.SourceAtop: + return SKBlendMode.SrcATop; + case BitmapBlendingMode.Destination: + return SKBlendMode.Dst; + case BitmapBlendingMode.DestinationIn: + return SKBlendMode.DstIn; + case BitmapBlendingMode.DestinationOut: + return SKBlendMode.DstOut; + case BitmapBlendingMode.DestinationOver: + return SKBlendMode.DstOver; + case BitmapBlendingMode.DestinationAtop: + return SKBlendMode.DstATop; + case BitmapBlendingMode.Xor: + return SKBlendMode.Xor; + case BitmapBlendingMode.Plus: + return SKBlendMode.Plus; + default: + throw new ArgumentOutOfRangeException(nameof(blendingMode), blendingMode, null); + } + } + public static SKPoint ToSKPoint(this Point p) { return new SKPoint((float)p.X, (float)p.Y); diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 47a19aad8c..0f5a932ef3 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -5,6 +5,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; +using Avalonia.Visuals.Media.Imaging; using SharpDX; using SharpDX.Direct2D1; using SharpDX.Mathematics.Interop; @@ -116,12 +117,14 @@ namespace Avalonia.Direct2D1.Media /// The rect in the image to draw. /// The rect in the output to draw to. /// The bitmap interpolation mode. - public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode, BitmapBlendingMode bitmapBlendingMode) { using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) { var interpolationMode = GetInterpolationMode(bitmapInterpolationMode); - + + // TODO: How to implement CompositeMode here? + _deviceContext.DrawBitmap( d2d.Value, destRect.ToSharpDX(), @@ -149,6 +152,35 @@ namespace Avalonia.Direct2D1.Media } } + public static CompositeMode GetCompositeMode(BitmapBlendingMode blendingMode) + { + switch (blendingMode) + { + case BitmapBlendingMode.SourceIn: + return CompositeMode.SourceIn; + case BitmapBlendingMode.SourceOut: + return CompositeMode.SourceOut; + case BitmapBlendingMode.SourceOver: + return CompositeMode.SourceOver; + case BitmapBlendingMode.SourceAtop: + return CompositeMode.SourceAtop; + case BitmapBlendingMode.DestinationIn: + return CompositeMode.DestinationIn; + case BitmapBlendingMode.DestinationOut: + return CompositeMode.DestinationOut; + case BitmapBlendingMode.DestinationOver: + return CompositeMode.DestinationOver; + case BitmapBlendingMode.DestinationAtop: + return CompositeMode.DestinationAtop; + case BitmapBlendingMode.Xor: + return CompositeMode.Xor; + case BitmapBlendingMode.Plus: + return CompositeMode.Plus; + default: + throw new ArgumentOutOfRangeException(nameof(blendingMode), blendingMode, null); + } + } + /// /// Draws a bitmap image. /// From 4bffeaf3276a86c11d7b84e9a886713d6099620e Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 21:24:38 +0800 Subject: [PATCH 019/129] use pop/push model for blendingmode instead. --- .../Platform/IDrawingContextImpl.cs | 13 +++++++++++- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 21 ++++++++++++++++--- .../Media/DrawingContextImpl.cs | 12 ++++++++++- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index e9a1a2a6ae..e2fefffecc 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -31,7 +31,7 @@ namespace Avalonia.Platform /// The rect in the output to draw to. /// The bitmap interpolation mode. /// The bitmap blending mode. - void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default, BitmapBlendingMode bitmapBlendMode = BitmapBlendingMode.SourceOver); + void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default); /// /// Draws a bitmap image. @@ -149,6 +149,17 @@ namespace Avalonia.Platform /// Pops the latest pushed geometry clip. /// void PopGeometryClip(); + + /// + /// Pushes an bitmap blending value. + /// + /// The opacity. + void PushBitmapBlendMode(BitmapBlendingMode blendingMode); + + /// + /// Pops the latest pushed bitmap blending value. + /// + void PopBitmapBlendMode(); /// /// Adds a custom draw operation diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 692d40a653..b7d5d3ec59 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -23,9 +23,11 @@ namespace Avalonia.Skia private readonly Vector _dpi; private readonly Stack _maskStack = new Stack(); private readonly Stack _opacityStack = new Stack(); + private readonly Stack _blendingModeStack = new Stack(); private readonly Matrix? _postTransform; private readonly IVisualBrushRenderer _visualBrushRenderer; private double _currentOpacity = 1.0f; + private BitmapBlendingMode _currentBlendingMode = BitmapBlendingMode.SourceOver; private readonly bool _canTextUseLcdRendering; private Matrix _currentTransform; private bool _disposed; @@ -132,7 +134,7 @@ namespace Avalonia.Skia } /// - public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode, BitmapBlendingMode bitmapBlendingMode) + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { var drawableImage = (IDrawableBitmapImpl)source.Item; var s = sourceRect.ToSKRect(); @@ -145,7 +147,7 @@ namespace Avalonia.Skia }) { paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality(); - paint.BlendMode = bitmapBlendingMode.ToSKBlendMode(); + paint.BlendMode = _currentBlendingMode.ToSKBlendMode(); drawableImage.Draw(this, s, d, paint); } @@ -155,7 +157,7 @@ namespace Avalonia.Skia public void DrawBitmap(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { PushOpacityMask(opacityMask, opacityMaskRect); - DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default, BitmapBlendingMode.SourceOver); + DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default); PopOpacityMask(); } @@ -509,6 +511,19 @@ namespace Avalonia.Skia Canvas.Restore(); } + /// + public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) + { + _blendingModeStack.Push(_currentBlendingMode); + _currentBlendingMode = blendingMode; + } + + /// + public void PopBitmapBlendMode() + { + _currentBlendingMode = _blendingModeStack.Pop(); + } + public void Custom(ICustomDrawOperation custom) => custom.Render(this); /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 0f5a932ef3..efe581ae0d 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -117,7 +117,7 @@ namespace Avalonia.Direct2D1.Media /// The rect in the image to draw. /// The rect in the output to draw to. /// The bitmap interpolation mode. - public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode, BitmapBlendingMode bitmapBlendingMode) + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) { @@ -557,6 +557,16 @@ namespace Avalonia.Direct2D1.Media PopLayer(); } + public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) + { + // Stubs for now + } + + public void PopBitmapBlendMode() + { + // Stubs for now + } + public void PushOpacityMask(IBrush mask, Rect bounds) { var parameters = new LayerParameters From c6491400fbbd4c5e5e96ad23b38d1365674573f7 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 21:31:59 +0800 Subject: [PATCH 020/129] use path cache helpers and eliminate spurious instantiations of SKPathMeasure --- src/Skia/Avalonia.Skia/GeometryImpl.cs | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index e8b5795f60..8b4ba66cf6 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -11,19 +11,12 @@ namespace Avalonia.Skia internal abstract class GeometryImpl : IGeometryImpl { private PathCache _pathCache; - + /// public abstract Rect Bounds { get; } /// - public double ContourLength - { - get - { - if (EffectivePath is null) return 0; - return new SKPathMeasure(EffectivePath).Length; - } - } + public double ContourLength => _pathCache.CachePathMeasure?.Length ?? 0; public abstract SKPath EffectivePath { get; } @@ -118,13 +111,13 @@ namespace Avalonia.Skia /// public bool TryGetPointAtDistance(double distance, out Point point) { - if (EffectivePath is null) + if (_pathCache.CachePathMeasure is null) { point = new Point(); return false; } - var res = new SKPathMeasure(EffectivePath).GetPosition((float)distance, out var skPoint); + var res = _pathCache.CachePathMeasure.GetPosition((float)distance, out var skPoint); point = new Point(skPoint.X, skPoint.Y); return res; } @@ -132,14 +125,14 @@ namespace Avalonia.Skia /// public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent) { - if (EffectivePath is null) + if (_pathCache.CachePathMeasure is null) { point = new Point(); tangent = new Point(); return false; } - var res = new SKPathMeasure(EffectivePath).GetPositionAndTangent((float)distance, out var skPoint, out var skTangent); + var res = _pathCache.CachePathMeasure.GetPositionAndTangent((float)distance, out var skPoint, out var skTangent); point = new Point(skPoint.X, skPoint.Y); tangent = new Point(skTangent.X, skTangent.Y); return res; @@ -156,7 +149,7 @@ namespace Avalonia.Skia private struct PathCache { private float _cachedStrokeWidth; - + /// /// Tolerance for two stroke widths to be deemed equal /// @@ -171,6 +164,11 @@ namespace Avalonia.Skia /// Cached geometry render bounds. /// public Rect CachedGeometryRenderBounds { get; private set; } + + /// + /// Cached path measurement helper. + /// + public SKPathMeasure CachePathMeasure { get; private set; } /// /// Is cached valid for given stroke width. @@ -196,7 +194,8 @@ namespace Avalonia.Skia } CachedStrokePath = path; - CachedGeometryRenderBounds = geometryRenderBounds; + CachedGeometryRenderBounds = geometryRenderBounds; + CachePathMeasure = new SKPathMeasure(path); _cachedStrokeWidth = strokeWidth; } @@ -206,6 +205,7 @@ namespace Avalonia.Skia public void Invalidate() { CachedStrokePath?.Dispose(); + CachePathMeasure?.Dispose(); CachedGeometryRenderBounds = Rect.Empty; _cachedStrokeWidth = default(float); } From cd35e2c35ea04b6f4b44d419c64e024eba4ddcbb Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 18 Mar 2021 21:34:50 +0800 Subject: [PATCH 021/129] Update src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs --- src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index a00dc1f618..ce5a8a2e86 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -393,7 +393,7 @@ namespace Avalonia.Headless { } - public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default, BitmapBlendingMode bitmapBlendingMode = BitmapBlendingMode.SourceOver) + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) { } From f9a432ec9dbc43288ddbc69dcff4076fef26eec7 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 21:40:39 +0800 Subject: [PATCH 022/129] fix HeadlessPlatformRenderInterface.cs --- .../HeadlessPlatformRenderInterface.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index ce5a8a2e86..48c46f50a1 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -375,6 +375,16 @@ namespace Avalonia.Headless } + public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) + { + + } + + public void PopBitmapBlendMode() + { + + } + public void Custom(ICustomDrawOperation custom) { From de3795514d0e1a345f1a65d82bb8b6d3a7841d10 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 22:17:50 +0800 Subject: [PATCH 023/129] add DeferredDrawingContextImpl implementations for blendmode --- .../SceneGraph/BitmapBlendModeNode.cs | 68 +++++++++++++++++++ .../SceneGraph/DeferredDrawingContextImpl.cs | 37 +++++++++- .../Rendering/SceneGraph/ImageNode.cs | 10 ++- 3 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs new file mode 100644 index 0000000000..b486b5060a --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs @@ -0,0 +1,68 @@ +using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; + +namespace Avalonia.Rendering.SceneGraph +{ + /// + /// A node in the scene graph which represents an opacity push or pop. + /// + internal class BitmapBlendModeNode : IDrawOperation + { + /// + /// Initializes a new instance of the class that represents an + /// push. + /// + /// The to push. + public BitmapBlendModeNode(BitmapBlendingMode bitmapBlend) + { + BlendingMode = bitmapBlend; + } + + /// + /// Initializes a new instance of the class that represents an + /// pop. + /// + public BitmapBlendModeNode() + { + } + + /// + public Rect Bounds => Rect.Empty; + + /// + /// Gets the BitmapBlend to be pushed or null if the operation represents a pop. + /// + public BitmapBlendingMode? BlendingMode { get; } + + /// + public bool HitTest(Point p) => false; + + /// + /// Determines if this draw operation equals another. + /// + /// The opacity of the other draw operation. + /// True if the draw operations are the same, otherwise false. + /// + /// The properties of the other draw operation are passed in as arguments to prevent + /// allocation of a not-yet-constructed draw operation object. + /// + public bool Equals(BitmapBlendingMode? blendingMode) => BlendingMode == blendingMode; + + /// + public void Render(IDrawingContextImpl context) + { + if (BlendingMode.HasValue) + { + context.PushBitmapBlendMode(BlendingMode.Value); + } + else + { + context.PopBitmapBlendMode(); + } + } + + public void Dispose() + { + } + } +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 1705bc6b1c..d4564c9f29 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -112,13 +112,13 @@ namespace Avalonia.Rendering.SceneGraph } /// - public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode, BitmapBlendingMode bitmapBlendingMode) + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { var next = NextDrawAs(); - if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode, bitmapBlendingMode)) + if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)) { - Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode, bitmapBlendingMode)); + Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)); } else { @@ -179,6 +179,7 @@ namespace Avalonia.Rendering.SceneGraph } } + public void Custom(ICustomDrawOperation custom) { var next = NextDrawAs(); @@ -253,6 +254,21 @@ namespace Avalonia.Rendering.SceneGraph } } + /// + public void PopBitmapBlendMode() + { + var next = NextDrawAs(); + + if (next == null || !next.Item.Equals(null)) + { + Add(new BitmapBlendModeNode()); + } + else + { + ++_drawOperationindex; + } + } + /// public void PopOpacity() { @@ -358,6 +374,21 @@ namespace Avalonia.Rendering.SceneGraph } } + /// + public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) + { + var next = NextDrawAs(); + + if (next == null || !next.Item.Equals(blendingMode)) + { + Add(new BitmapBlendModeNode(blendingMode)); + } + else + { + ++_drawOperationindex; + } + } + public readonly struct UpdateState : IDisposable { public UpdateState( diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index c50601120f..d3da19d8c9 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -18,7 +18,7 @@ namespace Avalonia.Rendering.SceneGraph /// The source rect. /// The destination rect. /// The bitmap interpolation mode. - public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode, BitmapBlendingMode bitmapBlendingMode) + public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) : base(destRect, transform) { Transform = transform; @@ -27,7 +27,6 @@ namespace Avalonia.Rendering.SceneGraph SourceRect = sourceRect; DestRect = destRect; BitmapInterpolationMode = bitmapInterpolationMode; - BitmapBlendingMode = bitmapBlendingMode; SourceVersion = Source.Item.Version; } @@ -91,7 +90,7 @@ namespace Avalonia.Rendering.SceneGraph /// The properties of the other draw operation are passed in as arguments to prevent /// allocation of a not-yet-constructed draw operation object. /// - public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode, BitmapBlendingMode bitmapBlendingMode) + public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { return transform == Transform && Equals(source.Item, Source.Item) && @@ -99,15 +98,14 @@ namespace Avalonia.Rendering.SceneGraph opacity == Opacity && sourceRect == SourceRect && destRect == DestRect && - bitmapInterpolationMode == BitmapInterpolationMode && - bitmapBlendingMode == BitmapBlendingMode; + bitmapInterpolationMode == BitmapInterpolationMode; } /// public override void Render(IDrawingContextImpl context) { context.Transform = Transform; - context.DrawBitmap(Source, Opacity, SourceRect, DestRect, BitmapInterpolationMode, BitmapBlendingMode); + context.DrawBitmap(Source, Opacity, SourceRect, DestRect, BitmapInterpolationMode); } /// From db70c9f94c4027e5919992f1666bbdb8e254f5a2 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 22:26:45 +0800 Subject: [PATCH 024/129] apicompat stuff --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index e781cb1d3e..0be3bedbd5 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -1,9 +1,8 @@ Compat issues with assembly Avalonia.Visuals: -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawBitmap(Avalonia.Utilities.IRef, System.Double, Avalonia.Rect, Avalonia.Rect, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the contract but not in the implementation. -MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawBitmap(Avalonia.Utilities.IRef, System.Double, Avalonia.Rect, Avalonia.Rect, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawBitmap(Avalonia.Utilities.IRef, System.Double, Avalonia.Rect, Avalonia.Rect, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode, Avalonia.Visuals.Media.Imaging.BitmapBlendingMode)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PopBitmapBlendMode()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PushBitmapBlendMode(Avalonia.Visuals.Media.Imaging.BitmapBlendingMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract. -Total Issues: 7 +Total Issues: 6 From f8596a60ea8965bb6c08e516c03b2c4f996a4c03 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 18 Mar 2021 22:49:01 +0800 Subject: [PATCH 025/129] add unimplemented stuff on MockRenderInterface.cs --- .../VisualTree/MockRenderInterface.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 02236e4107..bb6301365a 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -112,6 +112,8 @@ namespace Avalonia.Visuals.UnitTests.VisualTree } } + public double ContourLength { get; } + public IStreamGeometryImpl Clone() { return this; @@ -151,6 +153,16 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } + public bool TryGetPointAtDistance(double distance, out Point point) + { + throw new NotImplementedException(); + } + + public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent) + { + throw new NotImplementedException(); + } + class MockStreamGeometryContext : IStreamGeometryContextImpl { private List points = new List(); From be0cdd157b343b42ca9746f47e9337dd7bc3a20c Mon Sep 17 00:00:00 2001 From: amwx Date: Thu, 18 Mar 2021 21:27:22 -0500 Subject: [PATCH 026/129] Add ShowMode to FlyoutBase --- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 89 ++++++++++++++++++- .../Flyouts/FlyoutShowMode.cs | 24 +++++ 2 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 src/Avalonia.Controls/Flyouts/FlyoutShowMode.cs diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index ca267dc5e9..85e460a25f 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -1,7 +1,7 @@ using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Text; +using Avalonia.Input; +using Avalonia.Input.Raw; using Avalonia.Layout; #nullable enable @@ -20,12 +20,19 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty PlacementProperty = AvaloniaProperty.Register(nameof(Placement)); + public static readonly DirectProperty ShowModeProperty = + AvaloniaProperty.RegisterDirect(nameof(ShowMode), + x => x.ShowMode, (x, v) => x.ShowMode = v); + public static readonly AttachedProperty AttachedFlyoutProperty = AvaloniaProperty.RegisterAttached("AttachedFlyout", null); private bool _isOpen; private Control? _target; protected Popup? _popup; + private FlyoutShowMode _showMode = FlyoutShowMode.Standard; + Rect? enlargedPopupRect; + IDisposable? transientDisposable; public bool IsOpen { @@ -39,6 +46,12 @@ namespace Avalonia.Controls.Primitives set => SetValue(PlacementProperty, value); } + public FlyoutShowMode ShowMode + { + get => _showMode; + set => SetAndRaise(ShowModeProperty, ref _showMode, value); + } + public Control? Target { get => _target; @@ -96,7 +109,11 @@ namespace Avalonia.Controls.Primitives } IsOpen = _popup.IsOpen = false; - + + // Ensure this isn't active + transientDisposable?.Dispose(); + transientDisposable = null; + OnClosed(); } @@ -142,6 +159,72 @@ namespace Avalonia.Controls.Primitives PositionPopup(showAtPointer); IsOpen = _popup.IsOpen = true; OnOpened(); + + if (ShowMode == FlyoutShowMode.Standard) + { + // Try and focus content inside Flyout + if (_popup.Child.Focusable) + { + FocusManager.Instance?.Focus(_popup.Child); + } + else + { + var nextFocus = KeyboardNavigationHandler.GetNext(_popup.Child, NavigationDirection.Next); + if (nextFocus != null) + { + FocusManager.Instance?.Focus(nextFocus); + } + } + } + else if (ShowMode == FlyoutShowMode.TransientWithDismissOnPointerMoveAway) + { + transientDisposable = InputManager.Instance?.Process.Subscribe(HandleTransientDismiss); + } + } + + private void HandleTransientDismiss(RawInputEventArgs args) + { + if (args is RawPointerEventArgs pArgs && pArgs.Type == RawPointerEventType.Move) + { + if (enlargedPopupRect == null) + { + if (_popup?.Host is PopupRoot root) + { + var tmp = root.Bounds.Inflate(100); + var scPt = root.PointToScreen(tmp.TopLeft); + enlargedPopupRect = new Rect(scPt.X, scPt.Y, tmp.Width, tmp.Height); + } + else if (_popup?.Host is OverlayPopupHost host) + { + // Overlay popups are in Window client coordinates, just use that + enlargedPopupRect = host.Bounds.Inflate(100); + } + + return; + } + + if (_popup?.Host is PopupRoot) + { + var pt = pArgs.Root.PointToScreen(pArgs.Position); + if (!enlargedPopupRect?.Contains(new Point(pt.X, pt.Y)) ?? false) + { + Hide(false); + enlargedPopupRect = null; + transientDisposable?.Dispose(); + transientDisposable = null; + } + } + else if (_popup?.Host is OverlayPopupHost) + { + if (!enlargedPopupRect?.Contains(pArgs.Position) ?? false) + { + Hide(false); + enlargedPopupRect = null; + transientDisposable?.Dispose(); + transientDisposable = null; + } + } + } } protected virtual void OnOpening() diff --git a/src/Avalonia.Controls/Flyouts/FlyoutShowMode.cs b/src/Avalonia.Controls/Flyouts/FlyoutShowMode.cs new file mode 100644 index 0000000000..cb5b5f00c0 --- /dev/null +++ b/src/Avalonia.Controls/Flyouts/FlyoutShowMode.cs @@ -0,0 +1,24 @@ +namespace Avalonia.Controls +{ + // Note: FlyoutShowMode.Auto was removed. MS Docs just say: + // The show mode is determined automatically based on the method used to show the flyout. + // and AFAICT Flyouts generally open with "Standard" behavior + + public enum FlyoutShowMode + { + /// + /// Behavior is typical of a flyout shown reactively, like a context menu. The open flyout takes focus. For a CommandBarFlyout, it opens in it's expanded state. + /// + Standard, + + /// + /// Behavior is typical of a flyout shown proactively. The open flyout does not take focus. For a CommandBarFlyout, it opens in it's collapsed state. + /// + Transient, + + /// + /// The flyout exhibits Transient behavior while the cursor is close to it, but is dismissed when the cursor moves away. + /// + TransientWithDismissOnPointerMoveAway + } +} From 4bd187d362ada2fe60b8010fb1525c47e5173bae Mon Sep 17 00:00:00 2001 From: amwx Date: Thu, 18 Mar 2021 21:28:40 -0500 Subject: [PATCH 027/129] Add MenuFlyout --- src/Avalonia.Controls/Flyouts/MenuFlyout.cs | 87 +++++++++++++++++++ .../Flyouts/MenuFlyoutPresenter.cs | 36 ++++++++ .../Accents/FluentControlResourcesDark.xaml | 5 ++ .../Accents/FluentControlResourcesLight.xaml | 5 ++ .../Controls/FluentControls.xaml | 2 + .../Controls/FlyoutPresenter.xaml | 5 ++ .../Controls/MenuFlyoutPresenter.xaml | 36 ++++++++ 7 files changed, 176 insertions(+) create mode 100644 src/Avalonia.Controls/Flyouts/MenuFlyout.cs create mode 100644 src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs create mode 100644 src/Avalonia.Themes.Fluent/Controls/MenuFlyoutPresenter.xaml diff --git a/src/Avalonia.Controls/Flyouts/MenuFlyout.cs b/src/Avalonia.Controls/Flyouts/MenuFlyout.cs new file mode 100644 index 0000000000..8eea5f211d --- /dev/null +++ b/src/Avalonia.Controls/Flyouts/MenuFlyout.cs @@ -0,0 +1,87 @@ +using System.Collections; +using System.Collections.Specialized; +using Avalonia.Collections; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Input; +using Avalonia.Metadata; +using Avalonia.Styling; + +#nullable enable + +namespace Avalonia.Controls +{ + public class MenuFlyout : FlyoutBase + { + public MenuFlyout() + { + _items = new AvaloniaList(); + } + + public static readonly DirectProperty ItemsProperty = + ItemsControl.ItemsProperty.AddOwner(x => x.Items, + (x, v) => x.Items = v); + + public static readonly DirectProperty ItemTemplateProperty = + AvaloniaProperty.RegisterDirect(nameof(ItemTemplate), + x => x.ItemTemplate, (x, v) => x.ItemTemplate = v); + + public Styles? FlyoutPresenterStyle + { + get + { + if (_styles == null) + { + _styles = new Styles(); + _styles.CollectionChanged += OnMenuFlyoutPresenterStyleChanged; + } + + return _styles; + } + } + + [Content] + public IEnumerable Items + { + get => _items; + set => SetAndRaise(ItemsProperty, ref _items, value); + } + + public IDataTemplate? ItemTemplate + { + get => _itemTemplate; + set => SetAndRaise(ItemTemplateProperty, ref _itemTemplate, value); + } + + private Styles? _styles; + private bool _stylesDirty = true; + private IEnumerable _items; + private IDataTemplate? _itemTemplate; + + protected override Control CreatePresenter() + { + return new MenuFlyoutPresenter + { + [!ItemsControl.ItemsProperty] = this[!ItemsProperty], + [!ItemsControl.ItemTemplateProperty] = this[!ItemTemplateProperty] + }; + } + + protected override void OnOpened() + { + if (_styles != null && _stylesDirty) + { + // Presenter for flyout generally shouldn't be public, so + // we should be ok to just reset the styles + _popup.Child.Styles.Clear(); + _popup.Child.Styles.Add(_styles); + } + base.OnOpened(); + } + + private void OnMenuFlyoutPresenterStyleChanged(object sender, NotifyCollectionChangedEventArgs e) + { + _stylesDirty = true; + } + } +} diff --git a/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs b/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs new file mode 100644 index 0000000000..7e59d824fa --- /dev/null +++ b/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs @@ -0,0 +1,36 @@ +using Avalonia.Controls.Generators; +using Avalonia.Controls.Platform; +using Avalonia.Controls.Primitives; +using Avalonia.LogicalTree; + +namespace Avalonia.Controls +{ + public class MenuFlyoutPresenter : MenuBase + { + public MenuFlyoutPresenter() + :base(new DefaultMenuInteractionHandler(true)) + { + + } + + public override void Close() + { + // DefaultMenuInteractionHandler calls this + var host = this.FindLogicalAncestorOfType(); + if (host != null) + { + host.IsOpen = false; + } + } + + public override void Open() + { + //Ignore + } + + protected override IItemContainerGenerator CreateItemContainerGenerator() + { + return new MenuItemContainerGenerator(this); + } + } +} diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index abc02a6371..ae1fe42031 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -809,5 +809,10 @@ 1 + + + + + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index 6a2a04f732..5af24f57a8 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -807,5 +807,10 @@ 1 + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 0acb8f4f6e..16e05ffdfd 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -59,4 +59,6 @@ + + diff --git a/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml b/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml index 2be49d007b..3a8df060fa 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml @@ -1,4 +1,9 @@ + + 1 + 0 + + + + From b1b9f6f7d2ca93a995a04c583f33e72e53e0a321 Mon Sep 17 00:00:00 2001 From: amwx Date: Thu, 18 Mar 2021 22:39:10 -0500 Subject: [PATCH 028/129] ContextFlyouts --- src/Avalonia.Controls/Control.cs | 17 ++++++++++ src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 36 +++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 4aab92c428..ebfe98a175 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -37,9 +37,16 @@ namespace Avalonia.Controls /// /// Defines the property. /// + [Obsolete("Prefer ContextFlyout")] public static readonly StyledProperty ContextMenuProperty = AvaloniaProperty.Register(nameof(ContextMenu)); + /// + /// Defines the property + /// + public static readonly StyledProperty ContextFlyoutProperty = + AvaloniaProperty.Register(nameof(ContextFlyout)); + /// /// Event raised when an element wishes to be scrolled into view. /// @@ -70,12 +77,22 @@ namespace Avalonia.Controls /// /// Gets or sets a context menu to the control. /// + [Obsolete("Prefer ContextFlyout")] public ContextMenu? ContextMenu { get => GetValue(ContextMenuProperty); set => SetValue(ContextMenuProperty, value); } + /// + /// Gets or sets a context flyout to the control + /// + public FlyoutBase? ContextFlyout + { + get => GetValue(ContextFlyoutProperty); + set => SetValue(ContextFlyoutProperty, value); + } + /// /// Gets or sets a user-defined object attached to the control. /// diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 85e460a25f..f3f4febbb0 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -10,6 +10,11 @@ namespace Avalonia.Controls.Primitives { public abstract class FlyoutBase : AvaloniaObject { + static FlyoutBase() + { + Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged); + } + private static readonly DirectProperty IsOpenProperty = AvaloniaProperty.RegisterDirect(nameof(IsOpen), x => x.IsOpen); @@ -383,5 +388,36 @@ namespace Avalonia.Controls.Primitives break; } } + + private static void OnContextFlyoutPropertyChanged(AvaloniaPropertyChangedEventArgs args) + { + if (args.Sender is Control c) + { + if (args.OldValue.GetValueOrDefault() is FlyoutBase) + { + c.PointerReleased -= OnControlWithContextFlyoutPointerReleased; + } + if (args.NewValue.GetValueOrDefault() is FlyoutBase) + { + c.PointerReleased += OnControlWithContextFlyoutPointerReleased; + } + } + } + + private static void OnControlWithContextFlyoutPointerReleased(object sender, PointerReleasedEventArgs e) + { + if (sender is Control c) + { + if (e.InitialPressMouseButton == MouseButton.Right && + e.GetCurrentPoint(c).Properties.PointerUpdateKind == PointerUpdateKind.RightButtonReleased) + { + if (c.ContextFlyout != null) + { + c.ContextFlyout.ShowAt(c, true); + } + } + } + + } } } From 4ef47bfdb05d04e5f4be199da7c3e47893dfe5fa Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 19 Mar 2021 12:14:03 +0800 Subject: [PATCH 029/129] Add TryGetSegment --- .../HeadlessPlatformRenderInterface.cs | 6 +++++ .../Platform/IGeometryImpl.cs | 13 +++++++++++ src/Skia/Avalonia.Skia/GeometryImpl.cs | 22 +++++++++++++++++++ .../Avalonia.Direct2D1/Media/GeometryImpl.cs | 9 ++++++++ .../MockStreamGeometryImpl.cs | 6 +++++ .../VisualTree/MockRenderInterface.cs | 5 +++++ 6 files changed, 61 insertions(+) diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 48c46f50a1..1563c66a96 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -142,6 +142,12 @@ namespace Avalonia.Headless tangent = new Point(); return false; } + + public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + { + segmentGeometry = null; + return false; + } } class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl diff --git a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs index c61e6c6112..33092ca2b4 100644 --- a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs @@ -73,5 +73,18 @@ namespace Avalonia.Platform /// The tangent in the specified distance. /// If there's valid point and tangent at the specified distance. bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent); + + /// + /// Attempts to get the corresponding path segment + /// given by the two distances specified. + /// Imagine it like snipping a part of the current + /// geometry. + /// + /// The contour distance to start snipping from. + /// The contour distance to stop snipping to. + /// If ture, the resulting snipped path will start with a BeginFigure call. + /// The resulting snipped path. + /// If the snipping operation is successful. + bool TryGetSegment (float startDistance, float stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry); } } diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 8b4ba66cf6..0884399f4e 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -138,6 +138,28 @@ namespace Avalonia.Skia return res; } + public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + { + if (_pathCache.CachePathMeasure is null) + { + segmentGeometry = null; + return false; + } + + segmentGeometry = null; + + SKPath _skPathSegment = null; + + var res = _pathCache.CachePathMeasure.GetSegment(startDistance, stopDistance, _skPathSegment, startOnBeginFigure); + + if (res) + { + segmentGeometry = new StreamGeometryImpl(_skPathSegment); + } + + return res; + } + /// /// Invalidate all caches. Call after chaining path contents. /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs index 81c21c8648..e03c049ec2 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs @@ -82,6 +82,15 @@ namespace Avalonia.Direct2D1.Media return false; } + public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + { + // Direct2D doesnt have this too sadly. + Logger.TryGet(LogEventLevel.Warning, LogArea.Visual)?.Log(this, "TryGetSegment is not available in Direct2D."); + + segmentGeometry = null; + return false; + } + protected virtual Geometry GetSourceGeometry() => Geometry; } } diff --git a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs index 4e66eaeb24..dfe2021cc2 100644 --- a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs +++ b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs @@ -84,6 +84,12 @@ namespace Avalonia.UnitTests return false; } + public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + { + segmentGeometry = null; + return false; + } + class MockStreamGeometryContext : IStreamGeometryContextImpl { private List points = new List(); diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index bb6301365a..666ac75c4b 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -163,6 +163,11 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } + public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + { + throw new NotImplementedException(); + } + class MockStreamGeometryContext : IStreamGeometryContextImpl { private List points = new List(); From edf18a44370cd93c6fded0778ae06ba0e7883608 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 19 Mar 2021 12:22:37 +0800 Subject: [PATCH 030/129] api compat --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 0be3bedbd5..6c7b3efaf8 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -5,4 +5,5 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avaloni InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract. -Total Issues: 6 +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Single, System.Single, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract. +Total Issues: 7 From 58e71b6fa9c73eaf04441e01ee7aa58420c39c69 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 19 Mar 2021 14:46:07 +0800 Subject: [PATCH 031/129] fix init --- src/Skia/Avalonia.Skia/GeometryImpl.cs | 88 ++++++++++++++++---------- 1 file changed, 56 insertions(+), 32 deletions(-) diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 0884399f4e..5042e6ebd2 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -11,12 +11,27 @@ namespace Avalonia.Skia internal abstract class GeometryImpl : IGeometryImpl { private PathCache _pathCache; - + private SKPathMeasure _pathMeasureCache; + /// public abstract Rect Bounds { get; } - + /// - public double ContourLength => _pathCache.CachePathMeasure?.Length ?? 0; + public double ContourLength + { + get + { + if (EffectivePath is null) + return 0; + + if (_pathMeasureCache is null) + { + _pathMeasureCache = new SKPathMeasure(EffectivePath); + } + + return (double)_pathMeasureCache?.Length; + } + } public abstract SKPath EffectivePath { get; } @@ -34,12 +49,12 @@ namespace Avalonia.Skia // Usually this function is being called with same stroke width per path, so this saves a lot of Skia traffic. var strokeWidth = (float)(pen?.Thickness ?? 0); - + if (!_pathCache.HasCacheFor(strokeWidth)) { UpdatePathCache(strokeWidth); } - + return PathContainsCore(_pathCache.CachedStrokePath, point); } @@ -62,7 +77,7 @@ namespace Avalonia.Skia { paint.IsStroke = true; paint.StrokeWidth = strokeWidth; - + paint.GetFillPath(EffectivePath, strokePath); _pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect()); @@ -78,13 +93,13 @@ namespace Avalonia.Skia /// True, if point is contained in a path. private static bool PathContainsCore(SKPath path, Point point) { - return path.Contains((float)point.X, (float)point.Y); + return path.Contains((float)point.X, (float)point.Y); } /// public IGeometryImpl Intersect(IGeometryImpl geometry) { - var result = EffectivePath.Op(((GeometryImpl) geometry).EffectivePath, SKPathOp.Intersect); + var result = EffectivePath.Op(((GeometryImpl)geometry).EffectivePath, SKPathOp.Intersect); return result == null ? null : new StreamGeometryImpl(result); } @@ -93,70 +108,86 @@ namespace Avalonia.Skia public Rect GetRenderBounds(IPen pen) { var strokeWidth = (float)(pen?.Thickness ?? 0); - + if (!_pathCache.HasCacheFor(strokeWidth)) { UpdatePathCache(strokeWidth); } - + return _pathCache.CachedGeometryRenderBounds; } - + /// public ITransformedGeometryImpl WithTransform(Matrix transform) { return new TransformedGeometryImpl(this, transform); } - + /// public bool TryGetPointAtDistance(double distance, out Point point) { - if (_pathCache.CachePathMeasure is null) + if (EffectivePath is null) { point = new Point(); return false; } - - var res = _pathCache.CachePathMeasure.GetPosition((float)distance, out var skPoint); + + if (_pathMeasureCache is null) + { + _pathMeasureCache = new SKPathMeasure(EffectivePath); + } + + var res = _pathMeasureCache.GetPosition((float)distance, out var skPoint); point = new Point(skPoint.X, skPoint.Y); return res; } - + /// public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent) { - if (_pathCache.CachePathMeasure is null) + if (EffectivePath is null) { point = new Point(); tangent = new Point(); return false; } - - var res = _pathCache.CachePathMeasure.GetPositionAndTangent((float)distance, out var skPoint, out var skTangent); + + if (_pathMeasureCache is null) + { + _pathMeasureCache = new SKPathMeasure(EffectivePath); + } + + var res = _pathMeasureCache.GetPositionAndTangent((float)distance, out var skPoint, out var skTangent); point = new Point(skPoint.X, skPoint.Y); tangent = new Point(skTangent.X, skTangent.Y); return res; } - public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, + out IGeometryImpl segmentGeometry) { - if (_pathCache.CachePathMeasure is null) + if (EffectivePath is null) { segmentGeometry = null; return false; } + + if (_pathMeasureCache is null) + { + _pathMeasureCache = new SKPathMeasure(EffectivePath); + } segmentGeometry = null; SKPath _skPathSegment = null; - var res = _pathCache.CachePathMeasure.GetSegment(startDistance, stopDistance, _skPathSegment, startOnBeginFigure); + var res = _pathMeasureCache.GetSegment(startDistance, stopDistance, _skPathSegment, startOnBeginFigure); if (res) { segmentGeometry = new StreamGeometryImpl(_skPathSegment); } - + return res; } @@ -176,7 +207,7 @@ namespace Avalonia.Skia /// Tolerance for two stroke widths to be deemed equal /// public const float Tolerance = float.Epsilon; - + /// /// Cached contour path. /// @@ -186,11 +217,6 @@ namespace Avalonia.Skia /// Cached geometry render bounds. /// public Rect CachedGeometryRenderBounds { get; private set; } - - /// - /// Cached path measurement helper. - /// - public SKPathMeasure CachePathMeasure { get; private set; } /// /// Is cached valid for given stroke width. @@ -216,8 +242,7 @@ namespace Avalonia.Skia } CachedStrokePath = path; - CachedGeometryRenderBounds = geometryRenderBounds; - CachePathMeasure = new SKPathMeasure(path); + CachedGeometryRenderBounds = geometryRenderBounds; _cachedStrokeWidth = strokeWidth; } @@ -227,7 +252,6 @@ namespace Avalonia.Skia public void Invalidate() { CachedStrokePath?.Dispose(); - CachePathMeasure?.Dispose(); CachedGeometryRenderBounds = Rect.Empty; _cachedStrokeWidth = default(float); } From 74a74acf0ced08ea2380109e6fc1987900e8c1a1 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 19 Mar 2021 19:16:14 +0800 Subject: [PATCH 032/129] set from float to double --- src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs | 2 +- src/Avalonia.Visuals/Platform/IGeometryImpl.cs | 2 +- src/Skia/Avalonia.Skia/GeometryImpl.cs | 4 ++-- src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs | 2 +- tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs | 2 +- .../VisualTree/MockRenderInterface.cs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 1563c66a96..62cac378d7 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -143,7 +143,7 @@ namespace Avalonia.Headless return false; } - public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) { segmentGeometry = null; return false; diff --git a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs index 33092ca2b4..9dfaab9575 100644 --- a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs @@ -85,6 +85,6 @@ namespace Avalonia.Platform /// If ture, the resulting snipped path will start with a BeginFigure call. /// The resulting snipped path. /// If the snipping operation is successful. - bool TryGetSegment (float startDistance, float stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry); + bool TryGetSegment (double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry); } } diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 5042e6ebd2..1511640c56 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -163,7 +163,7 @@ namespace Avalonia.Skia return res; } - public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, + public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) { if (EffectivePath is null) @@ -181,7 +181,7 @@ namespace Avalonia.Skia SKPath _skPathSegment = null; - var res = _pathMeasureCache.GetSegment(startDistance, stopDistance, _skPathSegment, startOnBeginFigure); + var res = _pathMeasureCache.GetSegment((float)startDistance, (float)stopDistance, _skPathSegment, startOnBeginFigure); if (res) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs index e03c049ec2..ec88347a17 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs @@ -82,7 +82,7 @@ namespace Avalonia.Direct2D1.Media return false; } - public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) { // Direct2D doesnt have this too sadly. Logger.TryGet(LogEventLevel.Warning, LogArea.Visual)?.Log(this, "TryGetSegment is not available in Direct2D."); diff --git a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs index dfe2021cc2..864e2efbaf 100644 --- a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs +++ b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs @@ -84,7 +84,7 @@ namespace Avalonia.UnitTests return false; } - public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) { segmentGeometry = null; return false; diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 666ac75c4b..6d0683e699 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -163,7 +163,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } - public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) { throw new NotImplementedException(); } From dd33693eb738bf1baa6acd58a07bca5ecec9a822 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 19 Mar 2021 19:32:30 +0800 Subject: [PATCH 033/129] fix getsegment --- src/Skia/Avalonia.Skia/GeometryImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 1511640c56..e5794c1a64 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -179,7 +179,7 @@ namespace Avalonia.Skia segmentGeometry = null; - SKPath _skPathSegment = null; + var _skPathSegment = new SKPath(); var res = _pathMeasureCache.GetSegment((float)startDistance, (float)stopDistance, _skPathSegment, startOnBeginFigure); From 9291a8f74a79e2f651085bfdc241d8582954e971 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 19 Mar 2021 19:37:29 +0800 Subject: [PATCH 034/129] extend DrawingContext.cs to accept IGeometryImpl in DrawGeometry method --- src/Avalonia.Visuals/Media/DrawingContext.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index ae4c927ae2..4e3dc8699c 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -121,12 +121,23 @@ namespace Avalonia.Media /// The stroke pen. /// The geometry. public void DrawGeometry(IBrush brush, IPen pen, Geometry geometry) + { + DrawGeometry(brush, pen, geometry.PlatformImpl); + } + + /// + /// Draws a geometry. + /// + /// The fill brush. + /// The stroke pen. + /// The geometry. + public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) { Contract.Requires(geometry != null); if (brush != null || PenIsVisible(pen)) { - PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl); + PlatformImpl.DrawGeometry(brush, pen, geometry); } } From 9ebdef12a89d38f432acbb8898126786dac9fc77 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 19 Mar 2021 19:37:50 +0800 Subject: [PATCH 035/129] add path measure example --- .../RenderDemo/Pages/PathMeasurementPage.cs | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 samples/RenderDemo/Pages/PathMeasurementPage.cs diff --git a/samples/RenderDemo/Pages/PathMeasurementPage.cs b/samples/RenderDemo/Pages/PathMeasurementPage.cs new file mode 100644 index 0000000000..9f033e961f --- /dev/null +++ b/samples/RenderDemo/Pages/PathMeasurementPage.cs @@ -0,0 +1,119 @@ +using System.Diagnostics; +using System.Drawing.Drawing2D; +using Avalonia; +using Avalonia.Controls; +using Avalonia.LogicalTree; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Media.Immutable; +using Avalonia.Threading; +using Avalonia.Visuals.Media.Imaging; + +namespace RenderDemo.Pages +{ + public class PathMeasurementPage : Control + { + private RenderTargetBitmap _bitmap; + + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + _bitmap = new RenderTargetBitmap(new PixelSize(500, 500), new Vector(96, 96)); + base.OnAttachedToLogicalTree(e); + } + + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + _bitmap.Dispose(); + _bitmap = null; + base.OnDetachedFromLogicalTree(e); + } + + readonly Stopwatch _st = Stopwatch.StartNew(); + + + readonly IPen strokePen = new ImmutablePen(Brushes.DarkBlue, 10d, null, PenLineCap.Round, PenLineJoin.Round); + readonly IPen strokePen1 = new ImmutablePen(Brushes.Purple, 10d, null, PenLineCap.Round, PenLineJoin.Round); + readonly IPen strokePen2 = new ImmutablePen(Brushes.Green, 10d, null, PenLineCap.Round, PenLineJoin.Round); + readonly IPen strokePen3 = new ImmutablePen(Brushes.LightBlue, 10d, null, PenLineCap.Round, PenLineJoin.Round); + + public override void Render(DrawingContext context) + { + using (var ctxi = _bitmap.CreateDrawingContext(null)) + using (var ctx = new DrawingContext(ctxi, false)) + { + ctxi.Clear(default); + + var x = new PathGeometry(); + + using (var xsad = x.Open()) + { + xsad.BeginFigure(new Point(20, 20), false); + xsad.LineTo(new Point(400, 50)); + xsad.LineTo(new Point(80, 100)); + xsad.LineTo(new Point(300, 150)); + xsad.EndFigure(false); + } + + ctx.DrawGeometry(null, strokePen, x); + + + var length = x.PlatformImpl.ContourLength; + + + if (x.PlatformImpl.TryGetSegment(length * 0.05, length * 0.2, true, out var dst1)) + ctx.DrawGeometry(null, strokePen1, (Geometry)dst1); + + if (x.PlatformImpl.TryGetSegment(length * 0.2, length * 0.8, true, out var dst2)) + ctx.DrawGeometry(null, strokePen2, (Geometry)dst2); + + if (x.PlatformImpl.TryGetSegment(length * 0.8, length * 0.95, true, out var dst3)) + ctx.DrawGeometry(null, strokePen3, (Geometry)dst3); + + /* + * paint.Style = SKPaintStyle.Stroke;z + paint.StrokeWidth = 10;z + paint.IsAntialias = true;z + paint.StrokeCap = SKStrokeCap.Round;z + paint.StrokeJoin = SKStrokeJoin.Round;x + + path.MoveTo(20, 20); + path.LineTo(400, 50); + path.LineTo(80, 100); + path.LineTo(300, 150); + + paint.Color = SampleMedia.Colors.XamarinDarkBlue; + canvas.DrawPath(path, paint); + + using (var measure = new SKPathMeasure(path, false)) + using (var dst = new SKPath()) + { + var length = measure.Length; + + dst.Reset(); + measure.GetSegment(length * 0.05f, length * 0.2f, dst, true); + paint.Color = SampleMedia.Colors.XamarinPurple; + canvas.DrawPath(dst, paint); + + dst.Reset(); + measure.GetSegment( dst, true); + paint.Color = SampleMedia.Colors.XamarinGreen; + canvas.DrawPath(dst, paint); + + dst.Reset(); + measure.GetSegment(length * 0.8f, length * 0.95f, dst, true); + paint.Color = SampleMedia.Colors.XamarinLightBlue; + canvas.DrawPath(dst, paint); + } + */ + // + // ctx.FillRectangle(Brushes.Fuchsia, new Rect(50, 50, 100, 100)); + } + + context.DrawImage(_bitmap, + new Rect(0, 0, 500, 500), + new Rect(0, 0, 500, 500)); + Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); + base.Render(context); + } + } +} From b01dc1932a80d38767b40996e173e7a25b925698 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 19 Mar 2021 20:01:56 +0800 Subject: [PATCH 036/129] add path measure example --- .../RenderDemo/Pages/PathMeasurementPage.cs | 84 ++++++------------- 1 file changed, 25 insertions(+), 59 deletions(-) diff --git a/samples/RenderDemo/Pages/PathMeasurementPage.cs b/samples/RenderDemo/Pages/PathMeasurementPage.cs index 9f033e961f..6373e39ac5 100644 --- a/samples/RenderDemo/Pages/PathMeasurementPage.cs +++ b/samples/RenderDemo/Pages/PathMeasurementPage.cs @@ -1,5 +1,7 @@ +using System; using System.Diagnostics; using System.Drawing.Drawing2D; +using System.Security.Cryptography; using Avalonia; using Avalonia.Controls; using Avalonia.LogicalTree; @@ -19,6 +21,7 @@ namespace RenderDemo.Pages { _bitmap = new RenderTargetBitmap(new PixelSize(500, 500), new Vector(96, 96)); base.OnAttachedToLogicalTree(e); + AffectsRender(BoundsProperty); } protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) @@ -28,91 +31,54 @@ namespace RenderDemo.Pages base.OnDetachedFromLogicalTree(e); } - readonly Stopwatch _st = Stopwatch.StartNew(); - - readonly IPen strokePen = new ImmutablePen(Brushes.DarkBlue, 10d, null, PenLineCap.Round, PenLineJoin.Round); readonly IPen strokePen1 = new ImmutablePen(Brushes.Purple, 10d, null, PenLineCap.Round, PenLineJoin.Round); readonly IPen strokePen2 = new ImmutablePen(Brushes.Green, 10d, null, PenLineCap.Round, PenLineJoin.Round); readonly IPen strokePen3 = new ImmutablePen(Brushes.LightBlue, 10d, null, PenLineCap.Round, PenLineJoin.Round); + readonly IPen strokePen4 = new ImmutablePen(Brushes.Red, 1d, null, PenLineCap.Round, PenLineJoin.Round); public override void Render(DrawingContext context) { using (var ctxi = _bitmap.CreateDrawingContext(null)) - using (var ctx = new DrawingContext(ctxi, false)) + using (var bitmapCtx = new DrawingContext(ctxi, false)) { ctxi.Clear(default); - var x = new PathGeometry(); + var basePath = new PathGeometry(); - using (var xsad = x.Open()) + using (var basePathCtx = basePath.Open()) { - xsad.BeginFigure(new Point(20, 20), false); - xsad.LineTo(new Point(400, 50)); - xsad.LineTo(new Point(80, 100)); - xsad.LineTo(new Point(300, 150)); - xsad.EndFigure(false); + basePathCtx.BeginFigure(new Point(20, 20), false); + basePathCtx.LineTo(new Point(400, 50)); + basePathCtx.LineTo(new Point(80, 100)); + basePathCtx.LineTo(new Point(300, 150)); + basePathCtx.EndFigure(false); } - ctx.DrawGeometry(null, strokePen, x); + bitmapCtx.DrawGeometry(null, strokePen, basePath); - var length = x.PlatformImpl.ContourLength; + var length = basePath.PlatformImpl.ContourLength; + if (basePath.PlatformImpl.TryGetSegment(length * 0.05, length * 0.2, true, out var dst1)) + bitmapCtx.DrawGeometry(null, strokePen1, dst1); - if (x.PlatformImpl.TryGetSegment(length * 0.05, length * 0.2, true, out var dst1)) - ctx.DrawGeometry(null, strokePen1, (Geometry)dst1); + if (basePath.PlatformImpl.TryGetSegment(length * 0.2, length * 0.8, true, out var dst2)) + bitmapCtx.DrawGeometry(null, strokePen2, dst2); - if (x.PlatformImpl.TryGetSegment(length * 0.2, length * 0.8, true, out var dst2)) - ctx.DrawGeometry(null, strokePen2, (Geometry)dst2); + if (basePath.PlatformImpl.TryGetSegment(length * 0.8, length * 0.95, true, out var dst3)) + bitmapCtx.DrawGeometry(null, strokePen3, dst3); - if (x.PlatformImpl.TryGetSegment(length * 0.8, length * 0.95, true, out var dst3)) - ctx.DrawGeometry(null, strokePen3, (Geometry)dst3); - - /* - * paint.Style = SKPaintStyle.Stroke;z - paint.StrokeWidth = 10;z - paint.IsAntialias = true;z - paint.StrokeCap = SKStrokeCap.Round;z - paint.StrokeJoin = SKStrokeJoin.Round;x - - path.MoveTo(20, 20); - path.LineTo(400, 50); - path.LineTo(80, 100); - path.LineTo(300, 150); - - paint.Color = SampleMedia.Colors.XamarinDarkBlue; - canvas.DrawPath(path, paint); - - using (var measure = new SKPathMeasure(path, false)) - using (var dst = new SKPath()) - { - var length = measure.Length; - - dst.Reset(); - measure.GetSegment(length * 0.05f, length * 0.2f, dst, true); - paint.Color = SampleMedia.Colors.XamarinPurple; - canvas.DrawPath(dst, paint); - - dst.Reset(); - measure.GetSegment( dst, true); - paint.Color = SampleMedia.Colors.XamarinGreen; - canvas.DrawPath(dst, paint); - - dst.Reset(); - measure.GetSegment(length * 0.8f, length * 0.95f, dst, true); - paint.Color = SampleMedia.Colors.XamarinLightBlue; - canvas.DrawPath(dst, paint); - } - */ - // - // ctx.FillRectangle(Brushes.Fuchsia, new Rect(50, 50, 100, 100)); + var pathBounds = basePath.GetRenderBounds(strokePen); + + bitmapCtx.DrawRectangle(null, strokePen4, pathBounds); } + context.DrawImage(_bitmap, new Rect(0, 0, 500, 500), new Rect(0, 0, 500, 500)); - Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); + base.Render(context); } } From 8b8f4c6669adf58b8037992d8ebeaa8f85ba9c15 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 19 Mar 2021 20:33:52 +0800 Subject: [PATCH 037/129] add path measure example --- samples/RenderDemo/MainWindow.xaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml index 93fbe5e412..aa165d13f7 100644 --- a/samples/RenderDemo/MainWindow.xaml +++ b/samples/RenderDemo/MainWindow.xaml @@ -57,6 +57,9 @@ + + + From 149f1a09587f41de335aa56cc6a05c84121568d3 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 19 Mar 2021 20:34:04 +0800 Subject: [PATCH 038/129] api compat again --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 6c7b3efaf8..805d1955ea 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -5,5 +5,5 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avaloni InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Single, System.Single, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Double, System.Double, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract. Total Issues: 7 From 780dd1bc74ca87113cce57295a7ef343c25eed78 Mon Sep 17 00:00:00 2001 From: amwx Date: Fri, 19 Mar 2021 17:23:35 -0500 Subject: [PATCH 039/129] Show ContextFlyout on right click --- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index f3f4febbb0..5f00c1eafe 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -389,15 +389,15 @@ namespace Avalonia.Controls.Primitives } } - private static void OnContextFlyoutPropertyChanged(AvaloniaPropertyChangedEventArgs args) + private static void OnContextFlyoutPropertyChanged(AvaloniaPropertyChangedEventArgs args) { if (args.Sender is Control c) { - if (args.OldValue.GetValueOrDefault() is FlyoutBase) + if (args.OldValue is FlyoutBase) { c.PointerReleased -= OnControlWithContextFlyoutPointerReleased; } - if (args.NewValue.GetValueOrDefault() is FlyoutBase) + if (args.NewValue is FlyoutBase) { c.PointerReleased += OnControlWithContextFlyoutPointerReleased; } From a7472af1305a5be4b62957db6a8e2ef629512960 Mon Sep 17 00:00:00 2001 From: amwx Date: Fri, 19 Mar 2021 17:24:16 -0500 Subject: [PATCH 040/129] Use Classes instead of Styles --- src/Avalonia.Controls/Flyouts/Flyout.cs | 39 ++++++++------------- src/Avalonia.Controls/Flyouts/MenuFlyout.cs | 39 ++++++++------------- 2 files changed, 28 insertions(+), 50 deletions(-) diff --git a/src/Avalonia.Controls/Flyouts/Flyout.cs b/src/Avalonia.Controls/Flyouts/Flyout.cs index ed16628605..ff6f78a6f4 100644 --- a/src/Avalonia.Controls/Flyouts/Flyout.cs +++ b/src/Avalonia.Controls/Flyouts/Flyout.cs @@ -15,22 +15,9 @@ namespace Avalonia.Controls public static readonly StyledProperty ContentProperty = AvaloniaProperty.Register(nameof(Content)); - public Styles? FlyoutPresenterStyle - { - get - { - if (_styles == null) - { - _styles = new Styles(); - _styles.CollectionChanged += OnFlyoutPresenterStylesChanged; - } - - return _styles; - } - } + public Classes? FlyoutPresenterClasses => _classes ??= new Classes(); - private Styles? _styles; - private bool _stylesDirty; + private Classes? _classes; [Content] public object Content @@ -49,19 +36,21 @@ namespace Avalonia.Controls protected override void OnOpened() { - if (_styles != null && _stylesDirty) + if (FlyoutPresenterClasses != null) { - // Presenter for flyout generally shouldn't be public, so - // we should be ok to just reset the styles - _popup.Child.Styles.Clear(); - _popup.Child.Styles.Add(_styles); + //Remove any classes no longer in use + for (int i = _popup.Child.Classes.Count - 1; i >= 0; i--) + { + if (!FlyoutPresenterClasses.Contains(_popup.Child.Classes[i])) + { + _popup.Child.Classes.RemoveAt(i); + } + } + + //Add new classes + _popup.Child.Classes.AddRange(FlyoutPresenterClasses); } base.OnOpened(); } - - private void OnFlyoutPresenterStylesChanged(object sender, NotifyCollectionChangedEventArgs e) - { - _stylesDirty = true; - } } } diff --git a/src/Avalonia.Controls/Flyouts/MenuFlyout.cs b/src/Avalonia.Controls/Flyouts/MenuFlyout.cs index 8eea5f211d..4cea5199f1 100644 --- a/src/Avalonia.Controls/Flyouts/MenuFlyout.cs +++ b/src/Avalonia.Controls/Flyouts/MenuFlyout.cs @@ -26,19 +26,7 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(ItemTemplate), x => x.ItemTemplate, (x, v) => x.ItemTemplate = v); - public Styles? FlyoutPresenterStyle - { - get - { - if (_styles == null) - { - _styles = new Styles(); - _styles.CollectionChanged += OnMenuFlyoutPresenterStyleChanged; - } - - return _styles; - } - } + public Classes? FlyoutPresenterClasses => _classes ??= new Classes(); [Content] public IEnumerable Items @@ -53,8 +41,7 @@ namespace Avalonia.Controls set => SetAndRaise(ItemTemplateProperty, ref _itemTemplate, value); } - private Styles? _styles; - private bool _stylesDirty = true; + private Classes? _classes; private IEnumerable _items; private IDataTemplate? _itemTemplate; @@ -69,19 +56,21 @@ namespace Avalonia.Controls protected override void OnOpened() { - if (_styles != null && _stylesDirty) + if (FlyoutPresenterClasses != null) { - // Presenter for flyout generally shouldn't be public, so - // we should be ok to just reset the styles - _popup.Child.Styles.Clear(); - _popup.Child.Styles.Add(_styles); + //Remove any classes no longer in use + for (int i = _popup.Child.Classes.Count - 1; i >= 0; i--) + { + if (!FlyoutPresenterClasses.Contains(_popup.Child.Classes[i])) + { + _popup.Child.Classes.RemoveAt(i); + } + } + + //Add new classes + _popup.Child.Classes.AddRange(FlyoutPresenterClasses); } base.OnOpened(); } - - private void OnMenuFlyoutPresenterStyleChanged(object sender, NotifyCollectionChangedEventArgs e) - { - _stylesDirty = true; - } } } From 88fb92d8c0dc6d5b623cb5c2d13090db65aa9f73 Mon Sep 17 00:00:00 2001 From: amwx Date: Fri, 19 Mar 2021 17:42:00 -0500 Subject: [PATCH 041/129] DefaultTheme styles --- src/Avalonia.Themes.Default/DefaultTheme.xaml | 2 ++ .../FlyoutPresenter.xaml | 30 +++++++++++++++++++ .../MenuFlyoutPresenter.xaml | 28 +++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 src/Avalonia.Themes.Default/FlyoutPresenter.xaml create mode 100644 src/Avalonia.Themes.Default/MenuFlyoutPresenter.xaml diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 107fc07875..4ae9ea4812 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -60,4 +60,6 @@ + + diff --git a/src/Avalonia.Themes.Default/FlyoutPresenter.xaml b/src/Avalonia.Themes.Default/FlyoutPresenter.xaml new file mode 100644 index 0000000000..39ee8e5a6e --- /dev/null +++ b/src/Avalonia.Themes.Default/FlyoutPresenter.xaml @@ -0,0 +1,30 @@ + + + diff --git a/src/Avalonia.Themes.Default/MenuFlyoutPresenter.xaml b/src/Avalonia.Themes.Default/MenuFlyoutPresenter.xaml new file mode 100644 index 0000000000..612203d3d7 --- /dev/null +++ b/src/Avalonia.Themes.Default/MenuFlyoutPresenter.xaml @@ -0,0 +1,28 @@ + + + From b6f5571495e52ce75c7d920a10be1d4836d7eed0 Mon Sep 17 00:00:00 2001 From: amwx Date: Fri, 19 Mar 2021 17:44:28 -0500 Subject: [PATCH 042/129] TextBox uses ContextFlyout --- src/Avalonia.Themes.Default/TextBox.xaml | 10 +++++++++- src/Avalonia.Themes.Fluent/Controls/TextBox.xaml | 12 ++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Themes.Default/TextBox.xaml b/src/Avalonia.Themes.Default/TextBox.xaml index 9085cfc2c6..6dce77e96d 100644 --- a/src/Avalonia.Themes.Default/TextBox.xaml +++ b/src/Avalonia.Themes.Default/TextBox.xaml @@ -3,6 +3,14 @@ M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z + + + + + + + + @@ -17,7 +25,7 @@ - + M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z + + + + + + + + - + + + + + Context Flyout + A right click Flyout that can be applied to any control. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/ContextFlyoutPage.axaml.cs b/samples/ControlCatalog/Pages/ContextFlyoutPage.axaml.cs new file mode 100644 index 0000000000..e64d4a2cdd --- /dev/null +++ b/samples/ControlCatalog/Pages/ContextFlyoutPage.axaml.cs @@ -0,0 +1,45 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using ControlCatalog.ViewModels; +using Avalonia.Interactivity; +namespace ControlCatalog.Pages +{ + public class ContextFlyoutPage : UserControl + { + private TextBox _textBox; + + public ContextFlyoutPage() + { + InitializeComponent(); + + var vm = new ContextFlyoutPageViewModel(); + vm.View = this; + DataContext = vm; + + _textBox = this.FindControl("TextBox"); + + var cutButton = this.FindControl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs b/samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs new file mode 100644 index 0000000000..0803d178b9 --- /dev/null +++ b/samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs @@ -0,0 +1,81 @@ +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Markup.Xaml; +using Avalonia.Interactivity; + +namespace ControlCatalog.Pages +{ + public class FlyoutsPage : UserControl + { + public FlyoutsPage() + { + InitializeComponent(); + + var afp = this.FindControl("AttachedFlyoutPanel"); + if (afp != null) + { + afp.DoubleTapped += Afp_DoubleTapped; + } + + SetXamlTexts(); + } + + private void Afp_DoubleTapped(object sender, RoutedEventArgs e) + { + if (sender is Panel p) + { + FlyoutBase.ShowAttachedFlyout(p); + } + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void SetXamlTexts() + { + var bfxt = this.FindControl("ButtonFlyoutXamlText"); + bfxt.Text = ""; + + var mfxt = this.FindControl("MenuFlyoutXamlText"); + mfxt.Text = ""; + + var afxt = this.FindControl("AttachedFlyoutXamlText"); + afxt.Text = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n\n In DoubleTapped handler:\n" + + "FlyoutBase.ShowAttachedFlyout(AttachedFlyoutPanel);"; + + var sfxt = this.FindControl("SharedFlyoutXamlText"); + sfxt.Text = "Declare a flyout in Resources:\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n\n\n" + + "Then attach the flyout where you want it:\n" + + "