Browse Source

Merge remote-tracking branch 'origin/master' into feature/add-quality-value-to-IBitmapImpl-Save

pull/9106/head
Fabian Huegle 3 years ago
parent
commit
6267c33ba9
  1. 1
      .github/FUNDING.yml
  2. 1
      Avalonia.sln
  3. 2
      build/SharedVersion.props
  4. 4
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  5. 4
      native/Avalonia.Native/src/OSX/Screens.mm
  6. 5
      samples/ControlCatalog.Web/ControlCatalog.Web.csproj
  7. 1
      samples/ControlCatalog.Web/Roots.xml
  8. 21
      samples/ControlCatalog/Pages/ColorPickerPage.xaml
  9. 1
      samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs
  10. 17
      samples/ControlCatalog/Pages/ExpanderPage.xaml
  11. 4
      samples/ControlCatalog/Pages/ScreenPage.cs
  12. 2
      src/Avalonia.Base/Avalonia.Base.csproj
  13. 2
      src/Avalonia.Base/Input/DragDrop.cs
  14. 8
      src/Avalonia.Base/Input/DragDropDevice.cs
  15. 27
      src/Avalonia.Base/Media/FontSimulations.cs
  16. 24
      src/Avalonia.Base/Media/GlyphMetrics.cs
  17. 15
      src/Avalonia.Base/Media/IGlyphTypeface.cs
  18. 4
      src/Avalonia.Base/Media/PathMarkupParser.cs
  19. 29
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  20. 28
      src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs
  21. 2
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs
  22. 484
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
  23. 486
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
  24. 3
      src/Avalonia.Controls/ListBox.cs
  25. 6
      src/Avalonia.Controls/Platform/IScreenImpl.cs
  26. 57
      src/Avalonia.Controls/Platform/Screen.cs
  27. 45
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  28. 23
      src/Avalonia.Controls/Screens.cs
  29. 30
      src/Avalonia.Controls/TreeViewItem.cs
  30. 1
      src/Avalonia.Controls/Viewbox.cs
  31. 3
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  32. 13
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  33. 4
      src/Avalonia.Native/ScreenImpl.cs
  34. 2
      src/Avalonia.Native/WindowImplBase.cs
  35. 4
      src/Avalonia.Native/avn.idl
  36. 35
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  37. 36
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  38. 179
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  39. 28
      src/Avalonia.X11/X11Screens.cs
  40. 8
      src/Avalonia.X11/X11Window.cs
  41. 1
      src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
  42. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  43. 34
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs
  44. 25
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  45. 5
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  46. 1
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  47. 2
      src/Shared/StringCompatibilityExtensions.cs
  48. 16
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  49. 28
      src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
  50. 1
      src/Web/Avalonia.Web/Avalonia.Web.csproj
  51. 4
      src/Web/Avalonia.Web/Avalonia.Web.props
  52. 30
      src/Web/Avalonia.Web/Avalonia.Web.targets
  53. 88
      src/Web/Avalonia.Web/AvaloniaView.cs
  54. 11
      src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
  55. 2
      src/Web/Avalonia.Web/Interop/CanvasHelper.cs
  56. 6
      src/Web/Avalonia.Web/Interop/InputHelper.cs
  57. 2
      src/Web/Avalonia.Web/webapp/build.js
  58. 35
      src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts
  59. 6
      src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts
  60. 38
      src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
  61. 25
      src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
  62. 5
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  63. 8
      src/Windows/Avalonia.Win32/TrayIconImpl.cs
  64. 3
      src/Windows/Avalonia.Win32/WinScreen.cs
  65. 2
      src/Windows/Avalonia.Win32/WindowImpl.cs
  66. 1
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  67. 8
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
  68. 52
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  69. 21
      tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
  70. 51
      tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs
  71. 2
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  72. 32
      tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs
  73. 13
      tests/Avalonia.UnitTests/MockGlyphTypeface.cs

1
.github/FUNDING.yml

@ -1 +1,2 @@
github: avaloniaui
open_collective: avalonia

1
Avalonia.sln

@ -41,6 +41,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DE
src\Shared\IsExternalInit.cs = src\Shared\IsExternalInit.cs
src\Shared\ModuleInitializer.cs = src\Shared\ModuleInitializer.cs
src\Shared\SourceGeneratorAttributes.cs = src\Shared\SourceGeneratorAttributes.cs
src\Avalonia.Base\Compatibility\StringCompatibilityExtensions.cs = src\Avalonia.Base\Compatibility\StringCompatibilityExtensions.cs
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI", "src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj", "{6417B24E-49C2-4985-8DB2-3AB9D898EC91}"

2
build/SharedVersion.props

@ -8,7 +8,7 @@
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>CS1591</NoWarn>
<LangVersion>latest</LangVersion>
<LangVersion>preview</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Icon.png</PackageIcon>
<PackageDescription>Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.</PackageDescription>

4
native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -385,7 +385,7 @@
return true;
}
-(void)resignKeyWindow
-(void)windowDidResignKey:(NSNotification *)notification
{
if(_parent)
_parent->BaseEvents->Deactivated();
@ -393,8 +393,6 @@
[self showAppMenuOnly];
[self invalidateShadow];
[super resignKeyWindow];
}
- (void)windowDidMove:(NSNotification *_Nonnull)notification

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

@ -41,9 +41,9 @@ public:
ret->WorkingArea.X = [screen visibleFrame].origin.x;
ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height;
ret->PixelDensity = [screen backingScaleFactor];
ret->Scaling = [screen backingScaleFactor];
ret->Primary = index == 0;
ret->IsPrimary = index == 0;
return S_OK;
}

5
samples/ControlCatalog.Web/ControlCatalog.Web.csproj

@ -16,9 +16,8 @@
<TrimMode>full</TrimMode>
<WasmBuildNative>true</WasmBuildNative>
<InvariantGlobalization>true</InvariantGlobalization>
<WasmEnableSIMD>true</WasmEnableSIMD>
<EmccCompileOptimizationFlag>-O3</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-O3</EmccLinkOptimizationFlag>
<EmccCompileOptimizationFlag>-O2</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-O2</EmccLinkOptimizationFlag>
</PropertyGroup>
<ItemGroup>

1
samples/ControlCatalog.Web/Roots.xml

@ -3,4 +3,5 @@
<assembly fullname="ControlCatalog.Web" preserve="All" />
<assembly fullname="Avalonia.Themes.Fluent" preserve="All" />
<assembly fullname="Avalonia.Themes.Simple" preserve="All" />
<assembly fullname="Avalonia.Controls.ColorPicker" preserve="All" />
</linker>

21
samples/ControlCatalog/Pages/ColorPickerPage.xaml

@ -24,18 +24,19 @@
HsvColor="hsv(120, 1, 1)"
Margin="0,50,0,0">
<ColorPicker.Palette>
<controls:FlatColorPalette />
<controls:FlatHalfColorPalette />
</ColorPicker.Palette>
</ColorPicker>
<Grid Grid.Column="2"
Grid.Row="0"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<ColorSpectrum x:Name="ColorSpectrum1"
Grid.Row="0"
Color="Red"
CornerRadius="10"
Height="256"
Width="256" />
<!-- HSV Sliders -->
<ColorSlider Grid.Row="1"
Margin="0,10,0,0"
ColorComponent="Component1"
@ -53,7 +54,21 @@
ColorComponent="Alpha"
ColorModel="Hsva"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
<ColorPreviewer Grid.Row="5"
<!-- RGB Sliders -->
<!--<ColorSlider Grid.Row="5"
Margin="0,10,0,0"
ColorComponent="Component1"
ColorModel="Rgba"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
<ColorSlider Grid.Row="6"
ColorComponent="Component2"
ColorModel="Rgba"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
<ColorSlider Grid.Row="7"
ColorComponent="Component3"
ColorModel="Rgba"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />-->
<ColorPreviewer Grid.Row="8"
IsAccentColorsVisible="False"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
</Grid>

1
samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs

@ -20,6 +20,7 @@ namespace ControlCatalog.Pages
Color = Colors.Blue,
Margin = new Thickness(0, 50, 0, 0),
HorizontalAlignment = HorizontalAlignment.Center,
Palette = new MaterialHalfColorPalette(),
};
Grid.SetColumn(colorPicker, 2);
Grid.SetRow(colorPicker, 1);

17
samples/ControlCatalog/Pages/ExpanderPage.xaml

@ -32,6 +32,23 @@
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</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>
</StackPanel>
</StackPanel>

4
samples/ControlCatalog/Pages/ScreenPage.cs

@ -62,10 +62,10 @@ namespace ControlCatalog.Pages
CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20));
formattedText = CreateFormattedText($"Scaling: {screen.PixelDensity * 100}%");
formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40));
formattedText = CreateFormattedText($"Primary: {screen.Primary}");
formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60));
formattedText =

2
src/Avalonia.Base/Avalonia.Base.csproj

@ -23,6 +23,7 @@
<Import Project="..\..\build\SourceGenerators.props" />
<ItemGroup>
<Compile Include="..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" />
<Compile Include="..\Shared\StringCompatibilityExtensions.cs" Link="Compatibility\StringCompatibilityExtensions.cs" />
</ItemGroup>
<ItemGroup Label="InternalsVisibleTo">
@ -46,6 +47,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Compatibility\" />
<Folder Include="Rendering\Composition\Utils" />
</ItemGroup>
</Project>

2
src/Avalonia.Base/Input/DragDrop.cs

@ -13,7 +13,7 @@ namespace Avalonia.Input
/// <summary>
/// Event which is raised, when a drag-and-drop operation leaves the element.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> DragLeaveEvent = RoutedEvent.Register<RoutedEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop));
public static readonly RoutedEvent<DragEventArgs> DragLeaveEvent = RoutedEvent.Register<DragEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation is updated while over the element.
/// </summary>

8
src/Avalonia.Base/Input/DragDropDevice.cs

