Browse Source

Merge branch 'master' into fixes/datagrid-columns-displayindex

pull/5180/head
Benedikt Stebner 6 years ago
committed by GitHub
parent
commit
d9cb345444
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 31
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 20
      .github/ISSUE_TEMPLATE/feature_request.md
  3. 2
      azure-pipelines.yml
  4. 2
      build/SharedVersion.props
  5. 12
      native/Avalonia.Native/src/OSX/rendertarget.mm
  6. 7
      readme.md
  7. 107
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  8. 25
      src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs
  9. 2
      src/Avalonia.Controls/ContextMenu.cs
  10. 87
      src/Avalonia.Layout/ElementManager.cs
  11. 2
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  12. 2
      src/Avalonia.X11/X11Atoms.cs
  13. 94
      src/Avalonia.X11/X11Screens.cs
  14. 4
      src/Avalonia.X11/X11Structs.cs
  15. 7
      src/Avalonia.X11/XLib.cs
  16. 49
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

31
.github/ISSUE_TEMPLATE/bug_report.md

@ -0,0 +1,31 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows, Mac, Linux (State distribution)]
- Version [e.g. 0.10.0-rc1 or 0.9.12]
**Additional context**
Add any other context about the problem here.

20
.github/ISSUE_TEMPLATE/feature_request.md

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

2
azure-pipelines.yml

@ -51,7 +51,7 @@ jobs:
inputs: inputs:
actions: 'build' actions: 'build'
scheme: '' scheme: ''
sdk: 'macosx11.0' sdk: 'macosx11.1'
configuration: 'Release' configuration: 'Release'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: '12' # Options: 8, 9, default, specifyPath xcodeVersion: '12' # Options: 8, 9, default, specifyPath

2
build/SharedVersion.props

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

12
native/Avalonia.Native/src/OSX/rendertarget.mm

