Browse Source

Merge branch 'master' into fixes/FluentSubMenu

pull/4355/head
Benedikt Stebner 6 years ago
committed by GitHub
parent
commit
274474fda7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      Avalonia.sln
  2. 16
      native/Avalonia.Native/src/OSX/Screens.mm
  3. 13
      native/Avalonia.Native/src/OSX/window.mm
  4. 8
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  5. 31
      samples/ControlCatalog/Pages/MenuPage.xaml
  6. 36
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  7. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  8. 6
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  9. 6
      src/Avalonia.Controls/Chrome/CaptionButtons.cs
  10. 126
      src/Avalonia.Controls/Chrome/TitleBar.cs
  11. 16
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  12. 31
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  13. 51
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  14. 38
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  15. 2
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  16. 1
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  17. 6
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  18. 5
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  19. 21
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  20. 7
      src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
  21. 4
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs
  22. 20
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  23. 29
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  24. 2
      src/Avalonia.Controls/Repeater/ElementFactory.cs
  25. 1
      src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs
  26. 29
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs
  27. 6
      src/Avalonia.Controls/Templates/FuncTemplate`2.cs
  28. 12
      src/Avalonia.Controls/Templates/IDataTemplate.cs
  29. 25
      src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs
  30. 4
      src/Avalonia.Controls/TopLevel.cs
  31. 20
      src/Avalonia.Controls/Window.cs
  32. 1
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  33. 3
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  34. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs
  35. 9
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  36. 15
      src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs
  37. 5
      src/Avalonia.Native/OsxUnicodeKeys.cs
  38. 2
      src/Avalonia.Native/PopupImpl.cs
  39. 8
      src/Avalonia.Native/WindowImplBase.cs
  40. 2
      src/Avalonia.Themes.Default/TitleBar.xaml
  41. 34
      src/Avalonia.Themes.Default/Window.xaml
  42. 6
      src/Avalonia.Themes.Fluent/Menu.xaml
  43. 184
      src/Avalonia.Themes.Fluent/MenuItem.xaml
  44. 2
      src/Avalonia.Themes.Fluent/NumericUpDown.xaml
  45. 2
      src/Avalonia.Themes.Fluent/TitleBar.xaml
  46. 22
      src/Avalonia.Themes.Fluent/Window.xaml
  47. 20
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  48. 3
      src/Avalonia.X11/NativeDialogs/Gtk.cs
  49. 45
      src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
  50. 4
      src/Avalonia.X11/X11NativeControlHost.cs
  51. 47
      src/Avalonia.X11/X11Window.cs
  52. 4
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  53. 11
      src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
  54. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  55. 6
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  56. 2
      src/Windows/Avalonia.Win32/PopupImpl.cs
  57. 4
      src/Windows/Avalonia.Win32/Win32NativeControlHost.cs
  58. 16
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  59. 2
      src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
  60. 28
      src/Windows/Avalonia.Win32/WindowImpl.cs
  61. 2
      src/iOS/Avalonia.iOS/TopLevelImpl.cs
  62. 2
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  63. 3
      tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
  64. 40
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  65. 4
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  66. 2
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  67. 14
      tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
  68. 20
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  69. 4
      tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs
  70. 2
      tests/Avalonia.LeakTests/ControlTests.cs
  71. 62
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
  72. 5
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs

12
Avalonia.sln

@ -123,10 +123,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.NetCore", "s
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}"
ProjectSection(SolutionItems) = preProject
build\AndroidWorkarounds.props = build\AndroidWorkarounds.props
build\Base.props = build\Base.props
build\Binding.props = build\Binding.props
build\BuildTargets.targets = build\BuildTargets.targets
build\CoreLibraries.props = build\CoreLibraries.props
build\EmbedXaml.props = build\EmbedXaml.props
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
build\iOSWorkarounds.props = build\iOSWorkarounds.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props
@ -136,17 +139,24 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\NetCore.props = build\NetCore.props
build\NetFX.props = build\NetFX.props
build\ReactiveUI.props = build\ReactiveUI.props
build\ReferenceCoreLibraries.props = build\ReferenceCoreLibraries.props
build\Rx.props = build\Rx.props
build\SampleApp.props = build\SampleApp.props
build\SharedVersion.props = build\SharedVersion.props
build\SharpDX.props = build\SharpDX.props
build\SkiaSharp.props = build\SkiaSharp.props
build\SourceLink.props = build\SourceLink.props
build\System.Drawing.Common.props = build\System.Drawing.Common.props
build\System.Memory.props = build\System.Memory.props
build\UnitTests.NetFX.props = build\UnitTests.NetFX.props
build\XUnit.props = build\XUnit.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
ProjectSection(SolutionItems) = preProject
build\UnitTests.NetCore.targets = build\UnitTests.NetCore.targets
build\BuildTargets.targets = build\BuildTargets.targets
build\LegacyProject.targets = build\LegacyProject.targets
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}"

16
native/Avalonia.Native/src/OSX/Screens.mm

@ -4,6 +4,14 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
{
public:
FORWARD_IUNKNOWN()
private:
CGFloat PrimaryDisplayHeight()
{
return NSMaxY([[[NSScreen screens] firstObject] frame]);
}
public:
virtual HRESULT GetScreenCount (int* ret) override
{
@autoreleasepool
@ -25,15 +33,15 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
auto screen = [[NSScreen screens] objectAtIndex:index];
ret->Bounds.X = [screen frame].origin.x;
ret->Bounds.Y = [screen frame].origin.y;
ret->Bounds.Height = [screen frame].size.height;
ret->Bounds.Width = [screen frame].size.width;
ret->Bounds.X = [screen frame].origin.x;
ret->Bounds.Y = PrimaryDisplayHeight() - [screen frame].origin.y - ret->Bounds.Height;
ret->WorkingArea.X = [screen visibleFrame].origin.x;
ret->WorkingArea.Y = [screen visibleFrame].origin.y;
ret->WorkingArea.Height = [screen visibleFrame].size.height;
ret->WorkingArea.Width = [screen visibleFrame].size.width;
ret->WorkingArea.X = [screen visibleFrame].origin.x;
ret->WorkingArea.Y = ret->Bounds.Height - [screen visibleFrame].origin.y - ret->WorkingArea.Height;
ret->PixelDensity = [screen backingScaleFactor];

13
native/Avalonia.Native/src/OSX/window.mm

@ -1291,10 +1291,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_parent->UpdateCursor();
auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget];
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height)
{
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget];
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
}
}
- (void)updateLayer

8
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -10,7 +10,13 @@
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<ListBox Items="{Binding Items}" SelectedItem="{Binding SelectedItem}" AutoScrollToSelectedItem="True" SelectionMode="{Binding SelectionMode}" Width="250" Height="350"></ListBox>
<ListBox Items="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
SelectedItems="{Binding SelectedItems}"
AutoScrollToSelectedItem="True"
SelectionMode="{Binding SelectionMode}"
Width="250"
Height="350"/>
<Button Command="{Binding AddItemCommand}">Add</Button>

31
samples/ControlCatalog/Pages/MenuPage.xaml

@ -20,7 +20,9 @@
<Separator/>
<MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/>
<MenuItem Header="Submenu _2 with Submenu">
<MenuItem Header="Submenu Level 2" />
</MenuItem>
</MenuItem>
<MenuItem Header="Menu Item with _Icon" InputGesture="Ctrl+Shift+B">
<MenuItem.Icon>
@ -52,6 +54,33 @@
</Menu.Styles>
</Menu>
</StackPanel>
<StackPanel>
<TextBlock Classes="h3" Margin="4 8">Mixed</TextBlock>
<Menu>
<MenuItem Header="_File">
<MenuItem Header="_New" CommandParameter="{Binding}" InputGesture="Ctrl+N"/>
<Separator/>
<MenuItem Header="_Open..." InputGesture="Ctrl+O"/>
<Separator/>
<MenuItem Header="Execu_te Script..." />
<Separator/>
<MenuItem Header="_Recent" Items="{Binding RecentItems}">
<MenuItem.Styles>
<Style Selector="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
</Style>
</MenuItem.Styles>
</MenuItem>
<Separator/>
<MenuItem Header="E_xit" InputGesture="Alt+F4"/>
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="_About"/>
</MenuItem>
</Menu>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

36
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

@ -17,6 +17,23 @@ namespace ControlCatalog.ViewModels
SaveCommand = ReactiveCommand.Create(Save, Observable.Return(false));
OpenRecentCommand = ReactiveCommand.Create<string>(OpenRecent);
var recentItems = new[]
{
new MenuItemViewModel
{
Header = "File1.txt",
Command = OpenRecentCommand,
CommandParameter = @"c:\foo\File1.txt"
},
new MenuItemViewModel
{
Header = "File2.txt",
Command = OpenRecentCommand,
CommandParameter = @"c:\foo\File2.txt"
},
};
RecentItems = recentItems;
MenuItems = new[]
{
new MenuItemViewModel
@ -24,27 +41,13 @@ namespace ControlCatalog.ViewModels
Header = "_File",
Items = new[]
{
new MenuItemViewModel { Header = "_Open...", Command = OpenCommand },
new MenuItemViewModel { Header = "O_pen...", Command = OpenCommand },
new MenuItemViewModel { Header = "Save", Command = SaveCommand },
new MenuItemViewModel { Header = "-" },
new MenuItemViewModel
{
Header = "Recent",
Items = new[]
{
new MenuItemViewModel
{
Header = "File1.txt",
Command = OpenRecentCommand,
CommandParameter = @"c:\foo\File1.txt"
},
new MenuItemViewModel
{
Header = "File2.txt",
Command = OpenRecentCommand,
CommandParameter = @"c:\foo\File2.txt"
},
}
Items = recentItems
},
}
},
@ -61,6 +64,7 @@ namespace ControlCatalog.ViewModels
}
public IReadOnlyList<MenuItemViewModel> MenuItems { get; set; }
public IReadOnlyList<MenuItemViewModel> RecentItems { get; set; }
public ReactiveCommand<Unit, Unit> OpenCommand { get; }
public ReactiveCommand<Unit, Unit> SaveCommand { get; }
public ReactiveCommand<string, Unit> OpenRecentCommand { get; }

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -126,7 +126,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_view.Visibility = ViewStates.Visible;
}
public double Scaling => 1;
public double RenderScaling => 1;
void Draw()
{

6
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -3922,6 +3922,12 @@ namespace Avalonia.Controls
dataGridCell: editingCell);
EditingRow.InvalidateDesiredHeight();
var column = editingCell.OwningColumn;
if (column.Width.IsSizeToCells || column.Width.IsAuto)
{// Invalidate desired width and force recalculation
column.SetWidthDesiredValue(0);
EditingRow.OwningGrid.AutoSizeColumn(column, editingCell.DesiredSize.Width);
}
}
// We're done, so raise the CellEditEnded event

6
src/Avalonia.Controls/Chrome/CaptionButtons.cs

@ -38,12 +38,10 @@ namespace Avalonia.Controls.Chrome
{
if (_disposables != null)
{
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow);
layer?.Children.Remove(this);
_disposables.Dispose();
_disposables = null;
_hostWindow = null;
}
}

126
src/Avalonia.Controls/Chrome/TitleBar.cs

@ -12,106 +12,86 @@ namespace Avalonia.Controls.Chrome
public class TitleBar : TemplatedControl
{
private CompositeDisposable? _disposables;
private readonly Window? _hostWindow;
private CaptionButtons? _captionButtons;
public TitleBar(Window hostWindow)
private void UpdateSize(Window window)
{
_hostWindow = hostWindow;
}
public TitleBar()
{
}
public void Attach()
{
if (_disposables == null)
{
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow);
layer?.Children.Add(this);
if (_hostWindow != null)
{
_disposables = new CompositeDisposable
{
_hostWindow.GetObservable(Window.WindowDecorationMarginProperty)
.Subscribe(x => UpdateSize()),
_hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty)
.Subscribe(x => UpdateSize()),
_hostWindow.GetObservable(Window.OffScreenMarginProperty)
.Subscribe(x => UpdateSize()),
_hostWindow.GetObservable(Window.WindowStateProperty)
.Subscribe(x =>
{
PseudoClasses.Set(":minimized", x == WindowState.Minimized);
PseudoClasses.Set(":normal", x == WindowState.Normal);
PseudoClasses.Set(":maximized", x == WindowState.Maximized);
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
})
};
_captionButtons?.Attach(_hostWindow);
}
UpdateSize();
}
}
private void UpdateSize()
{
if (_hostWindow != null)
if (window != null)
{
Margin = new Thickness(
_hostWindow.OffScreenMargin.Left,
_hostWindow.OffScreenMargin.Top,
_hostWindow.OffScreenMargin.Right,
_hostWindow.OffScreenMargin.Bottom);
window.OffScreenMargin.Left,
window.OffScreenMargin.Top,
window.OffScreenMargin.Right,
window.OffScreenMargin.Bottom);
if (_hostWindow.WindowState != WindowState.FullScreen)
if (window.WindowState != WindowState.FullScreen)
{
Height = _hostWindow.WindowDecorationMargin.Top;
Height = window.WindowDecorationMargin.Top;
if (_captionButtons != null)
{
_captionButtons.Height = Height;
}
}
IsVisible = window.PlatformImpl.NeedsManagedDecorations;
}
}
public void Detach()
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_disposables != null)
{
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow);
layer?.Children.Remove(this);
base.OnApplyTemplate(e);
_disposables.Dispose();
_disposables = null;
_captionButtons?.Detach();
_captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons");
_captionButtons?.Detach();
if (VisualRoot is Window window)
{
_captionButtons?.Attach(window);
UpdateSize(window);
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnApplyTemplate(e);
_captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons");
base.OnAttachedToVisualTree(e);
if (_hostWindow != null)
if (VisualRoot is Window window)
{
_captionButtons.Attach(_hostWindow);
_disposables = new CompositeDisposable
{
window.GetObservable(Window.WindowDecorationMarginProperty)
.Subscribe(x => UpdateSize(window)),
window.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty)
.Subscribe(x => UpdateSize(window)),
window.GetObservable(Window.OffScreenMarginProperty)
.Subscribe(x => UpdateSize(window)),
window.GetObservable(Window.ExtendClientAreaChromeHintsProperty)
.Subscribe(x => UpdateSize(window)),
window.GetObservable(Window.WindowStateProperty)
.Subscribe(x =>
{
PseudoClasses.Set(":minimized", x == WindowState.Minimized);
PseudoClasses.Set(":normal", x == WindowState.Normal);
PseudoClasses.Set(":maximized", x == WindowState.Maximized);
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
}),
window.GetObservable(Window.IsExtendedIntoWindowDecorationsProperty)
.Subscribe(x => UpdateSize(window))
};
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
UpdateSize();
_disposables?.Dispose();
_captionButtons?.Detach();
_captionButtons = null;
}
}
}

16
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@ -88,7 +88,7 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset?>(nameof(SelectedDate),
x => x.SelectedDate, (x, v) => x.SelectedDate = v);
//Template Items
// Template Items
private Button _flyoutButton;
private TextBlock _dayText;
private TextBlock _monthText;
@ -359,10 +359,14 @@ namespace Avalonia.Controls
}
}
Grid.SetColumn(_spacer1, 1);
Grid.SetColumn(_spacer2, 3);
_spacer1.IsVisible = columnIndex > 1;
_spacer2.IsVisible = columnIndex > 2;
var isSpacer1Visible = columnIndex > 1;
var isSpacer2Visible = columnIndex > 2;
// ternary conditional operator is used to make sure grid cells will be validated
Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0);
Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0);
_spacer1.IsVisible = isSpacer1Visible;
_spacer2.IsVisible = isSpacer2Visible;
}
private void SetSelectedDateText()
@ -398,7 +402,7 @@ namespace Avalonia.Controls
var deltaY = _presenter.GetOffsetForPopup();
//The extra 5 px I think is related to default popup placement behavior
// The extra 5 px I think is related to default popup placement behavior
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);

31
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@ -77,7 +77,7 @@ namespace Avalonia.Controls
DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>(x =>
x.YearVisible, (x, v) => x.YearVisible = v);
//Template Items
// Template Items
private Grid _pickerContainer;
private Button _acceptButton;
private Button _dismissButton;
@ -107,7 +107,7 @@ namespace Avalonia.Controls
private bool _yearVisible = true;
private DateTimeOffset _syncDate;
private GregorianCalendar _calendar;
private readonly GregorianCalendar _calendar;
private bool _suppressUpdateSelection;
public DatePickerPresenter()
@ -234,7 +234,7 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
//These are requirements, so throw if not found
// These are requirements, so throw if not found
_pickerContainer = e.NameScope.Get<Grid>("PickerContainer");
_monthHost = e.NameScope.Get<Panel>("MonthHost");
_dayHost = e.NameScope.Get<Panel>("DayHost");
@ -326,7 +326,7 @@ namespace Avalonia.Controls
/// </summary>
private void InitPicker()
{
//OnApplyTemplate must've been called before we can init here...
// OnApplyTemplate must've been called before we can init here...
if (_pickerContainer == null)
return;
@ -344,12 +344,11 @@ namespace Avalonia.Controls
SetGrid();
//Date should've been set when we reach this point
// Date should've been set when we reach this point
var dt = Date;
if (DayVisible)
{
GregorianCalendar gc = new GregorianCalendar();
var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month);
var maxDays = _calendar.GetDaysInMonth(dt.Year, dt.Month);
_daySelector.MaximumValue = maxDays;
_daySelector.MinimumValue = 1;
_daySelector.SelectedValue = dt.Day;
@ -407,10 +406,14 @@ namespace Avalonia.Controls
}
}
Grid.SetColumn(_spacer1, 1);
Grid.SetColumn(_spacer2, 3);
_spacer1.IsVisible = columnIndex > 1;
_spacer2.IsVisible = columnIndex > 2;
var isSpacer1Visible = columnIndex > 1;
var isSpacer2Visible = columnIndex > 2;
// ternary conditional operator is used to make sure grid cells will be validated
Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0);
Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0);
_spacer1.IsVisible = isSpacer1Visible;
_spacer2.IsVisible = isSpacer2Visible;
}
private void SetInitialFocus()
@ -433,12 +436,12 @@ namespace Avalonia.Controls
}
}
private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
private void OnDismissButtonClicked(object sender, RoutedEventArgs e)
{
OnDismiss();
}
private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
private void OnAcceptButtonClicked(object sender, RoutedEventArgs e)
{
Date = _syncDate;
OnConfirmed();
@ -471,7 +474,7 @@ namespace Avalonia.Controls
_syncDate = newDate;
//We don't need to update the days if not displaying day, not february
// We don't need to update the days if not displaying day, not february
if (!DayVisible || _syncDate.Month != 2)
return;

51
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="MinuteIncrement"/> property
/// </summary>
public static readonly DirectProperty<TimePicker, int> MinuteIncrementProperty =
AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement),
AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement),
x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v);
/// <summary>
@ -34,17 +34,17 @@ namespace Avalonia.Controls
/// Defines the <see cref="ClockIdentifier"/> property
/// </summary>
public static readonly DirectProperty<TimePicker, string> ClockIdentifierProperty =
AvaloniaProperty.RegisterDirect<TimePicker, string>(nameof(ClockIdentifier),
AvaloniaProperty.RegisterDirect<TimePicker, string>(nameof(ClockIdentifier),
x => x.ClockIdentifier, (x, v) => x.ClockIdentifier = v);
/// <summary>
/// Defines the <see cref="SelectedTime"/> property
/// </summary>
public static readonly DirectProperty<TimePicker, TimeSpan?> SelectedTimeProperty =
AvaloniaProperty.RegisterDirect<TimePicker, TimeSpan?>(nameof(SelectedTime),
AvaloniaProperty.RegisterDirect<TimePicker, TimeSpan?>(nameof(SelectedTime),
x => x.SelectedTime, (x, v) => x.SelectedTime = v);
//Template Items
// Template Items
private TimePickerPresenter _presenter;
private Button _flyoutButton;
private Border _firstPickerHost;
@ -52,7 +52,7 @@ namespace Avalonia.Controls
private Border _thirdPickerHost;
private TextBlock _hourText;
private TextBlock _minuteText;
public TextBlock _periodText;
private TextBlock _periodText;
private Rectangle _firstSplitter;
private Rectangle _secondSplitter;
private Grid _contentGrid;
@ -145,7 +145,7 @@ namespace Avalonia.Controls
if (_flyoutButton != null)
_flyoutButton.Click -= OnFlyoutButtonClicked;
if(_presenter != null)
if (_presenter != null)
{
_presenter.Confirmed -= OnConfirmed;
_presenter.Dismissed -= OnDismissPicker;
@ -170,7 +170,6 @@ namespace Avalonia.Controls
_popup = e.NameScope.Find<Popup>("Popup");
_presenter = e.NameScope.Find<TimePickerPresenter>("PickerPresenter");
if (_flyoutButton != null)
_flyoutButton.Click += OnFlyoutButtonClicked;
@ -185,7 +184,6 @@ namespace Avalonia.Controls
_presenter[!TimePickerPresenter.MinuteIncrementProperty] = this[!MinuteIncrementProperty];
_presenter[!TimePickerPresenter.ClockIdentifierProperty] = this[!ClockIdentifierProperty];
}
}
private void SetGrid()
@ -195,30 +193,19 @@ namespace Avalonia.Controls
bool use24HourClock = ClockIdentifier == "24HourClock";
if (!use24HourClock)
{
_contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*");
_thirdPickerHost.IsVisible = true;
_secondSplitter.IsVisible = true;
var columnsD = use24HourClock ? "*, Auto, *" : "*, Auto, *, Auto, *";
_contentGrid.ColumnDefinitions = new ColumnDefinitions(columnsD);
Grid.SetColumn(_firstPickerHost, 0);
Grid.SetColumn(_secondPickerHost, 2);
Grid.SetColumn(_thirdPickerHost, 4);
_thirdPickerHost.IsVisible = !use24HourClock;
_secondSplitter.IsVisible = !use24HourClock;
Grid.SetColumn(_firstSplitter, 1);
Grid.SetColumn(_secondSplitter, 3);
}
else
{
_contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*");
_thirdPickerHost.IsVisible = false;
_secondSplitter.IsVisible = false;
Grid.SetColumn(_firstPickerHost, 0);
Grid.SetColumn(_secondPickerHost, 2);
Grid.SetColumn(_firstPickerHost, 0);
Grid.SetColumn(_secondPickerHost, 2);
Grid.SetColumn(_thirdPickerHost, use24HourClock ? 0 : 4);
Grid.SetColumn(_firstSplitter, 1);
}
Grid.SetColumn(_firstSplitter, 1);
Grid.SetColumn(_secondSplitter, use24HourClock ? 0 : 3);
}
private void SetSelectedTimeText()
@ -237,14 +224,13 @@ namespace Avalonia.Controls
hr = hr > 12 ? hr - 12 : hr == 0 ? 12 : hr;
newTime = new TimeSpan(hr, newTime.Minutes, 0);
}
_hourText.Text = newTime.ToString("%h");
_hourText.Text = newTime.ToString("%h");
_minuteText.Text = newTime.ToString("mm");
PseudoClasses.Set(":hasnotime", false);
_periodText.Text = time.Value.Hours >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator :
CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator;
}
else
{
@ -262,7 +248,7 @@ namespace Avalonia.Controls
SelectedTimeChanged?.Invoke(this, new TimePickerSelectedValueChangedEventArgs(oldTime, newTime));
}
private void OnFlyoutButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
private void OnFlyoutButtonClicked(object sender, Interactivity.RoutedEventArgs e)
{
_presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay;
@ -270,7 +256,7 @@ namespace Avalonia.Controls
var deltaY = _presenter.GetOffsetForPopup();
//The extra 5 px I think is related to default popup placement behavior
// The extra 5 px I think is related to default popup placement behavior
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
@ -287,6 +273,5 @@ namespace Avalonia.Controls
_popup.Close();
SelectedTime = _presenter.Time;
}
}
}

38
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@ -39,7 +39,7 @@ namespace Avalonia.Controls
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
}
//TemplateItems
// TemplateItems
private Grid _pickerContainer;
private Button _acceptButton;
private Button _dismissButton;
@ -55,8 +55,8 @@ namespace Avalonia.Controls
private Button _minuteDownButton;
private Button _periodDownButton;
//Backing Fields
private TimeSpan _Time;
// Backing Fields
private TimeSpan _time;
private int _minuteIncrement = 1;
private string _clockIdentifier = "12HourClock";
@ -83,7 +83,7 @@ namespace Avalonia.Controls
get => _clockIdentifier;
set
{
if (string.IsNullOrEmpty(value) || value == "" || !(value == "12HourClock" || value == "24HourClock"))
if (string.IsNullOrEmpty(value) || !(value == "12HourClock" || value == "24HourClock"))
throw new ArgumentException("Invalid ClockIdentifier");
SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value);
InitPicker();
@ -95,10 +95,10 @@ namespace Avalonia.Controls
/// </summary>
public TimeSpan Time
{
get => _Time;
get => _time;
set
{
SetAndRaise(TimeProperty, ref _Time, value);
SetAndRaise(TimeProperty, ref _time, value);
InitPicker();
}
}
@ -213,26 +213,24 @@ namespace Avalonia.Controls
private void SetGrid()
{
if (ClockIdentifier == "12HourClock")
{
_pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*");
_spacer2.IsVisible = true;
_periodHost.IsVisible = true;
}
else
{
_pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*");
_spacer2.IsVisible = false;
_periodHost.IsVisible = false;
}
bool use24HourClock = ClockIdentifier == "24HourClock";
var columnsD = use24HourClock ? "*, Auto, *" : "*, Auto, *, Auto, *";
_pickerContainer.ColumnDefinitions = new ColumnDefinitions(columnsD);
_spacer2.IsVisible = !use24HourClock;
_periodHost.IsVisible = !use24HourClock;
Grid.SetColumn(_spacer2, use24HourClock ? 0 : 3);
Grid.SetColumn(_periodHost, use24HourClock ? 0 : 4);
}
private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
private void OnDismissButtonClicked(object sender, RoutedEventArgs e)
{
OnDismiss();
}
private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
private void OnAcceptButtonClicked(object sender, RoutedEventArgs e)
{
OnConfirmed();
}

2
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -35,7 +35,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
}
}
public double Scaling
public double RenderScaling
{
get { return _scaling; }
set

1
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -142,7 +142,6 @@ namespace Avalonia.Controls.Generators
private readonly IDataTemplate _inner;
public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner;
public IControl Build(object param) => _inner.Build(param);
public bool SupportsRecycling => _inner.SupportsRecycling;
public bool Match(object data) => _inner.Match(data);
public InstancedBinding ItemsSelector(object item) => null;
}

6
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -23,10 +23,10 @@ namespace Avalonia.Platform
Size ClientSize { get; }
/// <summary>
/// Gets the scaling factor for the toplevel.
/// Gets the scaling factor for the toplevel. This is used for rendering.
/// </summary>
double Scaling { get; }
double RenderScaling { get; }
/// <summary>
/// The list of native platform's surfaces that can be consumed by rendering subsystems.
/// </summary>

5
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@ -13,6 +13,11 @@ namespace Avalonia.Platform
/// Hides the window.
/// </summary>
void Hide();
/// <summary>
/// Gets the scaling factor for Window positioning and sizing.
/// </summary>
double DesktopScaling { get; }
/// <summary>
/// Gets the position of the window in device pixels.

21
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -86,7 +86,7 @@ namespace Avalonia.Controls.Presenters
private IControl _child;
private bool _createdChild;
private IDataTemplate _dataTemplate;
private IRecyclingDataTemplate _recyclingDataTemplate;
private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
/// <summary>
@ -281,7 +281,7 @@ namespace Avalonia.Controls.Presenters
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
_dataTemplate = null;
_recyclingDataTemplate = null;
_createdChild = false;
InvalidateMeasure();
}
@ -307,22 +307,21 @@ namespace Avalonia.Controls.Presenters
{
var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
// We have content and it isn't a control, so if the new data template is the same
// as the old data template, try to recycle the existing child control to display
// the new data.
if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling)
if (dataTemplate is IRecyclingDataTemplate rdt)
{
newChild = oldChild;
var toRecycle = rdt == _recyclingDataTemplate ? oldChild : null;
newChild = rdt.Build(content, toRecycle);
_recyclingDataTemplate = rdt;
}
else
{
_dataTemplate = dataTemplate;
newChild = _dataTemplate.Build(content);
newChild = dataTemplate.Build(content);
_recyclingDataTemplate = null;
}
}
else
{
_dataTemplate = null;
_recyclingDataTemplate = null;
}
return newChild;
@ -422,7 +421,7 @@ namespace Avalonia.Controls.Presenters
LogicalChildren.Remove(Child);
((ISetInheritanceParent)Child).SetParent(Child.Parent);
Child = null;
_dataTemplate = null;
_recyclingDataTemplate = null;
}
InvalidateMeasure();

7
src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs

@ -8,7 +8,7 @@ namespace Avalonia.Controls.Primitives
{
public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest
{
public static ChromeOverlayLayer? GetOverlayLayer(IVisual visual)
public static Panel? GetOverlayLayer(IVisual visual)
{
foreach (var v in visual.GetVisualAncestors())
if (v is VisualLayerManager vlm)
@ -24,6 +24,11 @@ namespace Avalonia.Controls.Primitives
return null;
}
public void Add(Control c)
{
base.Children.Add(c);
}
public bool HitTest(Point point) => Children.HitTestCustom(point);
}
}

4
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs

@ -40,9 +40,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
public void MoveAndResize(Point devicePoint, Size virtualSize)
{
_moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling);
_moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.RenderScaling);
}
public virtual double Scaling => _parent.Scaling;
public virtual double Scaling => _parent.DesktopScaling;
}
}

20
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -692,14 +692,24 @@ namespace Avalonia.Controls.Primitives
}
}
foreach (var i in e.SelectedIndices)
if (e.SelectedIndices.Count > 0 || e.DeselectedIndices.Count > 0)
{
Mark(i.GetAt(0), true);
}
foreach (var i in e.SelectedIndices)
{
Mark(i.GetAt(0), true);
}
foreach (var i in e.DeselectedIndices)
foreach (var i in e.DeselectedIndices)
{
Mark(i.GetAt(0), false);
}
}
else if (e.DeselectedItems.Count > 0)
{
Mark(i.GetAt(0), false);
// (De)selected indices being empty means that a selected item was removed from
// the Items (it can't tell us the index of the item because the index is no longer
// valid). In this case, we just update the selection state of all containers.
UpdateContainerSelection();
}
var newSelectedIndex = SelectedIndex;

29
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@ -1,5 +1,6 @@
using System.Collections.Generic;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
namespace Avalonia.Controls.Primitives
{
@ -11,10 +12,12 @@ namespace Avalonia.Controls.Primitives
private ILogicalRoot _logicalRoot;
private readonly List<Control> _layers = new List<Control>();
public static readonly StyledProperty<ChromeOverlayLayer> ChromeOverlayLayerProperty =
AvaloniaProperty.Register<VisualLayerManager, ChromeOverlayLayer>(nameof(ChromeOverlayLayer));
public bool IsPopup { get; set; }
public AdornerLayer AdornerLayer
{
get
@ -30,10 +33,19 @@ namespace Avalonia.Controls.Primitives
{
get
{
var rv = FindLayer<ChromeOverlayLayer>();
if (rv == null)
AddLayer(rv = new ChromeOverlayLayer(), ChromeZIndex);
return rv;
var current = GetValue(ChromeOverlayLayerProperty);
if (current is null)
{
var chromeOverlayLayer = new ChromeOverlayLayer();
AddLayer(chromeOverlayLayer, ChromeZIndex);
SetValue(ChromeOverlayLayerProperty, chromeOverlayLayer);
current = chromeOverlayLayer;
}
return current;
}
}
@ -44,7 +56,7 @@ namespace Avalonia.Controls.Primitives
if (IsPopup)
return null;
var rv = FindLayer<OverlayLayer>();
if(rv == null)
if (rv == null)
AddLayer(rv = new OverlayLayer(), OverlayZIndex);
return rv;
}
@ -65,7 +77,8 @@ namespace Avalonia.Controls.Primitives
layer.ZIndex = zindex;
VisualChildren.Add(layer);
if (((ILogical)this).IsAttachedToLogicalTree)
((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_logicalRoot, layer, this));
((ILogical)layer).NotifyAttachedToLogicalTree(
new LogicalTreeAttachmentEventArgs(_logicalRoot, layer, this));
InvalidateArrange();
}

2
src/Avalonia.Controls/Repeater/ElementFactory.cs

@ -4,8 +4,6 @@ namespace Avalonia.Controls
{
public abstract class ElementFactory : IElementFactory
{
bool IDataTemplate.SupportsRecycling => false;
public IControl Build(object data)
{
return GetElementCore(new ElementFactoryGetArgs { Data = data });

1
src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs

@ -13,7 +13,6 @@ namespace Avalonia.Controls
public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate;
public bool SupportsRecycling => false;
public IControl Build(object param) => GetElement(null, param);
public bool Match(object data) => _dataTemplate.Match(data);

29
src/Avalonia.Controls/Templates/FuncDataTemplate.cs

@ -6,7 +6,7 @@ namespace Avalonia.Controls.Templates
/// <summary>
/// Builds a control for a piece of data.
/// </summary>
public class FuncDataTemplate : FuncTemplate<object, IControl>, IDataTemplate
public class FuncDataTemplate : FuncTemplate<object, IControl>, IRecyclingDataTemplate
{
/// <summary>
/// The default data template used in the case where no matching data template is found.
@ -30,10 +30,8 @@ namespace Avalonia.Controls.Templates
},
true);
/// <summary>
/// The implementation of the <see cref="Match"/> method.
/// </summary>
private readonly Func<object, bool> _match;
private readonly bool _supportsRecycling;
/// <summary>
/// Initializes a new instance of the <see cref="FuncDataTemplate"/> class.
@ -70,12 +68,9 @@ namespace Avalonia.Controls.Templates
Contract.Requires<ArgumentNullException>(match != null);
_match = match;
SupportsRecycling = supportsRecycling;
_supportsRecycling = supportsRecycling;
}
/// <inheritdoc/>
public bool SupportsRecycling { get; }
/// <summary>
/// Checks to see if this data template matches the specified data.
/// </summary>
@ -88,6 +83,24 @@ namespace Avalonia.Controls.Templates
return _match(data);
}
/// <summary>
/// Creates or recycles a control to display the specified data.
/// </summary>
/// <param name="data">The data to display.</param>
/// <param name="existing">An optional control to recycle.</param>
/// <returns>
/// The <paramref name="existing"/> control if supplied and applicable to
/// <paramref name="data"/>, otherwise a new control.
/// </returns>
/// <remarks>
/// The caller should ensure that any control passed to <paramref name="existing"/>
/// originated from the same data template.
/// </remarks>
public IControl Build(object data, IControl existing)
{
return _supportsRecycling && existing is object ? existing : Build(data);
}
/// <summary>
/// Determines of an object is of the specified type.
/// </summary>

6
src/Avalonia.Controls/Templates/FuncTemplate`2.cs

