Browse Source

Merge branch 'master' into feature/onplatform-xaml-compiler-support

release/11.0.0-preview3
Dan Walmsley 3 years ago
committed by GitHub
parent
commit
94049a14e7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      samples/ControlCatalog/Pages/ExpanderPage.xaml
  2. 27
      src/Avalonia.Base/Media/FontSimulations.cs
  3. 24
      src/Avalonia.Base/Media/GlyphMetrics.cs
  4. 15
      src/Avalonia.Base/Media/IGlyphTypeface.cs
  5. 29
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  6. 3
      src/Avalonia.Controls/ListBox.cs
  7. 45
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  8. 30
      src/Avalonia.Controls/TreeViewItem.cs
  9. 13
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  10. 35
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  11. 36
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  12. 179
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  13. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  14. 34
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs
  15. 25
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  16. 16
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  17. 28
      src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
  18. 1
      src/Web/Avalonia.Web/Avalonia.Web.csproj
  19. 4
      src/Web/Avalonia.Web/Avalonia.Web.props
  20. 30
      src/Web/Avalonia.Web/Avalonia.Web.targets
  21. 76
      src/Web/Avalonia.Web/AvaloniaView.cs
  22. 11
      src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
  23. 2
      src/Web/Avalonia.Web/Interop/CanvasHelper.cs
  24. 6
      src/Web/Avalonia.Web/Interop/InputHelper.cs
  25. 32
      src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
  26. 25
      src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
  27. 8
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
  28. 52
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  29. 51
      tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs
  30. 2
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  31. 32
      tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs
  32. 13
      tests/Avalonia.UnitTests/MockGlyphTypeface.cs

17
samples/ControlCatalog/Pages/ExpanderPage.xaml

@ -32,6 +32,23 @@
<TextBlock>Expanded content</TextBlock> <TextBlock>Expanded content</TextBlock>
</StackPanel> </StackPanel>
</Expander> </Expander>
<Expander ExpandDirection="Down"
CornerRadius="{Binding CornerRadius}">
<Expander.Header>
<Button Content="Control in Header" />
</Expander.Header>
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander Header="Disabled"
IsEnabled="False"
ExpandDirection="Down"
CornerRadius="{Binding CornerRadius}">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<CheckBox IsChecked="{Binding Rounded}">Rounded</CheckBox> <CheckBox IsChecked="{Binding Rounded}">Rounded</CheckBox>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>

27
src/Avalonia.Base/Media/FontSimulations.cs

@ -0,0 +1,27 @@
using System;
namespace Avalonia.Media
{
/// <summary>
/// Specifies algorithmic style simulations to be applied to the typeface.
/// Bold and oblique simulations can be combined via bitwise OR operation.
/// </summary>
[Flags]
public enum FontSimulations : byte
{
/// <summary>
/// No simulations are performed.
/// </summary>
None = 0x0000,
/// <summary>
/// Algorithmic emboldening is performed.
/// </summary>
Bold = 0x0001,
/// <summary>
/// Algorithmic italicization is performed.
/// </summary>
Oblique = 0x0002
}
}

24
src/Avalonia.Base/Media/GlyphMetrics.cs

@ -0,0 +1,24 @@
namespace Avalonia.Media;
public readonly struct GlyphMetrics
{
/// <summary>
/// Distance from the x-origin to the left extremum of the glyph.
/// </summary>
public int XBearing { get; init; }
/// <summary>
/// Distance from the top extremum of the glyph to the y-origin.
/// </summary>
public int YBearing{ get; init; }
/// <summary>
/// Distance from the left extremum of the glyph to the right extremum.
/// </summary>
public int Width{ get; init; }
/// <summary>
/// Distance from the top extremum of the glyph to the bottom extremum.
/// </summary>
public int Height{ get; init; }
}

15
src/Avalonia.Base/Media/IGlyphTypeface.cs

@ -19,6 +19,21 @@ namespace Avalonia.Media
/// </returns> /// </returns>
FontMetrics Metrics { get; } FontMetrics Metrics { get; }
/// <summary>
/// Gets the algorithmic style simulations applied to this glyph typeface.
/// </summary>
FontSimulations FontSimulations { get; }
/// <summary>
/// Tries to get a glyph's metrics in em units.
/// </summary>
/// <param name="glyph">The glyph id.</param>
/// <param name="metrics">The glyph metrics.</param>
/// <returns>
/// <c>true</c> if an glyph's metrics was found, <c>false</c> otherwise.
/// </returns>
bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics);
/// <summary> /// <summary>
/// Returns an glyph index for the specified codepoint. /// Returns an glyph index for the specified codepoint.
/// </summary> /// </summary>

29
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@ -477,25 +477,28 @@ namespace Avalonia.Media.TextFormatting
{ {
case ShapedTextCharacters shapedTextCharacters: case ShapedTextCharacters shapedTextCharacters:
{ {
var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphClusters[0]; if(shapedTextCharacters.ShapedBuffer.Length > 0)
var lastCluster = firstCluster;
for (var i = 0; i < shapedTextCharacters.ShapedBuffer.Length; i++)
{ {
var glyphInfo = shapedTextCharacters.ShapedBuffer[i]; var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphClusters[0];
var lastCluster = firstCluster;
if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth) for (var i = 0; i < shapedTextCharacters.ShapedBuffer.Length; i++)
{ {
measuredLength += Math.Max(0, lastCluster - firstCluster); var glyphInfo = shapedTextCharacters.ShapedBuffer[i];
goto found; if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth)
} {
measuredLength += Math.Max(0, lastCluster - firstCluster);
lastCluster = glyphInfo.GlyphCluster; goto found;
currentWidth += glyphInfo.GlyphAdvance; }
}
measuredLength += currentRun.TextSourceLength; lastCluster = glyphInfo.GlyphCluster;
currentWidth += glyphInfo.GlyphAdvance;
}
measuredLength += currentRun.TextSourceLength;
}
break; break;
} }

3
src/Avalonia.Controls/ListBox.cs

@ -139,7 +139,8 @@ namespace Avalonia.Controls
e.Source, e.Source,
true, true,
e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
e.KeyModifiers.HasAllFlags(KeyModifiers.Control)); e.KeyModifiers.HasAllFlags(KeyModifiers.Control),
fromFocus: true);
} }
} }

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