@ -54,7 +54,7 @@ namespace Avalonia.Input
try
{
if (_lastTarget != null)
_lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent));
RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragLeaveEvent, effects, data, modifiers);
return RaiseDragEvent(target, inputRoot, point, DragDrop.DragEnterEvent, effects, data, modifiers);
}
finally
@ -63,13 +63,13 @@ namespace Avalonia.Input
}
}
private void DragLeave(IInputElement inputRoot)
private void DragLeave(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
{
if (_lastTarget == null)
return;
try
{
_lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent));
RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragLeaveEvent, effects, data, modifiers);
}
finally
{
@ -106,7 +106,7 @@ namespace Avalonia.Input
e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break;
case RawDragEventType.DragLeave:
DragLeave(e.Root);
DragLeave(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break;
case RawDragEventType.Drop:
e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);

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>
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>
/// Returns an glyph index for the specified codepoint.
/// </summary>

4
src/Avalonia.Base/Media/PathMarkupParser.cs

@ -188,7 +188,7 @@ namespace Avalonia.Media
_isOpen = true;
}
private void SetFillRule(ref ReadOnlySpan<char> span)
private void SetFillRule(scoped ref ReadOnlySpan<char> span)
{
ThrowIfDisposed();
@ -452,7 +452,7 @@ namespace Avalonia.Media
return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || span[0] == '.' || char.IsDigit(span[0]));
}
private static bool ReadArgument(ref ReadOnlySpan<char> remaining, out ReadOnlySpan<char> argument)
private static bool ReadArgument(scoped ref ReadOnlySpan<char> remaining, out ReadOnlySpan<char> argument)
{
remaining = SkipWhitespace(remaining);
if (remaining.IsEmpty)

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

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

28
src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs

@ -14,33 +14,5 @@ namespace Avalonia.Controls
public ColorPicker() : base()
{
}
/// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
// Until this point the ColorPicker itself is responsible to process property updates.
// This, for example, syncs Color with HsvColor and updates primitive controls.
//
// However, when the template is created, hand-off this change processing to the
// ColorView within the control template itself. Remember ColorPicker derives from
// ColorView so we don't want two instances of the same logic fighting each other.
// It is best to hand-off to the ColorView in the control template because that is the
// primary point of user-interaction for the overall control. It also simplifies binding.
//
// Keep in mind this hand-off is not possible until the template controls are created
// which is done after the ColorPicker is instantiated. The ColorPicker must still
// process updates before the template is applied to ensure all property changes in
// XAML or object initializers are handled correctly. Otherwise, there can be bugs
// such as setting the Color property doesn't work because the HsvColor is never updated
// and then the Color value is lost once the template loads (and the template ColorView
// takes over).
//
// In order to complete this hand-off, completely ignore property changes here in the
// ColorPicker. This means the ColorView in the control template is now responsible to
// process property changes and handle primary calculations.
base.ignorePropertyChanged = true;
}
}
}

2
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs

@ -240,7 +240,7 @@ namespace Avalonia.Controls.Primitives
public ColorComponent ThirdComponent
{
get => GetValue(ThirdComponentProperty);
private set => SetValue(ThirdComponentProperty, value);
protected set => SetValue(ThirdComponentProperty, value);
}
}
}

484
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml

@ -1,8 +1,15 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls"
xmlns:converters="using:Avalonia.Controls.Converters"
xmlns:primitives="using:Avalonia.Controls.Primitives"
xmlns:pc="clr-namespace:Avalonia.Controls.Primitives.Converters;assembly=Avalonia.Controls.ColorPicker"
x:CompileBindings="True">
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml" />
</ResourceDictionary.MergedDictionaries>
<ControlTheme x:Key="{x:Type ColorPicker}"
TargetType="ColorPicker">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
@ -43,39 +50,454 @@
</DropDownButton.Content>
<DropDownButton.Flyout>
<Flyout FlyoutPresenterClasses="nopadding">
<ColorView x:Name="FlyoutColorView"
Color="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ColorModel="{Binding ColorModel, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ColorSpectrumComponents="{TemplateBinding ColorSpectrumComponents}"
ColorSpectrumShape="{TemplateBinding ColorSpectrumShape}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
IsAccentColorsVisible="{TemplateBinding IsAccentColorsVisible}"
IsAlphaEnabled="{TemplateBinding IsAlphaEnabled}"
IsAlphaVisible="{TemplateBinding IsAlphaVisible}"
IsColorComponentsVisible="{TemplateBinding IsColorComponentsVisible}"
IsColorModelVisible="{TemplateBinding IsColorModelVisible}"
IsColorPaletteVisible="{TemplateBinding IsColorPaletteVisible}"
IsColorPreviewVisible="{TemplateBinding IsColorPreviewVisible}"
IsColorSpectrumVisible="{TemplateBinding IsColorSpectrumVisible}"
IsColorSpectrumSliderVisible="{TemplateBinding IsColorSpectrumSliderVisible}"
IsComponentSliderVisible="{TemplateBinding IsComponentSliderVisible}"
IsComponentTextInputVisible="{TemplateBinding IsComponentTextInputVisible}"
IsHexInputVisible="{TemplateBinding IsHexInputVisible}"
MaxHue="{TemplateBinding MaxHue}"
MaxSaturation="{TemplateBinding MaxSaturation}"
MaxValue="{TemplateBinding MaxValue}"
MinHue="{TemplateBinding MinHue}"
MinSaturation="{TemplateBinding MinSaturation}"
MinValue="{TemplateBinding MinValue}"
PaletteColors="{TemplateBinding PaletteColors}"
PaletteColumnCount="{TemplateBinding PaletteColumnCount}"
Palette="{TemplateBinding Palette}"
SelectedIndex="{Binding SelectedIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<ColorView.Resources>
<!-- The following is copy-pasted from the ColorView's control template.
It MUST always be kept in sync with the ColorView (which is master).
Note the only changes are resources specific to the ColorPicker. -->
<Grid RowDefinitions="Auto,Auto">
<Grid.Resources>
<!-- This radius must follow OverlayCornerRadius -->
<CornerRadius x:Key="ColorViewTabBackgroundCornerRadius">5,5,0,0</CornerRadius>
</ColorView.Resources>
</ColorView>
</Grid.Resources>
<!-- Backgrounds -->
<!-- TODO: Background="{DynamicResource ColorViewTabBackgroundBrush}" -->
<Border x:Name="TabBackgroundBorder"
Grid.Row="0"
Grid.RowSpan="2"
Height="48"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"
BorderBrush="{DynamicResource ColorViewTabBorderBrush}"
CornerRadius="{DynamicResource ColorViewTabBackgroundCornerRadius}" />
<Border x:Name="ContentBackgroundBorder"
Grid.Row="0"
Grid.RowSpan="2"
Margin="0,48,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource BottomCornerRadiusFilterConverter}}"
Background="{DynamicResource ColorViewContentBackgroundBrush}"
BorderBrush="{DynamicResource ColorViewContentBorderBrush}"
BorderThickness="0,1,0,0" />
<TabControl x:Name="PART_TabControl"
Grid.Row="0"
Height="338"
Width="350"
Padding="0"
SelectedIndex="{Binding SelectedIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<TabControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="0"
Rows="1" />
</ItemsPanelTemplate>
</TabControl.ItemsPanel>
<!-- Spectrum Tab -->
<TabItem Theme="{StaticResource ColorViewTabItemTheme}"
IsVisible="{TemplateBinding IsColorSpectrumVisible}">
<TabItem.Header>
<Border Height="{DynamicResource ColorViewTabStripHeight}">
<PathIcon Width="20"
Height="20"
Data="{DynamicResource ColorViewSpectrumIconGeometry}" />
</Border>
</TabItem.Header>
<Grid RowDefinitions="*"
Margin="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
MinWidth="32" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"
MinWidth="32" />
</Grid.ColumnDefinitions>
<primitives:ColorSlider x:Name="ColorSpectrumThirdComponentSlider"
AutomationProperties.Name="Third Component"
Grid.Column="0"
IsAlphaMaxForced="True"
IsSaturationValueMaxForced="False"
Orientation="Vertical"
ColorModel="Hsva"
ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Margin="0,0,12,0"
IsVisible="{TemplateBinding IsColorSpectrumSliderVisible}" />
<primitives:ColorSpectrum x:Name="ColorSpectrum"
Grid.Column="1"
Components="{TemplateBinding ColorSpectrumComponents}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
MinHue="{TemplateBinding MinHue}"
MaxHue="{TemplateBinding MaxHue}"
MinSaturation="{TemplateBinding MinSaturation}"
MaxSaturation="{TemplateBinding MaxSaturation}"
MinValue="{TemplateBinding MinValue}"
MaxValue="{TemplateBinding MaxValue}"
Shape="{TemplateBinding ColorSpectrumShape}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
<primitives:ColorSlider x:Name="ColorSpectrumAlphaSlider"
AutomationProperties.Name="Alpha Component"
Grid.Column="2"
Orientation="Vertical"
ColorModel="Hsva"
ColorComponent="Alpha"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Margin="12,0,0,0"
IsEnabled="{TemplateBinding IsAlphaEnabled}">
<primitives:ColorSlider.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsAlphaVisible" />
</MultiBinding>
</primitives:ColorSlider.IsVisible>
</primitives:ColorSlider>
</Grid>
</TabItem>
<!-- Palette Tab -->
<TabItem Theme="{StaticResource ColorViewTabItemTheme}"
IsVisible="{TemplateBinding IsColorPaletteVisible}">
<TabItem.Header>
<Border Height="{DynamicResource ColorViewTabStripHeight}">
<PathIcon Width="20"
Height="20"
Data="{DynamicResource ColorViewPaletteIconGeometry}" />
</Border>
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Color}">
<Border AutomationProperties.Name="{Binding Converter={StaticResource ColorToDisplayNameConverter}}"
ToolTip.Tip="{Binding Converter={StaticResource ColorToDisplayNameConverter}}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding $parent[ColorView].PaletteColumnCount}" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</TabItem>
<!-- Components Tab -->
<TabItem Theme="{StaticResource ColorViewTabItemTheme}"
IsVisible="{TemplateBinding IsColorComponentsVisible}">
<TabItem.Header>
<Border Height="{DynamicResource ColorViewTabStripHeight}">
<PathIcon Width="20"
Height="20"
Data="{DynamicResource ColorViewComponentsIconGeometry}" />
</Border>
</TabItem.Header>
<Grid ColumnDefinitions="Auto,Auto,*"
RowDefinitions="Auto,24,1*,1*,1*,1*,12"
Margin="12">
<!-- Top color model & Hex input -->
<Grid Grid.Column="0"
Grid.ColumnSpan="3"
Grid.Row="0"
ColumnDefinitions="1*,12,1*">
<!-- Content RGB/HSV names are hard-coded and considered universal -->
<!-- RadioButtons are styled to look like a 'SegmentedControl' or 'ButtonGroup' -->
<Grid ColumnDefinitions="1*,1*"
IsVisible="{TemplateBinding IsColorModelVisible}">
<RadioButton x:Name="RgbRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
Grid.Column="0"
Content="RGB"
CornerRadius="4,0,0,4"
BorderThickness="1,1,0,1"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
<RadioButton x:Name="HsvRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
Grid.Column="1"
Content="HSV"
CornerRadius="0,4,4,0"
BorderThickness="0,1,1,1"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
</Grid>
<Grid x:Name="HexInputGrid"
Grid.Column="2"
IsVisible="{TemplateBinding IsHexInputVisible}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
Height="32"
Background="{DynamicResource TextControlBackgroundDisabled}"
BorderBrush="{DynamicResource TextControlBorderBrush}"
BorderThickness="1,1,0,1"
CornerRadius="4,0,0,4">
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="#"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<!-- Color updated in code-behind -->
<TextBox x:Name="PART_HexTextBox"
Grid.Column="1"
AutomationProperties.Name="Hexadecimal Color"
Height="32"
MaxLength="8"
HorizontalAlignment="Stretch"
CornerRadius="0,4,4,0" />
</Grid>
</Grid>
<!-- Color component editing controls -->
<!-- Component 1 RGB:Red HSV:Hue -->
<Border Grid.Column="0"
Grid.Row="2"
Height="{Binding ElementName=Component1NumericUpDown, Path=Bounds.Height}"
Width="{DynamicResource ColorViewComponentLabelWidth}"
Background="{DynamicResource TextControlBackgroundDisabled}"
BorderBrush="{DynamicResource TextControlBorderBrush}"
BorderThickness="1,1,0,1"
CornerRadius="4,0,0,4"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentTextInputVisible}">
<Panel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="R"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=OneWay}" />
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="H"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=OneWay}" />
</Panel>
</Border>
<NumericUpDown x:Name="Component1NumericUpDown"
Grid.Column="1"
Grid.Row="2"
AllowSpin="True"
ShowButtonSpinner="False"
Height="32"
Width="{DynamicResource ColorViewComponentTextInputWidth}"
CornerRadius="0,4,4,0"
Margin="0,0,12,0"
VerticalAlignment="Center"
NumberFormat="{StaticResource ColorViewComponentNumberFormat}"
Minimum="{Binding Minimum, ElementName=Component1Slider}"
Maximum="{Binding Maximum, ElementName=Component1Slider}"
Value="{Binding Value, ElementName=Component1Slider}"
IsVisible="{TemplateBinding IsComponentTextInputVisible}" />
<primitives:ColorSlider x:Name="Component1Slider"
Grid.Column="2"
Grid.Row="2"
Orientation="Horizontal"
IsRoundingEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="1"
ColorComponent="Component1"
ColorModel="{TemplateBinding ColorModel, Mode=OneWay}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentSliderVisible}" />
<!-- Component 2 RGB:Green HSV:Saturation -->
<Border Grid.Column="0"
Grid.Row="3"
Width="{DynamicResource ColorViewComponentLabelWidth}"
Height="{Binding ElementName=Component2NumericUpDown, Path=Bounds.Height}"
Background="{DynamicResource TextControlBackgroundDisabled}"
BorderBrush="{DynamicResource TextControlBorderBrush}"
BorderThickness="1,1,0,1"
CornerRadius="4,0,0,4"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentTextInputVisible}">
<Panel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="G"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=OneWay}" />
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="S"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=OneWay}" />
</Panel>
</Border>
<NumericUpDown x:Name="Component2NumericUpDown"
Grid.Column="1"
Grid.Row="3"
AllowSpin="True"
ShowButtonSpinner="False"
Height="32"
Width="{DynamicResource ColorViewComponentTextInputWidth}"
CornerRadius="0,4,4,0"
Margin="0,0,12,0"
VerticalAlignment="Center"
NumberFormat="{StaticResource ColorViewComponentNumberFormat}"
Minimum="{Binding Minimum, ElementName=Component2Slider}"
Maximum="{Binding Maximum, ElementName=Component2Slider}"
Value="{Binding Value, ElementName=Component2Slider}"
IsVisible="{TemplateBinding IsComponentTextInputVisible}" />
<primitives:ColorSlider x:Name="Component2Slider"
Grid.Column="2"
Grid.Row="3"
Orientation="Horizontal"
IsRoundingEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="1"
ColorComponent="Component2"
ColorModel="{TemplateBinding ColorModel, Mode=OneWay}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentSliderVisible}" />
<!-- Component 3 RGB:Blue HSV:Value -->
<Border Grid.Column="0"
Grid.Row="4"
Width="{DynamicResource ColorViewComponentLabelWidth}"
Height="{Binding ElementName=Component3NumericUpDown, Path=Bounds.Height}"
Background="{DynamicResource TextControlBackgroundDisabled}"
BorderBrush="{DynamicResource TextControlBorderBrush}"
BorderThickness="1,1,0,1"
CornerRadius="4,0,0,4"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentTextInputVisible}">
<Panel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="B"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=OneWay}" />
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="V"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=OneWay}" />
</Panel>
</Border>
<NumericUpDown x:Name="Component3NumericUpDown"
Grid.Column="1"
Grid.Row="4"
AllowSpin="True"
ShowButtonSpinner="False"
Height="32"
Width="{DynamicResource ColorViewComponentTextInputWidth}"
CornerRadius="0,4,4,0"
Margin="0,0,12,0"
VerticalAlignment="Center"
NumberFormat="{StaticResource ColorViewComponentNumberFormat}"
Minimum="{Binding Minimum, ElementName=Component3Slider}"
Maximum="{Binding Maximum, ElementName=Component3Slider}"
Value="{Binding Value, ElementName=Component3Slider}"
IsVisible="{TemplateBinding IsComponentTextInputVisible}" />
<primitives:ColorSlider x:Name="Component3Slider"
Grid.Column="2"
Grid.Row="4"
Orientation="Horizontal"
IsRoundingEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="1"
ColorComponent="Component3"
ColorModel="{TemplateBinding ColorModel, Mode=OneWay}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentSliderVisible}" />
<!-- Alpha Component -->
<Border Grid.Column="0"
Grid.Row="5"
Width="{DynamicResource ColorViewComponentLabelWidth}"
Height="{Binding ElementName=AlphaComponentNumericUpDown, Path=Bounds.Height}"
Background="{DynamicResource TextControlBackgroundDisabled}"
BorderBrush="{DynamicResource TextControlBorderBrush}"
BorderThickness="1,1,0,1"
CornerRadius="4,0,0,4"
VerticalAlignment="Center"
IsEnabled="{TemplateBinding IsAlphaEnabled}">
<TextBlock x:Name="AlphaComponentTextBlock"
Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="A"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<Border.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsAlphaVisible" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsComponentTextInputVisible" />
</MultiBinding>
</Border.IsVisible>
</Border>
<NumericUpDown x:Name="AlphaComponentNumericUpDown"
Grid.Column="1"
Grid.Row="5"
AllowSpin="True"
ShowButtonSpinner="False"
Height="32"
Width="{DynamicResource ColorViewComponentTextInputWidth}"
CornerRadius="0,4,4,0"
Margin="0,0,12,0"
VerticalAlignment="Center"
NumberFormat="{StaticResource ColorViewComponentNumberFormat}"
Minimum="{Binding Minimum, ElementName=AlphaComponentSlider}"
Maximum="{Binding Maximum, ElementName=AlphaComponentSlider}"
Value="{Binding Value, ElementName=AlphaComponentSlider}"
IsEnabled="{TemplateBinding IsAlphaEnabled}">
<NumericUpDown.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsAlphaVisible" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsComponentTextInputVisible" />
</MultiBinding>
</NumericUpDown.IsVisible>
</NumericUpDown>
<primitives:ColorSlider x:Name="AlphaComponentSlider"
Grid.Column="2"
Grid.Row="5"
Orientation="Horizontal"
IsRoundingEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="1"
ColorComponent="Alpha"
ColorModel="{TemplateBinding ColorModel, Mode=OneWay}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsEnabled="{TemplateBinding IsAlphaEnabled}">
<primitives:ColorSlider.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsAlphaVisible" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsComponentSliderVisible" />
</MultiBinding>
</primitives:ColorSlider.IsVisible>
</primitives:ColorSlider>
</Grid>
</TabItem>
</TabControl>
<!-- Previewer -->
<!-- Note that top/bottom margins have -5 to remove for drop shadow padding -->
<primitives:ColorPreviewer Grid.Row="1"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
IsAccentColorsVisible="{TemplateBinding IsAccentColorsVisible}"
Margin="12,-5,12,7"
IsVisible="{TemplateBinding IsColorPreviewVisible}" />
</Grid>
</Flyout>
</DropDownButton.Flyout>
</DropDownButton>