@ -1,5 +1,7 @@
using System;
#nullable enable
namespace Avalonia.Controls.Templates
{
/// <summary>
@ -18,9 +20,7 @@ namespace Avalonia.Controls.Templates
/// <param name="func">The function used to create the control.</param>
public FuncTemplate(Func<TParam, INameScope, TControl> func)
{
Contract.Requires<ArgumentNullException>(func != null);
_func = func;
_func = func ?? throw new ArgumentNullException(nameof(func));
}
/// <summary>

12
src/Avalonia.Controls/Templates/IDataTemplate.cs

@ -1,3 +1,7 @@
using System;
#nullable enable
namespace Avalonia.Controls.Templates
{
/// <summary>
@ -5,12 +9,6 @@ namespace Avalonia.Controls.Templates
/// </summary>
public interface IDataTemplate : ITemplate<object, IControl>
{
/// <summary>
/// Gets a value indicating whether the data template supports recycling of the generated
/// control.
/// </summary>
bool SupportsRecycling { get; }
/// <summary>
/// Checks to see if this data template matches the specified data.
/// </summary>
@ -20,4 +18,4 @@ namespace Avalonia.Controls.Templates
/// </returns>
bool Match(object data);
}
}
}

25
src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs

@ -0,0 +1,25 @@
#nullable enable
namespace Avalonia.Controls.Templates
{
/// <summary>
/// An <see cref="IDataTemplate"/> that supports recycling existing elements.
/// </summary>
public interface IRecyclingDataTemplate : IDataTemplate
{
/// <summary>
/// Creates or recycles a control to display the specified data.
/// </summary>
/// <param name="data">The data to display.</param>
/// <param name="existing">An optional control to recycle.</param>
/// <returns>
/// The <paramref name="existing"/> control if supplied and applicable to
/// <paramref name="data"/>, otherwise a new control.
/// </returns>
/// <remarks>
/// The caller should ensure that any control passed to <paramref name="existing"/>
/// originated from the same data template.
/// </remarks>
IControl Build(object data, IControl? existing);
}
}