@ -586,6 +586,14 @@ namespace Avalonia.Controls.Primitives
Selection.SelectAll(); Selection.SelectAll();
e.Handled = true; e.Handled = true;
} }
else if (e.Key == Key.Space || e.Key == Key.Enter)
{
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
e.KeyModifiers.HasFlag(KeyModifiers.Shift),
e.KeyModifiers.HasFlag(KeyModifiers.Control));
}
} }
} }
@ -662,12 +670,14 @@ namespace Avalonia.Controls.Primitives
/// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param> /// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
/// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param> /// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
/// <param name="rightButton">Whether the event is a right-click.</param> /// <param name="rightButton">Whether the event is a right-click.</param>
/// <param name="fromFocus">Wheter the event is a focus event</param>
protected void UpdateSelection( protected void UpdateSelection(
int index, int index,
bool select = true, bool select = true,
bool rangeModifier = false, bool rangeModifier = false,
bool toggleModifier = false, bool toggleModifier = false,
bool rightButton = false) bool rightButton = false,
bool fromFocus = false)
{ {
if (index < 0 || index >= ItemCount) if (index < 0 || index >= ItemCount)
{ {
@ -696,22 +706,25 @@ namespace Avalonia.Controls.Primitives
Selection.Clear(); Selection.Clear();
Selection.SelectRange(Selection.AnchorIndex, index); Selection.SelectRange(Selection.AnchorIndex, index);
} }
else if (multi && toggle) else if (!fromFocus && toggle)
{ {
if (Selection.IsSelected(index) == true) if (multi)
{ {
Selection.Deselect(index); if (Selection.IsSelected(index) == true)
{
Selection.Deselect(index);
}
else
{
Selection.Select(index);
}
} }
else else
{ {
Selection.Select(index); SelectedIndex = (SelectedIndex == index) ? -1 : index;
} }
} }
else if (toggle) else if (!toggle)
{
SelectedIndex = (SelectedIndex == index) ? -1 : index;
}
else
{ {
using var operation = Selection.BatchUpdate(); using var operation = Selection.BatchUpdate();
Selection.Clear(); Selection.Clear();
@ -735,18 +748,20 @@ namespace Avalonia.Controls.Primitives
/// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param> /// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
/// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param> /// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
/// <param name="rightButton">Whether the event is a right-click.</param> /// <param name="rightButton">Whether the event is a right-click.</param>
/// <param name="fromFocus">Wheter the event is a focus event</param>
protected void UpdateSelection( protected void UpdateSelection(
IControl container, IControl container,
bool select = true, bool select = true,
bool rangeModifier = false, bool rangeModifier = false,
bool toggleModifier = false, bool toggleModifier = false,
bool rightButton = false) bool rightButton = false,
bool fromFocus = false)
{ {
var index = ItemContainerGenerator?.IndexFromContainer(container) ?? -1; var index = ItemContainerGenerator?.IndexFromContainer(container) ?? -1;
if (index != -1) if (index != -1)
{ {
UpdateSelection(index, select, rangeModifier, toggleModifier, rightButton); UpdateSelection(index, select, rangeModifier, toggleModifier, rightButton, fromFocus);
} }
} }
@ -759,6 +774,7 @@ namespace Avalonia.Controls.Primitives
/// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param> /// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
/// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param> /// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
/// <param name="rightButton">Whether the event is a right-click.</param> /// <param name="rightButton">Whether the event is a right-click.</param>
/// <param name="fromFocus">Wheter the event is a focus event</param>
/// <returns> /// <returns>
/// True if the event originated from a container that belongs to the control; otherwise /// True if the event originated from a container that belongs to the control; otherwise
/// false. /// false.
@ -768,13 +784,14 @@ namespace Avalonia.Controls.Primitives
bool select = true, bool select = true,
bool rangeModifier = false, bool rangeModifier = false,
bool toggleModifier = false, bool toggleModifier = false,
bool rightButton = false) bool rightButton = false,
bool fromFocus = false)
{ {
var container = GetContainerFromEventSource(eventSource); var container = GetContainerFromEventSource(eventSource);
if (container != null) if (container != null)
{ {
UpdateSelection(container, select, rangeModifier, toggleModifier, rightButton); UpdateSelection(container, select, rangeModifier, toggleModifier, rightButton, fromFocus);
return true; return true;
} }

30
src/Avalonia.Controls/TreeViewItem.cs

@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Threading;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -45,6 +46,8 @@ namespace Avalonia.Controls
private IControl? _header; private IControl? _header;
private bool _isExpanded; private bool _isExpanded;
private int _level; private int _level;
private bool _templateApplied;
private bool _deferredBringIntoViewFlag;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="TreeViewItem"/> class. /// Initializes static members of the <see cref="TreeViewItem"/> class.
@ -136,15 +139,24 @@ namespace Avalonia.Controls
protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e) protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e)
{ {
if (e.TargetObject == this && _header != null) if (e.TargetObject == this)
{ {
var m = _header.TransformToVisual(this); if (!_templateApplied)
{
_deferredBringIntoViewFlag = true;
return;
}
if (m.HasValue) if (_header != null)
{ {
var bounds = new Rect(_header.Bounds.Size); var m = _header.TransformToVisual(this);
var rect = bounds.TransformToAABB(m.Value);
e.TargetRect = rect; if (m.HasValue)
{
var bounds = new Rect(_header.Bounds.Size);
var rect = bounds.TransformToAABB(m.Value);
e.TargetRect = rect;
}
} }
} }
} }
@ -187,6 +199,12 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
_header = e.NameScope.Find<IControl>("PART_Header"); _header = e.NameScope.Find<IControl>("PART_Header");
_templateApplied = true;
if (_deferredBringIntoViewFlag)
{
_deferredBringIntoViewFlag = false;
Dispatcher.UIThread.Post(this.BringIntoView); // must use the Dispatcher, otherwise the TreeView doesn't scroll
}
} }
private static int CalculateDistanceFromLogicalParent<T>(ILogical? logical, int @default = -1) where T : class private static int CalculateDistanceFromLogicalParent<T>(ILogical? logical, int @default = -1) where T : class

13
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -102,6 +102,8 @@ namespace Avalonia.Headless
public int GlyphCount => 1337; public int GlyphCount => 1337;
public FontSimulations FontSimulations { get; }
public void Dispose() public void Dispose()
{ {
} }
@ -138,6 +140,17 @@ namespace Avalonia.Headless
table = null; table = null;
return false; return false;
} }
public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics)
{
metrics = new GlyphMetrics
{
Height = 10,
Width = 10
};
return true;
}
} }
class HeadlessTextShaperStub : ITextShaperImpl class HeadlessTextShaperStub : ITextShaperImpl

35
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@ -281,7 +281,6 @@
<StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminatePointerOver" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" /> <StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminatePointerOver" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" />
<StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminatePressed" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" /> <StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminatePressed" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" />
<StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminateDisabled" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" /> <StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminateDisabled" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" />
<!-- Resources for Calendar.xaml, CalendarButton.xaml, CalendarDayButton.xaml, CalendarItem.xaml --> <!-- Resources for Calendar.xaml, CalendarButton.xaml, CalendarDayButton.xaml, CalendarItem.xaml -->
<StaticResource x:Key="CalendarViewSelectedHoverBorderBrush" ResourceKey="SystemControlHighlightListAccentMediumBrush" /> <StaticResource x:Key="CalendarViewSelectedHoverBorderBrush" ResourceKey="SystemControlHighlightListAccentMediumBrush" />
@ -305,7 +304,39 @@
<StaticResource x:Key="CalendarViewCalendarItemRevealBorderBrush" ResourceKey="SystemControlTransparentRevealBorderBrush" /> <StaticResource x:Key="CalendarViewCalendarItemRevealBorderBrush" ResourceKey="SystemControlTransparentRevealBorderBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" /> <StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrush" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="CalendarViewNavigationButtonBorderBrush" ResourceKey="SystemControlTransparentBrush" />
<!-- Resources for Expander.xaml -->
<!-- Expander:Header -->
<StaticResource x:Key="ExpanderHeaderBackground" ResourceKey="SystemAltMediumHighColor" />
<StaticResource x:Key="ExpanderHeaderBackgroundPointerOver" ResourceKey="SystemAltMediumHighColor" />
<StaticResource x:Key="ExpanderHeaderBackgroundPressed" ResourceKey="SystemAltMediumHighColor" />
<StaticResource x:Key="ExpanderHeaderBackgroundDisabled" ResourceKey="SystemAltMediumHighColor" />
<StaticResource x:Key="ExpanderHeaderForeground" ResourceKey="SystemBaseHighColor" />
<StaticResource x:Key="ExpanderHeaderForegroundPointerOver" ResourceKey="SystemBaseHighColor" />
<StaticResource x:Key="ExpanderHeaderForegroundPressed" ResourceKey="SystemBaseHighColor" />
<StaticResource x:Key="ExpanderHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="ExpanderHeaderBorderBrush" ResourceKey="SystemBaseLowColor" />
<StaticResource x:Key="ExpanderHeaderBorderBrushPointerOver" ResourceKey="SystemBaseLowColor" />
<StaticResource x:Key="ExpanderHeaderBorderBrushPressed" ResourceKey="SystemBaseLowColor" />
<StaticResource x:Key="ExpanderHeaderBorderBrushDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<SolidColorBrush x:Key="ExpanderChevronBackground" Color="Transparent" />
<SolidColorBrush x:Key="ExpanderChevronBackgroundPointerOver" Color="{StaticResource SystemBaseHighColor}" Opacity="0.1" />
<SolidColorBrush x:Key="ExpanderChevronBackgroundPressed" Color="Transparent" />
<SolidColorBrush x:Key="ExpanderChevronBackgroundDisabled" Color="Transparent" />
<StaticResource x:Key="ExpanderChevronForeground" ResourceKey="SystemBaseHighColor" />
<StaticResource x:Key="ExpanderChevronForegroundPointerOver" ResourceKey="SystemBaseHighColor" />
<StaticResource x:Key="ExpanderChevronForegroundPressed" ResourceKey="SystemBaseHighColor" />
<StaticResource x:Key="ExpanderChevronForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<SolidColorBrush x:Key="ExpanderChevronBorderBrush" Color="Transparent" />
<SolidColorBrush x:Key="ExpanderChevronBorderBrushPointerOver" Color="Transparent" />
<SolidColorBrush x:Key="ExpanderChevronBorderBrushPressed" Color="Transparent" />
<SolidColorBrush x:Key="ExpanderChevronBorderBrushDisabled" Color="Transparent" />
<!-- Expander:Content -->
<StaticResource x:Key="ExpanderContentBackground" ResourceKey="SystemChromeMediumLowColor" />
<StaticResource x:Key="ExpanderContentBorderBrush" ResourceKey="SystemBaseLowColor" />
<!--Resources for NotificationCard.xaml --> <!--Resources for NotificationCard.xaml -->
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444" /> <SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444" />
<StaticResource x:Key="NotificationCardForegroundBrush" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="NotificationCardForegroundBrush" ResourceKey="SystemControlForegroundBaseHighBrush" />