486
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml

@ -1,8 +1,15 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls"
xmlns:converters="using:Avalonia.Controls.Converters"
xmlns:primitives="using:Avalonia.Controls.Primitives"
xmlns:pc="clr-namespace:Avalonia.Controls.Primitives.Converters;assembly=Avalonia.Controls.ColorPicker"
x:CompileBindings="True">
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml" />
</ResourceDictionary.MergedDictionaries>
<ControlTheme x:Key="{x:Type ColorPicker}"
TargetType="ColorPicker">
<Setter Property="CornerRadius" Value="0" />
@ -42,40 +49,455 @@
</Panel>
</DropDownButton.Content>
<DropDownButton.Flyout>
<Flyout FlyoutPresenterClasses="nopadding">
<ColorView x:Name="FlyoutColorView"
Color="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ColorModel="{Binding ColorModel, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ColorSpectrumComponents="{TemplateBinding ColorSpectrumComponents}"
ColorSpectrumShape="{TemplateBinding ColorSpectrumShape}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
IsAccentColorsVisible="{TemplateBinding IsAccentColorsVisible}"
IsAlphaEnabled="{TemplateBinding IsAlphaEnabled}"
IsAlphaVisible="{TemplateBinding IsAlphaVisible}"
IsColorComponentsVisible="{TemplateBinding IsColorComponentsVisible}"
IsColorModelVisible="{TemplateBinding IsColorModelVisible}"
IsColorPaletteVisible="{TemplateBinding IsColorPaletteVisible}"
IsColorPreviewVisible="{TemplateBinding IsColorPreviewVisible}"
IsColorSpectrumVisible="{TemplateBinding IsColorSpectrumVisible}"
IsColorSpectrumSliderVisible="{TemplateBinding IsColorSpectrumSliderVisible}"
IsComponentSliderVisible="{TemplateBinding IsComponentSliderVisible}"
IsComponentTextInputVisible="{TemplateBinding IsComponentTextInputVisible}"
IsHexInputVisible="{TemplateBinding IsHexInputVisible}"
MaxHue="{TemplateBinding MaxHue}"
MaxSaturation="{TemplateBinding MaxSaturation}"
MaxValue="{TemplateBinding MaxValue}"
MinHue="{TemplateBinding MinHue}"
MinSaturation="{TemplateBinding MinSaturation}"
MinValue="{TemplateBinding MinValue}"
PaletteColors="{TemplateBinding PaletteColors}"
PaletteColumnCount="{TemplateBinding PaletteColumnCount}"
Palette="{TemplateBinding Palette}"
SelectedIndex="{Binding SelectedIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<ColorView.Resources>
<Flyout>
<!-- The following is copy-pasted from the ColorView's control template.
It MUST always be kept in sync with the ColorView (which is master).
Note the only changes are resources specific to the ColorPicker. -->
<Grid RowDefinitions="Auto,Auto">
<Grid.Resources>
<!-- This radius must follow OverlayCornerRadius -->
<CornerRadius x:Key="ColorViewTabBackgroundCornerRadius">0,0,0,0</CornerRadius>
</ColorView.Resources>
</ColorView>
</Grid.Resources>
<!-- Backgrounds -->
<!-- TODO: Background="{DynamicResource ColorViewTabBackgroundBrush}" -->
<Border x:Name="TabBackgroundBorder"
Grid.Row="0"
Grid.RowSpan="2"
Height="48"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"
BorderBrush="{DynamicResource ColorViewTabBorderBrush}"
CornerRadius="{DynamicResource ColorViewTabBackgroundCornerRadius}" />
<Border x:Name="ContentBackgroundBorder"
Grid.Row="0"
Grid.RowSpan="2"
Margin="0,48,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource BottomCornerRadiusFilterConverter}}"
Background="{DynamicResource ColorViewContentBackgroundBrush}"
BorderBrush="{DynamicResource ColorViewContentBorderBrush}"
BorderThickness="0,1,0,0" />
<TabControl x:Name="PART_TabControl"
Grid.Row="0"
Height="338"
Width="350"
Padding="0"
SelectedIndex="{Binding SelectedIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<TabControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="0"
Rows="1" />
</ItemsPanelTemplate>
</TabControl.ItemsPanel>
<!-- Spectrum Tab -->
<TabItem Theme="{StaticResource ColorViewTabItemTheme}"
IsVisible="{TemplateBinding IsColorSpectrumVisible}">
<TabItem.Header>
<Border Height="{DynamicResource ColorViewTabStripHeight}">
<PathIcon Width="20"
Height="20"
Data="{DynamicResource ColorViewSpectrumIconGeometry}" />
</Border>
</TabItem.Header>
<Grid RowDefinitions="*"
Margin="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
MinWidth="32" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"
MinWidth="32" />
</Grid.ColumnDefinitions>
<primitives:ColorSlider x:Name="ColorSpectrumThirdComponentSlider"
AutomationProperties.Name="Third Component"
Grid.Column="0"
IsAlphaMaxForced="True"
IsSaturationValueMaxForced="False"
Orientation="Vertical"
ColorModel="Hsva"
ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Margin="0,0,12,0"
IsVisible="{TemplateBinding IsColorSpectrumSliderVisible}" />
<primitives:ColorSpectrum x:Name="ColorSpectrum"
Grid.Column="1"
Components="{TemplateBinding ColorSpectrumComponents}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
MinHue="{TemplateBinding MinHue}"
MaxHue="{TemplateBinding MaxHue}"
MinSaturation="{TemplateBinding MinSaturation}"
MaxSaturation="{TemplateBinding MaxSaturation}"
MinValue="{TemplateBinding MinValue}"
MaxValue="{TemplateBinding MaxValue}"
Shape="{TemplateBinding ColorSpectrumShape}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
<primitives:ColorSlider x:Name="ColorSpectrumAlphaSlider"
AutomationProperties.Name="Alpha Component"
Grid.Column="2"
Orientation="Vertical"
ColorModel="Hsva"
ColorComponent="Alpha"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Margin="12,0,0,0"
IsEnabled="{TemplateBinding IsAlphaEnabled}">
<primitives:ColorSlider.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsAlphaVisible" />
</MultiBinding>
</primitives:ColorSlider.IsVisible>
</primitives:ColorSlider>
</Grid>
</TabItem>
<!-- Palette Tab -->
<TabItem Theme="{StaticResource ColorViewTabItemTheme}"
IsVisible="{TemplateBinding IsColorPaletteVisible}">
<TabItem.Header>
<Border Height="{DynamicResource ColorViewTabStripHeight}">
<PathIcon Width="20"
Height="20"
Data="{DynamicResource ColorViewPaletteIconGeometry}" />
</Border>
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Color}">
<Border AutomationProperties.Name="{Binding Converter={StaticResource ColorToDisplayNameConverter}}"
ToolTip.Tip="{Binding Converter={StaticResource ColorToDisplayNameConverter}}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding $parent[ColorView].PaletteColumnCount}" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</TabItem>
<!-- Components Tab -->
<TabItem Theme="{StaticResource ColorViewTabItemTheme}"
IsVisible="{TemplateBinding IsColorComponentsVisible}">
<TabItem.Header>
<Border Height="{DynamicResource ColorViewTabStripHeight}">
<PathIcon Width="20"
Height="20"
Data="{DynamicResource ColorViewComponentsIconGeometry}" />
</Border>
</TabItem.Header>
<Grid ColumnDefinitions="Auto,Auto,*"
RowDefinitions="Auto,24,1*,1*,1*,1*,12"
Margin="12">
<!-- Top color model & Hex input -->
<Grid Grid.Column="0"
Grid.ColumnSpan="3"
Grid.Row="0"
ColumnDefinitions="1*,12,1*">
<!-- Content RGB/HSV names are hard-coded and considered universal -->
<!-- RadioButtons are styled to look like a 'SegmentedControl' or 'ButtonGroup' -->
<Grid ColumnDefinitions="1*,1*"
IsVisible="{TemplateBinding IsColorModelVisible}">
<RadioButton x:Name="RgbRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
Grid.Column="0"
Content="RGB"
CornerRadius="0,0,0,0"
BorderThickness="1,1,0,1"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
<RadioButton x:Name="HsvRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
Grid.Column="1"
Content="HSV"
CornerRadius="0,0,0,0"
BorderThickness="0,1,1,1"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
</Grid>
<Grid x:Name="HexInputGrid"
Grid.Column="2"
IsVisible="{TemplateBinding IsHexInputVisible}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
Height="32"
Background="{DynamicResource ThemeControlMidBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1,1,0,1"
CornerRadius="0,0,0,0">
<TextBlock Foreground="{DynamicResource ThemeForegroundBrush}"
FontWeight="SemiBold"
Text="#"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<!-- Color updated in code-behind -->
<TextBox x:Name="PART_HexTextBox"
Grid.Column="1"
AutomationProperties.Name="Hexadecimal Color"
Height="32"
MaxLength="8"
HorizontalAlignment="Stretch"
CornerRadius="0,0,0,0" />
</Grid>
</Grid>
<!-- Color component editing controls -->
<!-- Component 1 RGB:Red HSV:Hue -->
<Border Grid.Column="0"
Grid.Row="2"
Height="{Binding ElementName=Component1NumericUpDown, Path=Bounds.Height}"
Width="{DynamicResource ColorViewComponentLabelWidth}"
Background="{DynamicResource ThemeControlMidBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1,1,0,1"
CornerRadius="0,0,0,0"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentTextInputVisible}">
<Panel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Foreground="{DynamicResource ThemeForegroundBrush}"
FontWeight="SemiBold"
Text="R"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=OneWay}" />
<TextBlock Foreground="{DynamicResource ThemeForegroundBrush}"
FontWeight="SemiBold"
Text="H"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=OneWay}" />
</Panel>
</Border>
<NumericUpDown x:Name="Component1NumericUpDown"
Grid.Column="1"
Grid.Row="2"
AllowSpin="True"
ShowButtonSpinner="False"
Height="32"
Width="{DynamicResource ColorViewComponentTextInputWidth}"
CornerRadius="0,0,0,0"
Margin="0,0,12,0"
VerticalAlignment="Center"
NumberFormat="{StaticResource ColorViewComponentNumberFormat}"
Minimum="{Binding Minimum, ElementName=Component1Slider}"
Maximum="{Binding Maximum, ElementName=Component1Slider}"
Value="{Binding Value, ElementName=Component1Slider}"
IsVisible="{TemplateBinding IsComponentTextInputVisible}" />
<primitives:ColorSlider x:Name="Component1Slider"
Grid.Column="2"
Grid.Row="2"
Orientation="Horizontal"
IsRoundingEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="1"
ColorComponent="Component1"
ColorModel="{TemplateBinding ColorModel, Mode=OneWay}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentSliderVisible}" />
<!-- Component 2 RGB:Green HSV:Saturation -->
<Border Grid.Column="0"
Grid.Row="3"
Width="{DynamicResource ColorViewComponentLabelWidth}"
Height="{Binding ElementName=Component2NumericUpDown, Path=Bounds.Height}"
Background="{DynamicResource ThemeControlMidBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1,1,0,1"
CornerRadius="0,0,0,0"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentTextInputVisible}">
<Panel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Foreground="{DynamicResource ThemeForegroundBrush}"
FontWeight="SemiBold"
Text="G"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=OneWay}" />
<TextBlock Foreground="{DynamicResource ThemeForegroundBrush}"
FontWeight="SemiBold"
Text="S"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=OneWay}" />
</Panel>
</Border>
<NumericUpDown x:Name="Component2NumericUpDown"
Grid.Column="1"
Grid.Row="3"
AllowSpin="True"
ShowButtonSpinner="False"
Height="32"
Width="{DynamicResource ColorViewComponentTextInputWidth}"
CornerRadius="0,0,0,0"
Margin="0,0,12,0"
VerticalAlignment="Center"
NumberFormat="{StaticResource ColorViewComponentNumberFormat}"
Minimum="{Binding Minimum, ElementName=Component2Slider}"
Maximum="{Binding Maximum, ElementName=Component2Slider}"
Value="{Binding Value, ElementName=Component2Slider}"
IsVisible="{TemplateBinding IsComponentTextInputVisible}" />
<primitives:ColorSlider x:Name="Component2Slider"
Grid.Column="2"
Grid.Row="3"
Orientation="Horizontal"
IsRoundingEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="1"
ColorComponent="Component2"
ColorModel="{TemplateBinding ColorModel, Mode=OneWay}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentSliderVisible}" />
<!-- Component 3 RGB:Blue HSV:Value -->
<Border Grid.Column="0"
Grid.Row="4"
Width="{DynamicResource ColorViewComponentLabelWidth}"
Height="{Binding ElementName=Component3NumericUpDown, Path=Bounds.Height}"
Background="{DynamicResource ThemeControlMidBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1,1,0,1"
CornerRadius="0,0,0,0"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentTextInputVisible}">
<Panel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Foreground="{DynamicResource ThemeForegroundBrush}"
FontWeight="SemiBold"
Text="B"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=OneWay}" />
<TextBlock Foreground="{DynamicResource ThemeForegroundBrush}"
FontWeight="SemiBold"
Text="V"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=OneWay}" />
</Panel>
</Border>
<NumericUpDown x:Name="Component3NumericUpDown"
Grid.Column="1"
Grid.Row="4"
AllowSpin="True"
ShowButtonSpinner="False"
Height="32"
Width="{DynamicResource ColorViewComponentTextInputWidth}"
CornerRadius="0,0,0,0"
Margin="0,0,12,0"
VerticalAlignment="Center"
NumberFormat="{StaticResource ColorViewComponentNumberFormat}"
Minimum="{Binding Minimum, ElementName=Component3Slider}"
Maximum="{Binding Maximum, ElementName=Component3Slider}"
Value="{Binding Value, ElementName=Component3Slider}"
IsVisible="{TemplateBinding IsComponentTextInputVisible}" />
<primitives:ColorSlider x:Name="Component3Slider"
Grid.Column="2"
Grid.Row="4"
Orientation="Horizontal"
IsRoundingEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="1"
ColorComponent="Component3"
ColorModel="{TemplateBinding ColorModel, Mode=OneWay}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentSliderVisible}" />
<!-- Alpha Component -->
<Border Grid.Column="0"
Grid.Row="5"
Width="{DynamicResource ColorViewComponentLabelWidth}"
Height="{Binding ElementName=AlphaComponentNumericUpDown, Path=Bounds.Height}"
Background="{DynamicResource ThemeControlMidBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1,1,0,1"
CornerRadius="0,0,0,0"
VerticalAlignment="Center"
IsEnabled="{TemplateBinding IsAlphaEnabled}">
<TextBlock x:Name="AlphaComponentTextBlock"
Foreground="{DynamicResource ThemeForegroundBrush}"
FontWeight="SemiBold"
Text="A"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<Border.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsAlphaVisible" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsComponentTextInputVisible" />
</MultiBinding>
</Border.IsVisible>
</Border>
<NumericUpDown x:Name="AlphaComponentNumericUpDown"
Grid.Column="1"
Grid.Row="5"
AllowSpin="True"
ShowButtonSpinner="False"
Height="32"
Width="{DynamicResource ColorViewComponentTextInputWidth}"
CornerRadius="0,0,0,0"
Margin="0,0,12,0"
VerticalAlignment="Center"
NumberFormat="{StaticResource ColorViewComponentNumberFormat}"
Minimum="{Binding Minimum, ElementName=AlphaComponentSlider}"
Maximum="{Binding Maximum, ElementName=AlphaComponentSlider}"
Value="{Binding Value, ElementName=AlphaComponentSlider}"
IsEnabled="{TemplateBinding IsAlphaEnabled}">
<NumericUpDown.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsAlphaVisible" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsComponentTextInputVisible" />
</MultiBinding>
</NumericUpDown.IsVisible>
</NumericUpDown>
<primitives:ColorSlider x:Name="AlphaComponentSlider"
Grid.Column="2"
Grid.Row="5"
Orientation="Horizontal"
IsRoundingEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="1"
ColorComponent="Alpha"
ColorModel="{TemplateBinding ColorModel, Mode=OneWay}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsEnabled="{TemplateBinding IsAlphaEnabled}">
<primitives:ColorSlider.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsAlphaVisible" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsComponentSliderVisible" />
</MultiBinding>
</primitives:ColorSlider.IsVisible>
</primitives:ColorSlider>
</Grid>
</TabItem>
</TabControl>
<!-- Previewer -->
<!-- Note that top/bottom margins have -5 to remove for drop shadow padding -->
<primitives:ColorPreviewer Grid.Row="1"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
IsAccentColorsVisible="{TemplateBinding IsAccentColorsVisible}"
Margin="12,-5,12,7"
IsVisible="{TemplateBinding IsColorPreviewVisible}" />
</Grid>
</Flyout>
</DropDownButton.Flyout>
</DropDownButton>