4
src/Avalonia.Controls/TopLevel.cs

@ -280,10 +280,10 @@ namespace Avalonia.Controls
}
/// <inheritdoc/>
double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1;
double ILayoutRoot.LayoutScaling => PlatformImpl?.RenderScaling ?? 1;
/// <inheritdoc/>
double IRenderRoot.RenderScaling => PlatformImpl?.Scaling ?? 1;
double IRenderRoot.RenderScaling => PlatformImpl?.RenderScaling ?? 1;
IStyleHost IStyleHost.StylingParent => _globalStyles;

20
src/Avalonia.Controls/Window.cs

@ -6,6 +6,7 @@ using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls.Chrome;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
@ -70,8 +71,7 @@ namespace Avalonia.Controls
/// </summary>
public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot
{
private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>();
private TitleBar _managedTitleBar;
private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>();
private bool _isExtendedIntoWindowDecorations;
private Thickness _windowDecorationMargin;
private Thickness _offScreenMargin;
@ -552,20 +552,6 @@ namespace Avalonia.Controls
IsExtendedIntoWindowDecorations = isExtended;
WindowDecorationMargin = PlatformImpl.ExtendedMargins;
OffScreenMargin = PlatformImpl.OffScreenMargin;
if (PlatformImpl.NeedsManagedDecorations)
{
if (_managedTitleBar == null)
{
_managedTitleBar = new TitleBar(this);
_managedTitleBar.Attach();
}
}
else
{
_managedTitleBar?.Detach();
_managedTitleBar = null;
}
}
/// <summary>
@ -818,7 +804,7 @@ namespace Avalonia.Controls
private void SetWindowStartupLocation(IWindowBaseImpl owner = null)
{
var scaling = owner?.Scaling ?? PlatformImpl?.Scaling ?? 1;
var scaling = owner?.DesktopScaling ?? PlatformImpl?.DesktopScaling ?? 1;
// TODO: We really need non-client size here.
var rect = new PixelRect(

1
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -36,6 +36,7 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public double DesktopScaling => 1.0;
public PixelPoint Position { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public Action Deactivated { get; set; }

3
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -21,7 +21,8 @@ namespace Avalonia.DesignerSupport.Remote
public IPlatformHandle Handle { get; }
public Size MaxAutoSizeHint { get; }
public Size ClientSize { get; }
public double Scaling { get; } = 1.0;
public double RenderScaling { get; } = 1.0;
public double DesktopScaling => 1.0;
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }

2
src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs

@ -7,8 +7,6 @@ namespace Avalonia.Diagnostics
{
internal class ViewLocator : IDataTemplate
{
public bool SupportsRecycling => false;
public IControl Build(object data)
{
var name = data.GetType().FullName.Replace("ViewModel", "View");

9
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -41,7 +41,8 @@ namespace Avalonia.Headless
}
public Size ClientSize { get; set; }
public double Scaling { get; } = 1;
public double RenderScaling { get; } = 1;
public double DesktopScaling => RenderScaling;
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
@ -62,9 +63,9 @@ namespace Avalonia.Headless
public IInputRoot InputRoot { get; set; }
public Point PointToClient(PixelPoint point) => point.ToPoint(Scaling);
public Point PointToClient(PixelPoint point) => point.ToPoint(RenderScaling);
public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, Scaling);
public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling);
public void SetCursor(IPlatformHandle cursor)
{
@ -201,7 +202,7 @@ namespace Avalonia.Headless
public ILockedFramebuffer Lock()
{
var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, Scaling), new Vector(96, 96) * Scaling);
var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, RenderScaling), new Vector(96, 96) * RenderScaling);
var fb = bmp.Lock();
return new FramebufferProxy(fb, () =>
{

15
src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs

@ -1,15 +0,0 @@
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Platform;
namespace Avalonia.Native
{
class OsxManagedPopupPositionerPopupImplHelper : ManagedPopupPositionerPopupImplHelper
{
public OsxManagedPopupPositionerPopupImplHelper(IWindowBaseImpl parent, MoveResizeDelegate moveResize) : base(parent, moveResize)
{
}
public override double Scaling => 1;
}
}

5
src/Avalonia.Native/OsxUnicodeKeys.cs

@ -140,6 +140,11 @@ namespace Avalonia.Native.Interop
}
else
{
if (key >= Key.D0 && key <= Key.D9)
{
return key.ToString().Replace("D", "");
}
return key.ToString().ToLower();
}
}