36
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml

@ -278,7 +278,7 @@
<StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminatePointerOver" ResourceKey="SystemControlForegroundChromeWhiteBrush" /> <StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminatePointerOver" ResourceKey="SystemControlForegroundChromeWhiteBrush" />
<StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminatePressed" ResourceKey="SystemControlForegroundChromeWhiteBrush" /> <StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminatePressed" ResourceKey="SystemControlForegroundChromeWhiteBrush" />
<StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminateDisabled" ResourceKey="SystemControlForegroundChromeWhiteBrush" /> <StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminateDisabled" ResourceKey="SystemControlForegroundChromeWhiteBrush" />
<!-- Resources for Calendar.xaml, CalendarButton.xaml, CalendarDayButton.xaml, CalendarItem.xaml --> <!-- Resources for Calendar.xaml, CalendarButton.xaml, CalendarDayButton.xaml, CalendarItem.xaml -->
<StaticResource x:Key="CalendarViewSelectedHoverBorderBrush" ResourceKey="SystemControlHighlightListAccentMediumBrush" /> <StaticResource x:Key="CalendarViewSelectedHoverBorderBrush" ResourceKey="SystemControlHighlightListAccentMediumBrush" />
<StaticResource x:Key="CalendarViewSelectedPressedBorderBrush" ResourceKey="SystemControlHighlightListAccentHighBrush" /> <StaticResource x:Key="CalendarViewSelectedPressedBorderBrush" ResourceKey="SystemControlHighlightListAccentHighBrush" />
@ -301,7 +301,39 @@
<StaticResource x:Key="CalendarViewCalendarItemRevealBorderBrush" ResourceKey="SystemControlTransparentRevealBorderBrush" /> <StaticResource x:Key="CalendarViewCalendarItemRevealBorderBrush" ResourceKey="SystemControlTransparentRevealBorderBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" /> <StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrush" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="CalendarViewNavigationButtonBorderBrush" ResourceKey="SystemControlTransparentBrush" />
<!-- Resources for Expander.xaml -->
<!-- Expander:Header -->
<StaticResource x:Key="ExpanderHeaderBackground" ResourceKey="SystemAltMediumHighColor" />
<StaticResource x:Key="ExpanderHeaderBackgroundPointerOver" ResourceKey="SystemAltMediumHighColor" />
<StaticResource x:Key="ExpanderHeaderBackgroundPressed" ResourceKey="SystemAltMediumHighColor" />
<StaticResource x:Key="ExpanderHeaderBackgroundDisabled" ResourceKey="SystemAltMediumHighColor" />
<StaticResource x:Key="ExpanderHeaderForeground" ResourceKey="SystemBaseHighColor" />
<StaticResource x:Key="ExpanderHeaderForegroundPointerOver" ResourceKey="SystemBaseHighColor" />
<StaticResource x:Key="ExpanderHeaderForegroundPressed" ResourceKey="SystemBaseHighColor" />
<StaticResource x:Key="ExpanderHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="ExpanderHeaderBorderBrush" ResourceKey="SystemBaseLowColor" />
<StaticResource x:Key="ExpanderHeaderBorderBrushPointerOver" ResourceKey="SystemBaseLowColor" />
<StaticResource x:Key="ExpanderHeaderBorderBrushPressed" ResourceKey="SystemBaseLowColor" />
<StaticResource x:Key="ExpanderHeaderBorderBrushDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<SolidColorBrush x:Key="ExpanderChevronBackground" Color="Transparent" />
<SolidColorBrush x:Key="ExpanderChevronBackgroundPointerOver" Color="{StaticResource SystemBaseHighColor}" Opacity="0.1" />
<SolidColorBrush x:Key="ExpanderChevronBackgroundPressed" Color="Transparent" />
<SolidColorBrush x:Key="ExpanderChevronBackgroundDisabled" Color="Transparent" />
<StaticResource x:Key="ExpanderChevronForeground" ResourceKey="SystemBaseHighColor" />
<StaticResource x:Key="ExpanderChevronForegroundPointerOver" ResourceKey="SystemBaseHighColor" />
<StaticResource x:Key="ExpanderChevronForegroundPressed" ResourceKey="SystemBaseHighColor" />
<StaticResource x:Key="ExpanderChevronForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<SolidColorBrush x:Key="ExpanderChevronBorderBrush" Color="Transparent" />
<SolidColorBrush x:Key="ExpanderChevronBorderBrushPointerOver" Color="Transparent" />
<SolidColorBrush x:Key="ExpanderChevronBorderBrushPressed" Color="Transparent" />
<SolidColorBrush x:Key="ExpanderChevronBorderBrushDisabled" Color="Transparent" />
<!-- Expander:Content -->
<StaticResource x:Key="ExpanderContentBackground" ResourceKey="SystemChromeMediumLowColor" />
<StaticResource x:Key="ExpanderContentBorderBrush" ResourceKey="SystemBaseLowColor" />
<!--Resources for NotificationCard.xaml --> <!--Resources for NotificationCard.xaml -->
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="White" /> <SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="White" />
<StaticResource x:Key="NotificationCardForegroundBrush" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="NotificationCardForegroundBrush" ResourceKey="SystemControlForegroundBaseHighBrush" />

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

