Browse Source

Merge branch 'master' into linux-ime-cjk

pull/5258/head
Dan Walmsley 5 years ago
committed by GitHub
parent
commit
57199a1179
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/SharedVersion.props
  2. 12
      native/Avalonia.Native/src/OSX/rendertarget.mm
  3. 4
      samples/ControlCatalog/Pages/DataGridPage.xaml
  4. 14
      samples/ControlCatalog/Pages/SliderPage.xaml
  5. 27
      samples/ControlCatalog/Pages/ToolTipPage.xaml
  6. 5
      src/Avalonia.Base/Data/Core/SettableNode.cs
  7. 125
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  8. 25
      src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs
  9. 32
      src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
  10. 2
      src/Avalonia.Controls/ContextMenu.cs
  11. 33
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  12. 40
      src/Avalonia.Controls/ToolTip.cs
  13. 10
      src/Avalonia.Controls/ToolTipService.cs
  14. 87
      src/Avalonia.Layout/ElementManager.cs
  15. 2
      src/Avalonia.Themes.Default/CalendarDatePicker.xaml
  16. 4
      src/Avalonia.Themes.Default/NumericUpDown.xaml
  17. 3
      src/Avalonia.Themes.Fluent/Controls/Button.xaml
  18. 10
      src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml
  19. 4
      src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
  20. 2
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  21. 6
      src/Avalonia.Themes.Fluent/Controls/FocusAdorner.xaml
  22. 2
      src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml
  23. 2
      src/Avalonia.Themes.Fluent/Controls/Menu.xaml
  24. 6
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
  25. 7
      src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml
  26. 2
      src/Avalonia.Themes.Fluent/Controls/TabControl.xaml
  27. 10
      src/Avalonia.Themes.Fluent/Controls/TabItem.xaml
  28. 2
      src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml
  29. 4
      src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
  30. 2
      src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml
  31. 2
      src/Avalonia.X11/X11Atoms.cs
  32. 94
      src/Avalonia.X11/X11Screens.cs
  33. 4
      src/Avalonia.X11/X11Structs.cs
  34. 7
      src/Avalonia.X11/XLib.cs
  35. 5
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs
  36. 11
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  37. 20
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  38. 49
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

2
build/SharedVersion.props

@ -21,6 +21,6 @@
</PropertyGroup>
<ItemGroup Label="PackageIcon">
<None Include="$(MSBuildThisFileDirectory)/Assets/Icon.png" Pack="true" PackagePath=""/>
<None Include="$(MSBuildThisFileDirectory)/Assets/Icon.png" Pack="true" Visible="false" PackagePath=""/>
</ItemGroup>
</Project>

12
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

4
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -38,8 +38,8 @@
<DataGrid.Columns>
<DataGridTextColumn Header="Country" Binding="{Binding Name}" Width="6*" />
<DataGridTextColumn Header="Region" Binding="{Binding Region}" Width="4*" />
<DataGridTextColumn Header="Population" Binding="{Binding Population}" Width="3*" />
<DataGridTextColumn Header="Area" Binding="{Binding Area}" Width="3*" />
<DataGridTextColumn DisplayIndex="3" Header="Population" Binding="{Binding Population}" Width="3*" />
<DataGridTextColumn DisplayIndex="2" Header="Area" Binding="{Binding Area}" Width="3*" />
<DataGridTextColumn Header="GDP" Binding="{Binding GDP}" Width="3*" />
</DataGrid.Columns>
</DataGrid>

14
samples/ControlCatalog/Pages/SliderPage.xaml

@ -22,6 +22,20 @@
IsSnapToTickEnabled="True"
Ticks="0,20,25,40,75,100"
Width="300" />
<Slider Name="SliderWithTooltip"
Value="0"
Minimum="0"
Maximum="100"
Width="300">
<Slider.Styles>
<Style Selector="Slider /template/ Thumb">
<Setter Property="ToolTip.Tip" Value="{Binding $parent[Slider].Value, Mode=OneWay, StringFormat='Value \{0:f\}'}" />
<Setter Property="ToolTip.Placement" Value="Top" />
<Setter Property="ToolTip.VerticalOffset" Value="-10" />
<Setter Property="ToolTip.HorizontalOffset" Value="-30" />
</Style>
</Slider.Styles>
</Slider>
<Slider Value="0"
Minimum="0"
Maximum="100"

27
samples/ControlCatalog/Pages/ToolTipPage.xaml