2
src/Avalonia.Native/PopupImpl.cs

@ -26,7 +26,7 @@ namespace Avalonia.Native
var context = _opts.UseGpu ? glFeature?.DeferredContext : null;
Init(factory.CreatePopup(e, context?.Context), factory.CreateScreens(), context);
}
PopupPositioner = new ManagedPopupPositioner(new OsxManagedPopupPositionerPopupImplHelper(parent, MoveResize));
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}
private void MoveResize(PixelPoint position, Size size, double scaling)

8
src/Avalonia.Native/WindowImplBase.cs

@ -81,7 +81,7 @@ namespace Avalonia.Native
_glSurface = new GlPlatformSurface(window, _glContext);
Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize;
_savedScaling = Scaling;
_savedScaling = RenderScaling;
_nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost());
var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
@ -369,7 +369,9 @@ namespace Avalonia.Native
_native.SetTopMost(value);
}
public double Scaling => _native?.GetScaling() ?? 1;
public double RenderScaling => _native?.GetScaling() ?? 1;
public double DesktopScaling => 1;
public Action Deactivated { get; set; }
public Action Activated { get; set; }
@ -432,7 +434,7 @@ namespace Avalonia.Native
TransparencyLevel = transparencyLevel;
_native.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur);
_native?.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur);
TransparencyLevelChanged?.Invoke(TransparencyLevel);
}
}