@ -1,6 +1,7 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui" <ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True"> x:CompileBindings="True">
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20"> <Border Padding="20">
<StackPanel Orientation="Vertical" Spacing="20" Width="350" Height="600"> <StackPanel Orientation="Vertical" Spacing="20" Width="350" Height="600">
@ -44,45 +45,59 @@
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Thickness x:Key="ExpanderHeaderPadding">16</Thickness> <!-- Shared header/content -->
<x:Double x:Key="ExpanderMinHeight">48</x:Double>
<!-- Header -->
<HorizontalAlignment x:Key="ExpanderHeaderHorizontalContentAlignment">Stretch</HorizontalAlignment>
<VerticalAlignment x:Key="ExpanderHeaderVerticalContentAlignment">Center</VerticalAlignment>
<Thickness x:Key="ExpanderHeaderPadding">16,0,0,0</Thickness>
<Thickness x:Key="ExpanderHeaderBorderThickness">1</Thickness>
<Thickness x:Key="ExpanderChevronBorderThickness">0</Thickness>
<Thickness x:Key="ExpanderChevronMargin">20,0,8,0</Thickness>
<x:Double x:Key="ExpanderChevronButtonSize">32</x:Double>
<!-- Content -->
<Thickness x:Key="ExpanderContentPadding">16</Thickness> <Thickness x:Key="ExpanderContentPadding">16</Thickness>
<Thickness x:Key="ExpanderBorderThickness">1</Thickness> <Thickness x:Key="ExpanderContentLeftBorderThickness">1,1,0,1</Thickness>
<Thickness x:Key="ExpanderDropdownLeftBorderThickness">1,1,0,1</Thickness> <Thickness x:Key="ExpanderContentUpBorderThickness">1,1,1,0</Thickness>
<Thickness x:Key="ExpanderDropdownUpBorderThickness">1,1,1,0</Thickness> <Thickness x:Key="ExpanderContentRightBorderThickness">0,1,1,1</Thickness>
<Thickness x:Key="ExpanderDropdownRightBorderThickness">0,1,1,1</Thickness> <Thickness x:Key="ExpanderContentDownBorderThickness">1,0,1,1</Thickness>
<Thickness x:Key="ExpanderDropdownDownBorderThickness">1,0,1,1</Thickness>
<SolidColorBrush x:Key="ExpanderBackground" Color="{DynamicResource SystemAltMediumHighColor}" />
<SolidColorBrush x:Key="ExpanderBorderBrush" Color="{DynamicResource SystemBaseLowColor}" />
<SolidColorBrush x:Key="ExpanderDropDownBackground" Color="{DynamicResource SystemChromeMediumLowColor}" />
<SolidColorBrush x:Key="ExpanderDropDownBorderBrush" Color="{DynamicResource SystemBaseLowColor}" />
<SolidColorBrush x:Key="ExpanderForeground" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="ExpanderChevronForeground" Color="{DynamicResource SystemBaseHighColor}" />
<ControlTheme x:Key="FluentExpanderToggleButtonTheme" TargetType="ToggleButton"> <ControlTheme x:Key="FluentExpanderToggleButtonTheme" TargetType="ToggleButton">
<Setter Property="Background" Value="{DynamicResource ExpanderHeaderBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderHeaderBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderHeaderBorderThickness}" />
<Setter Property="Foreground" Value="{DynamicResource ExpanderHeaderForeground}" />
<Setter Property="Padding" Value="{StaticResource ExpanderHeaderPadding}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="{StaticResource ExpanderHeaderHorizontalContentAlignment}" />
<Setter Property="VerticalContentAlignment" Value="{StaticResource ExpanderHeaderVerticalContentAlignment}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border x:Name="ToggleButtonBackground" <Border x:Name="ToggleButtonBackground"
CornerRadius="{TemplateBinding CornerRadius}"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderThickness="{TemplateBinding BorderThickness}">
<Grid ColumnDefinitions="*,Auto"> <Grid x:Name="ToggleButtonGrid"
ColumnDefinitions="*,Auto">
<ContentPresenter x:Name="PART_ContentPresenter" <ContentPresenter x:Name="PART_ContentPresenter"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="Center"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Foreground="{DynamicResource ExpanderForeground}" /> HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Foreground="{TemplateBinding Foreground}"
Margin="{TemplateBinding Padding}"/>
<Border x:Name="ExpandCollapseChevronBorder" <Border x:Name="ExpandCollapseChevronBorder"
Grid.Column="1" Grid.Column="1"
Width="32" Width="{DynamicResource ExpanderChevronButtonSize}"
Height="32" Height="{DynamicResource ExpanderChevronButtonSize}"
Margin="7" Margin="{DynamicResource ExpanderChevronMargin}"
RenderTransformOrigin="50%,50%"> CornerRadius="{DynamicResource ControlCornerRadius}"
BorderBrush="{DynamicResource ExpanderChevronBorderBrush}"
BorderThickness="{DynamicResource ExpanderChevronBorderThickness}"
Background="{DynamicResource ExpanderChevronBackground}">
<Path x:Name="ExpandCollapseChevron" <Path x:Name="ExpandCollapseChevron"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -98,6 +113,7 @@
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
<Style Selector="^:checked /template/ Border#ExpandCollapseChevronBorder"> <Style Selector="^:checked /template/ Border#ExpandCollapseChevronBorder">
<Style.Animations> <Style.Animations>
<Animation FillMode="Both" Duration="0:0:0.0625"> <Animation FillMode="Both" Duration="0:0:0.0625">
@ -107,6 +123,7 @@
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
<Style Selector="^:not(:checked) /template/ Border#ExpandCollapseChevronBorder"> <Style Selector="^:not(:checked) /template/ Border#ExpandCollapseChevronBorder">
<Style.Animations> <Style.Animations>
<Animation FillMode="Both" Duration="0:0:0.0625"> <Animation FillMode="Both" Duration="0:0:0.0625">
@ -119,60 +136,118 @@
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
<!-- PointerOver -->
<Style Selector="^:pointerover /template/ Border#ToggleButtonBackground">
<Setter Property="Background" Value="{DynamicResource ExpanderHeaderBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderHeaderBorderBrushPointerOver}" />
</Style>
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ExpanderHeaderForegroundPointerOver}" />
</Style>
<Style Selector="^:pointerover /template/ Border#ExpandCollapseChevronBorder">
<Setter Property="Background" Value="{DynamicResource ExpanderChevronBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderChevronBorderBrushPointerOver}" />
</Style>
<Style Selector="^:pointerover /template/ Path#ExpandCollapseChevron">
<Setter Property="Stroke" Value="{DynamicResource ExpanderChevronForegroundPointerOver}" />
</Style>
<!-- Pressed -->
<Style Selector="^:pressed /template/ Border#ToggleButtonBackground">
<Setter Property="Background" Value="{DynamicResource ExpanderHeaderBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderHeaderBorderBrushPressed}" />
</Style>
<Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ExpanderHeaderForegroundPressed}" />
</Style>
<Style Selector="^:pressed /template/ Border#ExpandCollapseChevronBorder">
<Setter Property="Background" Value="{DynamicResource ExpanderChevronBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderChevronBorderBrushPressed}" />
</Style>
<Style Selector="^:pressed /template/ Path#ExpandCollapseChevron">
<Setter Property="Stroke" Value="{DynamicResource ExpanderChevronForegroundPressed}" />
</Style>
<!-- Disabled -->
<Style Selector="^:disabled /template/ Border#ToggleButtonBackground">
<Setter Property="Background" Value="{DynamicResource ExpanderHeaderBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderHeaderBorderBrushDisabled}" />
</Style>
<Style Selector="^:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ExpanderHeaderForegroundDisabled}" />
</Style>
<Style Selector="^:disabled /template/ Border#ExpandCollapseChevronBorder">
<Setter Property="Background" Value="{DynamicResource ExpanderChevronBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderChevronBorderBrushDisabled}" />
</Style>
<Style Selector="^:disabled /template/ Path#ExpandCollapseChevron">
<Setter Property="Stroke" Value="{DynamicResource ExpanderChevronForegroundDisabled}" />
</Style>
</ControlTheme> </ControlTheme>
<ControlTheme x:Key="FluentExpanderToggleButtonUpTheme" TargetType="ToggleButton" BasedOn="{StaticResource FluentExpanderToggleButtonTheme}"> <ControlTheme x:Key="FluentExpanderToggleButtonUpTheme" TargetType="ToggleButton" BasedOn="{StaticResource FluentExpanderToggleButtonTheme}">
<Style Selector="^ /template/ Path#ExpandCollapseChevron"> <Style Selector="^ /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 0 7 L 7 0 L 14 7" /> <Setter Property="Data" Value="M 0 7 L 7 0 L 14 7" />
</Style> </Style>
</ControlTheme> </ControlTheme>
<ControlTheme x:Key="FluentExpanderToggleButtonDownTheme" TargetType="ToggleButton" BasedOn="{StaticResource FluentExpanderToggleButtonTheme}"> <ControlTheme x:Key="FluentExpanderToggleButtonDownTheme" TargetType="ToggleButton" BasedOn="{StaticResource FluentExpanderToggleButtonTheme}">
<Style Selector="^ /template/ Path#ExpandCollapseChevron"> <Style Selector="^ /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 0 0 L 7 7 L 14 0" /> <Setter Property="Data" Value="M 0 0 L 7 7 L 14 0" />
</Style> </Style>
</ControlTheme> </ControlTheme>
<ControlTheme x:Key="FluentExpanderToggleButtonLeftTheme" TargetType="ToggleButton" BasedOn="{StaticResource FluentExpanderToggleButtonTheme}"> <ControlTheme x:Key="FluentExpanderToggleButtonLeftTheme" TargetType="ToggleButton" BasedOn="{StaticResource FluentExpanderToggleButtonTheme}">
<Style Selector="^ /template/ Path#ExpandCollapseChevron"> <Style Selector="^ /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 7 0 L 0 7 L 7 14" /> <Setter Property="Data" Value="M 7 0 L 0 7 L 7 14" />
</Style> </Style>
</ControlTheme> </ControlTheme>
<ControlTheme x:Key="FluentExpanderToggleButtonRightTheme" TargetType="ToggleButton" BasedOn="{StaticResource FluentExpanderToggleButtonTheme}"> <ControlTheme x:Key="FluentExpanderToggleButtonRightTheme" TargetType="ToggleButton" BasedOn="{StaticResource FluentExpanderToggleButtonTheme}">
<Style Selector="^ /template/ Path#ExpandCollapseChevron"> <Style Selector="^ /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 0 0 L 7 7 L 0 14" /> <Setter Property="Data" Value="M 0 0 L 7 7 L 0 14" />
</Style> </Style>
</ControlTheme> </ControlTheme>
<ControlTheme x:Key="{x:Type Expander}" TargetType="Expander"> <ControlTheme x:Key="{x:Type Expander}" TargetType="Expander">
<Setter Property="Background" Value="{DynamicResource ExpanderBackground}" /> <Setter Property="IsTabStop" Value="False"/>
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderBorderThickness}" /> <Setter Property="MinWidth" Value="{DynamicResource FlyoutThemeMinWidth}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderBorderBrush}" /> <Setter Property="MinHeight" Value="{StaticResource ExpanderMinHeight}" />
<Setter Property="Background" Value="{DynamicResource ExpanderContentBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderContentBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderContentDownBorderThickness}" />
<Setter Property="Padding" Value="{StaticResource ExpanderContentPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Padding" Value="{DynamicResource ExpanderHeaderPadding}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<DockPanel> <DockPanel MinWidth="{TemplateBinding MinWidth}"
MaxWidth="{TemplateBinding MaxWidth}">
<ToggleButton x:Name="ExpanderHeader" <ToggleButton x:Name="ExpanderHeader"
Padding="{TemplateBinding Padding}" MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}" CornerRadius="{TemplateBinding CornerRadius}"
Background="{TemplateBinding Background}" IsEnabled="{TemplateBinding IsEnabled}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
HorizontalContentAlignment="Stretch"
Content="{TemplateBinding Header}" Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplate="{TemplateBinding HeaderTemplate}"
IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
IsEnabled="{TemplateBinding IsEnabled}"/>
<Border x:Name="ExpanderContent" <Border x:Name="ExpanderContent"
Padding="{DynamicResource ExpanderContentPadding}" IsVisible="{TemplateBinding IsExpanded, Mode=TwoWay}"
Background="{DynamicResource ExpanderDropDownBackground}" Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ExpanderDropDownBorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
IsVisible="{TemplateBinding IsExpanded, Mode=TwoWay}"> BorderThickness="{TemplateBinding BorderThickness}"
MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Padding="{TemplateBinding Padding}">
<ContentPresenter x:Name="PART_ContentPresenter" <ContentPresenter x:Name="PART_ContentPresenter"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" /> ContentTemplate="{TemplateBinding ContentTemplate}"
Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border> </Border>
</DockPanel> </DockPanel>
</ControlTemplate> </ControlTemplate>
@ -241,16 +316,16 @@
</Style> </Style>
<Style Selector="^:left /template/ Border#ExpanderContent"> <Style Selector="^:left /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownLeftBorderThickness}" /> <Setter Property="BorderThickness" Value="{DynamicResource ExpanderContentLeftBorderThickness}" />
</Style> </Style>
<Style Selector="^:up /template/ Border#ExpanderContent"> <Style Selector="^:up /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownUpBorderThickness}" /> <Setter Property="BorderThickness" Value="{DynamicResource ExpanderContentUpBorderThickness}" />
</Style> </Style>
<Style Selector="^:right /template/ Border#ExpanderContent"> <Style Selector="^:right /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownRightBorderThickness}" /> <Setter Property="BorderThickness" Value="{DynamicResource ExpanderContentRightBorderThickness}" />
</Style> </Style>
<Style Selector="^:down /template/ Border#ExpanderContent"> <Style Selector="^:down /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownDownBorderThickness}" /> <Setter Property="BorderThickness" Value="{DynamicResource ExpanderContentDownBorderThickness}" />
</Style> </Style>
</ControlTheme> </ControlTheme>
</ResourceDictionary> </ResourceDictionary>

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX; using XamlX;
using XamlX.Ast; using XamlX.Ast;
@ -51,6 +52,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlBindingPathParser(), new AvaloniaXamlIlBindingPathParser(),
new AvaloniaXamlIlPropertyPathTransformer(), new AvaloniaXamlIlPropertyPathTransformer(),
new AvaloniaXamlIlSetterTargetTypeMetadataTransformer(),
new AvaloniaXamlIlSetterTransformer(), new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(), new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer(), new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),