@ -6,7 +6,7 @@
<TextBlock Classes="h1">ToolTip</TextBlock>
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock>
<Grid RowDefinitions="Auto,Auto"
<Grid RowDefinitions="Auto,Auto,Auto"
ColumnDefinitions="Auto,Auto"
Margin="0,16,0,0"
HorizontalAlignment="Center">
@ -38,6 +38,31 @@
</ToolTip.Tip>
<TextBlock>ToolTip bottom placement</TextBlock>
</Border>
<Border Grid.Row="2"
Grid.ColumnSpan="2"
Background="{DynamicResource SystemAccentColor}"
Margin="5"
Padding="50"
ToolTip.Tip="Hello"
ToolTip.Placement="Top">
<Border.Styles>
<Style Selector="Border">
<Style.Animations>
<Animation Duration="0:0:2" IterationCount="Infinite">
<KeyFrame KeyTime="0:0:0">
<Setter Property="ToolTip.HorizontalOffset" Value="0" />
<Setter Property="ToolTip.VerticalOffset" Value="-50" />
</KeyFrame>
<KeyFrame KeyTime="0:0:2" >
<Setter Property="ToolTip.HorizontalOffset" Value="100" />
<Setter Property="ToolTip.VerticalOffset" Value="50" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
<TextBlock>Moving offset</TextBlock>
</Border>
</Grid>
</StackPanel>
</UserControl>

5
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);
}

125
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<DataGrid, bool>(nameof(CanUserReorderColumns));
/// <summary>
/// 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.
/// </summary>
public bool CanUserReorderColumns
@ -247,8 +247,8 @@ namespace Avalonia.Controls
/// Gets or sets the <see cref="T:System.Windows.Media.Brush" /> that is used to paint the background of odd-numbered rows.
/// </summary>
/// <returns>
/// The brush that is used to paint the background of odd-numbered rows. The default is a
/// <see cref="T:System.Windows.Media.SolidColorBrush" /> with a
/// The brush that is used to paint the background of odd-numbered rows. The default is a
/// <see cref="T:System.Windows.Media.SolidColorBrush" /> with a
/// <see cref="P:System.Windows.Media.SolidColorBrush.Color" /> value of white (ARGB value #00FFFFFF).
/// </returns>
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
}
/// <summary>
/// Gets or sets the maximum width of columns in the <see cref="T:Avalonia.Controls.DataGrid" /> .
/// Gets or sets the maximum width of columns in the <see cref="T:Avalonia.Controls.DataGrid" /> .
/// </summary>
public double MaxColumnWidth
{
@ -418,7 +418,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets or sets the minimum width of columns in the <see cref="T:Avalonia.Controls.DataGrid" />.
/// Gets or sets the minimum width of columns in the <see cref="T:Avalonia.Controls.DataGrid" />.
/// </summary>
public double MinColumnWidth
{
@ -496,7 +496,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, IBrush>(nameof(VerticalGridLinesBrush));
/// <summary>
/// Gets or sets the <see cref="T:System.Windows.Media.Brush" /> that is used to paint grid lines separating columns.
/// Gets or sets the <see cref="T:System.Windows.Media.Brush" /> that is used to paint grid lines separating columns.
/// </summary>
public IBrush VerticalGridLinesBrush
{
@ -542,7 +542,7 @@ namespace Avalonia.Controls
/// </summary>
/// <returns>
/// The index of the current selection, or -1 if the selection is empty.
/// </returns>
/// </returns>
public int SelectedIndex
{
get { return _selectedIndex; }
@ -582,7 +582,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, bool>(nameof(AutoGenerateColumns));
/// <summary>
/// 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 <see cref="P:Avalonia.Controls.DataGrid.ItemsSource" /> property is set.
/// </summary>
public bool AutoGenerateColumns
@ -626,7 +626,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, bool>(nameof(AreRowDetailsFrozen));
/// <summary>
/// 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.
/// </summary>
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
}
/// <summary>
/// Occurs one time for each public, non-static property in the bound data type when the
/// <see cref="P:Avalonia.Controls.DataGrid.ItemsSource" /> property is changed and the
/// Occurs one time for each public, non-static property in the bound data type when the
/// <see cref="P:Avalonia.Controls.DataGrid.ItemsSource" /> property is changed and the
/// <see cref="P:Avalonia.Controls.DataGrid.AutoGenerateColumns" /> property is true.
/// </summary>
public event EventHandler<DataGridAutoGeneratingColumnEventArgs> AutoGeneratingColumn;
/// <summary>
/// Occurs before a cell or row enters editing mode.
/// Occurs before a cell or row enters editing mode.
/// </summary>
public event EventHandler<DataGridBeginningEditEventArgs> BeginningEdit;
@ -1195,7 +1195,7 @@ namespace Avalonia.Controls
public event EventHandler<DataGridCellPointerPressedEventArgs> CellPointerPressed;
/// <summary>
/// Occurs when the <see cref="P:Avalonia.Controls.DataGridColumn.DisplayIndex" />
/// Occurs when the <see cref="P:Avalonia.Controls.DataGridColumn.DisplayIndex" />
/// property of a column changes.
/// </summary>
public event EventHandler<DataGridColumnEventArgs> ColumnDisplayIndexChanged;
@ -1218,14 +1218,14 @@ namespace Avalonia.Controls
public event EventHandler<EventArgs> CurrentCellChanged;
/// <summary>
/// Occurs after a <see cref="T:Avalonia.Controls.DataGridRow" />
/// Occurs after a <see cref="T:Avalonia.Controls.DataGridRow" />
/// is instantiated, so that you can customize it before it is used.
/// </summary>
public event EventHandler<DataGridRowEventArgs> LoadingRow;
/// <summary>
/// Occurs when a cell in a <see cref="T:Avalonia.Controls.DataGridTemplateColumn" /> enters editing mode.
///
///
/// </summary>
public event EventHandler<DataGridPreparingCellForEditEventArgs> PreparingCellForEdit;
@ -1243,7 +1243,7 @@ namespace Avalonia.Controls
RoutedEvent.Register<DataGrid, SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble);
/// <summary>
/// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.SelectedItem" /> or
/// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.SelectedItem" /> or
/// <see cref="P:Avalonia.Controls.DataGrid.SelectedItems" /> property value changes.
/// </summary>
public event EventHandler<SelectionChangedEventArgs> SelectionChanged
@ -1258,19 +1258,19 @@ namespace Avalonia.Controls
public event EventHandler<DataGridColumnEventArgs> Sorting;
/// <summary>
/// Occurs when a <see cref="T:Avalonia.Controls.DataGridRow" />
/// Occurs when a <see cref="T:Avalonia.Controls.DataGridRow" />
/// object becomes available for reuse.
/// </summary>
public event EventHandler<DataGridRowEventArgs> UnloadingRow;
/// <summary>
/// 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.
/// </summary>
public event EventHandler<DataGridRowDetailsEventArgs> LoadingRowDetails;
/// <summary>
/// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.RowDetailsVisibilityMode" />
/// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.RowDetailsVisibilityMode" />
/// property value changes.
/// </summary>
public event EventHandler<DataGridRowDetailsEventArgs> RowDetailsVisibilityChanged;
@ -1282,7 +1282,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets a collection that contains all the columns in the control.
/// </summary>
/// </summary>
public ObservableCollection<DataGridColumn> 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
}
/// <summary>
/// Measures the children of a <see cref="T:Avalonia.Controls.DataGridRow" /> to prepare for
/// arranging them during the
/// <see cref="M:Avalonia.Controls.DataGridRow.ArrangeOverride(System.Windows.Size)" /> pass.
/// Measures the children of a <see cref="T:Avalonia.Controls.DataGridRow" /> to prepare for
/// arranging them during the
/// <see cref="M:Avalonia.Controls.DataGridRow.ArrangeOverride(System.Windows.Size)" /> pass.
/// </summary>
/// <returns>
/// The size that the <see cref="T:Avalonia.Controls.DataGridRow" /> determines it needs during layout, based on its calculations of child object allocated sizes.
/// </returns>
/// <param name="availableSize">
/// 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.
/// </param>
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
}
}
/// <summary>
/// Comparator class so we can sort list by the display index
/// </summary>
public class DisplayIndexComparer : IComparer<DataGridColumn>
{
int IComparer<DataGridColumn>.Compare(DataGridColumn x, DataGridColumn y)
{
return (x.DisplayIndexWithFiller < y.DisplayIndexWithFiller) ? -1 : 1;
}
}
/// <summary>
/// Builds the visual tree for the column header when a new template is applied.
/// </summary>
@ -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<DataGridColumn> sortedInternal = new List<DataGridColumn>(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
}
/// <summary>
/// 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.
/// </summary>
//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;