2
src/Avalonia.Themes.Default/TitleBar.xaml

@ -15,7 +15,7 @@
<Panel x:Name="PART_MouseTracker" Height="1" VerticalAlignment="Top" />
<Panel x:Name="PART_Container">
<Border x:Name="PART_Background" Background="{TemplateBinding Background}" />
<CaptionButtons x:Name="PART_CaptionButtons" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="{TemplateBinding Foreground}" MaxHeight="30" />
<CaptionButtons x:Name="PART_CaptionButtons" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="{TemplateBinding Foreground}" />
</Panel>
</Panel>
</ControlTemplate>

34
src/Avalonia.Themes.Default/Window.xaml

@ -1,23 +1,25 @@
<Style xmlns="https://github.com/avaloniaui" Selector="Window">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/>
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
<Setter Property="TransparencyBackgroundFallback" Value="{DynamicResource SystemControlBackgroundAltHighBrush}" />
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource ContentControlFontSize}"/>
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="Template">
<ControlTemplate>
<Panel>
<Panel IsHitTestVisible="False" Margin="{TemplateBinding OffScreenMargin}">
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
</Panel>
<Border Background="{TemplateBinding Background}">
<VisualLayerManager>
<ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</VisualLayerManager>
</Border>
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
<Border Background="{TemplateBinding Background}" IsHitTestVisible="False" />
<VisualLayerManager>
<VisualLayerManager.ChromeOverlayLayer>
<TitleBar />
</VisualLayerManager.ChromeOverlayLayer>
<ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</VisualLayerManager>
</Panel>
</ControlTemplate>
</Setter>

6
src/Avalonia.Themes.Fluent/Menu.xaml

@ -11,10 +11,12 @@
</Design.PreviewWith>
<Style.Resources>
<x:Double x:Key="MenuHeight">32</x:Double>
<x:Double x:Key="MenuBarHeight">32</x:Double>
<Thickness x:Key="MenuBarItemPadding">12,0,12,0</Thickness>
</Style.Resources>
<Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="{StaticResource MenuHeight}" />
<Setter Property="Height" Value="{StaticResource MenuBarHeight}" />
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"

184
src/Avalonia.Themes.Fluent/MenuItem.xaml

@ -25,11 +25,12 @@
</MenuItem>
<MenuItem Header="Edit">
<MenuItem Header="Go To">
<MenuItem Header="Go To Line"/>
<MenuItem Header="Go To Line" />
</MenuItem>
</MenuItem>
<MenuItem Header="View">
<MenuItem Header="Designer" InputGesture="Shift+F7" />
<MenuItem Header="Designer"
InputGesture="Shift+F7" />
</MenuItem>
<MenuItem Header="Project">
<MenuItem Header="Add class" />
@ -40,9 +41,10 @@
<Styles.Resources>
<conv:PlatformKeyGestureConverter x:Key="KeyGestureConverter" />
<x:Double x:Key="MenuFlyoutSubItemPopupHorizontalOffset">-4</x:Double>
<Thickness x:Key="MenuFlyoutScrollerMargin">0,4,0,4</Thickness>
<Thickness x:Key="MenuIconPresenterMargin">0,0,12,0</Thickness>
<Thickness x:Key="MenuInputGestureTextMargin">24,0,0,0</Thickness>
<Thickness x:Key="MenuInputGestureTextMargin">24,0,0,0</Thickness>
<StreamGeometry x:Key="MenuItemChevronPathData">M 1,0 10,10 l -9,10 -1,-1 L 8,10 -0,1 Z</StreamGeometry>
</Styles.Resources>
@ -54,83 +56,84 @@
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="PART_LayoutRoot"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
SharedSizeGroup="MenuItemIcon" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"
SharedSizeGroup="MenuItemIGT" />
<ColumnDefinition Width="Auto"
SharedSizeGroup="MenuItemChevron" />
</Grid.ColumnDefinitions>
<ContentPresenter Name="PART_IconPresenter"
Content="{TemplateBinding Icon}"
Width="16"
Height="16"
Margin="{DynamicResource MenuIconPresenterMargin}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<Panel>
<Border Name="PART_LayoutRoot"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
SharedSizeGroup="MenuItemIcon" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"
SharedSizeGroup="MenuItemIGT" />
<ColumnDefinition Width="Auto"
SharedSizeGroup="MenuItemChevron" />
</Grid.ColumnDefinitions>
<ContentPresenter Name="PART_IconPresenter"
Content="{TemplateBinding Icon}"
Width="16"
Height="16"
Margin="{DynamicResource MenuIconPresenterMargin}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<ContentPresenter Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
TextBlock.Foreground="{TemplateBinding Foreground}"
Grid.Column="1">
<ContentPresenter.DataTemplates>
<DataTemplate DataType="sys:String">
<AccessText Text="{Binding}" />
</DataTemplate>
</ContentPresenter.DataTemplates>
</ContentPresenter>
<TextBlock x:Name="PART_InputGestureText"
Grid.Column="2"
Classes="CaptionTextBlockStyle"
Margin="{DynamicResource MenuInputGestureTextMargin}"
Text="{TemplateBinding InputGesture,
Converter={StaticResource KeyGestureConverter}}"
HorizontalAlignment="Right"
VerticalAlignment="Center" />
<Path Name="PART_ChevronPath"
Stretch="Uniform"
Width="8"
Height="16"
Data="{StaticResource MenuItemChevronPathData}"
Margin="{DynamicResource MenuFlyoutItemChevronMargin}"
VerticalAlignment="Center"
Grid.Column="3" />
<Popup Name="PART_Popup"
WindowManagerAddShadowHint="True"
PlacementMode="Right"
StaysOpen="True"
IsOpen="{TemplateBinding IsSubMenuOpen,
Mode=TwoWay}">
<Border Background="{DynamicResource MenuFlyoutPresenterBackground}"
BorderBrush="{DynamicResource MenuFlyoutPresenterBorderBrush}"
BorderThickness="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}"
Padding="{DynamicResource MenuFlyoutPresenterThemePadding}"
MaxWidth="{DynamicResource FlyoutThemeMaxWidth}"
MinHeight="{DynamicResource MenuFlyoutThemeMinHeight}"
HorizontalAlignment="Stretch"
CornerRadius="{DynamicResource OverlayCornerRadius}">
<ScrollViewer>
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
Margin="{DynamicResource MenuFlyoutScrollerMargin}"
Grid.IsSharedSizeScope="True" />
</ScrollViewer>
</Border>
</Popup>
</Grid>
</Border>
<ContentPresenter Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
TextBlock.Foreground="{TemplateBinding Foreground}"
Grid.Column="1">
<ContentPresenter.DataTemplates>
<DataTemplate DataType="sys:String">
<AccessText Text="{Binding}" />
</DataTemplate>
</ContentPresenter.DataTemplates>
</ContentPresenter>
<TextBlock x:Name="PART_InputGestureText"
Grid.Column="2"
Classes="CaptionTextBlockStyle"
Margin="{DynamicResource MenuInputGestureTextMargin}"
Text="{TemplateBinding InputGesture, Converter={StaticResource KeyGestureConverter}}"
HorizontalAlignment="Right"
VerticalAlignment="Center" />
<Path Name="PART_ChevronPath"
Stretch="Uniform"
Width="8"
Height="16"
Data="{StaticResource MenuItemChevronPathData}"
Margin="{DynamicResource MenuFlyoutItemChevronMargin}"
VerticalAlignment="Center"
Grid.Column="3" />
</Grid>
</Border>
<Popup Name="PART_Popup"
WindowManagerAddShadowHint="True"
PlacementMode="Right"
HorizontalOffset="{StaticResource MenuFlyoutSubItemPopupHorizontalOffset}"
StaysOpen="True"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
<Border Background="{DynamicResource MenuFlyoutPresenterBackground}"
BorderBrush="{DynamicResource MenuFlyoutPresenterBorderBrush}"
BorderThickness="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}"
Padding="{DynamicResource MenuFlyoutPresenterThemePadding}"
MaxWidth="{DynamicResource FlyoutThemeMaxWidth}"
MinHeight="{DynamicResource MenuFlyoutThemeMinHeight}"
HorizontalAlignment="Stretch"
CornerRadius="{DynamicResource OverlayCornerRadius}">
<ScrollViewer>
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
Margin="{DynamicResource MenuFlyoutScrollerMargin}"
Grid.IsSharedSizeScope="True" />
</ScrollViewer>
</Border>
</Popup>
</Panel>
</ControlTemplate>
</Setter>
</Style>
@ -145,6 +148,8 @@
<Panel>
<ContentPresenter Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Margin="{TemplateBinding Padding}">
<ContentPresenter.DataTemplates>
<DataTemplate DataType="sys:String">
@ -155,8 +160,7 @@
<Popup Name="PART_Popup"
WindowManagerAddShadowHint="False"
MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
IsOpen="{TemplateBinding IsSubMenuOpen,
Mode=TwoWay}"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}"
StaysOpen="True">
<Border Background="{DynamicResource MenuFlyoutPresenterBackground}"
BorderBrush="{DynamicResource MenuFlyoutPresenterBorderBrush}"
@ -183,10 +187,15 @@
</Style>
<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="{DynamicResource MenuFlyoutItemThemePaddingNarrow}" />
<!-- 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}" />
</Style>
<Style Selector="Menu > MenuItem">
<!-- Custom padding for Menu > MenuItem -->
<Setter Property="Padding" Value="{StaticResource MenuBarItemPadding}" />
</Style>
<Style Selector="MenuItem /template/ ContentPresenter#PART_IconPresenter">
<Setter Property="IsVisible" Value="False" />
</Style>
@ -212,14 +221,15 @@
<Setter Property="Fill" Value="{DynamicResource MenuFlyoutSubItemChevronPointerOver}" />
</Style>
<Style Selector="MenuItem:pressed /template/ Border#PART_LayoutRoot">
<!-- Listen for PART_LayoutRoot:pointerover, so it will not be triggered when subitem is pressed -->
<Style Selector="MenuItem:pressed /template/ Border#PART_LayoutRoot:pointerover">
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutItemBorderBrushPressed}" />
</Style>
<Style Selector="MenuItem:pressed /template/ TextBlock#PART_InputGestureText">
<Style Selector="MenuItem:pressed /template/ Border#PART_LayoutRoot:pointerover TextBlock#PART_InputGestureText">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundPressed}" />
</Style>
<Style Selector="MenuItem:pressed /template/ Path#PART_ChevronPath">
<Style Selector="MenuItem:pressed /template/ Border#PART_LayoutRoot:pointerover Path#PART_ChevronPath">
<Setter Property="Fill" Value="{DynamicResource MenuFlyoutSubItemChevronPressed}" />
</Style>