34
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs

@ -0,0 +1,34 @@
using System.Linq;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.Transform.Transformers;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
internal class AvaloniaXamlIlSetterTargetTypeMetadataTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlAstObjectNode on
&& on.Children.FirstOrDefault(c => c is XamlAstXmlDirective
{
Namespace: XamlNamespaces.Xaml2006,
Name: "SetterTargetType"
}) is { } typeDirective)
{
var value = ((XamlAstXmlDirective)typeDirective).Values.Single();
var type = value is XamlTypeExtensionNode typeNode ? typeNode.Value
: value is XamlAstTextNode tn ? TypeReferenceResolver.ResolveType(context, tn.Text, false, tn, true)
: null;
on.Children.Remove(typeDirective);
if (type is null)
{
throw new XamlParseException("Unable to resolve SetterTargetType type", typeDirective);
}
return new AvaloniaXamlIlTargetTypeMetadataNode(on, type, AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
}
return node;
}
}

25
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using XamlX;
using XamlX.Ast; using XamlX.Ast;
using XamlX.Emit; using XamlX.Emit;
using XamlX.IL; using XamlX.IL;
@ -8,7 +9,6 @@ using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{ {
using XamlParseException = XamlX.XamlParseException;
class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
{ {
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@ -17,10 +17,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
&& on.Type.GetClrType().FullName == "Avalonia.Styling.Setter")) && on.Type.GetClrType().FullName == "Avalonia.Styling.Setter"))
return node; return node;
var targetTypeNode = context.ParentNodes() IXamlType targetType = null;
IXamlLineInfo lineInfo = null;
var styleParent = context.ParentNodes()
.OfType<AvaloniaXamlIlTargetTypeMetadataNode>() .OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
.FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style) ?? .FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType", node);
if (styleParent != null)
{
targetType = styleParent.TargetType.GetClrType()
?? throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node);
lineInfo = on;
}
if (targetType == null)
{
throw new XamlParseException("Could not determine target type of Setter", node);
}
IXamlType propType = null; IXamlType propType = null;
var property = @on.Children.OfType<XamlAstXamlPropertyValueNode>() var property = @on.Children.OfType<XamlAstXamlPropertyValueNode>()
@ -31,9 +45,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (propertyName == null) if (propertyName == null)
throw new XamlParseException("Setter.Property must be a string", node); throw new XamlParseException("Setter.Property must be a string", node);
var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName, var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
new XamlAstClrTypeReference(targetTypeNode, targetTypeNode.TargetType.GetClrType(), false), property.Values[0]); new XamlAstClrTypeReference(lineInfo, targetType, false), property.Values[0]);
property.Values = new List<IXamlAstValueNode> {avaloniaPropertyNode}; property.Values = new List<IXamlAstValueNode> {avaloniaPropertyNode};
propType = avaloniaPropertyNode.AvaloniaPropertyType; propType = avaloniaPropertyNode.AvaloniaPropertyType;
} }

16
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -148,11 +148,19 @@ namespace Avalonia.Skia
$"Could not create glyph typeface for: {typeface.FontFamily.Name}."); $"Could not create glyph typeface for: {typeface.FontFamily.Name}.");
} }
var isFakeBold = (int)typeface.Weight >= 600 && !skTypeface.IsBold; var fontSimulations = FontSimulations.None;
var isFakeItalic = typeface.Style == FontStyle.Italic && !skTypeface.IsItalic; if((int)typeface.Weight >= 600 && !skTypeface.IsBold)
{
return new GlyphTypefaceImpl(skTypeface, isFakeBold, isFakeItalic); fontSimulations |= FontSimulations.Bold;
}
if(typeface.Style == FontStyle.Italic && !skTypeface.IsItalic)
{
fontSimulations |= FontSimulations.Oblique;
}
return new GlyphTypefaceImpl(skTypeface, fontSimulations);
} }
} }
} }

28
src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

