From 89fa3b77f5ae91e6c9dba253fef5ac7482850a3d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 May 2020 11:36:03 +0200 Subject: [PATCH 01/92] Added nullable annotations to ContextMenu. --- src/Avalonia.Controls/ContextMenu.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 86499530da..a026c8fa72 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using Avalonia.Controls.Generators; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; @@ -9,9 +8,10 @@ using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; -using Avalonia.LogicalTree; using Avalonia.Styling; +#nullable enable + namespace Avalonia.Controls { /// @@ -21,9 +21,9 @@ namespace Avalonia.Controls { private static readonly ITemplate DefaultPanel = new FuncTemplate(() => new StackPanel { Orientation = Orientation.Vertical }); - private Popup _popup; - private List _attachedControls; - private IInputElement _previousFocus; + private Popup? _popup; + private List? _attachedControls; + private IInputElement? _previousFocus; /// /// Initializes a new instance of the class. @@ -56,14 +56,14 @@ namespace Avalonia.Controls /// /// property is changing from false to true. /// - public event CancelEventHandler ContextMenuOpening; + public event CancelEventHandler? ContextMenuOpening; /// /// Occurs when the value of the /// /// property is changing from true to false. /// - public event CancelEventHandler ContextMenuClosing; + public event CancelEventHandler? ContextMenuClosing; /// /// Called when the property changes on a control. @@ -77,7 +77,7 @@ namespace Avalonia.Controls { control.PointerReleased -= ControlPointerReleased; oldMenu._attachedControls?.Remove(control); - ((ISetLogicalParent)oldMenu._popup)?.SetParent(null); + ((ISetLogicalParent?)oldMenu._popup)?.SetParent(null); } if (e.NewValue is ContextMenu newMenu) @@ -97,7 +97,7 @@ namespace Avalonia.Controls /// Opens a context menu on the specified control. /// /// The control. - public void Open(Control control) + public void Open(Control? control) { if (control is null && (_attachedControls is null || _attachedControls.Count == 0)) { @@ -113,7 +113,7 @@ namespace Avalonia.Controls nameof(control)); } - control ??= _attachedControls[0]; + control ??= _attachedControls![0]; if (IsOpen) { @@ -204,7 +204,7 @@ namespace Avalonia.Controls if (_attachedControls is null || _attachedControls.Count == 0) { - ((ISetLogicalParent)_popup).SetParent(null); + ((ISetLogicalParent)_popup!).SetParent(null); } // HACK: Reset the focus when the popup is closed. We need to fix this so it's automatic. From 372042b6f9afb67ec2934f5a76dd3f6442bad1fa Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 May 2020 11:39:12 +0200 Subject: [PATCH 02/92] Added positioning properties to ContextMenu. --- src/Avalonia.Controls/ContextMenu.cs | 69 ++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index a026c8fa72..5a60fd37af 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -19,6 +19,30 @@ namespace Avalonia.Controls /// public class ContextMenu : MenuBase, ISetterValue { + /// + /// Defines the property. + /// + public static readonly StyledProperty PlacementModeProperty = + Popup.PlacementModeProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty HorizontalOffsetProperty = + Popup.HorizontalOffsetProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty VerticalOffsetProperty = + Popup.VerticalOffsetProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty PlacementTargetProperty = + Popup.PlacementTargetProperty.AddOwner(); + private static readonly ITemplate DefaultPanel = new FuncTemplate(() => new StackPanel { Orientation = Orientation.Vertical }); private Popup? _popup; @@ -47,10 +71,47 @@ namespace Avalonia.Controls /// static ContextMenu() { - ItemsPanelProperty.OverrideDefaultValue(typeof(ContextMenu), DefaultPanel); + ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); + PlacementModeProperty.OverrideDefaultValue(PlacementMode.Pointer); ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); } + /// + /// Gets or sets the placement mode of the popup in relation to the . + /// + public PlacementMode PlacementMode + { + get { return GetValue(PlacementModeProperty); } + set { SetValue(PlacementModeProperty, value); } + } + + /// + /// Gets or sets the Horizontal offset of the popup in relation to the + /// + public double HorizontalOffset + { + get { return GetValue(HorizontalOffsetProperty); } + set { SetValue(HorizontalOffsetProperty, value); } + } + + /// + /// Gets or sets the Vertical offset of the popup in relation to the + /// + public double VerticalOffset + { + get { return GetValue(VerticalOffsetProperty); } + set { SetValue(VerticalOffsetProperty, value); } + } + + /// + /// Gets or sets the control that is used to determine the popup's position. + /// + public Control? PlacementTarget + { + get { return GetValue(PlacementTargetProperty); } + set { SetValue(PlacementTargetProperty, value); } + } + /// /// Occurs when the value of the /// @@ -124,8 +185,10 @@ namespace Avalonia.Controls { _popup = new Popup { - PlacementMode = PlacementMode.Pointer, - PlacementTarget = control, + HorizontalOffset = HorizontalOffset, + VerticalOffset = VerticalOffset, + PlacementMode = PlacementMode, + PlacementTarget = PlacementTarget ?? control, StaysOpen = false }; From 9bb71a248467f2827518b652797441e647d7ed30 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 May 2020 14:46:49 +0200 Subject: [PATCH 03/92] Added/tweaked docs. --- .../Primitives/IPopupHost.cs | 46 +++ .../PopupPositioning/IPopupPositioner.cs | 269 +++++++++++------- .../ManagedPopupPositioner.cs | 4 + 3 files changed, 218 insertions(+), 101 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs index 74a3ca8818..bb4daf38e1 100644 --- a/src/Avalonia.Controls/Primitives/IPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs @@ -5,19 +5,65 @@ using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { + /// + /// Represents the top-level control opened by a . + /// + /// + /// A popup host can be either be a popup window created by the operating system + /// () or an which is created + /// on an . + /// public interface IPopupHost : IDisposable { + /// + /// Sets the control to display in the popup. + /// + /// void SetChild(IControl control); + + /// + /// Gets the presenter from the control's template. + /// IContentPresenter Presenter { get; } + + /// + /// Gets the root of the visual tree in the case where the popup is presented using a + /// separate visual tree. + /// IVisual HostedVisualTreeRoot { get; } + /// + /// Raised when the control's template is applied. + /// event EventHandler TemplateApplied; + /// + /// Configures the position of the popup according to a target control and a set of + /// placement parameters. + /// + /// The placement target. + /// The placement mode. + /// The offset, in device-independent pixels. + /// The anchor point. + /// The anchor gravity. void ConfigurePosition(IVisual target, PlacementMode placement, Point offset, PopupPositioningEdge anchor = PopupPositioningEdge.None, PopupPositioningEdge gravity = PopupPositioningEdge.None); + + /// + /// Shows the popup. + /// void Show(); + + /// + /// Hides the popup. + /// void Hide(); + + /// + /// Binds the constraints of the popup host to a set of properties, usally those present on + /// . + /// IDisposable BindConstraints(AvaloniaObject popup, StyledProperty widthProperty, StyledProperty minWidthProperty, StyledProperty maxWidthProperty, StyledProperty heightProperty, StyledProperty minHeightProperty, diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index f0358ec04f..d2a403b602 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -50,45 +50,47 @@ using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives.PopupPositioning { /// - /// - /// The IPopupPositioner provides a collection of rules for the placement of a - /// a popup relative to its parent. Rules can be defined to ensure - /// the popup remains within the visible area's borders, and to - /// specify how the popup changes its position, such as sliding along - /// an axis, or flipping around a rectangle. These positioner-created rules are - /// constrained by the requirement that a popup must intersect with or - /// be at least partially adjacent to its parent surface. + /// Provides positioning parameters to . /// + /// + /// The IPopupPositioner provides a collection of rules for the placement of a a popup relative + /// to its parent. Rules can be defined to ensure the popup remains within the visible area's + /// borders, and to specify how the popup changes its position, such as sliding along an axis, + /// or flipping around a rectangle. These positioner-created rules are constrained by the + /// requirement that a popup must intersect with or be at least partially adjacent to its parent + /// surface. + /// public struct PopupPositionerParameters { private PopupPositioningEdge _gravity; private PopupPositioningEdge _anchor; /// - /// Set the size of the popup that is to be positioned with the positioner - /// object. The size is in scaled coordinates. + /// Set the size of the popup that is to be positioned with the positioner object, in device- + /// independent pixels. /// public Size Size { get; set; } /// - /// Specify the anchor rectangle within the parent that the popup - /// will be placed relative to. The rectangle is relative to the - /// parent geometry - /// - /// The anchor rectangle may not extend outside the window geometry of the - /// popup's parent. The anchor rectangle is in scaled coordinates + /// Specifies the anchor rectangle within the parent that the popup will be placed relative + /// to, in device-independent pixels. /// + /// + /// The rectangle is relative to the parent geometry and may not extend outside the window + /// geometry of the popup's parent. + /// public Rect AnchorRectangle { get; set; } - /// - /// Defines the anchor point for the anchor rectangle. The specified anchor - /// is used derive an anchor point that the popup will be - /// positioned relative to. If a corner anchor is set (e.g. 'TopLeft' or - /// 'BottomRight'), the anchor point will be at the specified corner; - /// otherwise, the derived anchor point will be centered on the specified - /// edge, or in the center of the anchor rectangle if no edge is specified. + /// Defines the anchor point for the anchor rectangle. /// + /// + /// The specified anchor is used derive an anchor point that the popup will be positioned + /// relative to. If a corner anchor is set (e.g. 'TopLeft' or 'BottomRight'), the anchor + /// point will be at the specified corner; otherwise, the derived anchor point will be + /// centered on the specified edge, or in the center of the anchor rectangle if no edge is + /// specified. + /// public PopupPositioningEdge Anchor { get => _anchor; @@ -100,13 +102,14 @@ namespace Avalonia.Controls.Primitives.PopupPositioning } /// - /// Defines in what direction a popup should be positioned, relative to - /// the anchor point of the parent. If a corner gravity is - /// specified (e.g. 'BottomRight' or 'TopLeft'), then the popup - /// will be placed towards the specified gravity; otherwise, the popup - /// will be centered over the anchor point on any axis that had no - /// gravity specified. + /// Defines in what direction a popup should be positioned, relative to the anchor point of + /// the parent. /// + /// + /// If a corner gravity is specified (e.g. 'BottomRight' or 'TopLeft'), then the popup will + /// be placed towards the specified gravity; otherwise, the popup will be centered over the + /// anchor point on any axis that had no gravity specified. + /// public PopupPositioningEdge Gravity { get => _gravity; @@ -118,48 +121,51 @@ namespace Avalonia.Controls.Primitives.PopupPositioning } /// - /// Specify how the popup should be positioned if the originally intended - /// position caused the popup to be constrained, meaning at least - /// partially outside positioning boundaries set by the positioner. The - /// adjustment is set by constructing a bitmask describing the adjustment to - /// be made when the popup is constrained on that axis. + /// Specify how the popup should be positioned if the originally intended position caused + /// the popup to be constrained. + /// + /// + /// Adjusts the popup position if the intended position caused the popup to be constrained; + /// meaning at least partially outside positioning boundaries set by the positioner. The + /// adjustment is set by constructing a bitmask describing the adjustment to be made when + /// the popup is constrained on that axis. /// - /// If no bit for one axis is set, the positioner will assume that the child - /// surface should not change its position on that axis when constrained. + /// If no bit for one axis is set, the positioner will assume that the child surface should + /// not change its position on that axis when constrained. /// - /// If more than one bit for one axis is set, the order of how adjustments - /// are applied is specified in the corresponding adjustment descriptions. + /// If more than one bit for one axis is set, the order of how adjustments are applied is + /// specified in the corresponding adjustment descriptions. /// /// The default adjustment is none. - /// + /// public PopupPositionerConstraintAdjustment ConstraintAdjustment { get; set; } - + /// /// Specify the popup position offset relative to the position of the - /// anchor on the anchor rectangle and the anchor on the popup. For - /// example if the anchor of the anchor rectangle is at (x, y), the popup - /// has the gravity bottom|right, and the offset is (ox, oy), the calculated - /// surface position will be (x + ox, y + oy). The offset position of the - /// surface is the one used for constraint testing. See - /// set_constraint_adjustment. - /// - /// An example use case is placing a popup menu on top of a user interface - /// element, while aligning the user interface element of the parent surface - /// with some user interface element placed somewhere in the popup. + /// anchor on the anchor rectangle and the anchor on the popup. /// + /// + /// For example if the anchor of the anchor rectangle is at (x, y), the popup has the + /// gravity bottom|right, and the offset is (ox, oy), the calculated surface position will + /// be (x + ox, y + oy). The offset position of the surface is the one used for constraint + /// testing. See set_constraint_adjustment. + /// + /// An example use case is placing a popup menu on top of a user interface element, while + /// aligning the user interface element of the parent surface with some user interface + /// element placed somewhere in the popup. + /// public Point Offset { get; set; } } - + /// - /// The constraint adjustment value define ways how popup position will - /// be adjusted if the unadjusted position would result in the popup - /// being partly constrained. - /// - /// Whether a popup is considered 'constrained' is left to the positioner - /// to determine. For example, the popup may be partly outside the - /// target platform defined 'work area', thus necessitating the popup's - /// position be adjusted until it is entirely inside the work area. + /// Defines how a popup position will be adjusted if the unadjusted position would result in + /// the popup being partly constrained. /// + /// + /// Whether a popup is considered 'constrained' is left to the positioner to determine. For + /// example, the popup may be partly outside the target platform defined 'work area', thus + /// necessitating the popup's position be adjusted until it is entirely inside the work area. + /// [Flags] public enum PopupPositionerConstraintAdjustment { @@ -171,62 +177,59 @@ namespace Avalonia.Controls.Primitives.PopupPositioning /// /// Slide the surface along the x axis until it is no longer constrained. - /// First try to slide towards the direction of the gravity on the x axis - /// until either the edge in the opposite direction of the gravity is - /// unconstrained or the edge in the direction of the gravity is - /// constrained. - /// - /// Then try to slide towards the opposite direction of the gravity on the - /// x axis until either the edge in the direction of the gravity is - /// unconstrained or the edge in the opposite direction of the gravity is - /// constrained. /// + /// + /// First try to slide towards the direction of the gravity on the x axis until either the + /// edge in the opposite direction of the gravity is unconstrained or the edge in the + /// direction of the gravity is constrained. + /// + /// Then try to slide towards the opposite direction of the gravity on the x axis until + /// either the edge in the direction of the gravity is unconstrained or the edge in the + /// opposite direction of the gravity is constrained. + /// SlideX = 1, - /// - /// Slide the surface along the y axis until it is no longer constrained. - /// - /// First try to slide towards the direction of the gravity on the y axis - /// until either the edge in the opposite direction of the gravity is - /// unconstrained or the edge in the direction of the gravity is - /// constrained. - /// - /// Then try to slide towards the opposite direction of the gravity on the - /// y axis until either the edge in the direction of the gravity is - /// unconstrained or the edge in the opposite direction of the gravity is - /// constrained. - /// */ + /// Slide the surface along the y axis until it is no longer constrained. /// + /// + /// First try to slide towards the direction of the gravity on the y axis until either the + /// edge in the opposite direction of the gravity is unconstrained or the edge in the + /// direction of the gravity is constrained. + /// + /// Then try to slide towards the opposite direction of the gravity on the y axis until + /// either the edge in the direction of the gravity is unconstrained or the edge in the + /// opposite direction of the gravity is constrained. + /// SlideY = 2, /// - /// Invert the anchor and gravity on the x axis if the surface is - /// constrained on the x axis. For example, if the left edge of the - /// surface is constrained, the gravity is 'left' and the anchor is - /// 'left', change the gravity to 'right' and the anchor to 'right'. - /// - /// If the adjusted position also ends up being constrained, the resulting - /// position of the flip_x adjustment will be the one before the - /// adjustment. + /// Invert the anchor and gravity on the x axis if the surface is constrained on the x axis. /// + /// + /// For example, if the left edge of the surface is constrained, the gravity is 'left' and + /// the anchor is 'left', change the gravity to 'right' and the anchor to 'right'. + /// + /// If the adjusted position also ends up being constrained, the resulting position of the + /// FlipX adjustment will be the one before the adjustment. + /// /// FlipX = 4, /// - /// Invert the anchor and gravity on the y axis if the surface is - /// constrained on the y axis. For example, if the bottom edge of the - /// surface is constrained, the gravity is 'bottom' and the anchor is - /// 'bottom', change the gravity to 'top' and the anchor to 'top'. + /// Invert the anchor and gravity on the y axis if the surface is constrained on the y axis. + /// + /// + /// For example, if the bottom edge of the surface is constrained, the gravity is 'bottom' + /// and the anchor is 'bottom', change the gravity to 'top' and the anchor to 'top'. /// - /// The adjusted position is calculated given the original anchor - /// rectangle and offset, but with the new flipped anchor and gravity - /// values. + /// The adjusted position is calculated given the original anchor rectangle and offset, but + /// with the new flipped anchor and gravity values. /// - /// If the adjusted position also ends up being constrained, the resulting - /// position of the flip_y adjustment will be the one before the - /// adjustment. - /// + /// If the adjusted position also ends up being constrained, the resulting position of the + /// FlipY adjustment will be the one before the adjustment. + /// FlipY = 8, + All = SlideX|SlideY|FlipX|FlipY } @@ -267,27 +270,91 @@ namespace Avalonia.Controls.Primitives.PopupPositioning } + /// + /// Defines the popup position edge for and + /// . + /// [Flags] public enum PopupPositioningEdge { + /// + /// The center of the anchor rectangle. + /// None, + + /// + /// The top edge of the anchor rectangle. + /// Top = 1, + + /// + /// The bottom edge of the anchor rectangle. + /// Bottom = 2, + + /// + /// The left edge of the anchor rectangle. + /// Left = 4, + + /// + /// The right edge of the anchor rectangle. + /// Right = 8, + + /// + /// The top-left corner of the anchor rectangle. + /// TopLeft = Top | Left, + + /// + /// The top-right corner of the anchor rectangle. + /// TopRight = Top | Right, + + /// + /// The bottom-left corner of the anchor rectangle. + /// BottomLeft = Bottom | Left, + + /// + /// The bottom-right corner of the anchor rectangle. + /// BottomRight = Bottom | Right, - + /// + /// A mask for the vertical component flags. + /// VerticalMask = Top | Bottom, + + /// + /// A mask for the horizontal component flags. + /// HorizontalMask = Left | Right, + + /// + /// A mask for all flags. + /// AllMask = VerticalMask|HorizontalMask } + /// + /// Positions an . + /// + /// + /// is an abstraction of the wayland xdg_positioner spec. + /// + /// The popup positioner implementation is determined by the platform implementation. A default + /// managed implementation is provided in for platforms + /// on which popups can be arbitrarily positioned. + /// public interface IPopupPositioner { + /// + /// Updates the position of the associated according to the + /// specified parameters. + /// + /// The positioning parameters. void Update(PopupPositionerParameters parameters); } diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs index 07348cdf78..bd23a13e7f 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs @@ -25,6 +25,10 @@ namespace Avalonia.Controls.Primitives.PopupPositioning } } + /// + /// An implementation for platforms on which a popup can be + /// aritrarily positioned. + /// public class ManagedPopupPositioner : IPopupPositioner { private readonly IManagedPopupPositionerPopup _popup; From 07d1f16fa0f7eeb58c35c9977bab06f4c9a6fa15 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 May 2020 15:35:25 +0200 Subject: [PATCH 04/92] Added additional placement properties to PopupRoot. And split `PopupPositioningEdge` into `PopupAnchor` and `PopupGravity` so their values can be documented more clearly. --- .../Primitives/IPopupHost.cs | 10 +- .../Primitives/OverlayPopupHost.cs | 5 +- src/Avalonia.Controls/Primitives/Popup.cs | 97 +++++++++--- .../PopupPositioning/IPopupPositioner.cs | 138 +++++++++++++----- .../ManagedPopupPositioner.cs | 42 +++--- src/Avalonia.Controls/Primitives/PopupRoot.cs | 7 +- 6 files changed, 215 insertions(+), 84 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs index bb4daf38e1..18fb45fd5a 100644 --- a/src/Avalonia.Controls/Primitives/IPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs @@ -45,10 +45,14 @@ namespace Avalonia.Controls.Primitives /// The placement mode. /// The offset, in device-independent pixels. /// The anchor point. - /// The anchor gravity. + /// The popup gravity. + /// + /// The anchor rect. If null, the bounds of will be used. + /// void ConfigurePosition(IVisual target, PlacementMode placement, Point offset, - PopupPositioningEdge anchor = PopupPositioningEdge.None, - PopupPositioningEdge gravity = PopupPositioningEdge.None); + PopupAnchor anchor = PopupAnchor.None, + PopupGravity gravity = PopupGravity.None, + Rect? rect = null); /// /// Shows the popup. diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index 3dc9d302db..15b09f29f1 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -71,10 +71,11 @@ namespace Avalonia.Controls.Primitives } public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset, - PopupPositioningEdge anchor = PopupPositioningEdge.None, PopupPositioningEdge gravity = PopupPositioningEdge.None) + PopupAnchor anchor = PopupAnchor.None, PopupGravity gravity = PopupGravity.None, + Rect? rect = null) { _positionerParameters.ConfigurePosition((TopLevel)_overlayLayer.GetVisualRoot(), target, placement, offset, anchor, - gravity); + gravity, rect); UpdatePosition(); } diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 66f2153b6c..ff1201286c 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Linq; using System.Reactive.Disposables; using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Interactivity; @@ -34,12 +35,36 @@ namespace Avalonia.Controls.Primitives o => o.IsOpen, (o, v) => o.IsOpen = v); + /// + /// Defines the property. + /// + public static readonly StyledProperty PlacementAnchorProperty = + AvaloniaProperty.Register(nameof(PlacementAnchor)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty PlacementGravityProperty = + AvaloniaProperty.Register(nameof(PlacementGravity)); + /// /// Defines the property. /// public static readonly StyledProperty PlacementModeProperty = AvaloniaProperty.Register(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); + /// + /// Defines the property. + /// + public static readonly StyledProperty PlacementRectProperty = + AvaloniaProperty.Register(nameof(PlacementRect)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty PlacementTargetProperty = + AvaloniaProperty.Register(nameof(PlacementTarget)); + #pragma warning disable 618 /// /// Defines the property. @@ -60,12 +85,6 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty VerticalOffsetProperty = AvaloniaProperty.Register(nameof(VerticalOffset)); - /// - /// Defines the property. - /// - public static readonly StyledProperty PlacementTargetProperty = - AvaloniaProperty.Register(nameof(PlacementTarget)); - /// /// Defines the property. /// @@ -136,6 +155,26 @@ namespace Avalonia.Controls.Primitives set { SetAndRaise(IsOpenProperty, ref _isOpen, value); } } + /// + /// Gets or sets the anchor point on the when + /// is . + /// + public PopupAnchor PlacementAnchor + { + get { return GetValue(PlacementAnchorProperty); } + set { SetValue(PlacementAnchorProperty, value); } + } + + /// + /// Gets or sets a value which defines in what direction the popup should open + /// when is . + /// + public PopupGravity PlacementGravity + { + get { return GetValue(PlacementGravityProperty); } + set { SetValue(PlacementGravityProperty, value); } + } + /// /// Gets or sets the placement mode of the popup in relation to the . /// @@ -145,6 +184,32 @@ namespace Avalonia.Controls.Primitives set { SetValue(PlacementModeProperty, value); } } + /// + /// Gets or sets the the anchor rectangle within the parent that the popup will be placed + /// relative to when is . + /// + /// + /// The placement rect defines a rectangle relative to around + /// which the popup will be opened, with determining which edge + /// of the placement target is used. + /// + /// If unset, the anchor rectangle will be the bounds of the . + /// + public Rect? PlacementRect + { + get { return GetValue(PlacementRectProperty); } + set { SetValue(PlacementRectProperty, value); } + } + + /// + /// Gets or sets the control that is used to determine the popup's position. + /// + public Control? PlacementTarget + { + get { return GetValue(PlacementTargetProperty); } + set { SetValue(PlacementTargetProperty, value); } + } + [Obsolete("This property has no effect")] public bool ObeyScreenEdges { @@ -153,7 +218,7 @@ namespace Avalonia.Controls.Primitives } /// - /// Gets or sets the Horizontal offset of the popup in relation to the + /// Gets or sets the Horizontal offset of the popup in relation to the . /// public double HorizontalOffset { @@ -162,7 +227,7 @@ namespace Avalonia.Controls.Primitives } /// - /// Gets or sets the Vertical offset of the popup in relation to the + /// Gets or sets the Vertical offset of the popup in relation to the . /// public double VerticalOffset { @@ -170,15 +235,6 @@ namespace Avalonia.Controls.Primitives set { SetValue(VerticalOffsetProperty, value); } } - /// - /// Gets or sets the control that is used to determine the popup's position. - /// - public Control? PlacementTarget - { - get { return GetValue(PlacementTargetProperty); } - set { SetValue(PlacementTargetProperty, value); } - } - /// /// Gets or sets a value indicating whether the popup should stay open when the popup is /// pressed or loses focus. @@ -251,8 +307,11 @@ namespace Avalonia.Controls.Primitives popupHost.ConfigurePosition( placementTarget, - PlacementMode, - new Point(HorizontalOffset, VerticalOffset)); + PlacementMode, + new Point(HorizontalOffset, VerticalOffset), + PlacementAnchor, + PlacementGravity, + PlacementRect); DeferCleanup(SubscribeToEventHandler>(popupHost, RootTemplateApplied, (x, handler) => x.TemplateApplied += handler, diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index d2a403b602..d6e54b80c1 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -62,8 +62,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning /// public struct PopupPositionerParameters { - private PopupPositioningEdge _gravity; - private PopupPositioningEdge _anchor; + private PopupGravity _gravity; + private PopupAnchor _anchor; /// /// Set the size of the popup that is to be positioned with the positioner object, in device- @@ -91,7 +91,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning /// centered on the specified edge, or in the center of the anchor rectangle if no edge is /// specified. /// - public PopupPositioningEdge Anchor + public PopupAnchor Anchor { get => _anchor; set @@ -110,12 +110,12 @@ namespace Avalonia.Controls.Primitives.PopupPositioning /// be placed towards the specified gravity; otherwise, the popup will be centered over the /// anchor point on any axis that had no gravity specified. /// - public PopupPositioningEdge Gravity + public PopupGravity Gravity { get => _gravity; set { - PopupPositioningEdgeHelper.ValidateEdge(value); + PopupPositioningEdgeHelper.ValidateGravity(value); _gravity = value; } } @@ -235,18 +235,23 @@ namespace Avalonia.Controls.Primitives.PopupPositioning static class PopupPositioningEdgeHelper { - public static void ValidateEdge(this PopupPositioningEdge edge) + public static void ValidateEdge(this PopupAnchor edge) { - if (((edge & PopupPositioningEdge.Left) != 0 && (edge & PopupPositioningEdge.Right) != 0) + if (((edge & PopupAnchor.Left) != 0 && (edge & PopupAnchor.Right) != 0) || - ((edge & PopupPositioningEdge.Top) != 0 && (edge & PopupPositioningEdge.Bottom) != 0)) + ((edge & PopupAnchor.Top) != 0 && (edge & PopupAnchor.Bottom) != 0)) throw new ArgumentException("Opposite edges specified"); } - public static PopupPositioningEdge Flip(this PopupPositioningEdge edge) + public static void ValidateGravity(this PopupGravity gravity) + { + ValidateEdge((PopupAnchor)gravity); + } + + public static PopupAnchor Flip(this PopupAnchor edge) { - var hmask = PopupPositioningEdge.Left | PopupPositioningEdge.Right; - var vmask = PopupPositioningEdge.Top | PopupPositioningEdge.Bottom; + var hmask = PopupAnchor.Left | PopupAnchor.Right; + var vmask = PopupAnchor.Top | PopupAnchor.Bottom; if ((edge & hmask) != 0) edge ^= hmask; if ((edge & vmask) != 0) @@ -254,28 +259,36 @@ namespace Avalonia.Controls.Primitives.PopupPositioning return edge; } - public static PopupPositioningEdge FlipX(this PopupPositioningEdge edge) + public static PopupAnchor FlipX(this PopupAnchor edge) { - if ((edge & PopupPositioningEdge.HorizontalMask) != 0) - edge ^= PopupPositioningEdge.HorizontalMask; + if ((edge & PopupAnchor.HorizontalMask) != 0) + edge ^= PopupAnchor.HorizontalMask; return edge; } - public static PopupPositioningEdge FlipY(this PopupPositioningEdge edge) + public static PopupAnchor FlipY(this PopupAnchor edge) { - if ((edge & PopupPositioningEdge.VerticalMask) != 0) - edge ^= PopupPositioningEdge.VerticalMask; + if ((edge & PopupAnchor.VerticalMask) != 0) + edge ^= PopupAnchor.VerticalMask; return edge; } - + + public static PopupGravity FlipX(this PopupGravity gravity) + { + return (PopupGravity)FlipX((PopupAnchor)gravity); + } + + public static PopupGravity FlipY(this PopupGravity gravity) + { + return (PopupGravity)FlipY((PopupAnchor)gravity); + } } /// - /// Defines the popup position edge for and - /// . + /// Defines the edges around an anchor rectangle on which a popup will open. /// [Flags] - public enum PopupPositioningEdge + public enum PopupAnchor { /// /// The center of the anchor rectangle. @@ -338,6 +351,58 @@ namespace Avalonia.Controls.Primitives.PopupPositioning AllMask = VerticalMask|HorizontalMask } + /// + /// Defines the direction in which a popup will open. + /// + [Flags] + public enum PopupGravity + { + /// + /// The popup will be centered over the anchor edge. + /// + None, + + /// + /// The popup will be positioned above the anchor edge + /// + Top = 1, + + /// + /// The popup will be positioned below the anchor edge + /// + Bottom = 2, + + /// + /// The popup will be positioned to the left of the anchor edge + /// + Left = 4, + + /// + /// The popup will be positioned to the right of the anchor edge + /// + Right = 8, + + /// + /// The popup will be positioned to the top-left of the anchor edge + /// + TopLeft = Top | Left, + + /// + /// The popup will be positioned to the top-right of the anchor edge + /// + TopRight = Top | Right, + + /// + /// The popup will be positioned to the bottom-left of the anchor edge + /// + BottomLeft = Bottom | Left, + + /// + /// The popup will be positioned to the bottom-right of the anchor edge + /// + BottomRight = Bottom | Right, + } + /// /// Positions an . /// @@ -363,7 +428,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning public static void ConfigurePosition(ref this PopupPositionerParameters positionerParameters, TopLevel topLevel, IVisual target, PlacementMode placement, Point offset, - PopupPositioningEdge anchor, PopupPositioningEdge gravity) + PopupAnchor anchor, PopupGravity gravity, Rect? rect) { // We need a better way for tracking the last pointer position var pointer = topLevel.PointToClient(topLevel.PlatformImpl.MouseDevice.Position); @@ -373,8 +438,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning if (placement == PlacementMode.Pointer) { positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1)); - positionerParameters.Anchor = PopupPositioningEdge.TopLeft; - positionerParameters.Gravity = PopupPositioningEdge.BottomRight; + positionerParameters.Anchor = PopupAnchor.TopLeft; + positionerParameters.Gravity = PopupGravity.BottomRight; } else { @@ -384,32 +449,33 @@ namespace Avalonia.Controls.Primitives.PopupPositioning if (matrix == null) { if (target.GetVisualRoot() == null) - throw new InvalidCastException("Target control is not attached to the visual tree"); - throw new InvalidCastException("Target control is not in the same tree as the popup parent"); + throw new InvalidOperationException("Target control is not attached to the visual tree"); + throw new InvalidOperationException("Target control is not in the same tree as the popup parent"); } - positionerParameters.AnchorRectangle = new Rect(default, target.Bounds.Size) - .TransformToAABB(matrix.Value); + var bounds = new Rect(default, target.Bounds.Size); + var anchorRect = rect ?? bounds; + positionerParameters.AnchorRectangle = anchorRect.Intersect(bounds).TransformToAABB(matrix.Value); if (placement == PlacementMode.Right) { - positionerParameters.Anchor = PopupPositioningEdge.TopRight; - positionerParameters.Gravity = PopupPositioningEdge.BottomRight; + positionerParameters.Anchor = PopupAnchor.TopRight; + positionerParameters.Gravity = PopupGravity.BottomRight; } else if (placement == PlacementMode.Bottom) { - positionerParameters.Anchor = PopupPositioningEdge.BottomLeft; - positionerParameters.Gravity = PopupPositioningEdge.BottomRight; + positionerParameters.Anchor = PopupAnchor.BottomLeft; + positionerParameters.Gravity = PopupGravity.BottomRight; } else if (placement == PlacementMode.Left) { - positionerParameters.Anchor = PopupPositioningEdge.TopLeft; - positionerParameters.Gravity = PopupPositioningEdge.BottomLeft; + positionerParameters.Anchor = PopupAnchor.TopLeft; + positionerParameters.Gravity = PopupGravity.BottomLeft; } else if (placement == PlacementMode.Top) { - positionerParameters.Anchor = PopupPositioningEdge.TopLeft; - positionerParameters.Gravity = PopupPositioningEdge.TopRight; + positionerParameters.Anchor = PopupAnchor.TopLeft; + positionerParameters.Gravity = PopupGravity.TopRight; } else if (placement == PlacementMode.AnchorAndGravity) { diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs index bd23a13e7f..04167bbd15 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs @@ -39,38 +39,38 @@ namespace Avalonia.Controls.Primitives.PopupPositioning } - private static Point GetAnchorPoint(Rect anchorRect, PopupPositioningEdge edge) + private static Point GetAnchorPoint(Rect anchorRect, PopupAnchor edge) { double x, y; - if ((edge & PopupPositioningEdge.Left) != 0) + if ((edge & PopupAnchor.Left) != 0) x = anchorRect.X; - else if ((edge & PopupPositioningEdge.Right) != 0) + else if ((edge & PopupAnchor.Right) != 0) x = anchorRect.Right; else x = anchorRect.X + anchorRect.Width / 2; - if ((edge & PopupPositioningEdge.Top) != 0) + if ((edge & PopupAnchor.Top) != 0) y = anchorRect.Y; - else if ((edge & PopupPositioningEdge.Bottom) != 0) + else if ((edge & PopupAnchor.Bottom) != 0) y = anchorRect.Bottom; else y = anchorRect.Y + anchorRect.Height / 2; return new Point(x, y); } - private static Point Gravitate(Point anchorPoint, Size size, PopupPositioningEdge gravity) + private static Point Gravitate(Point anchorPoint, Size size, PopupGravity gravity) { double x, y; - if ((gravity & PopupPositioningEdge.Left) != 0) + if ((gravity & PopupGravity.Left) != 0) x = -size.Width; - else if ((gravity & PopupPositioningEdge.Right) != 0) + else if ((gravity & PopupGravity.Right) != 0) x = 0; else x = -size.Width / 2; - if ((gravity & PopupPositioningEdge.Top) != 0) + if ((gravity & PopupGravity.Top) != 0) y = -size.Height; - else if ((gravity & PopupPositioningEdge.Bottom) != 0) + else if ((gravity & PopupGravity.Bottom) != 0) y = 0; else y = -size.Height / 2; @@ -89,7 +89,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning private void Update(Size translatedSize, Size originalSize, - Rect anchorRect, PopupPositioningEdge anchor, PopupPositioningEdge gravity, + Rect anchorRect, PopupAnchor anchor, PopupGravity gravity, PopupPositionerConstraintAdjustment constraintAdjustment, Point offset) { var parentGeometry = _popup.ParentClientAreaScreenGeometry; @@ -116,28 +116,28 @@ namespace Avalonia.Controls.Primitives.PopupPositioning var bounds = GetBounds(); - bool FitsInBounds(Rect rc, PopupPositioningEdge edge = PopupPositioningEdge.AllMask) + bool FitsInBounds(Rect rc, PopupAnchor edge = PopupAnchor.AllMask) { - if ((edge & PopupPositioningEdge.Left) != 0 + if ((edge & PopupAnchor.Left) != 0 && rc.X < bounds.X) return false; - if ((edge & PopupPositioningEdge.Top) != 0 + if ((edge & PopupAnchor.Top) != 0 && rc.Y < bounds.Y) return false; - if ((edge & PopupPositioningEdge.Right) != 0 + if ((edge & PopupAnchor.Right) != 0 && rc.Right > bounds.Right) return false; - if ((edge & PopupPositioningEdge.Bottom) != 0 + if ((edge & PopupAnchor.Bottom) != 0 && rc.Bottom > bounds.Bottom) return false; return true; } - Rect GetUnconstrained(PopupPositioningEdge a, PopupPositioningEdge g) => + Rect GetUnconstrained(PopupAnchor a, PopupGravity g) => new Rect(Gravitate(GetAnchorPoint(anchorRect, a), translatedSize, g) + offset, translatedSize); @@ -145,11 +145,11 @@ namespace Avalonia.Controls.Primitives.PopupPositioning // If flipping geometry and anchor is allowed and helps, use the flipped one, // otherwise leave it as is - if (!FitsInBounds(geo, PopupPositioningEdge.HorizontalMask) + if (!FitsInBounds(geo, PopupAnchor.HorizontalMask) && (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipX) != 0) { var flipped = GetUnconstrained(anchor.FlipX(), gravity.FlipX()); - if (FitsInBounds(flipped, PopupPositioningEdge.HorizontalMask)) + if (FitsInBounds(flipped, PopupAnchor.HorizontalMask)) geo = geo.WithX(flipped.X); } @@ -163,11 +163,11 @@ namespace Avalonia.Controls.Primitives.PopupPositioning // If flipping geometry and anchor is allowed and helps, use the flipped one, // otherwise leave it as is - if (!FitsInBounds(geo, PopupPositioningEdge.VerticalMask) + if (!FitsInBounds(geo, PopupAnchor.VerticalMask) && (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipY) != 0) { var flipped = GetUnconstrained(anchor.FlipY(), gravity.FlipY()); - if (FitsInBounds(flipped, PopupPositioningEdge.VerticalMask)) + if (FitsInBounds(flipped, PopupAnchor.VerticalMask)) geo = geo.WithY(flipped.Y); } diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 788fe03162..3b167d52e1 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -82,11 +82,12 @@ namespace Avalonia.Controls.Primitives } public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset, - PopupPositioningEdge anchor = PopupPositioningEdge.None, - PopupPositioningEdge gravity = PopupPositioningEdge.None) + PopupAnchor anchor = PopupAnchor.None, + PopupGravity gravity = PopupGravity.None, + Rect? rect = null) { _positionerParameters.ConfigurePosition(_parent, target, - placement, offset, anchor, gravity); + placement, offset, anchor, gravity, rect); if (_positionerParameters.Size != default) UpdatePosition(); From 4334c124b9ad177228d64e3ce7f1c7de6f12b889 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 May 2020 15:43:17 +0200 Subject: [PATCH 05/92] Added additional placement properties to ContextMenu. --- src/Avalonia.Controls/ContextMenu.cs | 92 +++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 5a60fd37af..286cb6d6d8 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using Avalonia.Controls.Generators; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Interactivity; @@ -19,11 +20,6 @@ namespace Avalonia.Controls /// public class ContextMenu : MenuBase, ISetterValue { - /// - /// Defines the property. - /// - public static readonly StyledProperty PlacementModeProperty = - Popup.PlacementModeProperty.AddOwner(); /// /// Defines the property. @@ -37,6 +33,30 @@ namespace Avalonia.Controls public static readonly StyledProperty VerticalOffsetProperty = Popup.VerticalOffsetProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty PlacementAnchorProperty = + Popup.PlacementAnchorProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty PlacementGravityProperty = + Popup.PlacementGravityProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty PlacementModeProperty = + Popup.PlacementModeProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty PlacementRectProperty = + AvaloniaProperty.Register(nameof(PlacementRect)); + /// /// Defines the property. /// @@ -77,16 +97,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the placement mode of the popup in relation to the . - /// - public PlacementMode PlacementMode - { - get { return GetValue(PlacementModeProperty); } - set { SetValue(PlacementModeProperty, value); } - } - - /// - /// Gets or sets the Horizontal offset of the popup in relation to the + /// Gets or sets the Horizontal offset of the context menu in relation to the . /// public double HorizontalOffset { @@ -95,7 +106,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the Vertical offset of the popup in relation to the + /// Gets or sets the Vertical offset of the popup in relation to the . /// public double VerticalOffset { @@ -103,6 +114,52 @@ namespace Avalonia.Controls set { SetValue(VerticalOffsetProperty, value); } } + /// + /// Gets or sets the anchor point on the when + /// is . + /// + public PopupAnchor PlacementAnchor + { + get { return GetValue(PlacementAnchorProperty); } + set { SetValue(PlacementAnchorProperty, value); } + } + + /// + /// Gets or sets a value which defines in what direction the context menu should open + /// when is . + /// + public PopupGravity PlacementGravity + { + get { return GetValue(PlacementGravityProperty); } + set { SetValue(PlacementGravityProperty, value); } + } + + /// + /// Gets or sets the placement mode of the context menu in relation to the. + /// + public PlacementMode PlacementMode + { + get { return GetValue(PlacementModeProperty); } + set { SetValue(PlacementModeProperty, value); } + } + + /// + /// Gets or sets the the anchor rectangle within the parent that the popup will be placed + /// relative to when is . + /// + /// + /// The placement rect defines a rectangle relative to around + /// which the popup will be opened, with determining which edge + /// of the placement target is used. + /// + /// If unset, the anchor rectangle will be the bounds of the . + /// + public Rect? PlacementRect + { + get { return GetValue(PlacementRectProperty); } + set { SetValue(PlacementRectProperty, value); } + } + /// /// Gets or sets the control that is used to determine the popup's position. /// @@ -187,7 +244,10 @@ namespace Avalonia.Controls { HorizontalOffset = HorizontalOffset, VerticalOffset = VerticalOffset, + PlacementAnchor = PlacementAnchor, + PlacementGravity = PlacementGravity, PlacementMode = PlacementMode, + PlacementRect = PlacementRect, PlacementTarget = PlacementTarget ?? control, StaysOpen = false }; From 964f0b85d44c64231e3eabbee8db5180dda64be2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 May 2020 16:04:51 +0200 Subject: [PATCH 06/92] Tweak doc comments. --- src/Avalonia.Controls/ContextMenu.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 286cb6d6d8..456af7bdde 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -106,7 +106,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the Vertical offset of the popup in relation to the . + /// Gets or sets the Vertical offset of the context menu in relation to the . /// public double VerticalOffset { @@ -144,7 +144,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the the anchor rectangle within the parent that the popup will be placed + /// Gets or sets the the anchor rectangle within the parent that the context menu will be placed /// relative to when is . /// /// From bd6d61c1724f16eae22ed8c117b17ec27923f19e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 13:06:36 -0300 Subject: [PATCH 07/92] initial test of styling menus and context menus. --- src/Avalonia.Themes.Fluent/ContextMenu.xaml | 37 +++++++++++---------- src/Avalonia.Themes.Fluent/Menu.xaml | 2 +- src/Avalonia.Themes.Fluent/MenuItem.xaml | 37 ++++++++++++++++----- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/ContextMenu.xaml b/src/Avalonia.Themes.Fluent/ContextMenu.xaml index 75f8f7c23d..9a583e2e30 100644 --- a/src/Avalonia.Themes.Fluent/ContextMenu.xaml +++ b/src/Avalonia.Themes.Fluent/ContextMenu.xaml @@ -1,22 +1,25 @@ \ No newline at end of file + diff --git a/src/Avalonia.Themes.Fluent/MenuItem.xaml b/src/Avalonia.Themes.Fluent/MenuItem.xaml index 314416cda0..d2586c6aa8 100644 --- a/src/Avalonia.Themes.Fluent/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/MenuItem.xaml @@ -2,13 +2,23 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:conv="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls" xmlns:sys="clr-namespace:System;assembly=netstandard"> + + + + + + + + + + 12,9,12,12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index 15e157f573..15e0925f85 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -343,5 +343,8 @@ + + + diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj index 3c3e14010d..00846f33fd 100644 --- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj +++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj @@ -17,6 +17,14 @@ + + + + + + MSBuild:Compile + + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml index 266acce971..dbbe3d6e69 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml @@ -52,4 +52,5 @@ + diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml new file mode 100644 index 0000000000..1fe9788839 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 97bbb3ba92bf35ea80aaa0bf0868117147070466 Mon Sep 17 00:00:00 2001 From: JamRemco <58340108+JamRemco@users.noreply.github.com> Date: Wed, 3 Jun 2020 14:10:54 +0200 Subject: [PATCH 11/92] added light fluent resources --- src/Avalonia.Themes.Default/ToggleSwitch.xaml | 335 +++++++++--------- .../Accents/FluentControlResourcesDark.xaml | 62 ++++ src/Avalonia.Themes.Fluent/ToggleSwitch.xaml | 39 +- 3 files changed, 254 insertions(+), 182 deletions(-) diff --git a/src/Avalonia.Themes.Default/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/ToggleSwitch.xaml index e929810376..3cdcb3f6c5 100644 --- a/src/Avalonia.Themes.Default/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Default/ToggleSwitch.xaml @@ -1,170 +1,177 @@ - + - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - + - + + + + + + + - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index 7364c339f1..8265c22a88 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -343,5 +343,67 @@ + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml index 1fe9788839..2b665753b1 100644 --- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -2,20 +2,21 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - - + + + - + @@ -31,17 +32,18 @@ - - - - - + + --> + + From 23fc2c945ee542a7a12d3185f03683274e7b65a9 Mon Sep 17 00:00:00 2001 From: JamRemco <58340108+JamRemco@users.noreply.github.com> Date: Thu, 4 Jun 2020 20:33:32 +0200 Subject: [PATCH 12/92] Dark resources and some minors --- src/Avalonia.Controls/ToggleSwitch.cs | 7 +- src/Avalonia.Themes.Default/ToggleSwitch.xaml | 11 +- .../Accents/FluentControlResourcesDark.xaml | 114 +++++++++--------- .../Accents/FluentControlResourcesLight.xaml | 60 ++++++++- src/Avalonia.Themes.Fluent/ToggleSwitch.xaml | 12 +- 5 files changed, 134 insertions(+), 70 deletions(-) diff --git a/src/Avalonia.Controls/ToggleSwitch.cs b/src/Avalonia.Controls/ToggleSwitch.cs index bc9c6f1ff4..19f282d2d1 100644 --- a/src/Avalonia.Controls/ToggleSwitch.cs +++ b/src/Avalonia.Controls/ToggleSwitch.cs @@ -3,9 +3,14 @@ namespace Avalonia.Controls { /// - /// A check box control. + /// A WinUi like ToggleSwitch control. /// public class ToggleSwitch : ToggleButton { } } +/********** Todo *********** + * + * Implement ContenOff property + * Implement ContentOn property. +*/ diff --git a/src/Avalonia.Themes.Default/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/ToggleSwitch.xaml index 3cdcb3f6c5..69ac64bb28 100644 --- a/src/Avalonia.Themes.Default/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Default/ToggleSwitch.xaml @@ -93,7 +93,7 @@ - + @@ -104,7 +104,7 @@ - + @@ -126,7 +126,7 @@ - + --> + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index 8265c22a88..da3c20e844 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -345,65 +345,65 @@ - + 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index 15e0925f85..00e2e38ade 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -345,6 +345,64 @@ - + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml index 2b665753b1..061551f9e7 100644 --- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -93,7 +93,7 @@ - + @@ -104,7 +104,7 @@ - + @@ -119,14 +119,14 @@ - + - + - + --> diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml index 061551f9e7..b423416d7c 100644 --- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -143,9 +143,9 @@ - + --> - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + - diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml index b423416d7c..2c9309fe93 100644 --- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -62,7 +62,7 @@ Name="PART_CircleThumb" Height="10" Width="10" Canvas.Top="7" - Canvas.Left="5"/> + /> @@ -126,7 +126,7 @@ - + + + + + + + + + + + + + + + + From 7b2ada673ac976b33015d83e799054d4bfcb18b1 Mon Sep 17 00:00:00 2001 From: JamRemco <58340108+JamRemco@users.noreply.github.com> Date: Wed, 10 Jun 2020 01:50:33 +0200 Subject: [PATCH 20/92] Fix Animation --- samples/ControlCatalog/App.xaml | 12 +- samples/ControlCatalog/MainView.xaml.cs | 44 +- samples/ControlCatalog/MainWindow.xaml | 2 +- .../Pages/AutoCompleteBoxPage.xaml | 1 + src/Avalonia.Themes.Default/ToggleSwitch.xaml | 141 +++++-- src/Avalonia.Themes.Fluent/ToggleSwitch.xaml | 396 ++++++++++-------- 6 files changed, 356 insertions(+), 240 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 20ca291910..de0c273dfb 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -2,10 +2,16 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.App"> - + + + + + + + + diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 2d01d37dbe..3fd94bd60a 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -32,30 +32,30 @@ namespace ControlCatalog } - var light = new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) - { - Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default") - }; - var dark = new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) - { - Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default") - }; + //var light = new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + //{ + // Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default") + //}; + //var dark = new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + //{ + // Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default") + //}; - var themes = this.Find("Themes"); - themes.SelectionChanged += (sender, e) => - { - switch (themes.SelectedIndex) - { - case 0: - Styles[0] = light; - break; - case 1: - Styles[0] = dark; - break; - } - }; - Styles.Add(light); + //var themes = this.Find("Themes"); + //themes.SelectionChanged += (sender, e) => + //{ + // switch (themes.SelectedIndex) + // { + // case 0: + // Styles[0] = light; + // break; + // case 1: + // Styles[0] = dark; + // break; + // } + //}; + //Styles.Add(light); var decorations = this.Find("Decorations"); decorations.SelectionChanged += (sender, e) => diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index a0bb956425..670e30725b 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -7,7 +7,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:ControlCatalog.ViewModels" xmlns:v="clr-namespace:ControlCatalog.Views" - x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="Transparent"> + x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}"> diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml index f90a0c4658..8888cf921a 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml @@ -32,6 +32,7 @@ + Light diff --git a/src/Avalonia.Themes.Default/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/ToggleSwitch.xaml index 556e32faab..f7e2c209a2 100644 --- a/src/Avalonia.Themes.Default/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Default/ToggleSwitch.xaml @@ -1,7 +1,9 @@ - + + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:sys="clr-namespace:System;assembly=netstandard"> + @@ -12,7 +14,7 @@ - @@ -44,9 +46,10 @@ - + + - - - + Canvas.Top="7"/> + - - + + + + - + + + - - + + + + + @@ -157,22 +182,45 @@ + - + + + + + - - - - - + + + + + + + + + + + + + + + + - - + + + + 5 + 28 + RoyalBlue + White - + diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml index 2c9309fe93..bae8151563 100644 --- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -1,177 +1,236 @@ + - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:sys="clr-namespace:System;assembly=netstandard"> + - + - - - + - + - - - + + /> - + + + + - - - - - - + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + 5 + 26 + RoyalBlue + + White + + + From 006186b981d0b4a2f92c270b7d9d4eab03dbc6bc Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 9 Jun 2020 23:59:29 +0300 Subject: [PATCH 21/92] Fix machine epsilon for double --- src/Avalonia.Base/Utilities/MathUtilities.cs | 9 ++-- .../Utilities/MathUtilitiesTests.cs | 51 +++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index a1a2d6c3d0..3fb5f7a162 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -8,6 +8,9 @@ namespace Avalonia.Utilities /// public static class MathUtilities { + // smallest such that 1.0+DoubleEpsilon != 1.0 + private const double DoubleEpsilon = 2.2204460492503131e-016; + /// /// AreClose - Returns whether or not two doubles are "close". That is, whether or /// not they are within epsilon of each other. @@ -18,7 +21,7 @@ namespace Avalonia.Utilities { //in case they are Infinities (then epsilon check does not work) if (value1 == value2) return true; - double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * double.Epsilon; + double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DoubleEpsilon; double delta = value1 - value2; return (-eps < delta) && (eps > delta); } @@ -78,7 +81,7 @@ namespace Avalonia.Utilities /// The double to compare to 1. public static bool IsOne(double value) { - return Math.Abs(value - 1.0) < 10.0 * double.Epsilon; + return Math.Abs(value - 1.0) < 10.0 * DoubleEpsilon; } /// @@ -88,7 +91,7 @@ namespace Avalonia.Utilities /// The double to compare to 0. public static bool IsZero(double value) { - return Math.Abs(value) < 10.0 * double.Epsilon; + return Math.Abs(value) < 10.0 * DoubleEpsilon; } /// diff --git a/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs new file mode 100644 index 0000000000..708e703a1d --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs @@ -0,0 +1,51 @@ +using Avalonia.Utilities; +using Xunit; + +namespace Avalonia.Base.UnitTests.Utilities +{ + public class MathUtilitiesTests + { + [Fact] + public void Two_Equivalent_Double_Values_Are_Close() + { + const int N = 10; + var x = 42.42; + var y = 0.0; + var dx = x / N; + + for (var i = 0; i < N; ++i) + y += dx; + var actual = MathUtilities.AreClose(x, y); + + Assert.True(actual); + } + + [Fact] + public void Calculated_Double_One_Is_One() + { + const int N = 10; + var dx = 1.0 / N; + var x = 0.0; + + for (var i = 0; i < N; ++i) + x += dx; + var actual = MathUtilities.IsOne(x); + + Assert.True(actual); + } + + [Fact] + public void Calculated_Double_Zero_Is_Zero() + { + const int N = 10; + var x = 1.0; + var dx = x / N; + + for (var i = 0; i < N; ++i) + x -= dx; + var actual = MathUtilities.IsZero(x); + + Assert.True(actual); + } + } +} From a2b5b3ad3b035692d81ec1645372cefbc2ea1426 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Wed, 10 Jun 2020 10:19:47 +0300 Subject: [PATCH 22/92] Fix machine epsilon for double elsewhere --- src/Avalonia.Base/Properties/AssemblyInfo.cs | 3 +- src/Avalonia.Base/Utilities/MathUtilities.cs | 2 +- src/Avalonia.Controls/Grid.cs | 35 ++++--------------- src/Avalonia.Controls/Slider.cs | 2 +- .../Utils/BorderRenderHelper.cs | 3 +- src/Avalonia.Visuals/Media/DrawingContext.cs | 5 +-- .../Media/TextFormatting/TextLayout.cs | 3 +- .../Media/DrawingContextImpl.cs | 2 +- 8 files changed, 19 insertions(+), 36 deletions(-) diff --git a/src/Avalonia.Base/Properties/AssemblyInfo.cs b/src/Avalonia.Base/Properties/AssemblyInfo.cs index 75d58f45d5..fd5e411850 100644 --- a/src/Avalonia.Base/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Base/Properties/AssemblyInfo.cs @@ -7,4 +7,5 @@ using Avalonia.Metadata; [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Data.Converters")] [assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.UnitTests")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +[assembly: InternalsVisibleTo("Avalonia.Controls")] diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 3fb5f7a162..6da695407b 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -9,7 +9,7 @@ namespace Avalonia.Utilities public static class MathUtilities { // smallest such that 1.0+DoubleEpsilon != 1.0 - private const double DoubleEpsilon = 2.2204460492503131e-016; + internal static readonly double DoubleEpsilon = 2.2204460492503131e-016; /// /// AreClose - Returns whether or not two doubles are "close". That is, whether or diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 1781067abb..e10d78917e 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1228,7 +1228,7 @@ namespace Avalonia.Controls Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Count); // avoid processing when asked to distribute "0" - if (!_IsZero(requestedSize)) + if (!MathUtilities.IsZero(requestedSize)) { DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting int end = start + count; @@ -1306,7 +1306,7 @@ namespace Avalonia.Controls } // sanity check: requested size must all be distributed - Debug.Assert(_IsZero(sizeToDistribute)); + Debug.Assert(MathUtilities.IsZero(sizeToDistribute)); } else if (requestedSize <= rangeMaxSize) { @@ -1346,7 +1346,7 @@ namespace Avalonia.Controls } // sanity check: requested size must all be distributed - Debug.Assert(_IsZero(sizeToDistribute)); + Debug.Assert(MathUtilities.IsZero(sizeToDistribute)); } else { @@ -1358,7 +1358,7 @@ namespace Avalonia.Controls double equalSize = requestedSize / count; if (equalSize < maxMaxSize - && !_AreClose(equalSize, maxMaxSize)) + && !MathUtilities.AreClose(equalSize, maxMaxSize)) { // equi-size is less than maximum of maxSizes. // in this case distribute so that smaller definitions grow faster than @@ -2151,7 +2151,7 @@ namespace Avalonia.Controls // and precision of floating-point computation. (However, the resulting // display is subject to anti-aliasing problems. TANSTAAFL.) - if (!_AreClose(roundedTakenSize, finalSize)) + if (!MathUtilities.AreClose(roundedTakenSize, finalSize)) { // Compute deltas for (int i = 0; i < definitions.Count; ++i) @@ -2168,7 +2168,7 @@ namespace Avalonia.Controls if (roundedTakenSize > finalSize) { int i = definitions.Count - 1; - while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0) + while ((adjustedSize > finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i >= 0) { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache - dpiIncrement; @@ -2184,7 +2184,7 @@ namespace Avalonia.Controls else if (roundedTakenSize < finalSize) { int i = 0; - while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Count) + while ((adjustedSize < finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i < definitions.Count) { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache + dpiIncrement; @@ -2595,27 +2595,6 @@ namespace Avalonia.Controls set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); } } - /// - /// fp version of d == 0. - /// - /// Value to check. - /// true if d == 0. - private static bool _IsZero(double d) - { - return (Math.Abs(d) < double.Epsilon); - } - - /// - /// fp version of d1 == d2 - /// - /// First value to compare - /// Second value to compare - /// true if d1 == d2 - private static bool _AreClose(double d1, double d2) - { - return (Math.Abs(d1 - d2) < double.Epsilon); - } - /// /// Returns reference to extended data bag. /// diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index ec23bfa396..6cebe32322 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -193,7 +193,7 @@ namespace Avalonia.Controls var orient = Orientation == Orientation.Horizontal; var pointDen = orient ? _track.Bounds.Width : _track.Bounds.Height; - pointDen += double.Epsilon; // Just add epsilon to avoid divide by zero exceptions. + pointDen += MathUtilities.DoubleEpsilon; // Just add epsilon to avoid divide by zero exceptions. var pointNum = orient ? x.Position.X : x.Position.Y; var logicalPos = MathUtilities.Clamp(pointNum / pointDen, 0.0d, 1.0d); diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs index 51eb6edbea..438cbc8b27 100644 --- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs +++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs @@ -1,6 +1,7 @@ ๏ปฟusing System; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Utilities; namespace Avalonia.Controls.Utils { @@ -119,7 +120,7 @@ namespace Avalonia.Controls.Utils } var rect = new Rect(_size); - if (Math.Abs(borderThickness) > double.Epsilon) + if (!MathUtilities.IsZero(borderThickness)) rect = rect.Deflate(borderThickness * 0.5); var rrect = new RoundedRect(rect, _cornerRadius.TopLeft, _cornerRadius.TopRight, _cornerRadius.BottomRight, _cornerRadius.BottomLeft); diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 6fdcd9631b..b1cf1aecc9 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -4,6 +4,7 @@ using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Avalonia.Threading; +using Avalonia.Utilities; using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Media @@ -154,12 +155,12 @@ namespace Avalonia.Media return; } - if (Math.Abs(radiusX) > double.Epsilon) + if (!MathUtilities.IsZero(radiusX)) { radiusX = Math.Min(radiusX, rect.Width / 2); } - if (Math.Abs(radiusY) > double.Epsilon) + if (!MathUtilities.IsZero(radiusY)) { radiusY = Math.Min(radiusY, rect.Height / 2); } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index 720185a3ad..0292398782 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs @@ -4,6 +4,7 @@ using System.Linq; using Avalonia.Media.Immutable; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Platform; +using Avalonia.Utilities; using Avalonia.Utility; namespace Avalonia.Media.TextFormatting @@ -184,7 +185,7 @@ namespace Avalonia.Media.TextFormatting /// private void UpdateLayout() { - if (_text.IsEmpty || Math.Abs(MaxWidth) < double.Epsilon || Math.Abs(MaxHeight) < double.Epsilon) + if (_text.IsEmpty || MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight)) { var textLine = CreateEmptyTextLine(0); diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index bbb45cf64c..9b7ba4844a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -236,7 +236,7 @@ namespace Avalonia.Direct2D1.Media Math.Max(rrect.RadiiTopRight.X, Math.Max(rrect.RadiiBottomRight.X, rrect.RadiiBottomLeft.X))); var radiusY = Math.Max(rrect.RadiiTopLeft.Y, Math.Max(rrect.RadiiTopRight.Y, Math.Max(rrect.RadiiBottomRight.Y, rrect.RadiiBottomLeft.Y))); - var isRounded = Math.Abs(radiusX) > double.Epsilon || Math.Abs(radiusY) > double.Epsilon; + var isRounded = !MathUtilities.IsZero(radiusX) || !MathUtilities.IsZero(radiusY); if (brush != null) { From c73e777ed2755e226a4b16b71ff25e27fbb26c81 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Wed, 10 Jun 2020 18:53:27 +0300 Subject: [PATCH 23/92] Return hack with double.Epsilon --- src/Avalonia.Base/Properties/AssemblyInfo.cs | 1 - src/Avalonia.Base/Utilities/MathUtilities.cs | 2 +- src/Avalonia.Controls/Slider.cs | 3 +- .../Utilities/MathUtilitiesTests.cs | 29 +++++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Properties/AssemblyInfo.cs b/src/Avalonia.Base/Properties/AssemblyInfo.cs index fd5e411850..0664f22dcb 100644 --- a/src/Avalonia.Base/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Base/Properties/AssemblyInfo.cs @@ -8,4 +8,3 @@ using Avalonia.Metadata; [assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.UnitTests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -[assembly: InternalsVisibleTo("Avalonia.Controls")] diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 6da695407b..3fb5f7a162 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -9,7 +9,7 @@ namespace Avalonia.Utilities public static class MathUtilities { // smallest such that 1.0+DoubleEpsilon != 1.0 - internal static readonly double DoubleEpsilon = 2.2204460492503131e-016; + private const double DoubleEpsilon = 2.2204460492503131e-016; /// /// AreClose - Returns whether or not two doubles are "close". That is, whether or diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 6cebe32322..64378a4eb2 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -193,7 +193,8 @@ namespace Avalonia.Controls var orient = Orientation == Orientation.Horizontal; var pointDen = orient ? _track.Bounds.Width : _track.Bounds.Height; - pointDen += MathUtilities.DoubleEpsilon; // Just add epsilon to avoid divide by zero exceptions. + // Just add epsilon to avoid NaN in case 0/0 + pointDen += double.Epsilon; var pointNum = orient ? x.Position.X : x.Position.Y; var logicalPos = MathUtilities.Clamp(pointNum / pointDen, 0.0d, 1.0d); diff --git a/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs index 708e703a1d..a36b22fee2 100644 --- a/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Utilities; using Xunit; @@ -18,6 +19,7 @@ namespace Avalonia.Base.UnitTests.Utilities var actual = MathUtilities.AreClose(x, y); Assert.True(actual); + Assert.Equal(x, Math.Round(y, 14)); } [Fact] @@ -32,6 +34,7 @@ namespace Avalonia.Base.UnitTests.Utilities var actual = MathUtilities.IsOne(x); Assert.True(actual); + Assert.Equal(1.0, Math.Round(x, 15)); } [Fact] @@ -46,6 +49,32 @@ namespace Avalonia.Base.UnitTests.Utilities var actual = MathUtilities.IsZero(x); Assert.True(actual); + Assert.Equal(0.0, Math.Round(x, 15)); + } + + [Fact] + public void Clamp_Input_NaN_Return_NaN() + { + var clamp = MathUtilities.Clamp(double.NaN, 0.0, 1.0); + Assert.True(double.IsNaN(clamp)); + } + + [Fact] + public void Clamp_Input_NegativeInfinity_Return_Min() + { + const double min = 0.0; + const double max = 1.0; + var actual = MathUtilities.Clamp(double.NegativeInfinity, min, max); + Assert.Equal(min, actual); + } + + [Fact] + public void Clamp_Input_PositiveInfinity_Return_Max() + { + const double min = 0.0; + const double max = 1.0; + var actual = MathUtilities.Clamp(double.PositiveInfinity, min, max); + Assert.Equal(max, actual); } } } From f7d0088d553593ab3079c7f0060a9664717e219b Mon Sep 17 00:00:00 2001 From: JamRemco <58340108+JamRemco@users.noreply.github.com> Date: Wed, 10 Jun 2020 18:46:54 +0200 Subject: [PATCH 24/92] Fix some colors --- samples/ControlCatalog/App.xaml | 11 +- src/Avalonia.Themes.Default/ToggleSwitch.xaml | 388 +++++++++-------- src/Avalonia.Themes.Fluent/ToggleSwitch.xaml | 404 +++++++++--------- 3 files changed, 421 insertions(+), 382 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index de0c273dfb..d289767100 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -2,14 +2,15 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.App"> - + - - + + + + + - - - - - - + - - + + - - - - - - - + + + + + + + + - - - - - - - - - - - + + + + + + + + + - - - - + + + + + + + - - - - - - + + + + + + - - - - - - - - + + + + + + + + - - - + + + + - - - - 5 - 28 - RoyalBlue - White - + + + 5 + 28 + RoyalBlue + + White + + + diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml index bae8151563..2f311043fd 100644 --- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -1,8 +1,8 @@ - + - + @@ -14,8 +14,8 @@ Text="Updates will be automaticly Downloaded and installed shile the computer is shutting down or restarting" TextWrapping="Wrap"/> + FontWeight="Medium" + VerticalAlignment="Bottom" /> @@ -25,154 +25,147 @@ - + FontWeight="Medium"/> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - + - - + + - - - - - - - + + + + + + + + - - - - - - - - - - - + + + + + + + + + - - - - + + + + + + + - - - - - - + + + + + + - - - - - - - - + + + + + + + + - - - + + + + + + + + 5 + 28 + RoyalBlue + White - - - 5 - 26 - RoyalBlue - - White - - + From 1ce6df71863841825651e83e48ea170ee64eb9c5 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Wed, 10 Jun 2020 19:56:00 +0300 Subject: [PATCH 25/92] Remove duplicate code for MathUtilities --- src/Avalonia.Base/Properties/AssemblyInfo.cs | 1 + src/Avalonia.Base/Utilities/MathUtilities.cs | 2 +- src/Avalonia.Controls.DataGrid/DataGrid.cs | 38 ++--- .../DataGridColumnHeader.cs | 2 +- .../DataGridColumns.cs | 16 +-- .../DataGridLength.cs | 2 +- src/Avalonia.Controls.DataGrid/DataGridRow.cs | 2 +- .../DataGridRows.cs | 60 ++++---- .../Primitives/DataGridCellsPresenter.cs | 6 +- .../Utils/DoubleUtil.cs | 136 ------------------ .../Utilities/MathUtilitiesTests.cs | 48 ++++++- 11 files changed, 110 insertions(+), 203 deletions(-) delete mode 100644 src/Avalonia.Controls.DataGrid/Utils/DoubleUtil.cs diff --git a/src/Avalonia.Base/Properties/AssemblyInfo.cs b/src/Avalonia.Base/Properties/AssemblyInfo.cs index 0664f22dcb..692982cdc6 100644 --- a/src/Avalonia.Base/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Base/Properties/AssemblyInfo.cs @@ -8,3 +8,4 @@ using Avalonia.Metadata; [assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.UnitTests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid")] diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 3fb5f7a162..6da695407b 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -9,7 +9,7 @@ namespace Avalonia.Utilities public static class MathUtilities { // smallest such that 1.0+DoubleEpsilon != 1.0 - private const double DoubleEpsilon = 2.2204460492503131e-016; + internal static readonly double DoubleEpsilon = 2.2204460492503131e-016; /// /// AreClose - Returns whether or not two doubles are "close". That is, whether or diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index cfe47a09d5..7e893d131e 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -2681,7 +2681,7 @@ namespace Avalonia.Controls { return; } - Debug.Assert(DoubleUtil.LessThanOrClose(_vScrollBar.Value, _vScrollBar.Maximum)); + Debug.Assert(MathUtilities.LessThanOrClose(_vScrollBar.Value, _vScrollBar.Maximum)); _verticalScrollChangesIgnored++; try @@ -2698,7 +2698,7 @@ namespace Avalonia.Controls } else if (scrollEventType == ScrollEventType.SmallDecrement) { - if (DoubleUtil.GreaterThan(NegVerticalOffset, 0)) + if (MathUtilities.GreaterThan(NegVerticalOffset, 0)) { DisplayData.PendingVerticalScrollHeight -= NegVerticalOffset; } @@ -2717,7 +2717,7 @@ namespace Avalonia.Controls DisplayData.PendingVerticalScrollHeight = _vScrollBar.Value - _verticalOffset; } - if (!DoubleUtil.IsZero(DisplayData.PendingVerticalScrollHeight)) + if (!MathUtilities.IsZero(DisplayData.PendingVerticalScrollHeight)) { // Invalidate so the scroll happens on idle InvalidateRowsMeasure(invalidateIndividualElements: false); @@ -3346,22 +3346,22 @@ namespace Avalonia.Controls bool needHorizScrollbarWithoutVertScrollbar = false; if (allowHorizScrollbar && - DoubleUtil.GreaterThan(totalVisibleWidth, cellsWidth) && - DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth) && - DoubleUtil.LessThanOrClose(horizScrollBarHeight, cellsHeight)) + MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) && + MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth) && + MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight)) { double oldDataHeight = cellsHeight; cellsHeight -= horizScrollBarHeight; Debug.Assert(cellsHeight >= 0); needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true; - if (allowVertScrollbar && (DoubleUtil.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) || - DoubleUtil.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth))) + if (allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) || + MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth))) { // Would we still need a horizontal scrollbar without the vertical one? UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight); if (DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount) { - needHorizScrollbar = DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth - vertScrollBarWidth); + needHorizScrollbar = MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth - vertScrollBarWidth); } } @@ -3374,8 +3374,8 @@ namespace Avalonia.Controls UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight); if (allowVertScrollbar && - DoubleUtil.GreaterThan(cellsHeight, 0) && - DoubleUtil.LessThanOrClose(vertScrollBarWidth, cellsWidth) && + MathUtilities.GreaterThan(cellsHeight, 0) && + MathUtilities.LessThanOrClose(vertScrollBarWidth, cellsWidth) && DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount) { cellsWidth -= vertScrollBarWidth; @@ -3389,9 +3389,9 @@ namespace Avalonia.Controls if (allowHorizScrollbar && needVertScrollbar && !needHorizScrollbar && - DoubleUtil.GreaterThan(totalVisibleWidth, cellsWidth) && - DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth) && - DoubleUtil.LessThanOrClose(horizScrollBarHeight, cellsHeight)) + MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) && + MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth) && + MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight)) { cellsWidth += vertScrollBarWidth; cellsHeight -= horizScrollBarHeight; @@ -3422,7 +3422,7 @@ namespace Avalonia.Controls if (allowVertScrollbar) { if (cellsHeight > 0 && - DoubleUtil.LessThanOrClose(vertScrollBarWidth, cellsWidth) && + MathUtilities.LessThanOrClose(vertScrollBarWidth, cellsWidth) && DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount) { cellsWidth -= vertScrollBarWidth; @@ -3439,9 +3439,9 @@ namespace Avalonia.Controls if (allowHorizScrollbar) { if (cellsWidth > 0 && - DoubleUtil.LessThanOrClose(horizScrollBarHeight, cellsHeight) && - DoubleUtil.GreaterThan(totalVisibleWidth, cellsWidth) && - DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth)) + MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight) && + MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) && + MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth)) { cellsHeight -= horizScrollBarHeight; Debug.Assert(cellsHeight >= 0); @@ -5387,7 +5387,7 @@ namespace Avalonia.Controls private void SetVerticalOffset(double newVerticalOffset) { _verticalOffset = newVerticalOffset; - if (_vScrollBar != null && !DoubleUtil.AreClose(newVerticalOffset, _vScrollBar.Value)) + if (_vScrollBar != null && !MathUtilities.AreClose(newVerticalOffset, _vScrollBar.Value)) { _vScrollBar.Value = _verticalOffset; } diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index df2b03798a..e6cc7e5e40 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -301,7 +301,7 @@ namespace Avalonia.Controls private static bool CanResizeColumn(DataGridColumn column) { if (column.OwningGrid != null && column.OwningGrid.ColumnsInternal != null && column.OwningGrid.UsesStarSizing && - (column.OwningGrid.ColumnsInternal.LastVisibleColumn == column || !DoubleUtil.AreClose(column.OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, column.OwningGrid.CellsWidth))) + (column.OwningGrid.ColumnsInternal.LastVisibleColumn == column || !MathUtilities.AreClose(column.OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, column.OwningGrid.CellsWidth))) { return false; } diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs index 16b63ad696..5b75bc73f9 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs @@ -44,7 +44,7 @@ namespace Avalonia.Controls /// The remaining amount of adjustment. internal double AdjustColumnWidths(int displayIndex, double amount, bool userInitiated) { - if (!DoubleUtil.IsZero(amount)) + if (!MathUtilities.IsZero(amount)) { if (amount < 0) { @@ -777,7 +777,7 @@ namespace Avalonia.Controls private double AdjustStarColumnWidths(int displayIndex, double adjustment, bool userInitiated) { double remainingAdjustment = adjustment; - if (DoubleUtil.IsZero(remainingAdjustment)) + if (MathUtilities.IsZero(remainingAdjustment)) { return remainingAdjustment; } @@ -843,7 +843,7 @@ namespace Avalonia.Controls /// The remaining amount of adjustment. private double AdjustStarColumnWidths(int displayIndex, double remainingAdjustment, bool userInitiated, Func targetWidth) { - if (DoubleUtil.IsZero(remainingAdjustment)) + if (MathUtilities.IsZero(remainingAdjustment)) { return remainingAdjustment; } @@ -1244,7 +1244,7 @@ namespace Avalonia.Controls Debug.Assert(amount < 0); Debug.Assert(column.Width.UnitType != DataGridLengthUnitType.Star); - if (DoubleUtil.GreaterThanOrClose(targetWidth, column.Width.DisplayValue)) + if (MathUtilities.GreaterThanOrClose(targetWidth, column.Width.DisplayValue)) { return amount; } @@ -1271,7 +1271,7 @@ namespace Avalonia.Controls /// The remaining amount of adjustment. private double DecreaseNonStarColumnWidths(int displayIndex, Func targetWidth, double amount, bool reverse, bool affectNewColumns) { - if (DoubleUtil.GreaterThanOrClose(amount, 0)) + if (MathUtilities.GreaterThanOrClose(amount, 0)) { return amount; } @@ -1285,7 +1285,7 @@ namespace Avalonia.Controls (affectNewColumns || column.IsInitialDesiredWidthDetermined))) { amount = DecreaseNonStarColumnWidth(column, Math.Max(column.ActualMinWidth, targetWidth(column)), amount); - if (DoubleUtil.IsZero(amount)) + if (MathUtilities.IsZero(amount)) { break; } @@ -1392,7 +1392,7 @@ namespace Avalonia.Controls /// The remaining amount of adjustment. private double IncreaseNonStarColumnWidths(int displayIndex, Func targetWidth, double amount, bool reverse, bool affectNewColumns) { - if (DoubleUtil.LessThanOrClose(amount, 0)) + if (MathUtilities.LessThanOrClose(amount, 0)) { return amount; } @@ -1406,7 +1406,7 @@ namespace Avalonia.Controls (affectNewColumns || column.IsInitialDesiredWidthDetermined))) { amount = IncreaseNonStarColumnWidth(column, Math.Min(column.ActualMaxWidth, targetWidth(column)), amount); - if (DoubleUtil.IsZero(amount)) + if (MathUtilities.IsZero(amount)) { break; } diff --git a/src/Avalonia.Controls.DataGrid/DataGridLength.cs b/src/Avalonia.Controls.DataGrid/DataGridLength.cs index 6a545a35ec..4841ddd494 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridLength.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridLength.cs @@ -529,7 +529,7 @@ namespace Avalonia.Controls // in this case drop value part and print only "Star" case DataGridLengthUnitType.Star: return ( - DoubleUtil.AreClose(1.0, dataGridLength.Value.Value) + MathUtilities.AreClose(1.0, dataGridLength.Value.Value) ? _starSuffix : Convert.ToString(dataGridLength.Value.Value, culture ?? CultureInfo.CurrentCulture) + DataGridLengthConverter._starSuffix); diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index df200240ff..830eff1102 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -879,7 +879,7 @@ namespace Avalonia.Controls && (double.IsNaN(_detailsContent.Height)) && (AreDetailsVisible) && (!double.IsNaN(_detailsDesiredHeight)) - && !DoubleUtil.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight) + && !MathUtilities.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight) && Slot != -1) { _detailsDesiredHeight = _detailsContent.Bounds.Inflate(_detailsContent.Margin).Height; diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index fda14cf5b4..924156f5f4 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -329,7 +329,7 @@ namespace Avalonia.Controls internal void OnRowsMeasure() { - if (!DoubleUtil.IsZero(DisplayData.PendingVerticalScrollHeight)) + if (!MathUtilities.IsZero(DisplayData.PendingVerticalScrollHeight)) { ScrollSlotsByHeight(DisplayData.PendingVerticalScrollHeight); DisplayData.PendingVerticalScrollHeight = 0; @@ -432,7 +432,7 @@ namespace Avalonia.Controls } else if (DisplayData.FirstScrollingSlot == slot && slot != -1) { - if (!DoubleUtil.IsZero(NegVerticalOffset)) + if (!MathUtilities.IsZero(NegVerticalOffset)) { // First displayed row is partially scrolled of. Let's scroll it so that NegVerticalOffset becomes 0. DisplayData.PendingVerticalScrollHeight = -NegVerticalOffset; @@ -447,7 +447,7 @@ namespace Avalonia.Controls { // Scroll up to the new row so it becomes the first displayed row firstFullSlot = DisplayData.FirstScrollingSlot - 1; - if (DoubleUtil.GreaterThan(NegVerticalOffset, 0)) + if (MathUtilities.GreaterThan(NegVerticalOffset, 0)) { deltaY = -NegVerticalOffset; } @@ -470,7 +470,7 @@ namespace Avalonia.Controls // Figure out how much of the last row is cut off double rowHeight = GetExactSlotElementHeight(DisplayData.LastScrollingSlot); double availableHeight = AvailableSlotElementRoom + rowHeight; - if (DoubleUtil.AreClose(rowHeight, availableHeight)) + if (MathUtilities.AreClose(rowHeight, availableHeight)) { if (DisplayData.LastScrollingSlot == slot) { @@ -499,7 +499,7 @@ namespace Avalonia.Controls { ResetDisplayedRows(); } - if (DoubleUtil.GreaterThanOrClose(GetExactSlotElementHeight(slot), CellsHeight)) + if (MathUtilities.GreaterThanOrClose(GetExactSlotElementHeight(slot), CellsHeight)) { // The entire row won't fit in the DataGrid so we start showing it from the top NegVerticalOffset = 0; @@ -519,7 +519,7 @@ namespace Avalonia.Controls } // - Debug.Assert(DoubleUtil.LessThanOrClose(NegVerticalOffset, _verticalOffset)); + Debug.Assert(MathUtilities.LessThanOrClose(NegVerticalOffset, _verticalOffset)); SetVerticalOffset(_verticalOffset); @@ -1660,7 +1660,7 @@ namespace Avalonia.Controls private void ScrollSlotsByHeight(double height) { Debug.Assert(DisplayData.FirstScrollingSlot >= 0); - Debug.Assert(!DoubleUtil.IsZero(height)); + Debug.Assert(!MathUtilities.IsZero(height)); _scrollingByHeight = true; try @@ -1672,7 +1672,7 @@ namespace Avalonia.Controls { // Scrolling Down int lastVisibleSlot = GetPreviousVisibleSlot(SlotCount); - if (_vScrollBar != null && DoubleUtil.AreClose(_vScrollBar.Maximum, newVerticalOffset)) + if (_vScrollBar != null && MathUtilities.AreClose(_vScrollBar.Maximum, newVerticalOffset)) { // We've scrolled to the bottom of the ScrollBar, automatically place the user at the very bottom // of the DataGrid. If this produces very odd behavior, evaluate the coping strategy used by @@ -1684,7 +1684,7 @@ namespace Avalonia.Controls else { deltaY = GetSlotElementHeight(newFirstScrollingSlot) - NegVerticalOffset; - if (DoubleUtil.LessThan(height, deltaY)) + if (MathUtilities.LessThan(height, deltaY)) { // We've merely covered up more of the same row we're on NegVerticalOffset += height; @@ -1707,7 +1707,7 @@ namespace Avalonia.Controls } else { - while (DoubleUtil.LessThanOrClose(deltaY, height)) + while (MathUtilities.LessThanOrClose(deltaY, height)) { if (newFirstScrollingSlot < lastVisibleSlot) { @@ -1727,7 +1727,7 @@ namespace Avalonia.Controls double rowHeight = GetExactSlotElementHeight(newFirstScrollingSlot); double remainingHeight = height - deltaY; - if (DoubleUtil.LessThanOrClose(rowHeight, remainingHeight)) + if (MathUtilities.LessThanOrClose(rowHeight, remainingHeight)) { deltaY += rowHeight; } @@ -1744,7 +1744,7 @@ namespace Avalonia.Controls else { // Scrolling Up - if (DoubleUtil.GreaterThanOrClose(height + NegVerticalOffset, 0)) + if (MathUtilities.GreaterThanOrClose(height + NegVerticalOffset, 0)) { // We've merely exposing more of the row we're on NegVerticalOffset += height; @@ -1778,7 +1778,7 @@ namespace Avalonia.Controls else { int lastScrollingSlot = DisplayData.LastScrollingSlot; - while (DoubleUtil.GreaterThan(deltaY, height)) + while (MathUtilities.GreaterThan(deltaY, height)) { if (newFirstScrollingSlot > 0) { @@ -1797,7 +1797,7 @@ namespace Avalonia.Controls } double rowHeight = GetExactSlotElementHeight(newFirstScrollingSlot); double remainingHeight = height - deltaY; - if (DoubleUtil.LessThanOrClose(rowHeight + remainingHeight, 0)) + if (MathUtilities.LessThanOrClose(rowHeight + remainingHeight, 0)) { deltaY -= rowHeight; } @@ -1809,7 +1809,7 @@ namespace Avalonia.Controls } } } - if (DoubleUtil.GreaterThanOrClose(0, newVerticalOffset) && newFirstScrollingSlot != 0) + if (MathUtilities.GreaterThanOrClose(0, newVerticalOffset) && newFirstScrollingSlot != 0) { // We've scrolled to the top of the ScrollBar, automatically place the user at the very top // of the DataGrid. If this produces very odd behavior, evaluate the RowHeight estimate. @@ -1822,7 +1822,7 @@ namespace Avalonia.Controls } double firstRowHeight = GetExactSlotElementHeight(newFirstScrollingSlot); - if (DoubleUtil.LessThan(firstRowHeight, NegVerticalOffset)) + if (MathUtilities.LessThan(firstRowHeight, NegVerticalOffset)) { // We've scrolled off more of the first row than what's possible. This can happen // if the first row got shorter (Ex: Collpasing RowDetails) or if the user has a recycling @@ -1838,11 +1838,11 @@ namespace Avalonia.Controls UpdateDisplayedRows(newFirstScrollingSlot, CellsHeight); double firstElementHeight = GetExactSlotElementHeight(DisplayData.FirstScrollingSlot); - if (DoubleUtil.GreaterThan(NegVerticalOffset, firstElementHeight)) + if (MathUtilities.GreaterThan(NegVerticalOffset, firstElementHeight)) { int firstElementSlot = DisplayData.FirstScrollingSlot; // We filled in some rows at the top and now we have a NegVerticalOffset that's greater than the first element - while (newFirstScrollingSlot > 0 && DoubleUtil.GreaterThan(NegVerticalOffset, firstElementHeight)) + while (newFirstScrollingSlot > 0 && MathUtilities.GreaterThan(NegVerticalOffset, firstElementHeight)) { int previousSlot = GetPreviousVisibleSlot(firstElementSlot); if (previousSlot == -1) @@ -1872,7 +1872,7 @@ namespace Avalonia.Controls { _verticalOffset = NegVerticalOffset; } - else if (DoubleUtil.GreaterThan(NegVerticalOffset, newVerticalOffset)) + else if (MathUtilities.GreaterThan(NegVerticalOffset, newVerticalOffset)) { // The scrolled-in row was larger than anticipated. Adjust the DataGrid so the ScrollBar thumb // can stay in the same place @@ -1890,8 +1890,8 @@ namespace Avalonia.Controls DisplayData.FullyRecycleElements(); - Debug.Assert(DoubleUtil.GreaterThanOrClose(NegVerticalOffset, 0)); - Debug.Assert(DoubleUtil.GreaterThanOrClose(_verticalOffset, NegVerticalOffset)); + Debug.Assert(MathUtilities.GreaterThanOrClose(NegVerticalOffset, 0)); + Debug.Assert(MathUtilities.GreaterThanOrClose(_verticalOffset, NegVerticalOffset)); } finally { @@ -2032,7 +2032,7 @@ namespace Avalonia.Controls double deltaY = -NegVerticalOffset; int visibleScrollingRows = 0; - if (DoubleUtil.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0) + if (MathUtilities.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0) { return; } @@ -2044,7 +2044,7 @@ namespace Avalonia.Controls } int slot = firstDisplayedScrollingSlot; - while (slot < SlotCount && !DoubleUtil.GreaterThanOrClose(deltaY, displayHeight)) + while (slot < SlotCount && !MathUtilities.GreaterThanOrClose(deltaY, displayHeight)) { deltaY += GetExactSlotElementHeight(slot); visibleScrollingRows++; @@ -2052,7 +2052,7 @@ namespace Avalonia.Controls slot = GetNextVisibleSlot(slot); } - while (DoubleUtil.LessThan(deltaY, displayHeight) && slot >= 0) + while (MathUtilities.LessThan(deltaY, displayHeight) && slot >= 0) { slot = GetPreviousVisibleSlot(firstDisplayedScrollingSlot); if (slot >= 0) @@ -2063,14 +2063,14 @@ namespace Avalonia.Controls } } // If we're up to the first row, and we still have room left, uncover as much of the first row as we can - if (firstDisplayedScrollingSlot == 0 && DoubleUtil.LessThan(deltaY, displayHeight)) + if (firstDisplayedScrollingSlot == 0 && MathUtilities.LessThan(deltaY, displayHeight)) { double newNegVerticalOffset = Math.Max(0, NegVerticalOffset - displayHeight + deltaY); deltaY += NegVerticalOffset - newNegVerticalOffset; NegVerticalOffset = newNegVerticalOffset; } - if (DoubleUtil.GreaterThan(deltaY, displayHeight) || (DoubleUtil.AreClose(deltaY, displayHeight) && DoubleUtil.GreaterThan(NegVerticalOffset, 0))) + if (MathUtilities.GreaterThan(deltaY, displayHeight) || (MathUtilities.AreClose(deltaY, displayHeight) && MathUtilities.GreaterThan(NegVerticalOffset, 0))) { DisplayData.NumTotallyDisplayedScrollingElements = visibleScrollingRows - 1; } @@ -2108,7 +2108,7 @@ namespace Avalonia.Controls double deltaY = 0; int visibleScrollingRows = 0; - if (DoubleUtil.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0) + if (MathUtilities.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0) { ResetDisplayedRows(); return; @@ -2120,7 +2120,7 @@ namespace Avalonia.Controls } int slot = lastDisplayedScrollingRow; - while (DoubleUtil.LessThan(deltaY, displayHeight) && slot >= 0) + while (MathUtilities.LessThan(deltaY, displayHeight) && slot >= 0) { deltaY += GetExactSlotElementHeight(slot); visibleScrollingRows++; @@ -2542,7 +2542,7 @@ namespace Avalonia.Controls double heightChange = UpdateRowGroupVisibility(rowGroupInfo, isVisible, isDisplayed: false); // Use epsilon instead of 0 here so that in the off chance that our estimates put the vertical offset negative // the user can still scroll to the top since the offset is non-zero - SetVerticalOffset(Math.Max(DoubleUtil.DBL_EPSILON, _verticalOffset + heightChange)); + SetVerticalOffset(Math.Max(MathUtilities.DoubleEpsilon, _verticalOffset + heightChange)); } else { @@ -3024,4 +3024,4 @@ namespace Avalonia.Controls } #endif } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs index 0f513e7f42..6e0703c90f 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs @@ -310,9 +310,9 @@ namespace Avalonia.Controls.Primitives double leftEdge = column.IsFrozen ? frozenLeftEdge : scrollingLeftEdge; double rightEdge = leftEdge + column.ActualWidth; return - DoubleUtil.GreaterThan(rightEdge, 0) && - DoubleUtil.LessThanOrClose(leftEdge, OwningGrid.CellsWidth) && - DoubleUtil.GreaterThan(rightEdge, frozenLeftEdge); // scrolling column covered up by frozen column(s) + MathUtilities.GreaterThan(rightEdge, 0) && + MathUtilities.LessThanOrClose(leftEdge, OwningGrid.CellsWidth) && + MathUtilities.GreaterThan(rightEdge, frozenLeftEdge); // scrolling column covered up by frozen column(s) } } } diff --git a/src/Avalonia.Controls.DataGrid/Utils/DoubleUtil.cs b/src/Avalonia.Controls.DataGrid/Utils/DoubleUtil.cs deleted file mode 100644 index ec0e8836d7..0000000000 --- a/src/Avalonia.Controls.DataGrid/Utils/DoubleUtil.cs +++ /dev/null @@ -1,136 +0,0 @@ -๏ปฟ// (c) Copyright Microsoft Corporation. -// This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. -// All other rights reserved. - -using System; - -namespace Avalonia.Controls.Utils -{ - internal static class DoubleUtil - { - internal const double DBL_EPSILON = 1e-6; - - /// - /// AreClose - Returns whether or not two doubles are "close". That is, whether or - /// not they are within epsilon of each other. Note that this epsilon is proportional - /// to the numbers themselves to that AreClose survives scalar multiplication. - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// - /// bool - the result of the AreClose comparison. - /// - /// The first double to compare. - /// The second double to compare. - public static bool AreClose(double value1, double value2) - { - //in case they are Infinities (then epsilon check does not work) - if (value1 == value2) return true; - // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DBL_EPSILON - double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DBL_EPSILON; - double delta = value1 - value2; - return (-eps < delta) && (eps > delta); - } - - /// - /// GreaterThan - Returns whether or not the first double is greater than the second double. - /// That is, whether or not the first is strictly greater than *and* not within epsilon of - /// the other number. Note that this epsilon is proportional to the numbers themselves - /// to that AreClose survives scalar multiplication. Note, - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// - /// bool - the result of the GreaterThan comparison. - /// - /// The first double to compare. - /// The second double to compare. - public static bool GreaterThan(double value1, double value2) - { - return (value1 > value2) && !AreClose(value1, value2); - } - - /// - /// GreaterThanOrClose - Returns whether or not the first double is greater than or close to - /// the second double. That is, whether or not the first is strictly greater than or within - /// epsilon of the other number. Note that this epsilon is proportional to the numbers - /// themselves to that AreClose survives scalar multiplication. Note, - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// - /// bool - the result of the GreaterThanOrClose comparison. - /// - /// The first double to compare. - /// The second double to compare. - public static bool GreaterThanOrClose(double value1, double value2) - { - return (value1 > value2) || AreClose(value1, value2); - } - - /// - /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0), - /// but this is faster. - /// - /// - /// bool - the result of the IsZero comparison. - /// - /// The double to compare to 0. - public static bool IsZero(double value) - { - return Math.Abs(value) < 10.0 * DBL_EPSILON; - } - - /// - /// LessThan - Returns whether or not the first double is less than the second double. - /// That is, whether or not the first is strictly less than *and* not within epsilon of - /// the other number. Note that this epsilon is proportional to the numbers themselves - /// to that AreClose survives scalar multiplication. Note, - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// - /// bool - the result of the LessThan comparison. - /// - /// The first double to compare. - /// The second double to compare. - public static bool LessThan(double value1, double value2) - { - return (value1 < value2) && !AreClose(value1, value2); - } - - /// - /// LessThanOrClose - Returns whether or not the first double is less than or close to - /// the second double. That is, whether or not the first is strictly less than or within - /// epsilon of the other number. Note that this epsilon is proportional to the numbers - /// themselves to that AreClose survives scalar multiplication. Note, - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// - /// bool - the result of the LessThanOrClose comparison. - /// - /// The first double to compare. - /// The second double to compare. - public static bool LessThanOrClose(double value1, double value2) - { - return (value1 < value2) || AreClose(value1, value2); - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs index a36b22fee2..ccb5aef315 100644 --- a/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs @@ -53,14 +53,14 @@ namespace Avalonia.Base.UnitTests.Utilities } [Fact] - public void Clamp_Input_NaN_Return_NaN() + public void Float_Clamp_Input_NaN_Return_NaN() { var clamp = MathUtilities.Clamp(double.NaN, 0.0, 1.0); Assert.True(double.IsNaN(clamp)); } [Fact] - public void Clamp_Input_NegativeInfinity_Return_Min() + public void Float_Clamp_Input_NegativeInfinity_Return_Min() { const double min = 0.0; const double max = 1.0; @@ -69,12 +69,54 @@ namespace Avalonia.Base.UnitTests.Utilities } [Fact] - public void Clamp_Input_PositiveInfinity_Return_Max() + public void Float_Clamp_Input_PositiveInfinity_Return_Max() { const double min = 0.0; const double max = 1.0; var actual = MathUtilities.Clamp(double.PositiveInfinity, min, max); Assert.Equal(max, actual); } + + [Fact] + public void Zero_Less_Than_One() + { + var actual = MathUtilities.LessThan(0, 1); + Assert.True(actual); + } + + [Fact] + public void One_Not_Less_Than_Zero() + { + var actual = MathUtilities.LessThan(1, 0); + Assert.False(actual); + } + + [Fact] + public void Zero_Not_Greater_Than_One() + { + var actual = MathUtilities.GreaterThan(0, 1); + Assert.False(actual); + } + + [Fact] + public void One_Greater_Than_Zero() + { + var actual = MathUtilities.GreaterThan(1, 0); + Assert.True(actual); + } + + [Fact] + public void One_Less_Than_Or_Close_One() + { + var actual = MathUtilities.LessThanOrClose(1, 1); + Assert.True(actual); + } + + [Fact] + public void One_Greater_Than_Or_Close_One() + { + var actual = MathUtilities.GreaterThanOrClose(1, 1); + Assert.True(actual); + } } } From de0f3285d47d268232196d117c4bccc6a9e5de4c Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 10 Jun 2020 19:10:18 -0400 Subject: [PATCH 26/92] Revert RepeatButton style removing --- src/Avalonia.Themes.Fluent/Button.xaml | 25 ++++++------ src/Avalonia.Themes.Fluent/FluentTheme.xaml | 1 + src/Avalonia.Themes.Fluent/RepeatButton.xaml | 40 ++++++++++++++++++++ 3 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 src/Avalonia.Themes.Fluent/RepeatButton.xaml diff --git a/src/Avalonia.Themes.Fluent/Button.xaml b/src/Avalonia.Themes.Fluent/Button.xaml index 3336e611c1..ee8060f823 100644 --- a/src/Avalonia.Themes.Fluent/Button.xaml +++ b/src/Avalonia.Themes.Fluent/Button.xaml @@ -4,14 +4,13 @@