2
src/Avalonia.Themes.Fluent/NumericUpDown.xaml

@ -43,7 +43,7 @@
<TextBox Name="PART_TextBox"
Background="Transparent"
BorderBrush="Transparent"
Margin="-2"
Margin="-1"
Padding="{TemplateBinding Padding}"
Watermark="{TemplateBinding Watermark}"
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"

2
src/Avalonia.Themes.Fluent/TitleBar.xaml

@ -15,7 +15,7 @@
<Panel x:Name="PART_MouseTracker" Height="1" VerticalAlignment="Top" />
<Panel x:Name="PART_Container">
<Border x:Name="PART_Background" Background="{TemplateBinding Background}" />
<CaptionButtons x:Name="PART_CaptionButtons" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="{TemplateBinding Foreground}" MaxHeight="30" />
<CaptionButtons x:Name="PART_CaptionButtons" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="{TemplateBinding Foreground}" />
</Panel>
</Panel>
</ControlTemplate>

22
src/Avalonia.Themes.Fluent/Window.xaml

@ -8,16 +8,18 @@
<ControlTemplate>
<Panel>
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
<Border Background="{TemplateBinding Background}">
<VisualLayerManager>
<ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</VisualLayerManager>
</Border>
<Border Background="{TemplateBinding Background}" IsHitTestVisible="False" />
<VisualLayerManager>
<VisualLayerManager.ChromeOverlayLayer>
<TitleBar />
</VisualLayerManager.ChromeOverlayLayer>
<ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</VisualLayerManager>
</Panel>
</ControlTemplate>
</Setter>

20
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@ -181,7 +181,7 @@ namespace Avalonia.Media.TextFormatting
return nextCharacterHit;
}
return new CharacterHit(TextRange.End); // Can't move, we're after the last character
return characterHit; // Can't move, we're after the last character
}
/// <inheritdoc/>
@ -192,7 +192,7 @@ namespace Avalonia.Media.TextFormatting
return previousCharacterHit;
}
return new CharacterHit(TextRange.Start); // Can't move, we're before the first character
return characterHit; // Can't move, we're before the first character
}
/// <inheritdoc/>
@ -247,9 +247,13 @@ namespace Avalonia.Media.TextFormatting
{
var run = _textRuns[runIndex];
nextCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
if (codepointIndex <= nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength)
nextCharacterHit = characterHit.TrailingLength != 0 ?
foundCharacterHit :
new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength);
if (nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex)
{
return true;
}
@ -283,9 +287,13 @@ namespace Avalonia.Media.TextFormatting
{
var run = _textRuns[runIndex];
previousCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
previousCharacterHit = characterHit.TrailingLength != 0 ?
foundCharacterHit :
new CharacterHit(foundCharacterHit.FirstCharacterIndex);
if (previousCharacterHit.FirstCharacterIndex < codepointIndex)
if (previousCharacterHit.FirstCharacterIndex < characterHit.FirstCharacterIndex)
{
return true;
}

3
src/Avalonia.X11/NativeDialogs/Gtk.cs

@ -204,6 +204,9 @@ namespace Avalonia.X11.NativeDialogs
[DllImport(GtkName)]
public static extern IntPtr gtk_file_chooser_add_filter(IntPtr chooser, IntPtr filter);
[DllImport(GtkName)]
public static extern IntPtr gtk_file_chooser_get_filter(IntPtr chooser);
[DllImport(GtkName)]
public static extern void gtk_widget_realize(IntPtr gtkWidget);

45
src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs

@ -16,7 +16,7 @@ namespace Avalonia.X11.NativeDialogs
{
private Task<bool> _initialized;
private unsafe Task<string[]> ShowDialog(string title, IWindowImpl parent, GtkFileChooserAction action,
bool multiSelect, string initialFileName, IEnumerable<FileDialogFilter> filters)
bool multiSelect, string initialFileName, IEnumerable<FileDialogFilter> filters, string defaultExtension)
{
IntPtr dlg;
using (var name = new Utf8Buffer(title))
@ -35,11 +35,13 @@ namespace Avalonia.X11.NativeDialogs
foreach (var d in disposables) d.Dispose();
disposables.Clear();
}
var filtersDic = new Dictionary<IntPtr, FileDialogFilter>();
if(filters != null)
foreach (var f in filters)
{
var filter = gtk_file_filter_new();
filtersDic[filter] = f;
using (var b = new Utf8Buffer(f.Name))
gtk_file_filter_set_name(filter, b);
@ -74,6 +76,15 @@ namespace Avalonia.X11.NativeDialogs
}
g_slist_free(gs);
result = resultList.ToArray();
// GTK doesn't auto-append the extension, so we need to do that manually
if (action == GtkFileChooserAction.Save)
{
var currentFilter = gtk_file_chooser_get_filter(dlg);
filtersDic.TryGetValue(currentFilter, out var selectedFilter);
for (var c = 0; c < result.Length; c++)
result[c] = NameWithExtension(result[c], defaultExtension, selectedFilter);
}
}
gtk_widget_hide(dlg);
@ -101,7 +112,30 @@ namespace Avalonia.X11.NativeDialogs
gtk_window_present(dlg);
return tcs.Task;
}
string NameWithExtension(string path, string defaultExtension, FileDialogFilter filter)
{
var name = Path.GetFileName(path);
if (name != null && !name.Contains("."))
{
if (filter?.Extensions?.Count > 0)
{
if (defaultExtension != null
&& filter.Extensions.Contains(defaultExtension))
return path + "." + defaultExtension.TrimStart('.');
var ext = filter.Extensions.FirstOrDefault(x => x != "*");
if (ext != null)
return path + "." + ext.TrimStart('.');
}
if (defaultExtension != null)
path += "." + defaultExtension.TrimStart('.');
}
return path;
}
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent)
{
await EnsureInitialized();
@ -113,7 +147,8 @@ namespace Avalonia.X11.NativeDialogs
dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save,
(dialog as OpenFileDialog)?.AllowMultiple ?? false,
Path.Combine(string.IsNullOrEmpty(dialog.Directory) ? "" : dialog.Directory,
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName), dialog.Filters));
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName), dialog.Filters,
(dialog as SaveFileDialog)?.DefaultExtension));
}
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent)
@ -125,7 +160,7 @@ namespace Avalonia.X11.NativeDialogs
return await await RunOnGlibThread(async () =>
{
var res = await ShowDialog(dialog.Title, platformImpl,
GtkFileChooserAction.SelectFolder, false, dialog.Directory, null);
GtkFileChooserAction.SelectFolder, false, dialog.Directory, null, null);
return res?.FirstOrDefault();
});
}

4
src/Avalonia.X11/X11NativeControlHost.cs

@ -167,7 +167,7 @@ namespace Avalonia.X11
XUnmapWindow(_display, _holder.Handle);
}
size *= _attachedTo.Window.Scaling;
size *= _attachedTo.Window.RenderScaling;
XResizeWindow(_display, _child.Handle,
Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height));
}
@ -179,7 +179,7 @@ namespace Avalonia.X11
CheckDisposed();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
bounds *= _attachedTo.Window.Scaling;
bounds *= _attachedTo.Window.RenderScaling;
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
Math.Max(1, (int)bounds.Height));

47
src/Avalonia.X11/X11Window.cs

@ -163,7 +163,7 @@ namespace Avalonia.X11
var surfaces = new List<object>
{
new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle,
depth, () => Scaling)
depth, () => RenderScaling)
};
if (egl != null)
@ -217,7 +217,7 @@ namespace Avalonia.X11
}
}
public double Scaling => _window.Scaling;
public double Scaling => _window.RenderScaling;
}
void UpdateMotifHints()
@ -284,9 +284,9 @@ namespace Avalonia.X11
XSetWMNormalHints(_x11.Display, _handle, ref hints);
}
public Size ClientSize => new Size(_realSize.Width / Scaling, _realSize.Height / Scaling);
public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling);
public double Scaling
public double RenderScaling
{
get
{
@ -296,6 +296,8 @@ namespace Avalonia.X11
}
private set => _scaling = value;
}
public double DesktopScaling => RenderScaling;
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
@ -538,14 +540,14 @@ namespace Avalonia.X11
{
var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity)
.FirstOrDefault(m => m.Bounds.Contains(Position));
newScaling = monitor?.PixelDensity ?? Scaling;
newScaling = monitor?.PixelDensity ?? RenderScaling;
}
if (Scaling != newScaling)
if (RenderScaling != newScaling)
{
var oldScaledSize = ClientSize;
Scaling = newScaling;
ScalingChanged?.Invoke(Scaling);
RenderScaling = newScaling;
ScalingChanged?.Invoke(RenderScaling);
SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
if(!skipResize)
Resize(oldScaledSize, true);
@ -707,9 +709,9 @@ namespace Avalonia.X11
private void ScheduleInput(RawInputEventArgs args)
{
if (args is RawPointerEventArgs mouse)
mouse.Position = mouse.Position / Scaling;
mouse.Position = mouse.Position / RenderScaling;
if (args is RawDragEvent drag)
drag.Location = drag.Location / Scaling;
drag.Location = drag.Location / RenderScaling;
_lastEvent = new InputEventContainer() {Event = args};
_inputQueue.Enqueue(_lastEvent);
@ -760,11 +762,7 @@ namespace Avalonia.X11
public void Dispose()
{
if (_handle != IntPtr.Zero)
{
XDestroyWindow(_x11.Display, _handle);
Cleanup();
}
Cleanup();
}
void Cleanup()
@ -787,8 +785,7 @@ namespace Avalonia.X11
}
if (_useRenderWindow && _renderHandle != IntPtr.Zero)
{
XDestroyWindow(_x11.Display, _renderHandle);
{
_renderHandle = IntPtr.Zero;
}
}
@ -821,11 +818,11 @@ namespace Avalonia.X11
public void Hide() => XUnmapWindow(_x11.Display, _handle);
public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling);
public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / RenderScaling, (point.Y - Position.Y) / RenderScaling);
public PixelPoint PointToScreen(Point point) => new PixelPoint(
(int)(point.X * Scaling + Position.X),
(int)(point.Y * Scaling + Position.Y));
(int)(point.X * RenderScaling + Position.X),
(int)(point.Y * RenderScaling + Position.Y));
public void SetSystemDecorations(SystemDecorations enabled)
{
@ -845,7 +842,7 @@ namespace Avalonia.X11
Resize(size, true);
}
PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * Scaling), (int)(size.Height * Scaling));
PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * RenderScaling), (int)(size.Height * RenderScaling));
void Resize(Size clientSize, bool force)
{
@ -1025,13 +1022,13 @@ namespace Avalonia.X11
{
_scaledMinMaxSize = (minSize, maxSize);
var min = new PixelSize(
(int)(minSize.Width < 1 ? 1 : minSize.Width * Scaling),
(int)(minSize.Height < 1 ? 1 : minSize.Height * Scaling));
(int)(minSize.Width < 1 ? 1 : minSize.Width * RenderScaling),
(int)(minSize.Height < 1 ? 1 : minSize.Height * RenderScaling));
const int maxDim = MaxWindowDimension;
var max = new PixelSize(
(int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * Scaling)),
(int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * Scaling)));
(int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * RenderScaling)),
(int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * RenderScaling)));
_minMaxSize = (min, max);
UpdateSizeHints(null);

