diff --git a/build/SharedVersion.props b/build/SharedVersion.props index 43ec995ed9..9a268a21e7 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -21,6 +21,6 @@ - + diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm index 00b6dab219..b2d4341bb9 100644 --- a/native/Avalonia.Native/src/OSX/rendertarget.mm +++ b/native/Avalonia.Native/src/OSX/rendertarget.mm @@ -111,7 +111,11 @@ if(_renderbuffer != 0) glDeleteRenderbuffers(1, &_renderbuffer); } - CFRelease(surface); + + if(surface != nullptr) + { + CFRelease(surface); + } } @end @@ -145,6 +149,12 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta } - (void)resize:(AvnPixelSize)size withScale: (float) scale{ + + if(size.Height <= 0) + size.Height = 1; + if(size.Width <= 0) + size.Width = 1; + @synchronized (lock) { if(surface == nil || surface->size.Width != size.Width diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index cacc2204bd..6817d0698e 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -38,8 +38,8 @@ - - + + diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml b/samples/ControlCatalog/Pages/SliderPage.xaml index b3f32ed421..eeb198976b 100644 --- a/samples/ControlCatalog/Pages/SliderPage.xaml +++ b/samples/ControlCatalog/Pages/SliderPage.xaml @@ -22,6 +22,20 @@ IsSnapToTickEnabled="True" Ticks="0,20,25,40,75,100" Width="300" /> + + + + + ToolTip A control which pops up a hint when a control is hovered - @@ -38,6 +38,31 @@ ToolTip bottom placement + + + + + Moving offset + diff --git a/src/Avalonia.Base/Data/Core/SettableNode.cs b/src/Avalonia.Base/Data/Core/SettableNode.cs index d0a918dc88..363d3da0ef 100644 --- a/src/Avalonia.Base/Data/Core/SettableNode.cs +++ b/src/Avalonia.Base/Data/Core/SettableNode.cs @@ -15,7 +15,8 @@ namespace Avalonia.Data.Core private bool ShouldNotSet(object value) { - if (PropertyType == null) + var propertyType = PropertyType; + if (propertyType == null) { return false; } @@ -37,7 +38,7 @@ namespace Avalonia.Data.Core return false; } - if (PropertyType.IsValueType) + if (propertyType.IsValueType) { return lastValue.Equals(value); } diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 5bb2763566..8f9b9583cf 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -1,6 +1,6 @@ // 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. +// All other rights reserved. using Avalonia.Collections; using Avalonia.Controls.Primitives; @@ -92,7 +92,7 @@ namespace Avalonia.Controls private ContentControl _topRightCornerHeader; private Control _frozenColumnScrollBarSpacer; - // the sum of the widths in pixels of the scrolling columns preceding + // the sum of the widths in pixels of the scrolling columns preceding // the first displayed scrolling column private double _horizontalOffset; @@ -143,7 +143,7 @@ namespace Avalonia.Controls private object _uneditedValue; // Represents the original current cell value at the time it enters editing mode. private ICellEditBinding _currentCellEditBinding; - // An approximation of the sum of the heights in pixels of the scrolling rows preceding + // An approximation of the sum of the heights in pixels of the scrolling rows preceding // the first displayed scrolling row. Since the scrolled off rows are discarded, the grid // does not know their actual height. The heights used for the approximation are the ones // set as the rows were scrolled off. @@ -162,7 +162,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(CanUserReorderColumns)); /// - /// Gets or sets a value that indicates whether the user can change + /// Gets or sets a value that indicates whether the user can change /// the column display order by dragging column headers with the mouse. /// public bool CanUserReorderColumns @@ -247,8 +247,8 @@ namespace Avalonia.Controls /// Gets or sets the that is used to paint the background of odd-numbered rows. /// /// - /// The brush that is used to paint the background of odd-numbered rows. The default is a - /// with a + /// The brush that is used to paint the background of odd-numbered rows. The default is a + /// with a /// value of white (ARGB value #00FFFFFF). /// public IBrush AlternatingRowBackground @@ -379,8 +379,8 @@ namespace Avalonia.Controls public bool IsValid { get { return _isValid; } - internal set - { + internal set + { SetAndRaise(IsValidProperty, ref _isValid, value); PseudoClasses.Set(":invalid", !value); } @@ -398,7 +398,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the maximum width of columns in the . + /// Gets or sets the maximum width of columns in the . /// public double MaxColumnWidth { @@ -418,7 +418,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the minimum width of columns in the . + /// Gets or sets the minimum width of columns in the . /// public double MinColumnWidth { @@ -496,7 +496,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(VerticalGridLinesBrush)); /// - /// Gets or sets the that is used to paint grid lines separating columns. + /// Gets or sets the that is used to paint grid lines separating columns. /// public IBrush VerticalGridLinesBrush { @@ -542,7 +542,7 @@ namespace Avalonia.Controls /// /// /// The index of the current selection, or -1 if the selection is empty. - /// + /// public int SelectedIndex { get { return _selectedIndex; } @@ -582,7 +582,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(AutoGenerateColumns)); /// - /// Gets or sets a value that indicates whether columns are created + /// Gets or sets a value that indicates whether columns are created /// automatically when the property is set. /// public bool AutoGenerateColumns @@ -626,7 +626,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(AreRowDetailsFrozen)); /// - /// Gets or sets a value that indicates whether the row details sections remain + /// Gets or sets a value that indicates whether the row details sections remain /// fixed at the width of the display area or can scroll horizontally. /// public bool AreRowDetailsFrozen @@ -881,7 +881,7 @@ namespace Avalonia.Controls { int index = (int)e.NewValue; - // GetDataItem returns null if index is >= Count, we do not check newValue + // GetDataItem returns null if index is >= Count, we do not check newValue // against Count here to avoid enumerating through an Enumerable twice // Setting SelectedItem coerces the finally value of the SelectedIndex object newSelectedItem = (index < 0) ? null : DataConnection.GetDataItem(index); @@ -1168,14 +1168,14 @@ namespace Avalonia.Controls } /// - /// Occurs one time for each public, non-static property in the bound data type when the - /// property is changed and the + /// Occurs one time for each public, non-static property in the bound data type when the + /// property is changed and the /// property is true. /// public event EventHandler AutoGeneratingColumn; /// - /// Occurs before a cell or row enters editing mode. + /// Occurs before a cell or row enters editing mode. /// public event EventHandler BeginningEdit; @@ -1195,7 +1195,7 @@ namespace Avalonia.Controls public event EventHandler CellPointerPressed; /// - /// Occurs when the + /// Occurs when the /// property of a column changes. /// public event EventHandler ColumnDisplayIndexChanged; @@ -1218,14 +1218,14 @@ namespace Avalonia.Controls public event EventHandler CurrentCellChanged; /// - /// Occurs after a + /// Occurs after a /// is instantiated, so that you can customize it before it is used. /// public event EventHandler LoadingRow; /// /// Occurs when a cell in a enters editing mode. - /// + /// /// public event EventHandler PreparingCellForEdit; @@ -1243,7 +1243,7 @@ namespace Avalonia.Controls RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble); /// - /// Occurs when the or + /// Occurs when the or /// property value changes. /// public event EventHandler SelectionChanged @@ -1258,19 +1258,19 @@ namespace Avalonia.Controls public event EventHandler Sorting; /// - /// Occurs when a + /// Occurs when a /// object becomes available for reuse. /// public event EventHandler UnloadingRow; /// - /// Occurs when a new row details template is applied to a row, so that you can customize + /// Occurs when a new row details template is applied to a row, so that you can customize /// the details section before it is used. /// public event EventHandler LoadingRowDetails; /// - /// Occurs when the + /// Occurs when the /// property value changes. /// public event EventHandler RowDetailsVisibilityChanged; @@ -1282,7 +1282,7 @@ namespace Avalonia.Controls /// /// Gets a collection that contains all the columns in the control. - /// + /// public ObservableCollection Columns { get @@ -1456,7 +1456,7 @@ namespace Avalonia.Controls } // Height currently available for cells this value is smaller. This height is reduced by the existence of ColumnHeaders - // or a horizontal scrollbar. Layout is asynchronous so changes to the ColumnHeaders or the horizontal scrollbar are + // or a horizontal scrollbar. Layout is asynchronous so changes to the ColumnHeaders or the horizontal scrollbar are // not reflected immediately. internal double CellsHeight { @@ -1555,7 +1555,7 @@ namespace Avalonia.Controls internal static double HorizontalGridLinesThickness => DATAGRID_horizontalGridLinesThickness; - // the sum of the widths in pixels of the scrolling columns preceding + // the sum of the widths in pixels of the scrolling columns preceding // the first displayed scrolling column internal double HorizontalOffset { @@ -2083,20 +2083,20 @@ namespace Avalonia.Controls } /// - /// Measures the children of a to prepare for - /// arranging them during the - /// pass. + /// Measures the children of a to prepare for + /// arranging them during the + /// pass. /// /// /// The size that the determines it needs during layout, based on its calculations of child object allocated sizes. /// /// - /// The available size that this element can give to child elements. Indicates an upper limit that + /// The available size that this element can give to child elements. Indicates an upper limit that /// child elements should not exceed. /// protected override Size MeasureOverride(Size availableSize) { - // Delay layout until after the initial measure to avoid invalid calculations when the + // Delay layout until after the initial measure to avoid invalid calculations when the // DataGrid is not part of the visual tree if (!_measured) { @@ -2285,6 +2285,17 @@ namespace Avalonia.Controls } } + /// + /// Comparator class so we can sort list by the display index + /// + public class DisplayIndexComparer : IComparer + { + int IComparer.Compare(DataGridColumn x, DataGridColumn y) + { + return (x.DisplayIndexWithFiller < y.DisplayIndexWithFiller) ? -1 : 1; + } + } + /// /// Builds the visual tree for the column header when a new template is applied. /// @@ -2309,8 +2320,11 @@ namespace Avalonia.Controls ColumnsInternal.FillerColumn.IsRepresented = false; } _columnHeadersPresenter.OwningGrid = this; - // Columns were added before before our Template was applied, add the ColumnHeaders now - foreach (DataGridColumn column in ColumnsItemsInternal) + + // Columns were added before our Template was applied, add the ColumnHeaders now + List sortedInternal = new List(ColumnsItemsInternal); + sortedInternal.Sort(new DisplayIndexComparer()); + foreach (DataGridColumn column in sortedInternal) { InsertDisplayedColumnHeader(column); } @@ -3006,7 +3020,7 @@ namespace Avalonia.Controls /// If the editing element has focus, this method will set focus to the DataGrid itself /// in order to force the element to lose focus. It will then wait for the editing element's /// LostFocus event, at which point it will perform the specified action. - /// + /// /// NOTE: It is important to understand that the specified action will be performed when the editing /// element loses focus only if this method returns true. If it returns false, then the action /// will not be performed later on, and should instead be performed by the caller, if necessary. @@ -3065,7 +3079,7 @@ namespace Avalonia.Controls { if (!_scrollingByHeight) { - // Update layout when RowDetails are expanded or collapsed, just updating the vertical scroll bar is not enough + // Update layout when RowDetails are expanded or collapsed, just updating the vertical scroll bar is not enough // since rows could be added or removed InvalidateMeasure(); } @@ -3278,7 +3292,7 @@ namespace Avalonia.Controls { // Current cell was reset because the commit deleted row(s). // Since the user wants to change the current cell, we don't - // want to end up with no current cell. We pick the last row + // want to end up with no current cell. We pick the last row // in the grid which may be the 'new row'. int lastSlot = LastVisibleSlot; if (forCurrentCellChange && @@ -3336,7 +3350,7 @@ namespace Avalonia.Controls if (_ignoreNextScrollBarsLayout) { _ignoreNextScrollBarsLayout = false; - // + // } @@ -3393,7 +3407,7 @@ namespace Avalonia.Controls } // Now cellsWidth is the width potentially available for displaying data cells. - // Now cellsHeight is the height potentially available for displaying data cells. + // Now cellsHeight is the height potentially available for displaying data cells. bool needHorizScrollbar = false; bool needVertScrollbar = false; @@ -3418,7 +3432,7 @@ namespace Avalonia.Controls Debug.Assert(cellsHeight >= 0); needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true; - if (vertScrollBarWidth > 0 && + if (vertScrollBarWidth > 0 && allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) || MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth))) { @@ -3458,7 +3472,7 @@ namespace Avalonia.Controls // we compute the number of visible columns only after we set up the vertical scroll bar. ComputeDisplayedColumns(); - if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) && + if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) && allowHorizScrollbar && needVertScrollbar && !needHorizScrollbar && MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) && @@ -3963,7 +3977,8 @@ namespace Avalonia.Controls { var errorList = binding.ValidationErrors - .SelectMany(ex => ValidationUtil.UnpackException(ex)) + .SelectMany(ValidationUtil.UnpackException) + .Select(ValidationUtil.UnpackDataValidationException) .ToList(); DataValidationErrors.SetErrors(editingElement, errorList); @@ -4124,7 +4139,7 @@ namespace Avalonia.Controls } /// - /// Exits editing mode without trying to commit or revert the editing, and + /// Exits editing mode without trying to commit or revert the editing, and /// without repopulating the edited row's cell. /// //TODO TabStop @@ -5103,9 +5118,9 @@ namespace Avalonia.Controls { if (ctrl || _editingColumnIndex == -1 || IsReadOnly) { - //Go to the next/previous control on the page when + //Go to the next/previous control on the page when // - Ctrl key is used - // - Potential current cell is not edited, or the datagrid is read-only. + // - Potential current cell is not edited, or the datagrid is read-only. return false; } @@ -5516,11 +5531,11 @@ namespace Avalonia.Controls // v---v //|<|_____|###|>| // ^ ^ - // min max + // min max // we want to make the relative size of the thumb reflect the relative size of the viewing area // viewportSize / (max + viewportSize) = cellsWidth / max - // -> viewportSize = max * cellsWidth / (max - cellsWidth) + // -> viewportSize = max * cellsWidth / (max - cellsWidth) // always zero _hScrollBar.Minimum = 0; @@ -5572,7 +5587,7 @@ namespace Avalonia.Controls _hScrollBar.Maximum = 0; if (_hScrollBar.IsVisible) { - // This will trigger a call to this method via Cells_SizeChanged for + // This will trigger a call to this method via Cells_SizeChanged for // which no processing is needed. _hScrollBar.IsVisible = false; _ignoreNextScrollBarsLayout = true; @@ -5591,14 +5606,14 @@ namespace Avalonia.Controls // v---v //|<|_____|###|>| // ^ ^ - // min max + // min max // we want to make the relative size of the thumb reflect the relative size of the viewing area // viewportSize / (max + viewportSize) = cellsWidth / max // -> viewportSize = max * cellsHeight / (totalVisibleHeight - cellsHeight) // -> = max * cellsHeight / (totalVisibleHeight - cellsHeight) // -> = max * cellsHeight / max - // -> = cellsHeight + // -> = cellsHeight // always zero _vScrollBar.Minimum = 0; @@ -5621,7 +5636,7 @@ namespace Avalonia.Controls if (!_vScrollBar.IsVisible) { - // This will trigger a call to this method via Cells_SizeChanged for + // This will trigger a call to this method via Cells_SizeChanged for // which no processing is needed. _vScrollBar.IsVisible = true; if (_vScrollBar.DesiredSize.Width == 0) @@ -5637,7 +5652,7 @@ namespace Avalonia.Controls _vScrollBar.Maximum = 0; if (_vScrollBar.IsVisible) { - // This will trigger a call to this method via Cells_SizeChanged for + // This will trigger a call to this method via Cells_SizeChanged for // which no processing is needed. _vScrollBar.IsVisible = false; _ignoreNextScrollBarsLayout = true; @@ -5660,8 +5675,8 @@ namespace Avalonia.Controls Debug.Assert(slot >= 0); // Before changing selection, check if the current cell needs to be committed, and - // check if the current row needs to be committed. If any of those two operations are required and fail, - // do not change selection, and do not change current cell. + // check if the current row needs to be committed. If any of those two operations are required and fail, + // do not change selection, and do not change current cell. bool wasInEdit = EditingColumnIndex != -1; diff --git a/src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs b/src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs index e1ecb2c562..c36d9708c1 100644 --- a/src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs +++ b/src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs @@ -80,19 +80,24 @@ namespace Avalonia.Controls.Utils { if (exception != null) { - var aggregate = exception as AggregateException; - var exceptions = aggregate == null ? - (IEnumerable)new[] { exception } : - aggregate.InnerExceptions; - var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList(); + var exceptions = exception is AggregateException aggregate ? + aggregate.InnerExceptions : + (IEnumerable)new[] { exception }; - if (filtered.Count > 0) - { - return filtered; - } + return exceptions.Where(x => !(x is BindingChainException)).ToList(); } - return null; + return Array.Empty(); + } + + public static object UnpackDataValidationException(Exception exception) + { + if (exception is DataValidationException dataValidationException) + { + return dataValidationException.ErrorData; + } + + return exception; } /// diff --git a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs index 7c259f0a09..af3fdbd662 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs @@ -11,6 +11,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Layout; namespace Avalonia.Controls { @@ -209,6 +210,18 @@ namespace Avalonia.Controls TextBox.UseFloatingWatermarkProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty HorizontalContentAlignmentProperty = + ContentControl.HorizontalContentAlignmentProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty VerticalContentAlignmentProperty = + ContentControl.VerticalContentAlignmentProperty.AddOwner(); + /// /// Gets or sets the date to display. /// @@ -364,6 +377,25 @@ namespace Avalonia.Controls set { SetValue(UseFloatingWatermarkProperty, value); } } + + /// + /// Gets or sets the horizontal alignment of the content within the control. + /// + public HorizontalAlignment HorizontalContentAlignment + { + get => GetValue(HorizontalContentAlignmentProperty); + set => SetValue(HorizontalContentAlignmentProperty, value); + } + + /// + /// Gets or sets the vertical alignment of the content within the control. + /// + public VerticalAlignment VerticalContentAlignment + { + get => GetValue(VerticalContentAlignmentProperty); + set => SetValue(VerticalContentAlignmentProperty, value); + } + /// /// Occurs when the drop-down /// is closed. diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 5e17182f3e..fb8080f0d4 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -246,7 +246,7 @@ namespace Avalonia.Controls /// /// Opens the menu. /// - public override void Open() => throw new NotSupportedException(); + public override void Open() => Open(null); /// /// Opens a context menu on the specified control. diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index aae041071d..b6b71644a3 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Layout; using Avalonia.Threading; using Avalonia.Utilities; @@ -105,6 +106,19 @@ namespace Avalonia.Controls public static readonly StyledProperty WatermarkProperty = AvaloniaProperty.Register(nameof(Watermark)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty HorizontalContentAlignmentProperty = + ContentControl.HorizontalContentAlignmentProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty VerticalContentAlignmentProperty = + ContentControl.VerticalContentAlignmentProperty.AddOwner(); + private IDisposable _textBoxTextChangedSubscription; private double _value; @@ -256,6 +270,25 @@ namespace Avalonia.Controls set { SetValue(WatermarkProperty, value); } } + + /// + /// Gets or sets the horizontal alignment of the content within the control. + /// + public HorizontalAlignment HorizontalContentAlignment + { + get => GetValue(HorizontalContentAlignmentProperty); + set => SetValue(HorizontalContentAlignmentProperty, value); + } + + /// + /// Gets or sets the vertical alignment of the content within the control. + /// + public VerticalAlignment VerticalContentAlignment + { + get => GetValue(VerticalContentAlignmentProperty); + set => SetValue(VerticalContentAlignmentProperty, value); + } + /// /// Initializes new instance of class. /// diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index 71bd0726d4..ab507d07a2 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Reactive.Linq; using Avalonia.Controls.Metadata; @@ -21,8 +22,8 @@ namespace Avalonia.Controls /// /// Defines the ToolTip.Tip attached property. /// - public static readonly AttachedProperty TipProperty = - AvaloniaProperty.RegisterAttached("Tip"); + public static readonly AttachedProperty TipProperty = + AvaloniaProperty.RegisterAttached("Tip"); /// /// Defines the ToolTip.IsOpen attached property. @@ -57,10 +58,10 @@ namespace Avalonia.Controls /// /// Stores the current instance in the control. /// - internal static readonly AttachedProperty ToolTipProperty = - AvaloniaProperty.RegisterAttached("ToolTip"); + internal static readonly AttachedProperty ToolTipProperty = + AvaloniaProperty.RegisterAttached("ToolTip"); - private IPopupHost _popup; + private IPopupHost? _popup; /// /// Initializes static members of the class. @@ -70,6 +71,10 @@ namespace Avalonia.Controls TipProperty.Changed.Subscribe(ToolTipService.Instance.TipChanged); IsOpenProperty.Changed.Subscribe(ToolTipService.Instance.TipOpenChanged); IsOpenProperty.Changed.Subscribe(IsOpenChanged); + + HorizontalOffsetProperty.Changed.Subscribe(RecalculatePositionOnPropertyChanged); + VerticalOffsetProperty.Changed.Subscribe(RecalculatePositionOnPropertyChanged); + PlacementProperty.Changed.Subscribe(RecalculatePositionOnPropertyChanged); } /// @@ -79,7 +84,7 @@ namespace Avalonia.Controls /// /// The content to be displayed in the control's tooltip. /// - public static object GetTip(Control element) + public static object? GetTip(Control element) { return element.GetValue(TipProperty); } @@ -89,7 +94,7 @@ namespace Avalonia.Controls /// /// The control to get the property from. /// The content to be displayed in the control's tooltip. - public static void SetTip(Control element, object value) + public static void SetTip(Control element, object? value) { element.SetValue(TipProperty, value); } @@ -207,8 +212,8 @@ namespace Avalonia.Controls private static void IsOpenChanged(AvaloniaPropertyChangedEventArgs e) { var control = (Control)e.Sender; - var newValue = (bool)e.NewValue; - ToolTip toolTip; + var newValue = (bool)e.NewValue!; + ToolTip? toolTip; if (newValue) { @@ -235,6 +240,23 @@ namespace Avalonia.Controls toolTip?.UpdatePseudoClasses(newValue); } + private static void RecalculatePositionOnPropertyChanged(AvaloniaPropertyChangedEventArgs args) + { + var control = (Control)args.Sender; + var tooltip = control.GetValue(ToolTipProperty); + if (tooltip == null) + { + return; + } + + tooltip.RecalculatePosition(control); + } + + internal void RecalculatePosition(Control control) + { + _popup?.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control))); + } + private void Open(Control control) { Close(); diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs index 341ab2fe81..9e0bf60f42 100644 --- a/src/Avalonia.Controls/ToolTipService.cs +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -51,10 +51,12 @@ namespace Avalonia.Controls if (e.OldValue is false && e.NewValue is true) { control.DetachedFromVisualTree += ControlDetaching; + control.EffectiveViewportChanged += ControlEffectiveViewportChanged; } else if(e.OldValue is true && e.NewValue is false) { control.DetachedFromVisualTree -= ControlDetaching; + control.EffectiveViewportChanged -= ControlEffectiveViewportChanged; } } @@ -62,6 +64,7 @@ namespace Avalonia.Controls { var control = (Control)sender; control.DetachedFromVisualTree -= ControlDetaching; + control.EffectiveViewportChanged -= ControlEffectiveViewportChanged; Close(control); } @@ -97,6 +100,13 @@ namespace Avalonia.Controls Close(control); } + private void ControlEffectiveViewportChanged(object sender, Layout.EffectiveViewportChangedEventArgs e) + { + var control = (Control)sender; + var toolTip = control.GetValue(ToolTip.ToolTipProperty); + toolTip?.RecalculatePosition(control); + } + private void StartShowTimer(int showDelay, Control control) { _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(showDelay) }; diff --git a/src/Avalonia.Layout/ElementManager.cs b/src/Avalonia.Layout/ElementManager.cs index 70805ba31c..bf5a45966b 100644 --- a/src/Avalonia.Layout/ElementManager.cs +++ b/src/Avalonia.Layout/ElementManager.cs @@ -129,7 +129,7 @@ namespace Avalonia.Layout { for (int i = 0; i < count; i++) { - // Clear from the edges so that ItemsRepeater can optimize on maintaining + // Clear from the edges so that ItemsRepeater can optimize on maintaining // realized indices without walking through all the children every time. int index = realizedIndex == 0 ? realizedIndex + i : (realizedIndex + count - 1) - i; var elementRef = _realizedElements[index]; @@ -212,7 +212,7 @@ namespace Avalonia.Layout public ILayoutable GetRealizedElement(int dataIndex) { return IsVirtualizingContext ? - GetAt(GetRealizedRangeIndexFromDataIndex(dataIndex)) : + GetAt(GetRealizedRangeIndexFromDataIndex(dataIndex)) : _context.GetOrCreateElementAt( dataIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); @@ -252,7 +252,6 @@ namespace Avalonia.Layout (orientation == ScrollOrientation.Vertical ? ScrollOrientation.Horizontal : ScrollOrientation.Vertical) : orientation; - var windowStart = effectiveOrientation == ScrollOrientation.Vertical ? window.Y : window.X; var windowEnd = effectiveOrientation == ScrollOrientation.Vertical ? window.Y + window.Height : window.X + window.Width; var firstElementStart = effectiveOrientation == ScrollOrientation.Vertical ? firstElementBounds.Y : firstElementBounds.X; @@ -273,53 +272,53 @@ namespace Avalonia.Layout switch (args.Action) { case NotifyCollectionChangedAction.Add: - { - OnItemsAdded(args.NewStartingIndex, args.NewItems.Count); - } - break; + { + OnItemsAdded(args.NewStartingIndex, args.NewItems.Count); + } + break; case NotifyCollectionChangedAction.Replace: - { - int oldSize = args.OldItems.Count; - int newSize = args.NewItems.Count; - int oldStartIndex = args.OldStartingIndex; - int newStartIndex = args.NewStartingIndex; - - if (oldSize == newSize && - oldStartIndex == newStartIndex && - IsDataIndexRealized(oldStartIndex) && - IsDataIndexRealized(oldStartIndex + oldSize -1)) { - // Straight up replace of n items within the realization window. - // Removing and adding might causes us to lose the anchor causing us - // to throw away all containers and start from scratch. - // Instead, we can just clear those items and set the element to - // null (sentinel) and let the next measure get new containers for them. - var startRealizedIndex = GetRealizedRangeIndexFromDataIndex(oldStartIndex); - for (int realizedIndex = startRealizedIndex; realizedIndex < startRealizedIndex + oldSize; realizedIndex++) + int oldSize = args.OldItems.Count; + int newSize = args.NewItems.Count; + int oldStartIndex = args.OldStartingIndex; + int newStartIndex = args.NewStartingIndex; + + if (oldSize == newSize && + oldStartIndex == newStartIndex && + IsDataIndexRealized(oldStartIndex) && + IsDataIndexRealized(oldStartIndex + oldSize - 1)) { - var elementRef = _realizedElements[realizedIndex]; - - if (elementRef != null) + // Straight up replace of n items within the realization window. + // Removing and adding might causes us to lose the anchor causing us + // to throw away all containers and start from scratch. + // Instead, we can just clear those items and set the element to + // null (sentinel) and let the next measure get new containers for them. + var startRealizedIndex = GetRealizedRangeIndexFromDataIndex(oldStartIndex); + for (int realizedIndex = startRealizedIndex; realizedIndex < startRealizedIndex + oldSize; realizedIndex++) { - _context.RecycleElement(elementRef); - _realizedElements[realizedIndex] = null; + var elementRef = _realizedElements[realizedIndex]; + + if (elementRef != null) + { + _context.RecycleElement(elementRef); + _realizedElements[realizedIndex] = null; + } } } + else + { + OnItemsRemoved(oldStartIndex, oldSize); + OnItemsAdded(newStartIndex, newSize); + } } - else - { - OnItemsRemoved(oldStartIndex, oldSize); - OnItemsAdded(newStartIndex, newSize); - } - } - break; + break; case NotifyCollectionChangedAction.Remove: - { - OnItemsRemoved(args.OldStartingIndex, args.OldItems.Count); - } - break; + { + OnItemsRemoved(args.OldStartingIndex, args.OldItems.Count); + } + break; case NotifyCollectionChangedAction.Reset: ClearRealizedRange(); @@ -376,7 +375,7 @@ namespace Avalonia.Layout int backCutoffIndex = realizedRangeSize; for (int i = 0; - i _firstRealizedDataIndex && + if (newStartingIndex >= _firstRealizedDataIndex && newStartingIndex <= lastRealizedDataIndex) { // Inserted within the realized range int insertRangeStartIndex = newStartingIndex - _firstRealizedDataIndex; for (int i = 0; i < count; i++) { - // Insert null (sentinel) here instead of an element, that way we dont + // Insert null (sentinel) here instead of an element, that way we dont // end up creating a lot of elements only to be thrown out in the next layout. int insertRangeIndex = insertRangeStartIndex + i; int dataIndex = newStartingIndex + i; diff --git a/src/Avalonia.Themes.Default/CalendarDatePicker.xaml b/src/Avalonia.Themes.Default/CalendarDatePicker.xaml index bc1aba1a03..aab7d06c46 100644 --- a/src/Avalonia.Themes.Default/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Default/CalendarDatePicker.xaml @@ -93,6 +93,8 @@ Watermark="{TemplateBinding Watermark}" UseFloatingWatermark="{TemplateBinding UseFloatingWatermark}" DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" + VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" + HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Grid.Column="0"/>