@ -12,7 +12,7 @@ namespace Avalonia.Skia
{ {
private bool _isDisposed; private bool _isDisposed;
public GlyphTypefaceImpl(SKTypeface typeface, bool isFakeBold = false, bool isFakeItalic = false) public GlyphTypefaceImpl(SKTypeface typeface, FontSimulations fontSimulations)
{ {
Typeface = typeface ?? throw new ArgumentNullException(nameof(typeface)); Typeface = typeface ?? throw new ArgumentNullException(nameof(typeface));
@ -52,9 +52,7 @@ namespace Avalonia.Skia
GlyphCount = Typeface.GlyphCount; GlyphCount = Typeface.GlyphCount;
IsFakeBold = isFakeBold; FontSimulations = fontSimulations;
IsFakeItalic = isFakeItalic;
} }
public Face Face { get; } public Face Face { get; }
@ -63,6 +61,8 @@ namespace Avalonia.Skia
public SKTypeface Typeface { get; } public SKTypeface Typeface { get; }
public FontSimulations FontSimulations { get; }
public int ReplacementCodepoint { get; } public int ReplacementCodepoint { get; }
public FontMetrics Metrics { get; } public FontMetrics Metrics { get; }
@ -73,6 +73,26 @@ namespace Avalonia.Skia
public bool IsFakeItalic { get; } public bool IsFakeItalic { get; }
public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics)
{
metrics = default;
if (!Font.TryGetGlyphExtents(glyph, out var extents))
{
return false;
}
metrics = new GlyphMetrics
{
XBearing = extents.XBearing,
YBearing = extents.YBearing,
Width = extents.Width,
Height = extents.Height
};
return true;
}
/// <inheritdoc cref="IGlyphTypeface"/> /// <inheritdoc cref="IGlyphTypeface"/>
public ushort GetGlyph(uint codepoint) public ushort GetGlyph(uint codepoint)
{ {

1
src/Web/Avalonia.Web/Avalonia.Web.csproj

@ -7,6 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<SupportedPlatform Remove="@(SupportedPlatform)" />
<SupportedPlatform Include="browser" /> <SupportedPlatform Include="browser" />
</ItemGroup> </ItemGroup>

4
src/Web/Avalonia.Web/Avalonia.Web.props

@ -1,5 +1,5 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<EmccExtraLDFlags>$(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js"</EmccExtraLDFlags> <EmccInitialHeapSize>16384000</EmccInitialHeapSize> <!-- must be a multiple of 64KiB, 1024000 * num MB, number grows -->
</PropertyGroup> </PropertyGroup>
</Project> </Project>

30
src/Web/Avalonia.Web/Avalonia.Web.targets

@ -4,4 +4,34 @@
<NativeFileReference Include="$(HarfBuzzSharpStaticLibraryPath)\3.1.7\libHarfBuzzSharp.a" /> <NativeFileReference Include="$(HarfBuzzSharpStaticLibraryPath)\3.1.7\libHarfBuzzSharp.a" />
<NativeFileReference Include="$(SkiaSharpStaticLibraryPath)\3.1.7\libSkiaSharp.a" /> <NativeFileReference Include="$(SkiaSharpStaticLibraryPath)\3.1.7\libSkiaSharp.a" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<UseAvaloniaWasmDefaultOptimizations Condition="'$(UseAvaloniaWasmDefaultOptimizations)'==''">True</UseAvaloniaWasmDefaultOptimizations>
<EmccExtraLDFlags>$(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js"</EmccExtraLDFlags>
<EmccFlags>$(EmccExtraLDFlags) -sERROR_ON_UNDEFINED_SYMBOLS=0</EmccFlags>
<WasmBuildNative>true</WasmBuildNative>
</PropertyGroup>
<PropertyGroup Condition="'$(UseAvaloniaWasmDefaultOptimizations)'=='True'">
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
<InvariantGlobalization>true</InvariantGlobalization>
<EmccCompileOptimizationFlag>-Oz</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-Oz</EmccLinkOptimizationFlag>
<WasmEmitSymbolMap>false</WasmEmitSymbolMap>
<WasmNativeDebugSymbols>false</WasmNativeDebugSymbols>
<WasmDebugLevel>0</WasmDebugLevel>
<WasmEnableExceptionHandling>false</WasmEnableExceptionHandling>
<TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
<DebuggerSupport>false</DebuggerSupport>
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
<EventSourceSupport>false</EventSourceSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
<UseNativeHttpHandler>true</UseNativeHttpHandler>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<IncludeSatelliteDllsProjectOutputGroup>false</IncludeSatelliteDllsProjectOutputGroup>
</PropertyGroup>
</Project> </Project>

76
src/Web/Avalonia.Web/AvaloniaView.cs

@ -1,5 +1,9 @@
using System; using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices.JavaScript; using System.Runtime.InteropServices.JavaScript;
using Avalonia.Collections.Pooled;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Embedding; using Avalonia.Controls.Embedding;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
@ -18,6 +22,7 @@ namespace Avalonia.Web
[System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings
public partial class AvaloniaView : ITextInputMethodImpl public partial class AvaloniaView : ITextInputMethodImpl
{ {
private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(ClearMode.Never);
private readonly BrowserTopLevelImpl _topLevelImpl; private readonly BrowserTopLevelImpl _topLevelImpl;
private EmbeddableControlRoot _topLevel; private EmbeddableControlRoot _topLevel;
@ -52,13 +57,13 @@ namespace Avalonia.Web
} }
_containerElement = hostContent.GetPropertyAsJSObject("host") _containerElement = hostContent.GetPropertyAsJSObject("host")
?? throw new InvalidOperationException("Host cannot be null"); ?? throw new InvalidOperationException("Host cannot be null");
_canvas = hostContent.GetPropertyAsJSObject("canvas") _canvas = hostContent.GetPropertyAsJSObject("canvas")
?? throw new InvalidOperationException("Canvas cannot be null"); ?? throw new InvalidOperationException("Canvas cannot be null");
_nativeControlsContainer = hostContent.GetPropertyAsJSObject("nativeHost") _nativeControlsContainer = hostContent.GetPropertyAsJSObject("nativeHost")
?? throw new InvalidOperationException("NativeHost cannot be null"); ?? throw new InvalidOperationException("NativeHost cannot be null");
_inputElement = hostContent.GetPropertyAsJSObject("inputElement") _inputElement = hostContent.GetPropertyAsJSObject("inputElement")
?? throw new InvalidOperationException("InputElement cannot be null"); ?? throw new InvalidOperationException("InputElement cannot be null");
_splash = DomHelper.GetElementById("avalonia-splash"); _splash = DomHelper.GetElementById("avalonia-splash");
@ -96,7 +101,8 @@ namespace Avalonia.Web
OnCompositionUpdate, OnCompositionUpdate,
OnCompositionEnd); OnCompositionEnd);
InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel); InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp,
OnPointerCancel, OnWheel);
var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>(); var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>();
@ -117,7 +123,12 @@ namespace Avalonia.Web
_context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024); _context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
} }
_topLevelImpl.Surfaces = new[] { new BrowserSkiaSurface(_context, _jsGlInfo, ColorType, new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, GRSurfaceOrigin.BottomLeft) }; _topLevelImpl.Surfaces = new[]
{
new BrowserSkiaSurface(_context, _jsGlInfo, ColorType,
new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi,
GRSurfaceOrigin.BottomLeft)
};
} }
else else
{ {
@ -135,7 +146,7 @@ namespace Avalonia.Web
DomHelper.ObserveSize(host, null, OnSizeChanged); DomHelper.ObserveSize(host, null, OnSizeChanged);
CanvasHelper.RequestAnimationFrame(_canvas, true); CanvasHelper.RequestAnimationFrame(_canvas, true);
InputHelper.FocusElement(_containerElement); InputHelper.FocusElement(_containerElement);
} }
@ -155,17 +166,36 @@ namespace Avalonia.Web
private bool OnPointerMove(JSObject args) private bool OnPointerMove(JSObject args)
{ {
var type = args.GetPropertyAsString("pointertype"); var pointerType = args.GetPropertyAsString("pointerType");
var point = ExtractRawPointerFromJSArgs(args); var point = ExtractRawPointerFromJSArgs(args);
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchUpdate,
_ => RawPointerEventType.Move
};
var coalescedEvents = new Lazy<IReadOnlyList<RawPointerPoint>?>(() =>
{
var points = InputHelper.GetCoalescedEvents(args);
s_intermediatePointsPooledList.Clear();
s_intermediatePointsPooledList.Capacity = points.Length - 1;
// Skip the last one, as it is already processed point.
for (var i = 0; i < points.Length - 1; i++)
{
var point = points[i];
s_intermediatePointsPooledList.Add(ExtractRawPointerFromJSArgs(point));
}
return s_intermediatePointsPooledList;
});
return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, type!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"), coalescedEvents);
} }
private bool OnPointerDown(JSObject args) private bool OnPointerDown(JSObject args)
{ {
var pointerType = args.GetPropertyAsString("pointerType"); var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
var type = pointerType switch var type = pointerType switch
{ {
"touch" => RawPointerEventType.TouchBegin, "touch" => RawPointerEventType.TouchBegin,
@ -176,20 +206,18 @@ namespace Avalonia.Web
2 => RawPointerEventType.RightButtonDown, 2 => RawPointerEventType.RightButtonDown,
3 => RawPointerEventType.XButton1Down, 3 => RawPointerEventType.XButton1Down,
4 => RawPointerEventType.XButton2Down, 4 => RawPointerEventType.XButton2Down,
// 5 => Pen eraser button, 5 => RawPointerEventType.XButton1Down, // should be pen eraser button,
_ => RawPointerEventType.Move _ => RawPointerEventType.Move
} }
}; };
var point = ExtractRawPointerFromJSArgs(args); var point = ExtractRawPointerFromJSArgs(args);
return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
} }
private bool OnPointerUp(JSObject args) private bool OnPointerUp(JSObject args)
{ {
var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
var type = pointerType switch var type = pointerType switch
{ {
"touch" => RawPointerEventType.TouchEnd, "touch" => RawPointerEventType.TouchEnd,
@ -200,15 +228,27 @@ namespace Avalonia.Web
2 => RawPointerEventType.RightButtonUp, 2 => RawPointerEventType.RightButtonUp,
3 => RawPointerEventType.XButton1Up, 3 => RawPointerEventType.XButton1Up,
4 => RawPointerEventType.XButton2Up, 4 => RawPointerEventType.XButton2Up,
// 5 => Pen eraser button, 5 => RawPointerEventType.XButton1Up, // should be pen eraser button,
_ => RawPointerEventType.Move _ => RawPointerEventType.Move
} }
}; };
var point = ExtractRawPointerFromJSArgs(args); var point = ExtractRawPointerFromJSArgs(args);
return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
} }
private bool OnPointerCancel(JSObject args)
{
var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
if (pointerType == "touch")
{
var point = ExtractRawPointerFromJSArgs(args);
_topLevelImpl.RawPointerEvent(RawPointerEventType.TouchCancel, pointerType, point,
GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
}
return false;
}
private bool OnWheel(JSObject args) private bool OnWheel(JSObject args)
{ {

11
src/Web/Avalonia.Web/BrowserTopLevelImpl.cs

@ -67,17 +67,22 @@ namespace Avalonia.Web
public bool RawPointerEvent( public bool RawPointerEvent(
RawPointerEventType eventType, string pointerType, RawPointerEventType eventType, string pointerType,
RawPointerPoint p, RawInputModifiers modifiers, long touchPointId) RawPointerPoint p, RawInputModifiers modifiers, long touchPointId,
Lazy<IReadOnlyList<RawPointerPoint>?>? intermediatePoints = null)
{ {
if (_inputRoot is { } if (_inputRoot is { }
&& Input is { } input) && Input is { } input)
{ {
var device = GetPointerDevice(pointerType); var device = GetPointerDevice(pointerType);
var args = device is TouchDevice ? var args = device is TouchDevice ?
new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId) : new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId)
{
IntermediatePoints = intermediatePoints
} :
new RawPointerEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers) new RawPointerEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers)
{ {
RawPointerId = touchPointId RawPointerId = touchPointId,
IntermediatePoints = intermediatePoints
}; };
input.Invoke(args); input.Invoke(args);

2
src/Web/Avalonia.Web/Interop/CanvasHelper.cs

@ -33,7 +33,7 @@ internal static partial class CanvasHelper
public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop); public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop);
[JSImport("Canvas.setCanvasSize", AvaloniaModule.MainModuleName)] [JSImport("Canvas.setCanvasSize", AvaloniaModule.MainModuleName)]
public static partial void SetCanvasSize(JSObject canvas, int height, int width); public static partial void SetCanvasSize(JSObject canvas, int width, int height);
[JSImport("Canvas.initGL", AvaloniaModule.MainModuleName)] [JSImport("Canvas.initGL", AvaloniaModule.MainModuleName)]
private static partial JSObject InitGL( private static partial JSObject InitGL(

6
src/Web/Avalonia.Web/Interop/InputHelper.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript; using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -36,6 +37,8 @@ internal static partial class InputHelper
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>] [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
Func<JSObject, bool> pointerUp, Func<JSObject, bool> pointerUp,
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>] [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
Func<JSObject, bool> pointerCancel,
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
Func<JSObject, bool> wheel); Func<JSObject, bool> wheel);
@ -45,6 +48,9 @@ internal static partial class InputHelper
[JSMarshalAs<JSType.Function<JSType.String, JSType.Boolean>>] [JSMarshalAs<JSType.Function<JSType.String, JSType.Boolean>>]
Func<string, bool> input); Func<string, bool> input);
[JSImport("InputHelper.getCoalescedEvents", AvaloniaModule.MainModuleName)]
[return: JSMarshalAs<JSType.Array<JSType.Object>>]
public static partial JSObject[] GetCoalescedEvents(JSObject pointerEvent);
[JSImport("InputHelper.clearInput", AvaloniaModule.MainModuleName)] [JSImport("InputHelper.clearInput", AvaloniaModule.MainModuleName)]
public static partial void ClearInputElement(JSObject htmlElement); public static partial void ClearInputElement(JSObject htmlElement);