4
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -65,7 +65,7 @@ namespace Avalonia.LinuxFramebuffer
public IMouseDevice MouseDevice => new MouseDevice();
public IPopupImpl CreatePopup() => null;
public double Scaling => _outputBackend.Scaling;
public double RenderScaling => _outputBackend.Scaling;
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
@ -77,7 +77,7 @@ namespace Avalonia.LinuxFramebuffer
public Action Closed { get; set; }
public Action LostFocus { get; set; }
public Size ScaledSize => _outputBackend.PixelSize.ToSize(Scaling);
public Size ScaledSize => _outputBackend.PixelSize.ToSize(RenderScaling);
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { }

11
src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs

@ -5,7 +5,7 @@ using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.Templates
{
public class DataTemplate : IDataTemplate
public class DataTemplate : IRecyclingDataTemplate
{
public Type DataType { get; set; }
@ -14,8 +14,6 @@ namespace Avalonia.Markup.Xaml.Templates
[TemplateContent]
public object Content { get; set; }
public bool SupportsRecycling { get; set; } = true;
public bool Match(object data)
{
if (DataType == null)
@ -28,6 +26,11 @@ namespace Avalonia.Markup.Xaml.Templates
}
}
public IControl Build(object data) => TemplateContent.Load(Content).Control;
public IControl Build(object data) => Build(data, null);
public IControl Build(object data, IControl existing)
{
return existing ?? TemplateContent.Load(Content).Control;
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -18,8 +18,6 @@ namespace Avalonia.Markup.Xaml.Templates
[AssignBinding]
public Binding ItemsSource { get; set; }
public bool SupportsRecycling { get; set; } = true;
public bool Match(object data)
{
if (DataType == null)

6
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -75,7 +75,7 @@ namespace Avalonia.Win32.Interop.Wpf
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
{
if (msg == (int)UnmanagedMethods.WindowsMessage.WM_DPICHANGED)
_ttl.ScalingChanged?.Invoke(_ttl.Scaling);
_ttl.ScalingChanged?.Invoke(_ttl.RenderScaling);
return IntPtr.Zero;
}
@ -84,7 +84,7 @@ namespace Avalonia.Win32.Interop.Wpf
_currentHwndSource?.RemoveHook(_hook);
_currentHwndSource = e.NewSource as HwndSource;
_currentHwndSource?.AddHook(_hook);
_ttl.ScalingChanged?.Invoke(_ttl.Scaling);
_ttl.ScalingChanged?.Invoke(_ttl.RenderScaling);
}
public IRenderer CreateRenderer(IRenderRoot root)
@ -102,7 +102,7 @@ namespace Avalonia.Win32.Interop.Wpf
Size ITopLevelImpl.ClientSize => _finalSize;
IMouseDevice ITopLevelImpl.MouseDevice => _mouse;
double ITopLevelImpl.Scaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1;
double ITopLevelImpl.RenderScaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1;
IEnumerable<object> ITopLevelImpl.Surfaces => _surfaces;

2
src/Windows/Avalonia.Win32/PopupImpl.cs

@ -57,7 +57,7 @@ namespace Avalonia.Win32
{
var info = UnmanagedMethods.MONITORINFO.Create();
UnmanagedMethods.GetMonitorInfo(monitor, ref info);
_maxAutoSize = info.rcWork.ToPixelRect().ToRect(Scaling).Size;
_maxAutoSize = info.rcWork.ToPixelRect().ToRect(RenderScaling).Size;
}
}

4
src/Windows/Avalonia.Win32/Win32NativeControlHost.cs

@ -176,7 +176,7 @@ namespace Avalonia.Win32
UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE);
if (_attachedTo == null || _child == null)
return;
size *= _attachedTo.Window.Scaling;
size *= _attachedTo.Window.RenderScaling;
UnmanagedMethods.MoveWindow(_child.Handle, 0, 0,
Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height), false);
}
@ -186,7 +186,7 @@ namespace Avalonia.Win32
CheckDisposed();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
bounds *= _attachedTo.Window.Scaling;
bounds *= _attachedTo.Window.RenderScaling;
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
Math.Max(1, (int)bounds.Height));