25
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<Exception>)new[] { exception } :
aggregate.InnerExceptions;
var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList();
var exceptions = exception is AggregateException aggregate ?
aggregate.InnerExceptions :
(IEnumerable<Exception>)new[] { exception };
if (filtered.Count > 0)
{
return filtered;
}
return exceptions.Where(x => !(x is BindingChainException)).ToList();
}
return null;
return Array.Empty<Exception>();
}
public static object UnpackDataValidationException(Exception exception)
{
if (exception is DataValidationException dataValidationException)
{
return dataValidationException.ErrorData;
}
return exception;
}
/// <summary>

32
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<CalendarDatePicker>();
/// <summary>
/// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary>
public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
ContentControl.HorizontalContentAlignmentProperty.AddOwner<CalendarDatePicker>();
/// <summary>
/// Defines the <see cref="VerticalContentAlignment"/> property.
/// </summary>
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<CalendarDatePicker>();
/// <summary>
/// Gets or sets the date to display.
/// </summary>
@ -364,6 +377,25 @@ namespace Avalonia.Controls
set { SetValue(UseFloatingWatermarkProperty, value); }
}
/// <summary>
/// Gets or sets the horizontal alignment of the content within the control.
/// </summary>
public HorizontalAlignment HorizontalContentAlignment
{
get => GetValue(HorizontalContentAlignmentProperty);
set => SetValue(HorizontalContentAlignmentProperty, value);
}
/// <summary>
/// Gets or sets the vertical alignment of the content within the control.
/// </summary>
public VerticalAlignment VerticalContentAlignment
{
get => GetValue(VerticalContentAlignmentProperty);
set => SetValue(VerticalContentAlignmentProperty, value);
}
/// <summary>
/// Occurs when the drop-down
/// <see cref="T:Avalonia.Controls.Calendar" /> is closed.