3
src/Avalonia.Controls/ListBox.cs

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

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

@ -6,8 +6,14 @@ namespace Avalonia.Platform
[Unstable]
public interface IScreenImpl
{
/// <summary>
/// Gets the total number of screens available on the device.
/// </summary>
int ScreenCount { get; }
/// <summary>
/// Gets the list of all screens available on the device.
/// </summary>
IReadOnlyList<Screen> AllScreens { get; }
Screen? ScreenFromWindow(IWindowBaseImpl window);

57
src/Avalonia.Controls/Platform/Screen.cs

@ -1,21 +1,64 @@
namespace Avalonia.Platform
using System;
namespace Avalonia.Platform
{
/// <summary>
/// Represents a single display screen.
/// </summary>
public class Screen
{
public double PixelDensity { get; }
/// <summary>
/// Gets the scaling factor applied to the screen by the operating system.
/// </summary>
/// <remarks>
/// Multiply this value by 100 to get a percentage.
/// Both X and Y scaling factors are assumed uniform.
/// </remarks>
public double Scaling { get; }
/// <inheritdoc cref="Scaling"/>
[Obsolete("Use the Scaling property instead.")]
public double PixelDensity => Scaling;
/// <summary>
/// Gets the overall pixel-size of the screen.
/// </summary>
/// <remarks>
/// This generally is the raw pixel counts in both the X and Y direction.
/// </remarks>
public PixelRect Bounds { get; }
/// <summary>
/// Gets the actual working-area pixel-size of the screen.
/// </summary>
/// <remarks>
/// This area may be smaller than <see href="Bounds"/> to account for notches and
/// other block-out areas such as taskbars etc.
/// </remarks>
public PixelRect WorkingArea { get; }
public bool Primary { get; }
public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary)
/// <summary>
/// Gets a value indicating whether the screen is the primary one.
/// </summary>
public bool IsPrimary { get; }
/// <inheritdoc cref="IsPrimary"/>
[Obsolete("Use the IsPrimary property instead.")]
public bool Primary => IsPrimary;
/// <summary>
/// Initializes a new instance of the <see cref="Screen"/> class.
/// </summary>
/// <param name="scaling">The scaling factor applied to the screen by the operating system.</param>
/// <param name="bounds">The overall pixel-size of the screen.</param>
/// <param name="workingArea">The actual working-area pixel-size of the screen.</param>
/// <param name="isPrimary">Whether the screen is the primary one.</param>
public Screen(double scaling, PixelRect bounds, PixelRect workingArea, bool isPrimary)
{
this.PixelDensity = pixelDensity;
this.Scaling = scaling;
this.Bounds = bounds;
this.WorkingArea = workingArea;
this.Primary = primary;
this.IsPrimary = isPrimary;
}
}
}

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