16
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -343,7 +343,7 @@ namespace Avalonia.Win32
{
if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero)
{
var f = Scaling;
var f = RenderScaling;
var r = ps.rcPaint;
Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f,
(r.bottom - r.top) / f));
@ -368,7 +368,7 @@ namespace Avalonia.Win32
size == SizeCommand.Maximized))
{
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
Resized(clientSize / Scaling);
Resized(clientSize / RenderScaling);
}
var windowState = size == SizeCommand.Maximized ?
@ -402,29 +402,31 @@ namespace Avalonia.Win32
case WindowsMessage.WM_GETMINMAXINFO:
{
MINMAXINFO mmi = Marshal.PtrToStructure<MINMAXINFO>(lParam);
_maxTrackSize = mmi.ptMaxTrackSize;
if (_minSize.Width > 0)
{
mmi.ptMinTrackSize.X =
(int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
(int)((_minSize.Width * RenderScaling) + BorderThickness.Left + BorderThickness.Right);
}
if (_minSize.Height > 0)
{
mmi.ptMinTrackSize.Y =
(int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom);
(int)((_minSize.Height * RenderScaling) + BorderThickness.Top + BorderThickness.Bottom);
}
if (!double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0)
{
mmi.ptMaxTrackSize.X =
(int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
(int)((_maxSize.Width * RenderScaling) + BorderThickness.Left + BorderThickness.Right);
}
if (!double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0)
{
mmi.ptMaxTrackSize.Y =
(int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom);
(int)((_maxSize.Height * RenderScaling) + BorderThickness.Top + BorderThickness.Bottom);
}
Marshal.StructureToPtr(mmi, lParam, true);
@ -480,7 +482,7 @@ namespace Avalonia.Win32
private Point DipFromLParam(IntPtr lParam)
{
return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling;
return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / RenderScaling;
}
private PixelPoint PointFromLParam(IntPtr lParam)

2
src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs

@ -37,7 +37,7 @@ namespace Avalonia.Win32
if (_extendTitleBarHint >= 0)
{
border_thickness.top = (int)(_extendedMargins.Top * Scaling);
border_thickness.top = (int)(_extendedMargins.Top * RenderScaling);
}
// Determine if the hit test is for resizing. Default middle (1,1).

28
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -164,7 +164,9 @@ namespace Avalonia.Win32
}
}
public double Scaling => _scaling;
public double RenderScaling => _scaling;
public double DesktopScaling => RenderScaling;
public Size ClientSize
{
@ -172,7 +174,7 @@ namespace Avalonia.Win32
{
GetClientRect(_hwnd, out var rect);
return new Size(rect.right, rect.bottom) / Scaling;
return new Size(rect.right, rect.bottom) / RenderScaling;
}
}
@ -180,7 +182,7 @@ namespace Avalonia.Win32
public IPlatformHandle Handle { get; private set; }
public virtual Size MaxAutoSizeHint => new Size(_maxTrackSize.X / Scaling, _maxTrackSize.Y / Scaling);
public virtual Size MaxAutoSizeHint => new Size(_maxTrackSize.X / RenderScaling, _maxTrackSize.Y / RenderScaling);
public IMouseDevice MouseDevice => _mouseDevice;
@ -342,8 +344,8 @@ namespace Avalonia.Win32
public void Resize(Size value)
{
int requestedClientWidth = (int)(value.Width * Scaling);
int requestedClientHeight = (int)(value.Height * Scaling);
int requestedClientWidth = (int)(value.Width * RenderScaling);
int requestedClientHeight = (int)(value.Height * RenderScaling);
GetClientRect(_hwnd, out var clientRect);
@ -395,7 +397,7 @@ namespace Avalonia.Win32
public void Invalidate(Rect rect)
{
var scaling = Scaling;
var scaling = RenderScaling;
var r = new RECT
{
left = (int)Math.Floor(rect.X * scaling),
@ -411,12 +413,12 @@ namespace Avalonia.Win32
{
var p = new POINT { X = point.X, Y = point.Y };
UnmanagedMethods.ScreenToClient(_hwnd, ref p);
return new Point(p.X, p.Y) / Scaling;
return new Point(p.X, p.Y) / RenderScaling;
}
public PixelPoint PointToScreen(Point point)
{
point *= Scaling;
point *= RenderScaling;
var p = new POINT { X = (int)point.X, Y = (int)point.Y };
ClientToScreen(_hwnd, ref p);
return new PixelPoint(p.X, p.Y);
@ -710,19 +712,19 @@ namespace Avalonia.Win32
if (_extendTitleBarHint != -1)
{
borderCaptionThickness.top = (int)(_extendTitleBarHint * Scaling);
borderCaptionThickness.top = (int)(_extendTitleBarHint * RenderScaling);
}
margins.cyTopHeight = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1;
if (WindowState == WindowState.Maximized)
{
_extendedMargins = new Thickness(0, (borderCaptionThickness.top - borderThickness.top) / Scaling, 0, 0);
_offScreenMargin = new Thickness(borderThickness.left / Scaling, borderThickness.top / Scaling, borderThickness.right / Scaling, borderThickness.bottom / Scaling);
_extendedMargins = new Thickness(0, (borderCaptionThickness.top - borderThickness.top) / RenderScaling, 0, 0);
_offScreenMargin = new Thickness(borderThickness.left / RenderScaling, borderThickness.top / RenderScaling, borderThickness.right / RenderScaling, borderThickness.bottom / RenderScaling);
}
else
{
_extendedMargins = new Thickness(0, (borderCaptionThickness.top) / Scaling, 0, 0);
_extendedMargins = new Thickness(0, (borderCaptionThickness.top) / RenderScaling, 0, 0);
_offScreenMargin = new Thickness();
}
@ -1034,6 +1036,8 @@ namespace Avalonia.Win32
}
}
double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => RenderScaling;
IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle;
public void SetExtendClientAreaToDecorationsHint(bool hint)

2
src/iOS/Avalonia.iOS/TopLevelImpl.cs

@ -48,7 +48,7 @@ namespace Avalonia.iOS
public new IPlatformHandle Handle => null;
public double Scaling => UIScreen.MainScreen.Scale;
public double RenderScaling => UIScreen.MainScreen.Scale;
public override void LayoutSubviews() => Resized?.Invoke(ClientSize);

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

@ -296,7 +296,7 @@ namespace Avalonia.Controls.UnitTests
var windowImpl = MockWindowingPlatform.CreateWindowMock();
popupImpl = MockWindowingPlatform.CreatePopupMock(windowImpl.Object);
popupImpl.SetupGet(x => x.Scaling).Returns(1);
popupImpl.SetupGet(x => x.RenderScaling).Returns(1);
windowImpl.Setup(x => x.CreatePopup()).Returns(popupImpl.Object);
windowImpl.Setup(x => x.Screen).Returns(screenImpl.Object);

3
tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs

@ -191,7 +191,8 @@ namespace Avalonia.Controls.UnitTests
{
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
var services = TestServices.StyledWindow.With(
windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object));

40
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -531,6 +531,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedIndex = 1;
Assert.Equal(items[1], target.SelectedItem);
@ -549,6 +550,45 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.NotNull(receivedArgs);
Assert.Empty(receivedArgs.AddedItems);
Assert.Equal(new[] { removed }, receivedArgs.RemovedItems);
Assert.False(items.Single().IsSelected);
}
[Fact]
public void Removing_Selected_Item_Should_Clear_Selection_With_BeginInit()
{
var items = new AvaloniaList<Item>
{
new Item(),
new Item(),
};
var target = new SelectingItemsControl();
target.BeginInit();
target.Items = items;
target.Template = Template();
target.EndInit();
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedIndex = 0;
Assert.Equal(items[0], target.SelectedItem);
Assert.Equal(0, target.SelectedIndex);
SelectionChangedEventArgs receivedArgs = null;
target.SelectionChanged += (_, args) => receivedArgs = args;
var removed = items[0];
items.RemoveAt(0);
Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex);
Assert.NotNull(receivedArgs);
Assert.Empty(receivedArgs.AddedItems);
Assert.Equal(new[] { removed }, receivedArgs.RemovedItems);
Assert.False(items.Single().IsSelected);
}
[Fact]

4
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -93,7 +93,7 @@ namespace Avalonia.Controls.UnitTests
{
var impl = new Mock<ITopLevelImpl>();
impl.SetupProperty(x => x.Resized);
impl.SetupGet(x => x.Scaling).Returns(1);
impl.SetupGet(x => x.RenderScaling).Returns(1);
var target = new TestTopLevel(impl.Object)
{
@ -290,7 +290,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
impl.SetupGet(x => x.Scaling).Returns(1);
impl.SetupGet(x => x.RenderScaling).Returns(1);
var child = new Border { Classes = { "foo" } };
var target = new TestTopLevel(impl.Object)

2
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -1273,8 +1273,6 @@ namespace Avalonia.Controls.UnitTests
return new TextBlock { Text = node.Value };
}
public bool SupportsRecycling => false;
public InstancedBinding ItemsSelector(object item)
{
var obs = ExpressionObserver.Create(item, o => (o as Node).Children);

14
tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs

@ -110,7 +110,8 @@ namespace Avalonia.Controls.UnitTests
public void IsVisible_Should_Be_False_Atfer_Impl_Signals_Close()
{
var windowImpl = new Mock<IPopupImpl>();
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.SetupProperty(x => x.Closed);
using (UnitTestApplication.Start(TestServices.StyledWindow))
@ -128,7 +129,8 @@ namespace Avalonia.Controls.UnitTests
public void Setting_IsVisible_True_Shows_Window()
{
var windowImpl = new Mock<IPopupImpl>();
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
@ -143,7 +145,8 @@ namespace Avalonia.Controls.UnitTests
public void Setting_IsVisible_False_Hides_Window()
{
var windowImpl = new Mock<IPopupImpl>();
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
@ -208,7 +211,8 @@ namespace Avalonia.Controls.UnitTests
{
var renderer = new Mock<IRenderer>();
var windowImpl = new Mock<IPopupImpl>();
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
@ -237,7 +241,7 @@ namespace Avalonia.Controls.UnitTests
public TestWindowBase(IRenderer renderer = null)
: base(Mock.Of<IWindowBaseImpl>(x =>
x.Scaling == 1 &&
x.RenderScaling == 1 &&
x.CreateRenderer(It.IsAny<IRenderRoot>()) == renderer))
{
}

20
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -100,7 +100,8 @@ namespace Avalonia.Controls.UnitTests
{
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
var services = TestServices.StyledWindow.With(
windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object));
@ -206,7 +207,8 @@ namespace Avalonia.Controls.UnitTests
var parent = new Mock<Window>();
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
var target = new Window(windowImpl.Object);
var task = target.ShowDialog<bool>(parent.Object);
@ -245,7 +247,8 @@ namespace Avalonia.Controls.UnitTests
var parent = new Mock<Window>();
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
var target = new Window(windowImpl.Object);
var task = target.ShowDialog<bool>(parent.Object);
@ -273,7 +276,8 @@ namespace Avalonia.Controls.UnitTests
var windowImpl = MockWindowingPlatform.CreateWindowMock();
windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480));
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.Setup(x => x.Screen).Returns(screens.Object);
using (UnitTestApplication.Start(TestServices.StyledWindow))
@ -298,12 +302,14 @@ namespace Avalonia.Controls.UnitTests
var parentWindowImpl = MockWindowingPlatform.CreateWindowMock();
parentWindowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480));
parentWindowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080));
parentWindowImpl.Setup(x => x.Scaling).Returns(1);
parentWindowImpl.Setup(x => x.DesktopScaling).Returns(1);
parentWindowImpl.Setup(x => x.RenderScaling).Returns(1);
var windowImpl = MockWindowingPlatform.CreateWindowMock();
windowImpl.Setup(x => x.ClientSize).Returns(new Size(320, 200));
windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080));
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
var parentWindowServices = TestServices.StyledWindow.With(
windowingPlatform: new MockWindowingPlatform(() => parentWindowImpl.Object));
@ -565,7 +571,7 @@ namespace Avalonia.Controls.UnitTests
private IWindowImpl CreateImpl(Mock<IRenderer> renderer)
{
return Mock.Of<IWindowImpl>(x =>
x.Scaling == 1 &&
x.RenderScaling == 1 &&
x.CreateRenderer(It.IsAny<IRenderRoot>()) == renderer.Object);
}

4
tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs

@ -17,7 +17,7 @@ namespace Avalonia.Controls.UnitTests
public IWindowImpl CreateWindow()
{
return _windowImpl?.Invoke() ?? Mock.Of<IWindowImpl>(x => x.Scaling == 1);
return _windowImpl?.Invoke() ?? Mock.Of<IWindowImpl>(x => x.RenderScaling == 1);
}
public IWindowImpl CreateEmbeddableWindow()
@ -25,6 +25,6 @@ namespace Avalonia.Controls.UnitTests
throw new NotImplementedException();
}
public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of<IPopupImpl>(x => x.Scaling == 1);
public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of<IPopupImpl>(x => x.RenderScaling == 1);
}
}

2
tests/Avalonia.LeakTests/ControlTests.cs

@ -355,7 +355,7 @@ namespace Avalonia.LeakTests
var renderer = new Mock<IRenderer>();
renderer.Setup(x => x.Dispose());
var impl = new Mock<IWindowImpl>();
impl.SetupGet(x => x.Scaling).Returns(1);
impl.SetupGet(x => x.RenderScaling).Returns(1);
impl.SetupProperty(x => x.Closed);
impl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed());

62
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -31,12 +31,37 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var nextCharacterHit = new CharacterHit(0);
for (var i = 1; i < clusters.Length; i++)
for (var i = 0; i < clusters.Length; i++)
{
Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex);
nextCharacterHit = textLine.GetNextCaretCharacterHit(nextCharacterHit);
}
var lastCharacterHit = nextCharacterHit;
nextCharacterHit = textLine.GetNextCaretCharacterHit(lastCharacterHit);
Assert.Equal(lastCharacterHit.FirstCharacterIndex, nextCharacterHit.FirstCharacterIndex);
Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength);
nextCharacterHit = new CharacterHit(0, clusters[1] - clusters[0]);
Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength);
for (var i = 0; i < clusters.Length; i++)
{
Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex);
nextCharacterHit = textLine.GetNextCaretCharacterHit(nextCharacterHit);
}
lastCharacterHit = nextCharacterHit;
nextCharacterHit = textLine.GetNextCaretCharacterHit(lastCharacterHit);
Assert.Equal(lastCharacterHit.FirstCharacterIndex, nextCharacterHit.FirstCharacterIndex);
Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength);
}
}
@ -60,14 +85,41 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var clusters = textLine.TextRuns.Cast<ShapedTextCharacters>().SelectMany(x => x.GlyphRun.GlyphClusters)
.ToArray();
var previousCharacterHit = new CharacterHit(clusters[^1]);
var previousCharacterHit = new CharacterHit(text.Length);
for (var i = clusters.Length - 2; i > 0; i--)
for (var i = clusters.Length - 1; i >= 0; i--)
{
previousCharacterHit = textLine.GetPreviousCaretCharacterHit(previousCharacterHit);
Assert.Equal(clusters[i], previousCharacterHit.FirstCharacterIndex);
Assert.Equal(clusters[i],
previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength);
}
var firstCharacterHit = previousCharacterHit;
previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex);
Assert.Equal(firstCharacterHit.TrailingLength, previousCharacterHit.TrailingLength);
previousCharacterHit = new CharacterHit(clusters[^1], text.Length - clusters[^1]);
for (var i = clusters.Length - 1; i > 0; i--)
{
previousCharacterHit = textLine.GetPreviousCaretCharacterHit(previousCharacterHit);
Assert.Equal(clusters[i],
previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength);
}
firstCharacterHit = previousCharacterHit;
previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex);
Assert.Equal(firstCharacterHit.TrailingLength, previousCharacterHit.TrailingLength);
}
}

5
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@ -29,7 +29,8 @@ namespace Avalonia.UnitTests
windowImpl.SetupAllProperties();
windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize);
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object);
windowImpl.Setup(x => x.Position).Returns(() => position);
SetupToplevel(windowImpl);
@ -81,7 +82,7 @@ namespace Avalonia.UnitTests
popupImpl.SetupAllProperties();
popupImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
popupImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize);
popupImpl.Setup(x => x.Scaling).Returns(1);
popupImpl.Setup(x => x.RenderScaling).Returns(1);
popupImpl.Setup(x => x.PopupPositioner).Returns(positioner);
SetupToplevel(popupImpl);

Loading…
Cancel
Save