2
src/Avalonia.Controls/ContextMenu.cs

@ -246,7 +246,7 @@ namespace Avalonia.Controls
/// <summary>
/// Opens the menu.
/// </summary>
public override void Open() => throw new NotSupportedException();
public override void Open() => Open(null);
/// <summary>
/// Opens a context menu on the specified control.

33
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<string> WatermarkProperty =
AvaloniaProperty.Register<NumericUpDown, string>(nameof(Watermark));
/// <summary>
/// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary>
public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
ContentControl.HorizontalContentAlignmentProperty.AddOwner<NumericUpDown>();
/// <summary>
/// Defines the <see cref="VerticalContentAlignment"/> property.
/// </summary>
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<NumericUpDown>();
private IDisposable _textBoxTextChangedSubscription;
private double _value;
@ -256,6 +270,25 @@ namespace Avalonia.Controls
set { SetValue(WatermarkProperty, value); }
}
/// <summary>
/// Gets or sets the horizontal alignment of the content within the control.
/// </summary>
public HorizontalAlignment HorizontalContentAlignment
{
get => GetValue(HorizontalContentAlignmentProperty);
set => SetValue(HorizontalContentAlignmentProperty, value);
}
/// <summary>
/// Gets or sets the vertical alignment of the content within the control.
/// </summary>
public VerticalAlignment VerticalContentAlignment
{
get => GetValue(VerticalContentAlignmentProperty);
set => SetValue(VerticalContentAlignmentProperty, value);
}
/// <summary>
/// Initializes new instance of <see cref="NumericUpDown"/> class.
/// </summary>

40
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
/// <summary>
/// Defines the ToolTip.Tip attached property.
/// </summary>
public static readonly AttachedProperty<object> TipProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, object>("Tip");
public static readonly AttachedProperty<object?> TipProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, object?>("Tip");
/// <summary>
/// Defines the ToolTip.IsOpen attached property.
@ -57,10 +58,10 @@ namespace Avalonia.Controls
/// <summary>
/// Stores the current <see cref="ToolTip"/> instance in the control.
/// </summary>
internal static readonly AttachedProperty<ToolTip> ToolTipProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, ToolTip>("ToolTip");
internal static readonly AttachedProperty<ToolTip?> ToolTipProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, ToolTip?>("ToolTip");
private IPopupHost _popup;
private IPopupHost? _popup;
/// <summary>
/// Initializes static members of the <see cref="ToolTip"/> 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);
}
/// <summary>
@ -79,7 +84,7 @@ namespace Avalonia.Controls
/// <returns>
/// The content to be displayed in the control's tooltip.
/// </returns>
public static object GetTip(Control element)
public static object? GetTip(Control element)
{
return element.GetValue(TipProperty);
}
@ -89,7 +94,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="element">The control to get the property from.</param>
/// <param name="value">The content to be displayed in the control's tooltip.</param>
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();

10
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) };