32
src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts

@ -95,41 +95,45 @@ export class InputHelper {
pointerMoveCallback: (args: PointerEvent) => boolean, pointerMoveCallback: (args: PointerEvent) => boolean,
pointerDownCallback: (args: PointerEvent) => boolean, pointerDownCallback: (args: PointerEvent) => boolean,
pointerUpCallback: (args: PointerEvent) => boolean, pointerUpCallback: (args: PointerEvent) => boolean,
pointerCancelCallback: (args: PointerEvent) => boolean,
wheelCallback: (args: WheelEvent) => boolean wheelCallback: (args: WheelEvent) => boolean
) { ) {
const pointerMoveHandler = (args: PointerEvent) => { const pointerMoveHandler = (args: PointerEvent) => {
if (pointerMoveCallback(args)) { pointerMoveCallback(args);
args.preventDefault(); args.preventDefault();
}
}; };
const pointerDownHandler = (args: PointerEvent) => { const pointerDownHandler = (args: PointerEvent) => {
if (pointerDownCallback(args)) { pointerDownCallback(args);
args.preventDefault(); args.preventDefault();
}
}; };
const pointerUpHandler = (args: PointerEvent) => { const pointerUpHandler = (args: PointerEvent) => {
if (pointerUpCallback(args)) { pointerUpCallback(args);
args.preventDefault(); args.preventDefault();
} };
const pointerCancelHandler = (args: PointerEvent) => {
pointerCancelCallback(args);
args.preventDefault();
}; };
const wheelHandler = (args: WheelEvent) => { const wheelHandler = (args: WheelEvent) => {
if (wheelCallback(args)) { wheelCallback(args);
args.preventDefault(); args.preventDefault();
}
}; };
element.addEventListener("pointermove", pointerMoveHandler); element.addEventListener("pointermove", pointerMoveHandler);
element.addEventListener("pointerdown", pointerDownHandler); element.addEventListener("pointerdown", pointerDownHandler);
element.addEventListener("pointerup", pointerUpHandler); element.addEventListener("pointerup", pointerUpHandler);
element.addEventListener("wheel", wheelHandler); element.addEventListener("wheel", wheelHandler);
element.addEventListener("pointercancel", pointerCancelHandler);
return () => { return () => {
element.removeEventListener("pointerover", pointerMoveHandler); element.removeEventListener("pointerover", pointerMoveHandler);
element.removeEventListener("pointerdown", pointerDownHandler); element.removeEventListener("pointerdown", pointerDownHandler);
element.removeEventListener("pointerup", pointerUpHandler); element.removeEventListener("pointerup", pointerUpHandler);
element.removeEventListener("pointercancel", pointerCancelHandler);
element.removeEventListener("wheel", wheelHandler); element.removeEventListener("wheel", wheelHandler);
}; };
} }
@ -150,6 +154,10 @@ export class InputHelper {
}; };
} }
public static getCoalescedEvents(pointerEvent: PointerEvent): PointerEvent[] {
return pointerEvent.getCoalescedEvents();
}
public static clearInput(inputElement: HTMLInputElement) { public static clearInput(inputElement: HTMLInputElement) {
inputElement.value = ""; inputElement.value = "";
} }