@ -111,7 +111,11 @@
if(_renderbuffer != 0) if(_renderbuffer != 0)
glDeleteRenderbuffers(1, &_renderbuffer); glDeleteRenderbuffers(1, &_renderbuffer);
} }
CFRelease(surface);
if(surface != nullptr)
{
CFRelease(surface);
}
} }
@end @end
@ -145,6 +149,12 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta
} }
- (void)resize:(AvnPixelSize)size withScale: (float) scale{ - (void)resize:(AvnPixelSize)size withScale: (float) scale{
if(size.Height <= 0)
size.Height = 1;
if(size.Width <= 0)
size.Width = 1;
@synchronized (lock) { @synchronized (lock) {
if(surface == nil if(surface == nil
|| surface->size.Width != size.Width || surface->size.Width != size.Width

7
readme.md

@ -10,6 +10,8 @@ Avalonia is a cross-platform XAML-based UI framework providing a flexible stylin
<img src="https://user-images.githubusercontent.com/6759207/84751662-7c79da00-afc5-11ea-8780-dda28db70b76.png" width="700" /> <img src="https://user-images.githubusercontent.com/6759207/84751662-7c79da00-afc5-11ea-8780-dda28db70b76.png" width="700" />
([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
> **Note:** The UI theme you see in the picture above is still work-in-progress and will be available in the upcoming Avalonia 0.10.0 release. However, you can connect to our nightly build feed and install latest pre-release versions of Avalonia NuGet packages, if you are willing to help out with the development and testing. See [Using nightly build feed](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed) for more info. > **Note:** The UI theme you see in the picture above is still work-in-progress and will be available in the upcoming Avalonia 0.10.0 release. However, you can connect to our nightly build feed and install latest pre-release versions of Avalonia NuGet packages, if you are willing to help out with the development and testing. See [Using nightly build feed](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed) for more info.
To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239). You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been. [Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia! To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239). You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been. [Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia!
@ -31,11 +33,16 @@ Install-Package Avalonia.Desktop
Examples of UIs built with Avalonia Examples of UIs built with Avalonia
![image](https://user-images.githubusercontent.com/4672627/84707589-5b69a880-af35-11ea-87a6-7ad57a31d314.png) ![image](https://user-images.githubusercontent.com/4672627/84707589-5b69a880-af35-11ea-87a6-7ad57a31d314.png)
([Synfonia](https://github.com/jmacato/Synfonia))
![image](https://user-images.githubusercontent.com/4672627/85069644-d8419000-b18a-11ea-8732-be9055bb61fd.PNG) ![image](https://user-images.githubusercontent.com/4672627/85069644-d8419000-b18a-11ea-8732-be9055bb61fd.PNG)
([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
![image](https://user-images.githubusercontent.com/4672627/85069659-dc6dad80-b18a-11ea-8375-39ef95315b5c.PNG) ![image](https://user-images.githubusercontent.com/4672627/85069659-dc6dad80-b18a-11ea-8375-39ef95315b5c.PNG)
([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
![image](https://user-images.githubusercontent.com/4672627/84708947-c3b98980-af37-11ea-8c9d-503334615bbf.png) ![image](https://user-images.githubusercontent.com/4672627/84708947-c3b98980-af37-11ea-8c9d-503334615bbf.png)
([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
## JetBrains Rider ## JetBrains Rider

107
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -1,6 +1,6 @@
// This source is subject to the Microsoft Public License (Ms-PL). // This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved. // All other rights reserved.
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
@ -92,7 +92,7 @@ namespace Avalonia.Controls
private ContentControl _topRightCornerHeader; private ContentControl _topRightCornerHeader;
private Control _frozenColumnScrollBarSpacer; 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 // the first displayed scrolling column
private double _horizontalOffset; 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 object _uneditedValue; // Represents the original current cell value at the time it enters editing mode.
private ICellEditBinding _currentCellEditBinding; 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 // 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 // does not know their actual height. The heights used for the approximation are the ones
// set as the rows were scrolled off. // set as the rows were scrolled off.
@ -162,7 +162,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, bool>(nameof(CanUserReorderColumns)); AvaloniaProperty.Register<DataGrid, bool>(nameof(CanUserReorderColumns));
/// <summary> /// <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. /// the column display order by dragging column headers with the mouse.
/// </summary> /// </summary>
public bool CanUserReorderColumns 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. /// Gets or sets the <see cref="T:System.Windows.Media.Brush" /> that is used to paint the background of odd-numbered rows.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// The brush that is used to paint the background of odd-numbered rows. The default is 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="T:System.Windows.Media.SolidColorBrush" /> with a
/// <see cref="P:System.Windows.Media.SolidColorBrush.Color" /> value of white (ARGB value #00FFFFFF). /// <see cref="P:System.Windows.Media.SolidColorBrush.Color" /> value of white (ARGB value #00FFFFFF).
/// </returns> /// </returns>
public IBrush AlternatingRowBackground public IBrush AlternatingRowBackground
@ -379,8 +379,8 @@ namespace Avalonia.Controls
public bool IsValid public bool IsValid
{ {
get { return _isValid; } get { return _isValid; }
internal set internal set
{ {
SetAndRaise(IsValidProperty, ref _isValid, value); SetAndRaise(IsValidProperty, ref _isValid, value);
PseudoClasses.Set(":invalid", !value); PseudoClasses.Set(":invalid", !value);
} }
@ -398,7 +398,7 @@ namespace Avalonia.Controls
} }
/// <summary> /// <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> /// </summary>
public double MaxColumnWidth public double MaxColumnWidth
{ {
@ -418,7 +418,7 @@ namespace Avalonia.Controls
} }
/// <summary> /// <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> /// </summary>
public double MinColumnWidth public double MinColumnWidth
{ {
@ -496,7 +496,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, IBrush>(nameof(VerticalGridLinesBrush)); AvaloniaProperty.Register<DataGrid, IBrush>(nameof(VerticalGridLinesBrush));
/// <summary> /// <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> /// </summary>
public IBrush VerticalGridLinesBrush public IBrush VerticalGridLinesBrush
{ {
@ -542,7 +542,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <returns> /// <returns>
/// The index of the current selection, or -1 if the selection is empty. /// The index of the current selection, or -1 if the selection is empty.
/// </returns> /// </returns>
public int SelectedIndex public int SelectedIndex
{ {
get { return _selectedIndex; } get { return _selectedIndex; }
@ -582,7 +582,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, bool>(nameof(AutoGenerateColumns)); AvaloniaProperty.Register<DataGrid, bool>(nameof(AutoGenerateColumns));
/// <summary> /// <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. /// automatically when the <see cref="P:Avalonia.Controls.DataGrid.ItemsSource" /> property is set.
/// </summary> /// </summary>
public bool AutoGenerateColumns public bool AutoGenerateColumns
@ -626,7 +626,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, bool>(nameof(AreRowDetailsFrozen)); AvaloniaProperty.Register<DataGrid, bool>(nameof(AreRowDetailsFrozen));
/// <summary> /// <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. /// fixed at the width of the display area or can scroll horizontally.
/// </summary> /// </summary>
public bool AreRowDetailsFrozen public bool AreRowDetailsFrozen
@ -881,7 +881,7 @@ namespace Avalonia.Controls
{ {
int index = (int)e.NewValue; 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 // against Count here to avoid enumerating through an Enumerable twice
// Setting SelectedItem coerces the finally value of the SelectedIndex // Setting SelectedItem coerces the finally value of the SelectedIndex
object newSelectedItem = (index < 0) ? null : DataConnection.GetDataItem(index); object newSelectedItem = (index < 0) ? null : DataConnection.GetDataItem(index);
@ -1168,14 +1168,14 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Occurs one time for each public, non-static property in the bound data type when 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.ItemsSource" /> property is changed and the
/// <see cref="P:Avalonia.Controls.DataGrid.AutoGenerateColumns" /> property is true. /// <see cref="P:Avalonia.Controls.DataGrid.AutoGenerateColumns" /> property is true.
/// </summary> /// </summary>
public event EventHandler<DataGridAutoGeneratingColumnEventArgs> AutoGeneratingColumn; public event EventHandler<DataGridAutoGeneratingColumnEventArgs> AutoGeneratingColumn;
/// <summary> /// <summary>
/// Occurs before a cell or row enters editing mode. /// Occurs before a cell or row enters editing mode.
/// </summary> /// </summary>
public event EventHandler<DataGridBeginningEditEventArgs> BeginningEdit; public event EventHandler<DataGridBeginningEditEventArgs> BeginningEdit;
@ -1195,7 +1195,7 @@ namespace Avalonia.Controls
public event EventHandler<DataGridCellPointerPressedEventArgs> CellPointerPressed; public event EventHandler<DataGridCellPointerPressedEventArgs> CellPointerPressed;
/// <summary> /// <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. /// property of a column changes.
/// </summary> /// </summary>
public event EventHandler<DataGridColumnEventArgs> ColumnDisplayIndexChanged; public event EventHandler<DataGridColumnEventArgs> ColumnDisplayIndexChanged;
@ -1218,14 +1218,14 @@ namespace Avalonia.Controls
public event EventHandler<EventArgs> CurrentCellChanged; public event EventHandler<EventArgs> CurrentCellChanged;
/// <summary> /// <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. /// is instantiated, so that you can customize it before it is used.
/// </summary> /// </summary>
public event EventHandler<DataGridRowEventArgs> LoadingRow; public event EventHandler<DataGridRowEventArgs> LoadingRow;
/// <summary> /// <summary>
/// Occurs when a cell in a <see cref="T:Avalonia.Controls.DataGridTemplateColumn" /> enters editing mode. /// Occurs when a cell in a <see cref="T:Avalonia.Controls.DataGridTemplateColumn" /> enters editing mode.
/// ///
/// </summary> /// </summary>
public event EventHandler<DataGridPreparingCellForEditEventArgs> PreparingCellForEdit; public event EventHandler<DataGridPreparingCellForEditEventArgs> PreparingCellForEdit;
@ -1243,7 +1243,7 @@ namespace Avalonia.Controls
RoutedEvent.Register<DataGrid, SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble); RoutedEvent.Register<DataGrid, SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble);
/// <summary> /// <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. /// <see cref="P:Avalonia.Controls.DataGrid.SelectedItems" /> property value changes.
/// </summary> /// </summary>
public event EventHandler<SelectionChangedEventArgs> SelectionChanged public event EventHandler<SelectionChangedEventArgs> SelectionChanged
@ -1258,19 +1258,19 @@ namespace Avalonia.Controls
public event EventHandler<DataGridColumnEventArgs> Sorting; public event EventHandler<DataGridColumnEventArgs> Sorting;
/// <summary> /// <summary>
/// Occurs when a <see cref="T:Avalonia.Controls.DataGridRow" /> /// Occurs when a <see cref="T:Avalonia.Controls.DataGridRow" />
/// object becomes available for reuse. /// object becomes available for reuse.
/// </summary> /// </summary>
public event EventHandler<DataGridRowEventArgs> UnloadingRow; public event EventHandler<DataGridRowEventArgs> UnloadingRow;
/// <summary> /// <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. /// the details section before it is used.
/// </summary> /// </summary>
public event EventHandler<DataGridRowDetailsEventArgs> LoadingRowDetails; public event EventHandler<DataGridRowDetailsEventArgs> LoadingRowDetails;
/// <summary> /// <summary>
/// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.RowDetailsVisibilityMode" /> /// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.RowDetailsVisibilityMode" />
/// property value changes. /// property value changes.
/// </summary> /// </summary>
public event EventHandler<DataGridRowDetailsEventArgs> RowDetailsVisibilityChanged; public event EventHandler<DataGridRowDetailsEventArgs> RowDetailsVisibilityChanged;
@ -1282,7 +1282,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets a collection that contains all the columns in the control. /// Gets a collection that contains all the columns in the control.
/// </summary> /// </summary>
public ObservableCollection<DataGridColumn> Columns public ObservableCollection<DataGridColumn> Columns
{ {
get 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 // 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. // not reflected immediately.
internal double CellsHeight internal double CellsHeight
{ {
@ -1555,7 +1555,7 @@ namespace Avalonia.Controls
internal static double HorizontalGridLinesThickness => DATAGRID_horizontalGridLinesThickness; 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 // the first displayed scrolling column
internal double HorizontalOffset internal double HorizontalOffset
{ {
@ -2083,20 +2083,20 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Measures the children of a <see cref="T:Avalonia.Controls.DataGridRow" /> to prepare for /// Measures the children of a <see cref="T:Avalonia.Controls.DataGridRow" /> to prepare for
/// arranging them during the /// arranging them during the
/// <see cref="M:Avalonia.Controls.DataGridRow.ArrangeOverride(System.Windows.Size)" /> pass. /// <see cref="M:Avalonia.Controls.DataGridRow.ArrangeOverride(System.Windows.Size)" /> pass.
/// </summary> /// </summary>
/// <returns> /// <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. /// 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> /// </returns>
/// <param name="availableSize"> /// <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. /// child elements should not exceed.
/// </param> /// </param>
protected override Size MeasureOverride(Size availableSize) 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 // DataGrid is not part of the visual tree
if (!_measured) if (!_measured)
{ {
@ -3020,7 +3020,7 @@ namespace Avalonia.Controls
/// If the editing element has focus, this method will set focus to the DataGrid itself /// 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 /// 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. /// 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 /// 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 /// 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. /// will not be performed later on, and should instead be performed by the caller, if necessary.
@ -3079,7 +3079,7 @@ namespace Avalonia.Controls
{ {
if (!_scrollingByHeight) 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 // since rows could be added or removed
InvalidateMeasure(); InvalidateMeasure();
} }
@ -3292,7 +3292,7 @@ namespace Avalonia.Controls
{ {
// Current cell was reset because the commit deleted row(s). // Current cell was reset because the commit deleted row(s).
// Since the user wants to change the current cell, we don't // 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'. // in the grid which may be the 'new row'.
int lastSlot = LastVisibleSlot; int lastSlot = LastVisibleSlot;
if (forCurrentCellChange && if (forCurrentCellChange &&
@ -3350,7 +3350,7 @@ namespace Avalonia.Controls
if (_ignoreNextScrollBarsLayout) if (_ignoreNextScrollBarsLayout)
{ {
_ignoreNextScrollBarsLayout = false; _ignoreNextScrollBarsLayout = false;
// //
} }
@ -3407,7 +3407,7 @@ namespace Avalonia.Controls
} }
// Now cellsWidth is the width potentially available for displaying data cells. // 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 needHorizScrollbar = false;
bool needVertScrollbar = false; bool needVertScrollbar = false;
@ -3432,7 +3432,7 @@ namespace Avalonia.Controls
Debug.Assert(cellsHeight >= 0); Debug.Assert(cellsHeight >= 0);
needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true; needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true;
if (vertScrollBarWidth > 0 && if (vertScrollBarWidth > 0 &&
allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) || allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth))) MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth)))
{ {
@ -3472,7 +3472,7 @@ namespace Avalonia.Controls
// we compute the number of visible columns only after we set up the vertical scroll bar. // we compute the number of visible columns only after we set up the vertical scroll bar.
ComputeDisplayedColumns(); ComputeDisplayedColumns();
if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) && if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) &&
allowHorizScrollbar && allowHorizScrollbar &&
needVertScrollbar && !needHorizScrollbar && needVertScrollbar && !needHorizScrollbar &&
MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) && MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
@ -3977,7 +3977,8 @@ namespace Avalonia.Controls
{ {
var errorList = var errorList =
binding.ValidationErrors binding.ValidationErrors
.SelectMany(ex => ValidationUtil.UnpackException(ex)) .SelectMany(ValidationUtil.UnpackException)
.Select(ValidationUtil.UnpackDataValidationException)
.ToList(); .ToList();
DataValidationErrors.SetErrors(editingElement, errorList); DataValidationErrors.SetErrors(editingElement, errorList);
@ -4138,7 +4139,7 @@ namespace Avalonia.Controls
} }
/// <summary> /// <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. /// without repopulating the edited row's cell.
/// </summary> /// </summary>
//TODO TabStop //TODO TabStop
@ -5117,9 +5118,9 @@ namespace Avalonia.Controls
{ {
if (ctrl || _editingColumnIndex == -1 || IsReadOnly) 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 // - 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; return false;
} }
@ -5530,11 +5531,11 @@ namespace Avalonia.Controls
// v---v // v---v
//|<|_____|###|>| //|<|_____|###|>|
// ^ ^ // ^ ^
// min max // min max
// we want to make the relative size of the thumb reflect the relative size of the viewing area // 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 + viewportSize) = cellsWidth / max
// -> viewportSize = max * cellsWidth / (max - cellsWidth) // -> viewportSize = max * cellsWidth / (max - cellsWidth)
// always zero // always zero
_hScrollBar.Minimum = 0; _hScrollBar.Minimum = 0;
@ -5586,7 +5587,7 @@ namespace Avalonia.Controls
_hScrollBar.Maximum = 0; _hScrollBar.Maximum = 0;
if (_hScrollBar.IsVisible) 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. // which no processing is needed.
_hScrollBar.IsVisible = false; _hScrollBar.IsVisible = false;
_ignoreNextScrollBarsLayout = true; _ignoreNextScrollBarsLayout = true;
@ -5605,14 +5606,14 @@ namespace Avalonia.Controls
// v---v // v---v
//|<|_____|###|>| //|<|_____|###|>|
// ^ ^ // ^ ^
// min max // min max
// we want to make the relative size of the thumb reflect the relative size of the viewing area // 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 + viewportSize) = cellsWidth / max
// -> viewportSize = max * cellsHeight / (totalVisibleHeight - cellsHeight) // -> viewportSize = max * cellsHeight / (totalVisibleHeight - cellsHeight)
// -> = max * cellsHeight / (totalVisibleHeight - cellsHeight) // -> = max * cellsHeight / (totalVisibleHeight - cellsHeight)
// -> = max * cellsHeight / max // -> = max * cellsHeight / max
// -> = cellsHeight // -> = cellsHeight
// always zero // always zero
_vScrollBar.Minimum = 0; _vScrollBar.Minimum = 0;
@ -5635,7 +5636,7 @@ namespace Avalonia.Controls
if (!_vScrollBar.IsVisible) 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. // which no processing is needed.
_vScrollBar.IsVisible = true; _vScrollBar.IsVisible = true;
if (_vScrollBar.DesiredSize.Width == 0) if (_vScrollBar.DesiredSize.Width == 0)
@ -5651,7 +5652,7 @@ namespace Avalonia.Controls
_vScrollBar.Maximum = 0; _vScrollBar.Maximum = 0;
if (_vScrollBar.IsVisible) 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. // which no processing is needed.
_vScrollBar.IsVisible = false; _vScrollBar.IsVisible = false;
_ignoreNextScrollBarsLayout = true; _ignoreNextScrollBarsLayout = true;
@ -5674,8 +5675,8 @@ namespace Avalonia.Controls
Debug.Assert(slot >= 0); Debug.Assert(slot >= 0);
// Before changing selection, check if the current cell needs to be committed, and // 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, // 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. // do not change selection, and do not change current cell.
bool wasInEdit = EditingColumnIndex != -1; bool wasInEdit = EditingColumnIndex != -1;

25
src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs

@ -80,19 +80,24 @@ namespace Avalonia.Controls.Utils
{ {
if (exception != null) if (exception != null)
{ {
var aggregate = exception as AggregateException; var exceptions = exception is AggregateException aggregate ?
var exceptions = aggregate == null ? aggregate.InnerExceptions :
(IEnumerable<Exception>)new[] { exception } : (IEnumerable<Exception>)new[] { exception };
aggregate.InnerExceptions;
var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList();
if (filtered.Count > 0) return exceptions.Where(x => !(x is BindingChainException)).ToList();
{
return filtered;
}
} }
return null; return Array.Empty<Exception>();
}
public static object UnpackDataValidationException(Exception exception)
{
if (exception is DataValidationException dataValidationException)
{
return dataValidationException.ErrorData;
}
return exception;
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/ContextMenu.cs

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

87
src/Avalonia.Layout/ElementManager.cs

@ -129,7 +129,7 @@ namespace Avalonia.Layout
{ {
for (int i = 0; i < count; i++) 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. // realized indices without walking through all the children every time.
int index = realizedIndex == 0 ? realizedIndex + i : (realizedIndex + count - 1) - i; int index = realizedIndex == 0 ? realizedIndex + i : (realizedIndex + count - 1) - i;
var elementRef = _realizedElements[index]; var elementRef = _realizedElements[index];
@ -212,7 +212,7 @@ namespace Avalonia.Layout
public ILayoutable GetRealizedElement(int dataIndex) public ILayoutable GetRealizedElement(int dataIndex)
{ {
return IsVirtualizingContext ? return IsVirtualizingContext ?
GetAt(GetRealizedRangeIndexFromDataIndex(dataIndex)) : GetAt(GetRealizedRangeIndexFromDataIndex(dataIndex)) :
_context.GetOrCreateElementAt( _context.GetOrCreateElementAt(
dataIndex, dataIndex,
ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle);
@ -252,7 +252,6 @@ namespace Avalonia.Layout
(orientation == ScrollOrientation.Vertical ? ScrollOrientation.Horizontal : ScrollOrientation.Vertical) : (orientation == ScrollOrientation.Vertical ? ScrollOrientation.Horizontal : ScrollOrientation.Vertical) :
orientation; orientation;
var windowStart = effectiveOrientation == ScrollOrientation.Vertical ? window.Y : window.X; var windowStart = effectiveOrientation == ScrollOrientation.Vertical ? window.Y : window.X;
var windowEnd = effectiveOrientation == ScrollOrientation.Vertical ? window.Y + window.Height : window.X + window.Width; var windowEnd = effectiveOrientation == ScrollOrientation.Vertical ? window.Y + window.Height : window.X + window.Width;
var firstElementStart = effectiveOrientation == ScrollOrientation.Vertical ? firstElementBounds.Y : firstElementBounds.X; var firstElementStart = effectiveOrientation == ScrollOrientation.Vertical ? firstElementBounds.Y : firstElementBounds.X;
@ -273,53 +272,53 @@ namespace Avalonia.Layout
switch (args.Action) switch (args.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
{ {
OnItemsAdded(args.NewStartingIndex, args.NewItems.Count); OnItemsAdded(args.NewStartingIndex, args.NewItems.Count);
} }
break; break;
case NotifyCollectionChangedAction.Replace: 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. int oldSize = args.OldItems.Count;
// Removing and adding might causes us to lose the anchor causing us int newSize = args.NewItems.Count;
// to throw away all containers and start from scratch. int oldStartIndex = args.OldStartingIndex;
// Instead, we can just clear those items and set the element to int newStartIndex = args.NewStartingIndex;
// null (sentinel) and let the next measure get new containers for them.
var startRealizedIndex = GetRealizedRangeIndexFromDataIndex(oldStartIndex); if (oldSize == newSize &&
for (int realizedIndex = startRealizedIndex; realizedIndex < startRealizedIndex + oldSize; realizedIndex++) oldStartIndex == newStartIndex &&
IsDataIndexRealized(oldStartIndex) &&
IsDataIndexRealized(oldStartIndex + oldSize - 1))
{ {
var elementRef = _realizedElements[realizedIndex]; // Straight up replace of n items within the realization window.
// Removing and adding might causes us to lose the anchor causing us
if (elementRef != null) // 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); var elementRef = _realizedElements[realizedIndex];
_realizedElements[realizedIndex] = null;
if (elementRef != null)
{
_context.RecycleElement(elementRef);
_realizedElements[realizedIndex] = null;
}
} }
} }
else
{
OnItemsRemoved(oldStartIndex, oldSize);
OnItemsAdded(newStartIndex, newSize);
}
} }
else break;
{
OnItemsRemoved(oldStartIndex, oldSize);
OnItemsAdded(newStartIndex, newSize);
}
}
break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
{ {
OnItemsRemoved(args.OldStartingIndex, args.OldItems.Count); OnItemsRemoved(args.OldStartingIndex, args.OldItems.Count);
} }
break; break;
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
ClearRealizedRange(); ClearRealizedRange();
@ -376,7 +375,7 @@ namespace Avalonia.Layout
int backCutoffIndex = realizedRangeSize; int backCutoffIndex = realizedRangeSize;
for (int i = 0; for (int i = 0;
i<realizedRangeSize && i < realizedRangeSize &&
!Intersects(window, _realizedElementLayoutBounds[i], orientation); !Intersects(window, _realizedElementLayoutBounds[i], orientation);
++i) ++i)
{ {
@ -391,7 +390,7 @@ namespace Avalonia.Layout
--backCutoffIndex; --backCutoffIndex;
} }
if (backCutoffIndex<realizedRangeSize - 1) if (backCutoffIndex < realizedRangeSize - 1)
{ {
ClearRealizedRange(backCutoffIndex + 1, realizedRangeSize - backCutoffIndex - 1); ClearRealizedRange(backCutoffIndex + 1, realizedRangeSize - backCutoffIndex - 1);
} }
@ -419,14 +418,14 @@ namespace Avalonia.Layout
// to insert items. // to insert items.
int lastRealizedDataIndex = _firstRealizedDataIndex + GetRealizedElementCount() - 1; int lastRealizedDataIndex = _firstRealizedDataIndex + GetRealizedElementCount() - 1;
int newStartingIndex = index; int newStartingIndex = index;
if (newStartingIndex > _firstRealizedDataIndex && if (newStartingIndex >= _firstRealizedDataIndex &&
newStartingIndex <= lastRealizedDataIndex) newStartingIndex <= lastRealizedDataIndex)
{ {
// Inserted within the realized range // Inserted within the realized range
int insertRangeStartIndex = newStartingIndex - _firstRealizedDataIndex; int insertRangeStartIndex = newStartingIndex - _firstRealizedDataIndex;
for (int i = 0; i < count; i++) 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. // end up creating a lot of elements only to be thrown out in the next layout.
int insertRangeIndex = insertRangeStartIndex + i; int insertRangeIndex = insertRangeStartIndex + i;
int dataIndex = newStartingIndex + i; int dataIndex = newStartingIndex + i;

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

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

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_CLASS = (IntPtr)67;
public readonly IntPtr XA_WM_TRANSIENT_FOR = (IntPtr)68; 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_PROTOCOLS;
public readonly IntPtr WM_DELETE_WINDOW; public readonly IntPtr WM_DELETE_WINDOW;
public readonly IntPtr WM_TAKE_FOCUS; public readonly IntPtr WM_TAKE_FOCUS;

94
src/Avalonia.X11/X11Screens.cs

@ -66,7 +66,8 @@ namespace Avalonia.X11
private X11Screen[] _cache; private X11Screen[] _cache;
private X11Info _x11; private X11Info _x11;
private IntPtr _window; 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) public Randr15ScreensImpl(AvaloniaX11Platform platform, X11ScreensUserSettings settings)
{ {
_settings = settings; _settings = settings;
@ -82,6 +83,38 @@ namespace Avalonia.X11
_cache = null; _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 public unsafe X11Screen[] Screens
{ {
get get
@ -97,24 +130,28 @@ namespace Avalonia.X11
var namePtr = XGetAtomName(_x11.Display, mon.Name); var namePtr = XGetAtomName(_x11.Display, mon.Name);
var name = Marshal.PtrToStringAnsi(namePtr); var name = Marshal.PtrToStringAnsi(namePtr);
XFree(namePtr); XFree(namePtr);
var bounds = new PixelRect(mon.X, mon.Y, mon.Width, mon.Height);
var density = 1d; Size? pSize = null;
double density = 0;
if (_settings.NamedScaleFactors?.TryGetValue(name, out density) != true) if (_settings.NamedScaleFactors?.TryGetValue(name, out density) != true)
{ {
if (mon.MWidth == 0) for(int o = 0; o < mon.NOutput; o++)
density = 1; {
else var outputSize = GetPhysicalMonitorSizeFromEDID(mon.Outputs[o]);
density = X11Screen.GuessPixelDensity(mon.Width, mon.MWidth); 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; density *= _settings.GlobalScaleFactor;
screens[c] = new X11Screen(bounds, mon.Primary != 0, name, pSize, density);
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);
} }
XFree(new IntPtr(monitors)); XFree(new IntPtr(monitors));
@ -163,7 +200,6 @@ namespace Avalonia.X11
} }
public int ScreenCount => _impl.Screens.Length; public int ScreenCount => _impl.Screens.Length;
public IReadOnlyList<Screen> AllScreens => public IReadOnlyList<Screen> AllScreens =>
@ -229,6 +265,7 @@ namespace Avalonia.X11
class X11Screen class X11Screen
{ {
private const int FullHDWidth = 1920; private const int FullHDWidth = 1920;
private const int FullHDHeight = 1080;
public bool Primary { get; } public bool Primary { get; }
public string Name { get; set; } public string Name { get; set; }
public PixelRect Bounds { get; set; } public PixelRect Bounds { get; set; }
@ -248,7 +285,7 @@ namespace Avalonia.X11
} }
else if (pixelDensity == null) else if (pixelDensity == null)
{ {
PixelDensity = GuessPixelDensity(bounds.Width, physicalSize.Value.Width); PixelDensity = GuessPixelDensity(bounds, physicalSize.Value);
} }
else else
{ {
@ -257,7 +294,26 @@ namespace Avalonia.X11
} }
} }
public static double GuessPixelDensity(double pixelWidth, double mmWidth) public static double GuessPixelDensity(PixelRect pixel, Size physical)
=> pixelWidth <= FullHDWidth ? 1 : Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96)); {
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

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

7
src/Avalonia.X11/XLib.cs

@ -500,6 +500,13 @@ namespace Avalonia.X11
[DllImport(libX11Randr)] [DllImport(libX11Randr)]
public static extern XRRMonitorInfo* public static extern XRRMonitorInfo*
XRRGetMonitors(IntPtr dpy, IntPtr window, bool get_active, out int nmonitors); 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)] [DllImport(libX11Randr)]
public static extern void XRRSelectInput(IntPtr dpy, IntPtr window, RandrEventMask mask); public static extern void XRRSelectInput(IntPtr dpy, IntPtr window, RandrEventMask mask);

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] [Fact]
public void Closing_Raises_Single_Closed_Event() public void Closing_Raises_Single_Closed_Event()
{ {

Loading…
Cancel
Save