@ -586,6 +586,14 @@ namespace Avalonia.Controls.Primitives
Selection.SelectAll();
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="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="fromFocus">Wheter the event is a focus event</param>
protected void UpdateSelection(
int index,
bool select = true,
bool rangeModifier = false,
bool toggleModifier = false,
bool rightButton = false)
bool rightButton = false,
bool fromFocus = false)
{
if (index < 0 || index >= ItemCount)
{
@ -696,22 +706,25 @@ namespace Avalonia.Controls.Primitives
Selection.Clear();
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
{
Selection.Select(index);
SelectedIndex = (SelectedIndex == index) ? -1 : index;
}
}
else if (toggle)
{
SelectedIndex = (SelectedIndex == index) ? -1 : index;
}
else
else if (!toggle)
{
using var operation = Selection.BatchUpdate();
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="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="fromFocus">Wheter the event is a focus event</param>
protected void UpdateSelection(
IControl container,
bool select = true,
bool rangeModifier = false,
bool toggleModifier = false,
bool rightButton = false)
bool rightButton = false,
bool fromFocus = false)
{
var index = ItemContainerGenerator?.IndexFromContainer(container) ?? -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="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="fromFocus">Wheter the event is a focus event</param>
/// <returns>
/// True if the event originated from a container that belongs to the control; otherwise
/// false.
@ -768,13 +784,14 @@ namespace Avalonia.Controls.Primitives
bool select = true,
bool rangeModifier = false,
bool toggleModifier = false,
bool rightButton = false)
bool rightButton = false,
bool fromFocus = false)
{
var container = GetContainerFromEventSource(eventSource);
if (container != null)
{
UpdateSelection(container, select, rangeModifier, toggleModifier, rightButton);
UpdateSelection(container, select, rangeModifier, toggleModifier, rightButton, fromFocus);
return true;
}

23
src/Avalonia.Controls/Screens.cs

@ -8,14 +8,31 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls
{
/// <summary>
/// Represents all screens available on a device.
/// </summary>
public class Screens
{
private readonly IScreenImpl _iScreenImpl;
/// <summary>
/// Gets the total number of screens available on the device.
/// </summary>
public int ScreenCount => _iScreenImpl?.ScreenCount ?? 0;
/// <summary>
/// Gets the list of all screens available on the device.
/// </summary>
public IReadOnlyList<Screen> All => _iScreenImpl?.AllScreens ?? Array.Empty<Screen>();
public Screen? Primary => All.FirstOrDefault(x => x.Primary);
/// <summary>
/// Gets the primary screen on the device.
/// </summary>
public Screen? Primary => All.FirstOrDefault(x => x.IsPrimary);
/// <summary>
/// Initializes a new instance of the <see cref="Screens"/> class.
/// </summary>
public Screens(IScreenImpl iScreenImpl)
{
_iScreenImpl = iScreenImpl;
@ -25,14 +42,14 @@ namespace Avalonia.Controls
{
return _iScreenImpl.ScreenFromRect(bounds);
}
public Screen? ScreenFromWindow(IWindowBaseImpl window)
{
return _iScreenImpl.ScreenFromWindow(window);
}
public Screen? ScreenFromPoint(PixelPoint point)
{
{
return _iScreenImpl.ScreenFromPoint(point);
}

30
src/Avalonia.Controls/TreeViewItem.cs

@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Threading;
namespace Avalonia.Controls
{
@ -45,6 +46,8 @@ namespace Avalonia.Controls
private IControl? _header;
private bool _isExpanded;
private int _level;
private bool _templateApplied;
private bool _deferredBringIntoViewFlag;
/// <summary>
/// Initializes static members of the <see cref="TreeViewItem"/> class.
@ -136,15 +139,24 @@ namespace Avalonia.Controls
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 rect = bounds.TransformToAABB(m.Value);
e.TargetRect = rect;
var m = _header.TransformToVisual(this);
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)
{
_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

1
src/Avalonia.Controls/Viewbox.cs

@ -42,6 +42,7 @@ namespace Avalonia.Controls
// can be applied independently of the Viewbox and Child transforms.
_containerVisual = new ViewboxContainer();
_containerVisual.RenderTransformOrigin = RelativePoint.TopLeft;
((ISetLogicalParent)_containerVisual).SetParent(this);
VisualChildren.Add(_containerVisual);
}

3
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -19,7 +19,8 @@
<ProjectReference Include="..\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="3.8.0" />
</ItemGroup>
<Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\Rx.props" />

13
src/Avalonia.Headless/HeadlessPlatformStubs.cs

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

4
src/Avalonia.Native/ScreenImpl.cs

@ -30,10 +30,10 @@ namespace Avalonia.Native
var screen = _native.GetScreen(i);
result[i] = new Screen(
screen.PixelDensity,
screen.Scaling,
screen.Bounds.ToAvaloniaPixelRect(),
screen.WorkingArea.ToAvaloniaPixelRect(),
screen.Primary.FromComBool());
screen.IsPrimary.FromComBool());
}
return result;

2
src/Avalonia.Native/WindowImplBase.cs

@ -92,7 +92,7 @@ namespace Avalonia.Native
_savedScaling = RenderScaling;
_nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost());
var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
var monitor = Screen.AllScreens.OrderBy(x => x.Scaling)
.FirstOrDefault(m => m.Bounds.Contains(Position));
Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d), PlatformResizeReason.Layout);