25
src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs

@ -1,10 +1,11 @@
using System; using System;
using System.Drawing.Drawing2D;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using HarfBuzzSharp; using HarfBuzzSharp;
using SharpDX.DirectWrite; using SharpDX.DirectWrite;
using FontMetrics = Avalonia.Media.FontMetrics; using FontMetrics = Avalonia.Media.FontMetrics;
using FontSimulations = Avalonia.Media.FontSimulations;
using GlyphMetrics = Avalonia.Media.GlyphMetrics;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
@ -82,6 +83,8 @@ namespace Avalonia.Direct2D1.Media
public int GlyphCount { get; set; } public int GlyphCount { get; set; }
public FontSimulations FontSimulations => FontSimulations.None;
/// <inheritdoc cref="IGlyphTypeface"/> /// <inheritdoc cref="IGlyphTypeface"/>
public ushort GetGlyph(uint codepoint) public ushort GetGlyph(uint codepoint)
{ {
@ -135,6 +138,26 @@ namespace Avalonia.Direct2D1.Media
return Font.GetHorizontalGlyphAdvances(glyphIndices); return Font.GetHorizontalGlyphAdvances(glyphIndices);
} }
public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics)
{
metrics = default;
if (!Font.TryGetGlyphExtents(glyph, out var extents))
{
return false;
}
metrics = new GlyphMetrics
{
XBearing = extents.XBearing,
YBearing = extents.YBearing,
Width = extents.Width,
Height = extents.Height
};
return true;
}
private void Dispose(bool disposing) private void Dispose(bool disposing)
{ {
if (_isDisposed) if (_isDisposed)

8
tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs

@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests
} }
[Fact] [Fact]
public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Add_To_Selection() public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Not_Add_To_Selection()
{ {
var target = new ListBox var target = new ListBox
{ {
@ -56,11 +56,11 @@ namespace Avalonia.Controls.UnitTests
KeyModifiers = KeyModifiers.Control KeyModifiers = KeyModifiers.Control
}); });
Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); Assert.Equal(new[] { "Foo" }, target.SelectedItems);
} }
[Fact] [Fact]
public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Remove_From_Selection() public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Not_Remove_From_Selection()
{ {
var target = new ListBox var target = new ListBox
{ {
@ -81,7 +81,7 @@ namespace Avalonia.Controls.UnitTests
KeyModifiers = KeyModifiers.Control KeyModifiers = KeyModifiers.Control
}); });
Assert.Equal(new[] { "Bar" }, target.SelectedItems); Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
} }
private Control CreateListBoxTemplate(ITemplatedControl parent, INameScope scope) private Control CreateListBoxTemplate(ITemplatedControl parent, INameScope scope)

52
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@ -59,6 +59,58 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, target.SelectedIndex); Assert.Equal(0, target.SelectedIndex);
} }
[Fact]
public void Focusing_Item_With_Arrow_Key_And_Ctrl_Pressed_Should_Not_Select_It()
{
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
};
ApplyTemplate(target);
target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
{
RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = NavigationMethod.Directional,
KeyModifiers = KeyModifiers.Control
});
Assert.Equal(-1, target.SelectedIndex);
}
[Fact]
public void Pressing_Space_On_Focused_Item_With_Ctrl_Pressed_Should_Select_It()
{
using (UnitTestApplication.Start())
{
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
ApplyTemplate(target);
target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
{
RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = NavigationMethod.Directional,
KeyModifiers = KeyModifiers.Control
});
target.Presenter.Panel.Children[0].RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = Key.Space,
KeyModifiers = KeyModifiers.Control
});
Assert.Equal(0, target.SelectedIndex);
}
}
[Fact] [Fact]
public void Clicking_Item_Should_Select_It() public void Clicking_Item_Should_Select_It()
{ {

51
tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs

@ -0,0 +1,51 @@
using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests;
public class SetterTests : XamlTestBase
{
[Fact]
public void SetterTargetType_Should_Understand_xType_Extensions()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Animation xmlns='https://github.com/avaloniaui' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' x:SetterTargetType='{x:Type ContentControl}'>
<KeyFrame>
<Setter Property='Content' Value='{Binding}'/>
</KeyFrame>
<KeyFrame>
<Setter Property='Content' Value='{Binding}'/>
</KeyFrame>
</Animation>";
var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml);
var setter = (Setter)animation.Children[0].Setters[0];
Assert.Equal(typeof(ContentControl), setter.Property.OwnerType);
}
}
[Fact]
public void SetterTargetType_Should_Understand_Type_From_Xmlns()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<av:Animation xmlns:av='https://github.com/avaloniaui' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' x:SetterTargetType='av:ContentControl'>
<av:KeyFrame>
<av:Setter Property='Content' Value='{av:Binding}'/>
</av:KeyFrame>
<av:KeyFrame>
<av:Setter Property='Content' Value='{av:Binding}'/>
</av:KeyFrame>
</av:Animation>";
var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml);
var setter = (Setter)animation.Children[0].Setters[0];
Assert.Equal(typeof(ContentControl), setter.Property.OwnerType);
}
}
}

2
tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs

@ -103,7 +103,7 @@ namespace Avalonia.Skia.UnitTests.Media
} }
} }
return new GlyphTypefaceImpl(skTypeface); return new GlyphTypefaceImpl(skTypeface, FontSimulations.None);
} }
} }
} }

32
tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs

@ -10,7 +10,7 @@ namespace Avalonia.UnitTests
private bool _isDisposed; private bool _isDisposed;
private Blob _blob; private Blob _blob;
public HarfBuzzGlyphTypefaceImpl(Stream data, bool isFakeBold = false, bool isFakeItalic = false) public HarfBuzzGlyphTypefaceImpl(Stream data)
{ {
_blob = Blob.FromStream(data); _blob = Blob.FromStream(data);
@ -45,10 +45,6 @@ namespace Avalonia.UnitTests
}; };
GlyphCount = Face.GlyphCount; GlyphCount = Face.GlyphCount;
IsFakeBold = isFakeBold;
IsFakeItalic = isFakeItalic;
} }
public FontMetrics Metrics { get; } public FontMetrics Metrics { get; }
@ -58,10 +54,8 @@ namespace Avalonia.UnitTests
public Font Font { get; } public Font Font { get; }
public int GlyphCount { get; set; } public int GlyphCount { get; set; }
public bool IsFakeBold { get; } public FontSimulations FontSimulations { get; }
public bool IsFakeItalic { get; }
/// <inheritdoc cref="IGlyphTypeface"/> /// <inheritdoc cref="IGlyphTypeface"/>
public ushort GetGlyph(uint codepoint) public ushort GetGlyph(uint codepoint)
@ -162,5 +156,25 @@ namespace Avalonia.UnitTests
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics)
{
metrics = default;
if (!Font.TryGetGlyphExtents(glyph, out var extents))
{
return false;
}
metrics = new GlyphMetrics
{
XBearing = extents.XBearing,
YBearing = extents.YBearing,
Width = extents.Width,
Height = extents.Height
};
return true;
}
} }
} }

13
tests/Avalonia.UnitTests/MockGlyphTypeface.cs

@ -15,6 +15,8 @@ namespace Avalonia.UnitTests
public int GlyphCount => 1337; public int GlyphCount => 1337;
public FontSimulations FontSimulations => throw new NotImplementedException();
public ushort GetGlyph(uint codepoint) public ushort GetGlyph(uint codepoint)
{ {
return (ushort)codepoint; return (ushort)codepoint;
@ -56,5 +58,16 @@ namespace Avalonia.UnitTests
table = null; table = null;
return false; return false;
} }
public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics)
{
metrics = new GlyphMetrics
{
Width = 10,
Height = 10
};
return true;
}
} }
} }

Loading…
Cancel
Save