87
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<realizedRangeSize &&
i < realizedRangeSize &&
!Intersects(window, _realizedElementLayoutBounds[i], orientation);
++i)
{
@ -391,7 +390,7 @@ namespace Avalonia.Layout
--backCutoffIndex;
}
if (backCutoffIndex<realizedRangeSize - 1)
if (backCutoffIndex < realizedRangeSize - 1)
{
ClearRealizedRange(backCutoffIndex + 1, realizedRangeSize - backCutoffIndex - 1);
}
@ -419,14 +418,14 @@ namespace Avalonia.Layout
// to insert items.
int lastRealizedDataIndex = _firstRealizedDataIndex + GetRealizedElementCount() - 1;
int newStartingIndex = index;
if (newStartingIndex > _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;

2
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"/>
<Button Name="PART_Button"

4
src/Avalonia.Themes.Default/NumericUpDown.xaml

@ -23,6 +23,8 @@
Watermark="{TemplateBinding Watermark}"
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"
IsReadOnly="{TemplateBinding IsReadOnly}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Text="{TemplateBinding Text}"
AcceptsReturn="False"
TextWrapping="NoWrap">
@ -35,4 +37,4 @@
<Setter Property="Margin" Value="4"/>
<Setter Property="MinWidth" Value="20"/>
</Style>
</Styles>
</Styles>

3
src/Avalonia.Themes.Fluent/Controls/Button.xaml

@ -16,8 +16,7 @@
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="8,5,8,5" />
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
<Setter Property="Padding" Value="{DynamicResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontWeight" Value="Normal" />

10
src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml

@ -9,8 +9,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Design.PreviewWith>
<Border Margin="20">
<CalendarDatePicker/>
<Border Margin="20, 20, 20, 200">
<CalendarDatePicker Width="200"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center" />
</Border>
</Design.PreviewWith>
@ -74,7 +76,7 @@
Grid.Row="1"
Grid.ColumnSpan="4"
Grid.RowSpan="3"
FontSize="{StaticResource CalendarDatePickerCurrentDayFontSize}"
FontSize="{DynamicResource CalendarDatePickerCurrentDayFontSize}"
Text="{Binding Source={x:Static sys:DateTime.Today}, Path=Day}"/>
<Ellipse HorizontalAlignment="Center" VerticalAlignment="Center" Fill="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" StrokeThickness="0" Grid.ColumnSpan="4" Width="3" Height="3"/>
@ -104,6 +106,8 @@
Watermark="{TemplateBinding Watermark}"
UseFloatingWatermark="{TemplateBinding UseFloatingWatermark}"
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Grid.Column="0"/>
<Button Name="PART_Button"

4
src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml

@ -141,8 +141,8 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
IsEnabled="{TemplateBinding IsEnabled}"
MinWidth="{StaticResource DatePickerThemeMinWidth}"
MaxWidth="{StaticResource DatePickerThemeMaxWidth}"
MinWidth="{DynamicResource DatePickerThemeMinWidth}"
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"

2
src/Avalonia.Themes.Fluent/Controls/Expander.xaml

@ -108,7 +108,7 @@
</Setter>
</Style>
<Style Selector="Expander /template/ ToggleButton#PART_toggle:pointerover /template/ Border">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlTransientBorderBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightBaseMediumBrush}" />
</Style>
<Style Selector="Expander:down:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">

6
src/Avalonia.Themes.Fluent/Controls/FocusAdorner.xaml

@ -10,10 +10,10 @@
<Style Selector=":is(Control)">
<Setter Property="FocusAdorner">
<FocusAdornerTemplate>
<Border BorderThickness="{StaticResource SystemControlFocusVisualPrimaryThickness}"
<Border BorderThickness="{DynamicResource SystemControlFocusVisualPrimaryThickness}"
BorderBrush="{DynamicResource SystemControlFocusVisualPrimaryBrush}"
Margin="{StaticResource SystemControlFocusVisualMargin}">
<Border BorderThickness="{StaticResource SystemControlFocusVisualSecondaryThickness}"
Margin="{DynamicResource SystemControlFocusVisualMargin}">
<Border BorderThickness="{DynamicResource SystemControlFocusVisualSecondaryThickness}"
BorderBrush="{DynamicResource SystemControlFocusVisualSecondaryBrush}" />
</Border>
</FocusAdornerTemplate>

2
src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml

@ -17,7 +17,7 @@
</Styles.Resources>
<Style Selector="ListBoxItem">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="{StaticResource ListBoxItemPadding}" />
<Setter Property="Padding" Value="{DynamicResource ListBoxItemPadding}" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="Template">
<ControlTemplate>

2
src/Avalonia.Themes.Fluent/Controls/Menu.xaml

@ -16,7 +16,7 @@
</Style.Resources>
<Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="{StaticResource MenuBarHeight}" />
<Setter Property="Height" Value="{DynamicResource MenuBarHeight}" />
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"

6
src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

@ -112,7 +112,7 @@
<Popup Name="PART_Popup"
WindowManagerAddShadowHint="False"
PlacementMode="Right"
HorizontalOffset="{StaticResource MenuFlyoutSubItemPopupHorizontalOffset}"
HorizontalOffset="{DynamicResource MenuFlyoutSubItemPopupHorizontalOffset}"
IsLightDismissEnabled="True"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
<Border Background="{DynamicResource MenuFlyoutPresenterBackground}"
@ -189,12 +189,12 @@
<Style Selector="MenuItem">
<!-- Narrow padding should be used for mouse input, when non-narrow one should be used for touch input in future. -->
<Setter Property="Padding" Value="{StaticResource MenuFlyoutItemThemePaddingNarrow}" />
<Setter Property="Padding" Value="{DynamicResource MenuFlyoutItemThemePaddingNarrow}" />
</Style>
<Style Selector="Menu > MenuItem">
<!-- Custom padding for Menu > MenuItem -->
<Setter Property="Padding" Value="{StaticResource MenuBarItemPadding}" />
<Setter Property="Padding" Value="{DynamicResource MenuBarItemPadding}" />
</Style>
<Style Selector="MenuItem /template/ ContentPresenter#PART_IconPresenter">

7
src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml

@ -1,8 +1,7 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border Padding="20"
Background="Black">
<Border Padding="20">
<StackPanel Spacing="20">
<NumericUpDown Minimum="0"
Maximum="10"
@ -13,6 +12,8 @@
Maximum="10"
Increment="0.5"
Width="150"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
ButtonSpinnerLocation="Left"
Watermark="Enter text" />
</StackPanel>
@ -48,6 +49,8 @@
Padding="{TemplateBinding Padding}"
Watermark="{TemplateBinding Watermark}"
IsReadOnly="{TemplateBinding IsReadOnly}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Text="{TemplateBinding Text}"
AcceptsReturn="False"
TextWrapping="NoWrap" />

2
src/Avalonia.Themes.Fluent/Controls/TabControl.xaml

@ -60,6 +60,6 @@
<Setter Property="Orientation" Value="Vertical" />
</Style>
<Style Selector="TabControl[TabStripPlacement=Top] /template/ ItemsPresenter#PART_ItemsPresenter">
<Setter Property="Margin" Value="{StaticResource TabControlTopPlacementItemMargin}" />
<Setter Property="Margin" Value="{DynamicResource TabControlTopPlacementItemMargin}" />
</Style>
</Styles>

10
src/Avalonia.Themes.Fluent/Controls/TabItem.xaml

@ -97,8 +97,8 @@
<!-- TabStripPlacement States Group -->
<Style Selector="TabItem[TabStripPlacement=Left] /template/ Border#PART_SelectedPipe">
<Setter Property="Width" Value="{StaticResource TabItemPipeThickness}" />
<Setter Property="Height" Value="{StaticResource TabItemVerticalPipeHeight}" />
<Setter Property="Width" Value="{DynamicResource TabItemPipeThickness}" />
<Setter Property="Height" Value="{DynamicResource TabItemVerticalPipeHeight}" />
<Setter Property="Margin" Value="0,0,2,0" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
@ -108,15 +108,15 @@
</Style>
<Style Selector="TabItem[TabStripPlacement=Top] /template/ Border#PART_SelectedPipe, TabItem[TabStripPlacement=Bottom] /template/ Border#PART_SelectedPipe">
<Setter Property="Height" Value="{StaticResource TabItemPipeThickness}" />
<Setter Property="Height" Value="{DynamicResource TabItemPipeThickness}" />
<Setter Property="Margin" Value="0,0,0,2" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Bottom" />
</Style>
<Style Selector="TabItem[TabStripPlacement=Right] /template/ Border#PART_SelectedPipe">
<Setter Property="Width" Value="{StaticResource TabItemPipeThickness}" />
<Setter Property="Height" Value="{StaticResource TabItemVerticalPipeHeight}" />
<Setter Property="Width" Value="{DynamicResource TabItemPipeThickness}" />
<Setter Property="Height" Value="{DynamicResource TabItemVerticalPipeHeight}" />
<Setter Property="Margin" Value="2,0,0,0" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />

2
src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml

@ -57,7 +57,7 @@
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="TabStripItem /template/ Border#PART_SelectedPipe, TabItem[TabStripPlacement=Bottom] /template/ Border#PART_SelectedPipe">
<Setter Property="Height" Value="{StaticResource TabStripItemPipeThickness}" />
<Setter Property="Height" Value="{DynamicResource TabStripItemPipeThickness}" />
<Setter Property="Margin" Value="0,0,0,2" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Bottom" />

4
src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml

@ -60,8 +60,8 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
IsEnabled="{TemplateBinding IsEnabled}"
MinWidth="{StaticResource TimePickerThemeMinWidth}"
MaxWidth="{StaticResource TimePickerThemeMaxWidth}"
MinWidth="{DynamicResource TimePickerThemeMinWidth}"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Top"

2
src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml

@ -56,7 +56,7 @@
VerticalAlignment="Top"/>
<Grid Grid.Row="1"
MinWidth="{StaticResource ToggleSwitchThemeMinWidth}"
MinWidth="{DynamicResource ToggleSwitchThemeMinWidth}"
HorizontalAlignment="Left"
VerticalAlignment="Top">

2
src/Avalonia.X11/X11Atoms.cs

@ -114,6 +114,8 @@ namespace Avalonia.X11
public readonly IntPtr XA_WM_CLASS = (IntPtr)67;
public readonly IntPtr XA_WM_TRANSIENT_FOR = (IntPtr)68;
public readonly IntPtr RR_PROPERTY_RANDR_EDID = (IntPtr)82;
public readonly IntPtr WM_PROTOCOLS;
public readonly IntPtr WM_DELETE_WINDOW;
public readonly IntPtr WM_TAKE_FOCUS;

94
src/Avalonia.X11/X11Screens.cs