4
src/Avalonia.Native/avn.idl

@ -256,8 +256,8 @@ struct AvnScreen
{
AvnRect Bounds;
AvnRect WorkingArea;
float PixelDensity;
bool Primary;
float Scaling;
bool IsPrimary;
}
enum AvnPixelFormat

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

@ -281,7 +281,6 @@
<StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminatePointerOver" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" />
<StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminatePressed" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" />
<StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminateDisabled" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" />
<!-- Resources for Calendar.xaml, CalendarButton.xaml, CalendarDayButton.xaml, CalendarItem.xaml -->
<StaticResource x:Key="CalendarViewSelectedHoverBorderBrush" ResourceKey="SystemControlHighlightListAccentMediumBrush" />
@ -305,7 +304,39 @@
<StaticResource x:Key="CalendarViewCalendarItemRevealBorderBrush" ResourceKey="SystemControlTransparentRevealBorderBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<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 -->
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444" />
<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="CheckBoxCheckGlyphForegroundIndeterminatePressed" ResourceKey="SystemControlForegroundChromeWhiteBrush" />
<StaticResource x:Key="CheckBoxCheckGlyphForegroundIndeterminateDisabled" ResourceKey="SystemControlForegroundChromeWhiteBrush" />
<!-- Resources for Calendar.xaml, CalendarButton.xaml, CalendarDayButton.xaml, CalendarItem.xaml -->
<StaticResource x:Key="CalendarViewSelectedHoverBorderBrush" ResourceKey="SystemControlHighlightListAccentMediumBrush" />
<StaticResource x:Key="CalendarViewSelectedPressedBorderBrush" ResourceKey="SystemControlHighlightListAccentHighBrush" />
@ -301,7 +301,39 @@
<StaticResource x:Key="CalendarViewCalendarItemRevealBorderBrush" ResourceKey="SystemControlTransparentRevealBorderBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<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 -->
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="White" />
<StaticResource x:Key="NotificationCardForegroundBrush" ResourceKey="SystemControlForegroundBaseHighBrush" />

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

@ -1,6 +1,7 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Orientation="Vertical" Spacing="20" Width="350" Height="600">
@ -44,45 +45,59 @@
</Border>
</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="ExpanderBorderThickness">1</Thickness>
<Thickness x:Key="ExpanderDropdownLeftBorderThickness">1,1,0,1</Thickness>
<Thickness x:Key="ExpanderDropdownUpBorderThickness">1,1,1,0</Thickness>
<Thickness x:Key="ExpanderDropdownRightBorderThickness">0,1,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}" />
<Thickness x:Key="ExpanderContentLeftBorderThickness">1,1,0,1</Thickness>
<Thickness x:Key="ExpanderContentUpBorderThickness">1,1,1,0</Thickness>
<Thickness x:Key="ExpanderContentRightBorderThickness">0,1,1,1</Thickness>
<Thickness x:Key="ExpanderContentDownBorderThickness">1,0,1,1</Thickness>
<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">
<ControlTemplate>
<Border x:Name="ToggleButtonBackground"
CornerRadius="{TemplateBinding CornerRadius}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid ColumnDefinitions="*,Auto">
<Grid x:Name="ToggleButtonGrid"
ColumnDefinitions="*,Auto">
<ContentPresenter x:Name="PART_ContentPresenter"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="Center"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Foreground="{DynamicResource ExpanderForeground}" />
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Foreground="{TemplateBinding Foreground}"
Margin="{TemplateBinding Padding}"/>
<Border x:Name="ExpandCollapseChevronBorder"
Grid.Column="1"
Width="32"
Height="32"
Margin="7"
RenderTransformOrigin="50%,50%">
Width="{DynamicResource ExpanderChevronButtonSize}"
Height="{DynamicResource ExpanderChevronButtonSize}"
Margin="{DynamicResource ExpanderChevronMargin}"
CornerRadius="{DynamicResource ControlCornerRadius}"
BorderBrush="{DynamicResource ExpanderChevronBorderBrush}"
BorderThickness="{DynamicResource ExpanderChevronBorderThickness}"
Background="{DynamicResource ExpanderChevronBackground}">
<Path x:Name="ExpandCollapseChevron"
HorizontalAlignment="Center"
VerticalAlignment="Center"
@ -98,6 +113,7 @@
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:checked /template/ Border#ExpandCollapseChevronBorder">
<Style.Animations>
<Animation FillMode="Both" Duration="0:0:0.0625">
@ -107,6 +123,7 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="^:not(:checked) /template/ Border#ExpandCollapseChevronBorder">
<Style.Animations>
<Animation FillMode="Both" Duration="0:0:0.0625">
@ -119,60 +136,118 @@
</Animation>
</Style.Animations>
</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 x:Key="FluentExpanderToggleButtonUpTheme" TargetType="ToggleButton" BasedOn="{StaticResource FluentExpanderToggleButtonTheme}">
<Style Selector="^ /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 0 7 L 7 0 L 14 7" />
</Style>
</ControlTheme>
<ControlTheme x:Key="FluentExpanderToggleButtonDownTheme" TargetType="ToggleButton" BasedOn="{StaticResource FluentExpanderToggleButtonTheme}">
<Style Selector="^ /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 0 0 L 7 7 L 14 0" />
</Style>
</ControlTheme>
<ControlTheme x:Key="FluentExpanderToggleButtonLeftTheme" TargetType="ToggleButton" BasedOn="{StaticResource FluentExpanderToggleButtonTheme}">
<Style Selector="^ /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 7 0 L 0 7 L 7 14" />
</Style>
</ControlTheme>
<ControlTheme x:Key="FluentExpanderToggleButtonRightTheme" TargetType="ToggleButton" BasedOn="{StaticResource FluentExpanderToggleButtonTheme}">
<Style Selector="^ /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 0 0 L 7 7 L 0 14" />
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type Expander}" TargetType="Expander">
<Setter Property="Background" Value="{DynamicResource ExpanderBackground}" />
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderBorderThickness}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderBorderBrush}" />
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="MinWidth" Value="{DynamicResource FlyoutThemeMinWidth}" />
<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="Padding" Value="{DynamicResource ExpanderHeaderPadding}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<ControlTemplate>
<DockPanel>
<DockPanel MinWidth="{TemplateBinding MinWidth}"
MaxWidth="{TemplateBinding MaxWidth}">
<ToggleButton x:Name="ExpanderHeader"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
HorizontalContentAlignment="Stretch"
MinHeight="{TemplateBinding MinHeight}"
CornerRadius="{TemplateBinding CornerRadius}"
IsEnabled="{TemplateBinding IsEnabled}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}"
IsEnabled="{TemplateBinding IsEnabled}"/>
IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<Border x:Name="ExpanderContent"
Padding="{DynamicResource ExpanderContentPadding}"
Background="{DynamicResource ExpanderDropDownBackground}"
BorderBrush="{DynamicResource ExpanderDropDownBorderBrush}"
IsVisible="{TemplateBinding IsExpanded, Mode=TwoWay}">
IsVisible="{TemplateBinding IsExpanded, Mode=TwoWay}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Padding="{TemplateBinding Padding}">
<ContentPresenter x:Name="PART_ContentPresenter"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
ContentTemplate="{TemplateBinding ContentTemplate}"
Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</DockPanel>
</ControlTemplate>
@ -241,16 +316,16 @@
</Style>
<Style Selector="^:left /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownLeftBorderThickness}" />
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderContentLeftBorderThickness}" />
</Style>
<Style Selector="^:up /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownUpBorderThickness}" />
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderContentUpBorderThickness}" />
</Style>
<Style Selector="^:right /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownRightBorderThickness}" />
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderContentRightBorderThickness}" />
</Style>
<Style Selector="^:down /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownDownBorderThickness}" />
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderContentDownBorderThickness}" />
</Style>
</ControlTheme>
</ResourceDictionary>

28
src/Avalonia.X11/X11Screens.cs

@ -9,7 +9,7 @@ using JetBrains.Annotations;
namespace Avalonia.X11
{
class X11Screens : IScreenImpl
class X11Screens : IScreenImpl
{
private IX11Screens _impl;
@ -218,7 +218,7 @@ namespace Avalonia.X11
public int ScreenCount => _impl.Screens.Length;
public IReadOnlyList<Screen> AllScreens =>
_impl.Screens.Select(s => new Screen(s.PixelDensity, s.Bounds, s.WorkingArea, s.Primary)).ToArray();
_impl.Screens.Select(s => new Screen(s.Scaling, s.Bounds, s.WorkingArea, s.IsPrimary)).ToArray();
}
interface IX11Screens
@ -281,30 +281,34 @@ namespace Avalonia.X11
{
private const int FullHDWidth = 1920;
private const int FullHDHeight = 1080;
public bool Primary { get; }
public bool IsPrimary { get; }
public string Name { get; set; }
public PixelRect Bounds { get; set; }
public Size? PhysicalSize { get; set; }
public double PixelDensity { get; set; }
public double Scaling { get; set; }
public PixelRect WorkingArea { get; set; }
public X11Screen(PixelRect bounds, bool primary,
string name, Size? physicalSize, double? pixelDensity)
public X11Screen(
PixelRect bounds,
bool isPrimary,
string name,
Size? physicalSize,
double? scaling)
{
Primary = primary;
IsPrimary = isPrimary;
Name = name;
Bounds = bounds;
if (physicalSize == null && pixelDensity == null)
if (physicalSize == null && scaling == null)
{
PixelDensity = 1;
Scaling = 1;
}
else if (pixelDensity == null)
else if (scaling == null)
{
PixelDensity = GuessPixelDensity(bounds, physicalSize.Value);
Scaling = GuessPixelDensity(bounds, physicalSize.Value);
}
else
{
PixelDensity = pixelDensity.Value;
Scaling = scaling.Value;
PhysicalSize = physicalSize;
}
}

8
src/Avalonia.X11/X11Window.cs

@ -120,7 +120,7 @@ namespace Avalonia.X11
if (!_popup && Screen != null)
{
var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
var monitor = Screen.AllScreens.OrderBy(x => x.Scaling)
.FirstOrDefault(m => m.Bounds.Contains(Position));
if (monitor != null)
@ -570,9 +570,9 @@ namespace Avalonia.X11
newScaling = _scalingOverride.Value;
else
{
var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity)
var monitor = _platform.X11Screens.Screens.OrderBy(x => x.Scaling)
.FirstOrDefault(m => m.Bounds.Contains(Position));
newScaling = monitor?.PixelDensity ?? RenderScaling;
newScaling = monitor?.Scaling ?? RenderScaling;
}
if (RenderScaling != newScaling)
@ -994,7 +994,7 @@ namespace Avalonia.X11
public IScreenImpl Screen => _platform.Screens;
public Size MaxAutoSizeHint => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity))
public Size MaxAutoSizeHint => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.Scaling))
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault();

1
src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj

@ -5,7 +5,6 @@
<IsPackable>true</IsPackable>
<PackageId>Avalonia.Markup.Xaml.Loader</PackageId>
<DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants>
<LangVersion>11</LangVersion>
</PropertyGroup>
<!--Disable Net Perf. analyzer for submodule to avoid commit issue -->
<PropertyGroup>

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

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX;
using XamlX.Ast;
@ -51,6 +52,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlBindingPathParser(),
new AvaloniaXamlIlPropertyPathTransformer(),
new AvaloniaXamlIlSetterTargetTypeMetadataTransformer(),
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
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.Linq;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
@ -8,7 +9,6 @@ using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
using XamlParseException = XamlX.XamlParseException;
class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
{
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"))
return node;
var targetTypeNode = context.ParentNodes()
IXamlType targetType = null;
IXamlLineInfo lineInfo = null;
var styleParent = context.ParentNodes()
.OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
.FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style) ??
throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType", node);
.FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
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;
var property = @on.Children.OfType<XamlAstXamlPropertyValueNode>()
@ -31,9 +45,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (propertyName == null)
throw new XamlParseException("Setter.Property must be a string", node);
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};
propType = avaloniaPropertyNode.AvaloniaPropertyType;
}

5
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -9,6 +9,7 @@
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\Shared\StringCompatibilityExtensions.cs" Link="Compatibility\StringCompatibilityExtensions.cs" />
<Compile Include="AvaloniaXamlLoader.cs" />
<Compile Include="Converters\AvaloniaUriTypeConverter.cs" />
<Compile Include="Converters\ColorToBrushConverter.cs" />
@ -68,4 +69,8 @@
<ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.Markup.Xaml.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
</ItemGroup>
<ItemGroup>
<Folder Include="Compatibility\" />
</ItemGroup>
</Project>

1
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<RootNamespace>Avalonia</RootNamespace>
<LangVersion>11</LangVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="Markup\Parsers\Nodes\ExpressionGrammer" />

2
src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs → src/Shared/StringCompatibilityExtensions.cs

@ -3,7 +3,7 @@
namespace System;
#if !NET6_0_OR_GREATER
public static class StringCompatibilityExtensions
internal static class StringCompatibilityExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Contains(this string str, char search) =>

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

@ -148,11 +148,19 @@ namespace Avalonia.Skia
$"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;
return new GlyphTypefaceImpl(skTypeface, isFakeBold, isFakeItalic);
if((int)typeface.Weight >= 600 && !skTypeface.IsBold)
{
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;
public GlyphTypefaceImpl(SKTypeface typeface, bool isFakeBold = false, bool isFakeItalic = false)
public GlyphTypefaceImpl(SKTypeface typeface, FontSimulations fontSimulations)
{
Typeface = typeface ?? throw new ArgumentNullException(nameof(typeface));
@ -52,9 +52,7 @@ namespace Avalonia.Skia
GlyphCount = Typeface.GlyphCount;
IsFakeBold = isFakeBold;
IsFakeItalic = isFakeItalic;
FontSimulations = fontSimulations;
}
public Face Face { get; }
@ -63,6 +61,8 @@ namespace Avalonia.Skia
public SKTypeface Typeface { get; }
public FontSimulations FontSimulations { get; }
public int ReplacementCodepoint { get; }
public FontMetrics Metrics { get; }
@ -73,6 +73,26 @@ namespace Avalonia.Skia
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"/>
public ushort GetGlyph(uint codepoint)
{

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

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

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

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

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

@ -4,4 +4,34 @@
<NativeFileReference Include="$(HarfBuzzSharpStaticLibraryPath)\3.1.7\libHarfBuzzSharp.a" />
<NativeFileReference Include="$(SkiaSharpStaticLibraryPath)\3.1.7\libSkiaSharp.a" />
</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>

88
src/Web/Avalonia.Web/AvaloniaView.cs

@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices.JavaScript;
using Avalonia.Collections.Pooled;
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Controls.Platform;
@ -18,6 +22,7 @@ namespace Avalonia.Web
[System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings
public partial class AvaloniaView : ITextInputMethodImpl
{
private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(ClearMode.Never);
private readonly BrowserTopLevelImpl _topLevelImpl;
private EmbeddableControlRoot _topLevel;
@ -52,13 +57,13 @@ namespace Avalonia.Web
}
_containerElement = hostContent.GetPropertyAsJSObject("host")
?? throw new InvalidOperationException("Host cannot be null");
?? throw new InvalidOperationException("Host cannot be null");
_canvas = hostContent.GetPropertyAsJSObject("canvas")
?? throw new InvalidOperationException("Canvas cannot be null");
?? throw new InvalidOperationException("Canvas cannot be null");
_nativeControlsContainer = hostContent.GetPropertyAsJSObject("nativeHost")
?? throw new InvalidOperationException("NativeHost cannot be null");
?? throw new InvalidOperationException("NativeHost cannot be null");
_inputElement = hostContent.GetPropertyAsJSObject("inputElement")
?? throw new InvalidOperationException("InputElement cannot be null");
?? throw new InvalidOperationException("InputElement cannot be null");
_splash = DomHelper.GetElementById("avalonia-splash");
@ -77,8 +82,7 @@ namespace Avalonia.Web
_topLevelImpl.SetCssCursor = (cursor) =>
{
InputHelper.SetCursor(_containerElement, cursor); // macOS
InputHelper.SetCursor(_canvas, cursor); // windows
InputHelper.SetCursor(_containerElement, cursor);
};
_topLevel.Prepare();
@ -97,7 +101,8 @@ namespace Avalonia.Web
OnCompositionUpdate,
OnCompositionEnd);
InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel);
InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp,
OnPointerCancel, OnWheel);
var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>();
@ -118,7 +123,12 @@ namespace Avalonia.Web
_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
{
@ -136,6 +146,8 @@ namespace Avalonia.Web
DomHelper.ObserveSize(host, null, OnSizeChanged);
CanvasHelper.RequestAnimationFrame(_canvas, true);
InputHelper.FocusElement(_containerElement);
}
private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args)
@ -154,17 +166,36 @@ namespace Avalonia.Web
private bool OnPointerMove(JSObject args)
{
var type = args.GetPropertyAsString("pointertype");
var pointerType = args.GetPropertyAsString("pointerType");
var point = ExtractRawPointerFromJSArgs(args);
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchUpdate,
_ => RawPointerEventType.Move
};
return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, type!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
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(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"), coalescedEvents);
}
private bool OnPointerDown(JSObject args)
{
var pointerType = args.GetPropertyAsString("pointerType");
var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchBegin,
@ -175,20 +206,18 @@ namespace Avalonia.Web
2 => RawPointerEventType.RightButtonDown,
3 => RawPointerEventType.XButton1Down,
4 => RawPointerEventType.XButton2Down,
// 5 => Pen eraser button,
5 => RawPointerEventType.XButton1Down, // should be pen eraser button,
_ => RawPointerEventType.Move
}
};
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)
{
var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchEnd,
@ -199,15 +228,27 @@ namespace Avalonia.Web
2 => RawPointerEventType.RightButtonUp,
3 => RawPointerEventType.XButton1Up,
4 => RawPointerEventType.XButton2Up,
// 5 => Pen eraser button,
5 => RawPointerEventType.XButton1Up, // should be pen eraser button,
_ => RawPointerEventType.Move
}
};
var point = ExtractRawPointerFromJSArgs(args);
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)
{
@ -252,7 +293,14 @@ namespace Avalonia.Web
private bool OnKeyDown (string code, string key, int modifier)
{
return _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier);
var handled = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier);
if (!handled && key.Length == 1)
{
handled = _topLevelImpl.RawTextEvent(key);
}
return handled;
}
private bool OnKeyUp(string code, string key, int modifier)

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

@ -67,17 +67,22 @@ namespace Avalonia.Web
public bool RawPointerEvent(
RawPointerEventType eventType, string pointerType,
RawPointerPoint p, RawInputModifiers modifiers, long touchPointId)
RawPointerPoint p, RawInputModifiers modifiers, long touchPointId,
Lazy<IReadOnlyList<RawPointerPoint>?>? intermediatePoints = null)
{
if (_inputRoot is { }
&& Input is { } input)
{
var device = GetPointerDevice(pointerType);
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)
{
RawPointerId = touchPointId
RawPointerId = touchPointId,
IntermediatePoints = intermediatePoints
};
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);
[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)]
private static partial JSObject InitGL(

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

@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
@ -36,6 +37,8 @@ internal static partial class InputHelper
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
Func<JSObject, bool> pointerUp,
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
Func<JSObject, bool> pointerCancel,
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
Func<JSObject, bool> wheel);
@ -45,6 +48,9 @@ internal static partial class InputHelper
[JSMarshalAs<JSType.Function<JSType.String, JSType.Boolean>>]
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)]
public static partial void ClearInputElement(JSObject htmlElement);

2
src/Web/Avalonia.Web/webapp/build.js