@ -66,7 +66,8 @@ namespace Avalonia.X11
private X11Screen[] _cache;
private X11Info _x11;
private IntPtr _window;
const int EDIDStructureLength = 32; // Length of a EDID-Block-Length(128 bytes), XRRGetOutputProperty multiplies offset and length by 4
public Randr15ScreensImpl(AvaloniaX11Platform platform, X11ScreensUserSettings settings)
{
_settings = settings;
@ -82,6 +83,38 @@ namespace Avalonia.X11
_cache = null;
}
private unsafe Size? GetPhysicalMonitorSizeFromEDID(IntPtr rrOutput)
{
if(rrOutput == IntPtr.Zero)
return null;
var properties = XRRListOutputProperties(_x11.Display,rrOutput, out int propertyCount);
var hasEDID = false;
for(var pc = 0; pc < propertyCount; pc++)
{
if(properties[pc] == _x11.Atoms.RR_PROPERTY_RANDR_EDID)
hasEDID = true;
}
if(!hasEDID)
return null;
XRRGetOutputProperty(_x11.Display, rrOutput, _x11.Atoms.RR_PROPERTY_RANDR_EDID, 0, EDIDStructureLength, false, false, _x11.Atoms.AnyPropertyType, out IntPtr actualType, out int actualFormat, out int bytesAfter, out _, out IntPtr prop);
if(actualType != _x11.Atoms.XA_INTEGER)
return null;
if(actualFormat != 8) // Expecting an byte array
return null;
var edid = new byte[bytesAfter];
Marshal.Copy(prop,edid,0,bytesAfter);
XFree(prop);
XFree(new IntPtr(properties));
if(edid.Length < 22)
return null;
var width = edid[21]; // 0x15 1 Max. Horizontal Image Size cm.
var height = edid[22]; // 0x16 1 Max. Vertical Image Size cm.
if(width == 0 && height == 0)
return null;
return new Size(width * 10, height * 10);
}
public unsafe X11Screen[] Screens
{
get
@ -97,24 +130,28 @@ namespace Avalonia.X11
var namePtr = XGetAtomName(_x11.Display, mon.Name);
var name = Marshal.PtrToStringAnsi(namePtr);
XFree(namePtr);
var density = 1d;
var bounds = new PixelRect(mon.X, mon.Y, mon.Width, mon.Height);
Size? pSize = null;
double density = 0;
if (_settings.NamedScaleFactors?.TryGetValue(name, out density) != true)
{
if (mon.MWidth == 0)
density = 1;
else
density = X11Screen.GuessPixelDensity(mon.Width, mon.MWidth);
for(int o = 0; o < mon.NOutput; o++)
{
var outputSize = GetPhysicalMonitorSizeFromEDID(mon.Outputs[o]);
var outputDensity = 1d;
if(outputSize != null)
outputDensity = X11Screen.GuessPixelDensity(bounds, outputSize.Value);
if(density == 0 || density > outputDensity)
{
density = outputDensity;
pSize = outputSize;
}
}
}
if(density == 0)
density = 1;
density *= _settings.GlobalScaleFactor;
var bounds = new PixelRect(mon.X, mon.Y, mon.Width, mon.Height);
screens[c] = new X11Screen(bounds,
mon.Primary != 0,
name,
(mon.MWidth == 0 || mon.MHeight == 0) ? (Size?)null : new Size(mon.MWidth, mon.MHeight),
density);
screens[c] = new X11Screen(bounds, mon.Primary != 0, name, pSize, density);
}
XFree(new IntPtr(monitors));
@ -163,7 +200,6 @@ namespace Avalonia.X11
}
public int ScreenCount => _impl.Screens.Length;
public IReadOnlyList<Screen> AllScreens =>
@ -229,6 +265,7 @@ namespace Avalonia.X11
class X11Screen
{
private const int FullHDWidth = 1920;
private const int FullHDHeight = 1080;
public bool Primary { get; }
public string Name { get; set; }
public PixelRect Bounds { get; set; }
@ -248,7 +285,7 @@ namespace Avalonia.X11
}
else if (pixelDensity == null)
{
PixelDensity = GuessPixelDensity(bounds.Width, physicalSize.Value.Width);
PixelDensity = GuessPixelDensity(bounds, physicalSize.Value);
}
else
{
@ -257,7 +294,26 @@ namespace Avalonia.X11
}
}
public static double GuessPixelDensity(double pixelWidth, double mmWidth)
=> pixelWidth <= FullHDWidth ? 1 : Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96));
public static double GuessPixelDensity(PixelRect pixel, Size physical)
{
var calculatedDensity = 1d;
if(physical.Width > 0)
calculatedDensity = pixel.Width <= FullHDWidth ? 1 : Math.Max(1, pixel.Width / physical.Width * 25.4 / 96);
else if(physical.Height > 0)
calculatedDensity = pixel.Height <= FullHDHeight ? 1 : Math.Max(1, pixel.Height / physical.Height * 25.4 / 96);
if(calculatedDensity > 3)
return 1;
else
{
var sanePixelDensities = new double[] { 1, 1.25, 1.50, 1.75, 2 };
foreach(var saneDensity in sanePixelDensities)
{
if(calculatedDensity <= saneDensity + 0.20)
return saneDensity;
}
return sanePixelDensities.Last();
}
}
}
}