@ -5,7 +5,7 @@ require("esbuild").build({
],
outdir: "../wwwroot",
bundle: true,
minify: false,
minify: true,
format: "esm",
target: "es2016",
platform: "browser",

35
src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts

@ -106,12 +106,6 @@ export class Canvas {
// add the draw to the next frame
this.renderLoopRequest = window.requestAnimationFrame(() => {
if (this.glInfo) {
const GL = (globalThis as any).AvaloniaGL;
// make current
GL.makeContextCurrent(this.glInfo.context);
}
if (this.htmlCanvas.width !== this.newWidth) {
this.htmlCanvas.width = this.newWidth ?? 0;
}
@ -131,6 +125,11 @@ export class Canvas {
}
public setCanvasSize(width: number, height: number): void {
if (this.renderLoopRequest !== 0) {
window.cancelAnimationFrame(this.renderLoopRequest);
this.renderLoopRequest = 0;
}
this.newWidth = width;
this.newHeight = height;
@ -142,11 +141,7 @@ export class Canvas {
this.htmlCanvas.height = this.newHeight;
}
if (this.glInfo) {
const GL = (globalThis as any).AvaloniaGL;
// make current
GL.makeContextCurrent(this.glInfo.context);
}
this.requestAnimationFrame();
}
public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number): void {
@ -210,23 +205,25 @@ interface SizeWatcherInstance {
export class SizeWatcher {
static observer: ResizeObserver;
static elements: Map<string, HTMLElement>;
private static lastMove: number;
public static observe(element: HTMLElement, elementId: string | undefined, callback: (width: number, height: number) => void): void {
if (!element || !callback) {
return;
}
SizeWatcher.init();
SizeWatcher.lastMove = Date.now();
const watcherElement = element as SizeWatcherElement;
watcherElement.SizeWatcher = {
callback
};
callback(element.clientWidth, element.clientHeight);
SizeWatcher.elements.set(elementId ?? element.id, element);
SizeWatcher.observer.observe(element);
const handleResize = (args: UIEvent) => {
if (Date.now() - SizeWatcher.lastMove > 33) {
callback(element.clientWidth, element.clientHeight);
SizeWatcher.lastMove = Date.now();
}
};
SizeWatcher.invoke(element);
window.addEventListener("resize", handleResize);
}
public static unobserve(elementId: string): void {

6
src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts

@ -11,6 +11,7 @@ export class AvaloniaDOM {
host.tabIndex = 0;
host.oncontextmenu = function () { return false; };
host.style.overflow = "hidden";
host.style.touchAction = "none";
// Rendering target canvas
const canvas = document.createElement("canvas");
@ -18,12 +19,11 @@ export class AvaloniaDOM {
canvas.classList.add("avalonia-canvas");
canvas.style.backgroundColor = "#ccc";
canvas.style.width = "100%";
canvas.style.height = "100%";
canvas.style.position = "absolute";
// Native controls host
const nativeHost = document.createElement("div");
canvas.id = `nativeHost${randomIdPart}`;
nativeHost.id = `nativeHost${randomIdPart}`;
nativeHost.classList.add("avalonia-native-host");
nativeHost.style.left = "0px";
nativeHost.style.top = "0px";
@ -33,7 +33,7 @@ export class AvaloniaDOM {
// IME
const inputElement = document.createElement("input");
canvas.id = `input${randomIdPart}`;
inputElement.id = `inputElement${randomIdPart}`;
inputElement.classList.add("avalonia-input-element");
inputElement.autocapitalize = "none";
inputElement.type = "text";

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

@ -95,41 +95,45 @@ export class InputHelper {
pointerMoveCallback: (args: PointerEvent) => boolean,
pointerDownCallback: (args: PointerEvent) => boolean,
pointerUpCallback: (args: PointerEvent) => boolean,
pointerCancelCallback: (args: PointerEvent) => boolean,
wheelCallback: (args: WheelEvent) => boolean
) {
const pointerMoveHandler = (args: PointerEvent) => {
if (pointerMoveCallback(args)) {
args.preventDefault();
}
pointerMoveCallback(args);
args.preventDefault();
};
const pointerDownHandler = (args: PointerEvent) => {
if (pointerDownCallback(args)) {
args.preventDefault();
}
pointerDownCallback(args);
args.preventDefault();
};
const pointerUpHandler = (args: PointerEvent) => {
if (pointerUpCallback(args)) {
args.preventDefault();
}
pointerUpCallback(args);
args.preventDefault();
};
const pointerCancelHandler = (args: PointerEvent) => {
pointerCancelCallback(args);
args.preventDefault();
};
const wheelHandler = (args: WheelEvent) => {
if (wheelCallback(args)) {
args.preventDefault();
}
wheelCallback(args);
args.preventDefault();
};
element.addEventListener("pointermove", pointerMoveHandler);
element.addEventListener("pointerdown", pointerDownHandler);
element.addEventListener("pointerup", pointerUpHandler);
element.addEventListener("wheel", wheelHandler);
element.addEventListener("pointercancel", pointerCancelHandler);
return () => {
element.removeEventListener("pointerover", pointerMoveHandler);
element.removeEventListener("pointerdown", pointerDownHandler);
element.removeEventListener("pointerup", pointerUpHandler);
element.removeEventListener("pointercancel", pointerCancelHandler);
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) {
inputElement.value = "";
}
@ -159,7 +167,11 @@ export class InputHelper {
}
public static setCursor(inputElement: HTMLInputElement, kind: string) {
inputElement.style.cursor = kind;
if (kind === "pointer") {
inputElement.style.removeProperty("cursor");
} else {
inputElement.style.cursor = kind;
}
}
public static setBounds(inputElement: HTMLInputElement, x: number, y: number, caretWidth: number, caretHeight: number, caret: number) {

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

@ -1,10 +1,11 @@
using System;
using System.Drawing.Drawing2D;
using Avalonia.Media;
using Avalonia.Metadata;
using HarfBuzzSharp;
using SharpDX.DirectWrite;
using FontMetrics = Avalonia.Media.FontMetrics;
using FontSimulations = Avalonia.Media.FontSimulations;
using GlyphMetrics = Avalonia.Media.GlyphMetrics;
namespace Avalonia.Direct2D1.Media
{
@ -82,6 +83,8 @@ namespace Avalonia.Direct2D1.Media
public int GlyphCount { get; set; }
public FontSimulations FontSimulations => FontSimulations.None;
/// <inheritdoc cref="IGlyphTypeface"/>
public ushort GetGlyph(uint codepoint)
{
@ -135,6 +138,26 @@ namespace Avalonia.Direct2D1.Media
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)
{
if (_isDisposed)

5
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1878,7 +1878,10 @@ namespace Avalonia.Win32.Interop
public static uint LGID(IntPtr HKL)
{
return (uint)(HKL.ToInt32() & 0xffff);
unchecked
{
return (uint)((ulong)HKL & 0xffff);
}
}
public const int SORT_DEFAULT = 0;

8
src/Windows/Avalonia.Win32/TrayIconImpl.cs

@ -216,7 +216,7 @@ namespace Avalonia.Win32
{
Anchor = PopupAnchor.TopLeft,
Gravity = PopupGravity.BottomRight,
AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.PixelDensity, new Size(1, 1)),
AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.Scaling, new Size(1, 1)),
Size = finalRect.Size,
ConstraintAdjustment = PopupPositionerConstraintAdjustment.FlipX | PopupPositionerConstraintAdjustment.FlipY,
});
@ -244,16 +244,16 @@ namespace Avalonia.Win32
{
var point = _hiddenWindow.Screens.Primary.Bounds.TopLeft;
var size = _hiddenWindow.Screens.Primary.Bounds.Size;
return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.PixelDensity, size.Height * _hiddenWindow.Screens.Primary.PixelDensity);
return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.Scaling, size.Height * _hiddenWindow.Screens.Primary.Scaling);
}
}
public void MoveAndResize(Point devicePoint, Size virtualSize)
{
_moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.PixelDensity);
_moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.Scaling);
}
public double Scaling => _hiddenWindow.Screens.Primary.PixelDensity;
public double Scaling => _hiddenWindow.Screens.Primary.Scaling;
}
}

3
src/Windows/Avalonia.Win32/WinScreen.cs

@ -7,7 +7,8 @@ namespace Avalonia.Win32
{
private readonly IntPtr _hMonitor;
public WinScreen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(pixelDensity, bounds, workingArea, primary)
public WinScreen(double scaling, PixelRect bounds, PixelRect workingArea, bool isPrimary, IntPtr hMonitor)
: base(scaling, bounds, workingArea, isPrimary)
{
_hMonitor = hMonitor;
}

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

@ -224,7 +224,7 @@ namespace Avalonia.Win32
}
}
private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.Primary)?.PixelDensity ?? 1;
private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.Scaling ?? 1;
public double RenderScaling => _scaling;

1
src/iOS/Avalonia.iOS/AvaloniaView.cs

@ -46,7 +46,6 @@ namespace Avalonia.iOS
);
_topLevelImpl.Surfaces = new[] { new EaglLayerSurface(l) };
MultipleTouchEnabled = true;
AddSubviews(new UIView[] { new UIKit.UIButton(UIButtonType.InfoDark) });
}
public override bool CanBecomeFirstResponder => true;

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

@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests
}
[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
{
@ -56,11 +56,11 @@ namespace Avalonia.Controls.UnitTests
KeyModifiers = KeyModifiers.Control
});
Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
Assert.Equal(new[] { "Foo" }, target.SelectedItems);
}
[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
{
@ -81,7 +81,7 @@ namespace Avalonia.Controls.UnitTests
KeyModifiers = KeyModifiers.Control
});
Assert.Equal(new[] { "Bar" }, target.SelectedItems);
Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
}
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);
}
[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]
public void Clicking_Item_Should_Select_It()
{

21
tests/Avalonia.Controls.UnitTests/ViewboxTests.cs

@ -1,4 +1,5 @@
using Avalonia.Controls.Shapes;
using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.UnitTests;
@ -207,6 +208,26 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(200, 200), target.DesiredSize);
}
[Fact]
public void Child_DataContext_Binding_Works()
{
var data = new
{
Foo = "foo",
};
var target = new Viewbox()
{
DataContext = data,
Child = new Canvas
{
[!Canvas.DataContextProperty] = new Binding("Foo"),
},
};
Assert.Equal("foo", target.Child.DataContext);
}
private bool TryGetScale(Viewbox viewbox, out Vector scale)
{
if (viewbox.InternalTransform is null)

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 Blob _blob;
public HarfBuzzGlyphTypefaceImpl(Stream data, bool isFakeBold = false, bool isFakeItalic = false)
public HarfBuzzGlyphTypefaceImpl(Stream data)
{
_blob = Blob.FromStream(data);
@ -45,10 +45,6 @@ namespace Avalonia.UnitTests
};
GlyphCount = Face.GlyphCount;
IsFakeBold = isFakeBold;
IsFakeItalic = isFakeItalic;
}
public FontMetrics Metrics { get; }
@ -58,10 +54,8 @@ namespace Avalonia.UnitTests
public Font Font { get; }
public int GlyphCount { get; set; }
public bool IsFakeBold { get; }
public bool IsFakeItalic { get; }
public FontSimulations FontSimulations { get; }
/// <inheritdoc cref="IGlyphTypeface"/>
public ushort GetGlyph(uint codepoint)
@ -162,5 +156,25 @@ namespace Avalonia.UnitTests
Dispose(true);
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 FontSimulations FontSimulations => throw new NotImplementedException();
public ushort GetGlyph(uint codepoint)
{
return (ushort)codepoint;
@ -56,5 +58,16 @@ namespace Avalonia.UnitTests
table = null;
return false;
}
public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics)
{
metrics = new GlyphMetrics
{
Width = 10,
Height = 10
};
return true;
}
}
}

Loading…
Cancel
Save