4
src/Avalonia.X11/X11Structs.cs

@ -1892,7 +1892,7 @@ namespace Avalonia.X11 {
public const string XNFontSet = "fontSet";
}
struct XRRMonitorInfo {
unsafe struct XRRMonitorInfo {
public IntPtr Name;
public int Primary;
public int Automatic;
@ -1903,6 +1903,6 @@ namespace Avalonia.X11 {
public int Height;
public int MWidth;
public int MHeight;
public IntPtr Outputs;
public IntPtr* Outputs;
}
}

7
src/Avalonia.X11/XLib.cs

@ -550,6 +550,13 @@ namespace Avalonia.X11
[DllImport(libX11Randr)]
public static extern XRRMonitorInfo*
XRRGetMonitors(IntPtr dpy, IntPtr window, bool get_active, out int nmonitors);
[DllImport(libX11Randr)]
public static extern IntPtr* XRRListOutputProperties(IntPtr dpy, IntPtr output, out int count);
[DllImport(libX11Randr)]
public static extern int XRRGetOutputProperty(IntPtr dpy, IntPtr output, IntPtr atom, int offset, int length, bool _delete, bool pending, IntPtr req_type, out IntPtr actual_type, out int actual_format, out int nitems, out long bytes_after, out IntPtr prop);
[DllImport(libX11Randr)]
public static extern void XRRSelectInput(IntPtr dpy, IntPtr window, RandrEventMask mask);

5
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs

@ -129,7 +129,10 @@ namespace Avalonia.Markup.Parsers.Nodes
{
get
{
Target.TryGetTarget(out object target);
if (!Target.TryGetTarget(out object target))
{
return null;
}
return GetIndexer(target.GetType().GetTypeInfo())?.PropertyType;
}

11
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -109,20 +109,23 @@ namespace Avalonia.Skia
if (typeface.FontFamily.Key == null)
{
var defaultName = SKTypeface.Default.FamilyName;
var fontStyle = new SKFontStyle((SKFontStyleWeight)typeface.Weight, SKFontStyleWidth.Normal, (SKFontStyleSlant)typeface.Style);
foreach (var familyName in typeface.FontFamily.FamilyNames)
{
skTypeface = SKTypeface.FromFamilyName(familyName, (SKFontStyleWeight)typeface.Weight,
SKFontStyleWidth.Normal, (SKFontStyleSlant)typeface.Style);
skTypeface = _skFontManager.MatchFamily(familyName, fontStyle);
if (!skTypeface.FamilyName.Equals(familyName, StringComparison.Ordinal) &&
defaultName.Equals(skTypeface.FamilyName, StringComparison.Ordinal))
if (skTypeface is null
|| (!skTypeface.FamilyName.Equals(familyName, StringComparison.Ordinal)
&& defaultName.Equals(skTypeface.FamilyName, StringComparison.Ordinal)))
{
continue;
}
break;
}
skTypeface ??= _skFontManager.MatchTypeface(SKTypeface.Default, fontStyle);
}
else
{

20
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@ -4,6 +4,8 @@ using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Logging;
using Avalonia.Platform;
@ -797,6 +799,24 @@ namespace Avalonia.Base.UnitTests
Assert.False(source.SetterCalled);
}
[Fact]
public void TwoWay_Binding_Should_Not_Fail_With_Null_DataContext()
{
var target = new TextBlock();
target.DataContext = null;
target.Bind(TextBlock.TextProperty, new Binding("Missing", BindingMode.TwoWay));
}
[Fact]
public void TwoWay_Binding_Should_Not_Fail_With_Null_DataContext_Indexer()
{
var target = new TextBlock();
target.DataContext = null;
target.Bind(TextBlock.TextProperty, new Binding("[0]", BindingMode.TwoWay));
}
[Fact]
public void Disposing_Completed_Binding_Does_Not_Throw()
{

49
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@ -44,6 +44,55 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Open_Should_Use_Default_Control()
{
using (Application())
{
var sut = new ContextMenu();
var target = new Panel
{
ContextMenu = sut
};
var window = new Window { Content = target };
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
bool opened = false;
sut.MenuOpened += (sender, args) =>
{
opened = true;
};
sut.Open();
Assert.True(opened);
}
}
[Fact]
public void Open_Should_Raise_Exception_If_AlreadyDetached()
{
using (Application())
{
var sut = new ContextMenu();
var target = new Panel
{
ContextMenu = sut
};
var window = new Window { Content = target };
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
target.ContextMenu = null;
Assert.ThrowsAny<Exception>(()=> sut.Open());
}
}
[Fact]
public void Closing_Raises_Single_Closed_Event()
{

Loading…
Cancel
Save