committed by
GitHub
88 changed files with 2523 additions and 451 deletions
@ -0,0 +1,76 @@ |
|||
# Contributor Covenant Code of Conduct |
|||
|
|||
## Our Pledge |
|||
|
|||
In the interest of fostering an open and welcoming environment, we as |
|||
contributors and maintainers pledge to making participation in our project and |
|||
our community a harassment-free experience for everyone, regardless of age, body |
|||
size, disability, ethnicity, sex characteristics, gender identity and expression, |
|||
level of experience, education, socio-economic status, nationality, personal |
|||
appearance, race, religion, or sexual identity and orientation. |
|||
|
|||
## Our Standards |
|||
|
|||
Examples of behavior that contributes to creating a positive environment |
|||
include: |
|||
|
|||
* Using welcoming and inclusive language |
|||
* Being respectful of differing viewpoints and experiences |
|||
* Gracefully accepting constructive criticism |
|||
* Focusing on what is best for the community |
|||
* Showing empathy towards other community members |
|||
|
|||
Examples of unacceptable behavior by participants include: |
|||
|
|||
* The use of sexualized language or imagery and unwelcome sexual attention or |
|||
advances |
|||
* Trolling, insulting/derogatory comments, and personal or political attacks |
|||
* Public or private harassment |
|||
* Publishing others' private information, such as a physical or electronic |
|||
address, without explicit permission |
|||
* Other conduct which could reasonably be considered inappropriate in a |
|||
professional setting |
|||
|
|||
## Our Responsibilities |
|||
|
|||
Project maintainers are responsible for clarifying the standards of acceptable |
|||
behavior and are expected to take appropriate and fair corrective action in |
|||
response to any instances of unacceptable behavior. |
|||
|
|||
Project maintainers have the right and responsibility to remove, edit, or |
|||
reject comments, commits, code, wiki edits, issues, and other contributions |
|||
that are not aligned to this Code of Conduct, or to ban temporarily or |
|||
permanently any contributor for other behaviors that they deem inappropriate, |
|||
threatening, offensive, or harmful. |
|||
|
|||
## Scope |
|||
|
|||
This Code of Conduct applies both within project spaces and in public spaces |
|||
when an individual is representing the project or its community. Examples of |
|||
representing a project or community include using an official project e-mail |
|||
address, posting via an official social media account, or acting as an appointed |
|||
representative at an online or offline event. Representation of a project may be |
|||
further defined and clarified by project maintainers. |
|||
|
|||
## Enforcement |
|||
|
|||
Instances of abusive, harassing, or otherwise unacceptable behavior may be |
|||
reported by contacting the project team at steven@avaloniaui.net. All |
|||
complaints will be reviewed and investigated and will result in a response that |
|||
is deemed necessary and appropriate to the circumstances. The project team is |
|||
obligated to maintain confidentiality with regard to the reporter of an incident. |
|||
Further details of specific enforcement policies may be posted separately. |
|||
|
|||
Project maintainers who do not follow or enforce the Code of Conduct in good |
|||
faith may face temporary or permanent repercussions as determined by other |
|||
members of the project's leadership. |
|||
|
|||
## Attribution |
|||
|
|||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, |
|||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html |
|||
|
|||
[homepage]: https://www.contributor-covenant.org |
|||
|
|||
For answers to common questions about this code of conduct, see |
|||
https://www.contributor-covenant.org/faq |
|||
@ -0,0 +1,6 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="HarfBuzzSharp" Version="2.6.1-rc.153" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1-rc.153" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -1,6 +1,6 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="SkiaSharp" Version="1.68.0" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.2" /> |
|||
<PackageReference Include="SkiaSharp" Version="1.68.1-rc.153" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1-rc.153" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
/// <summary>
|
|||
/// Provides extension methods for enums.
|
|||
/// </summary>
|
|||
public static class EnumExtensions |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static unsafe bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum |
|||
{ |
|||
var intValue = *(int*)&value; |
|||
var intFlag = *(int*)&flag; |
|||
|
|||
return (intValue & intFlag) == intFlag; |
|||
} |
|||
} |
|||
} |
|||
@ -1,12 +1,19 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Collections; |
|||
using System.Collections.Specialized; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
public interface IItemsPresenter : IPresenter |
|||
{ |
|||
IEnumerable Items { get; set; } |
|||
|
|||
IPanel Panel { get; } |
|||
|
|||
void ItemsChanged(NotifyCollectionChangedEventArgs e); |
|||
|
|||
void ScrollIntoView(object item); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,112 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// The font manager is used to query the system's installed fonts and is responsible for caching loaded fonts.
|
|||
/// It is also responsible for the font fallback.
|
|||
/// </summary>
|
|||
public abstract class FontManager |
|||
{ |
|||
public static readonly FontManager Default = CreateDefault(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the system's default font family's name.
|
|||
/// </summary>
|
|||
public string DefaultFontFamilyName |
|||
{ |
|||
get; |
|||
protected set; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get all installed fonts in the system.
|
|||
/// <param name="checkForUpdates">If <c>true</c> the font collection is updated.</param>
|
|||
/// </summary>
|
|||
public abstract IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false); |
|||
|
|||
/// <summary>
|
|||
/// Get a cached typeface from specified parameters.
|
|||
/// </summary>
|
|||
/// <param name="fontFamily">The font family.</param>
|
|||
/// <param name="fontWeight">The font weight.</param>
|
|||
/// <param name="fontStyle">The font style.</param>
|
|||
/// <returns>
|
|||
/// The cached typeface.
|
|||
/// </returns>
|
|||
public abstract Typeface GetCachedTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle); |
|||
|
|||
/// <summary>
|
|||
/// Tries to match a specified character to a typeface that supports specified font properties.
|
|||
/// Returns <c>null</c> if no fallback was found.
|
|||
/// </summary>
|
|||
/// <param name="codepoint">The codepoint to match against.</param>
|
|||
/// <param name="fontWeight">The font weight.</param>
|
|||
/// <param name="fontStyle">The font style.</param>
|
|||
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
|
|||
/// <param name="culture">The culture.</param>
|
|||
/// <returns>
|
|||
/// The matched typeface.
|
|||
/// </returns>
|
|||
public abstract Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, |
|||
FontStyle fontStyle = default, |
|||
FontFamily fontFamily = null, CultureInfo culture = null); |
|||
|
|||
public static FontManager CreateDefault() |
|||
{ |
|||
var platformImpl = AvaloniaLocator.Current.GetService<IFontManagerImpl>(); |
|||
|
|||
if (platformImpl != null) |
|||
{ |
|||
return new PlatformFontManager(platformImpl); |
|||
} |
|||
|
|||
return new EmptyFontManager(); |
|||
} |
|||
|
|||
private class PlatformFontManager : FontManager |
|||
{ |
|||
private readonly IFontManagerImpl _platformImpl; |
|||
|
|||
public PlatformFontManager(IFontManagerImpl platformImpl) |
|||
{ |
|||
_platformImpl = platformImpl; |
|||
|
|||
DefaultFontFamilyName = _platformImpl.DefaultFontFamilyName; |
|||
} |
|||
|
|||
public override IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) => |
|||
_platformImpl.GetInstalledFontFamilyNames(checkForUpdates); |
|||
|
|||
public override Typeface GetCachedTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) => |
|||
_platformImpl.GetTypeface(fontFamily, fontWeight, fontStyle); |
|||
|
|||
public override Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, |
|||
FontStyle fontStyle = default, |
|||
FontFamily fontFamily = null, CultureInfo culture = null) => |
|||
_platformImpl.MatchCharacter(codepoint, fontWeight, fontStyle, fontFamily, culture); |
|||
} |
|||
|
|||
private class EmptyFontManager : FontManager |
|||
{ |
|||
public EmptyFontManager() |
|||
{ |
|||
DefaultFontFamilyName = "Empty"; |
|||
} |
|||
|
|||
public override IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) => |
|||
new[] { DefaultFontFamilyName }; |
|||
|
|||
public override Typeface GetCachedTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) => new Typeface(fontFamily, fontWeight, fontStyle); |
|||
|
|||
public override Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, |
|||
FontStyle fontStyle = default, |
|||
FontFamily fontFamily = null, CultureInfo culture = null) => null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
public sealed class GlyphTypeface : IDisposable |
|||
{ |
|||
private static readonly IPlatformRenderInterface s_platformRenderInterface = |
|||
AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); |
|||
|
|||
public GlyphTypeface(Typeface typeface) : this(s_platformRenderInterface.CreateGlyphTypeface(typeface)) |
|||
{ |
|||
} |
|||
|
|||
public GlyphTypeface(IGlyphTypefaceImpl platformImpl) |
|||
{ |
|||
PlatformImpl = platformImpl; |
|||
} |
|||
|
|||
public IGlyphTypefaceImpl PlatformImpl { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the font design units per em.
|
|||
/// </summary>
|
|||
public short DesignEmHeight => PlatformImpl.DesignEmHeight; |
|||
|
|||
/// <summary>
|
|||
/// Gets the recommended distance above the baseline in design em size.
|
|||
/// </summary>
|
|||
public int Ascent => PlatformImpl.Ascent; |
|||
|
|||
/// <summary>
|
|||
/// Gets the recommended distance under the baseline in design em size.
|
|||
/// </summary>
|
|||
public int Descent => PlatformImpl.Descent; |
|||
|
|||
/// <summary>
|
|||
/// Gets the recommended additional space between two lines of text in design em size.
|
|||
/// </summary>
|
|||
public int LineGap => PlatformImpl.LineGap; |
|||
|
|||
/// <summary>
|
|||
/// Gets the recommended line height.
|
|||
/// </summary>
|
|||
public int LineHeight => Descent - Ascent + LineGap; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
|
|||
/// </summary>
|
|||
public int UnderlinePosition => PlatformImpl.UnderlinePosition; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates the thickness of the underline in design em size.
|
|||
/// </summary>
|
|||
public int UnderlineThickness => PlatformImpl.UnderlineThickness; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
|
|||
/// </summary>
|
|||
public int StrikethroughPosition => PlatformImpl.StrikethroughPosition; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates the thickness of the underline in design em size.
|
|||
/// </summary>
|
|||
public int StrikethroughThickness => PlatformImpl.StrikethroughThickness; |
|||
|
|||
/// <summary>
|
|||
/// Returns an glyph index for the specified codepoint.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Returns <c>0</c> if a glyph isn't found.
|
|||
/// </remarks>
|
|||
/// <param name="codepoint">The codepoint.</param>
|
|||
/// <returns>
|
|||
/// A glyph index.
|
|||
/// </returns>
|
|||
public ushort GetGlyph(uint codepoint) => PlatformImpl.GetGlyph(codepoint); |
|||
|
|||
/// <summary>
|
|||
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
|
|||
/// </summary>
|
|||
/// <param name="codepoints">The codepoints to map.</param>
|
|||
/// <returns></returns>
|
|||
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) => PlatformImpl.GetGlyphs(codepoints); |
|||
|
|||
/// <summary>
|
|||
/// Returns the glyph advance for the specified glyph.
|
|||
/// </summary>
|
|||
/// <param name="glyph">The glyph.</param>
|
|||
/// <returns>
|
|||
/// The advance.
|
|||
/// </returns>
|
|||
public int GetGlyphAdvance(ushort glyph) => PlatformImpl.GetGlyphAdvance(glyph); |
|||
|
|||
/// <summary>
|
|||
/// Returns an array of glyph advances in design em size.
|
|||
/// </summary>
|
|||
/// <param name="glyphs">The glyph indices.</param>
|
|||
/// <returns></returns>
|
|||
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) => PlatformImpl.GetGlyphAdvances(glyphs); |
|||
|
|||
void IDisposable.Dispose() |
|||
{ |
|||
PlatformImpl?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Platform |
|||
{ |
|||
public interface IFontManagerImpl |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the system's default font family's name.
|
|||
/// </summary>
|
|||
string DefaultFontFamilyName { get; } |
|||
|
|||
/// <summary>
|
|||
/// Get all installed fonts in the system.
|
|||
/// <param name="checkForUpdates">If <c>true</c> the font collection is updated.</param>
|
|||
/// </summary>
|
|||
IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false); |
|||
|
|||
/// <summary>
|
|||
/// Get a typeface from specified parameters.
|
|||
/// </summary>
|
|||
/// <param name="fontFamily">The font family.</param>
|
|||
/// <param name="fontWeight">The font weight.</param>
|
|||
/// <param name="fontStyle">The font style.</param>
|
|||
/// <returns>
|
|||
/// The typeface.
|
|||
/// </returns>
|
|||
Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle); |
|||
|
|||
/// <summary>
|
|||
/// Tries to match a specified character to a typeface that supports specified font properties.
|
|||
/// </summary>
|
|||
/// <param name="codepoint">The codepoint to match against.</param>
|
|||
/// <param name="fontWeight">The font weight.</param>
|
|||
/// <param name="fontStyle">The font style.</param>
|
|||
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
|
|||
/// <param name="culture">The culture.</param>
|
|||
/// <returns>
|
|||
/// The typeface.
|
|||
/// </returns>
|
|||
Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default, |
|||
FontFamily fontFamily = null, CultureInfo culture = null); |
|||
} |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Platform |
|||
{ |
|||
public interface IGlyphTypefaceImpl : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the font design units per em.
|
|||
/// </summary>
|
|||
short DesignEmHeight { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the recommended distance above the baseline in design em size.
|
|||
/// </summary>
|
|||
int Ascent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the recommended distance under the baseline in design em size.
|
|||
/// </summary>
|
|||
int Descent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the recommended additional space between two lines of text in design em size.
|
|||
/// </summary>
|
|||
int LineGap { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
|
|||
/// </summary>
|
|||
int UnderlinePosition { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates the thickness of the underline in design em size.
|
|||
/// </summary>
|
|||
int UnderlineThickness { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
|
|||
/// </summary>
|
|||
int StrikethroughPosition { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates the thickness of the underline in design em size.
|
|||
/// </summary>
|
|||
int StrikethroughThickness { get; } |
|||
|
|||
/// <summary>
|
|||
/// Returns an glyph index for the specified codepoint.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Returns <c>0</c> if a glyph isn't found.
|
|||
/// </remarks>
|
|||
/// <param name="codepoint">The codepoint.</param>
|
|||
/// <returns>
|
|||
/// A glyph index.
|
|||
/// </returns>
|
|||
ushort GetGlyph(uint codepoint); |
|||
|
|||
/// <summary>
|
|||
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
|
|||
/// </summary>
|
|||
/// <param name="codepoints">The codepoints to map.</param>
|
|||
/// <returns>
|
|||
/// An array of glyph indices.
|
|||
/// </returns>
|
|||
ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints); |
|||
|
|||
/// <summary>
|
|||
/// Returns the glyph advance for the specified glyph.
|
|||
/// </summary>
|
|||
/// <param name="glyph">The glyph.</param>
|
|||
/// <returns>
|
|||
/// The advance.
|
|||
/// </returns>
|
|||
int GetGlyphAdvance(ushort glyph); |
|||
|
|||
/// <summary>
|
|||
/// Returns an array of glyph advances in design em size.
|
|||
/// </summary>
|
|||
/// <param name="glyphs">The glyph indices.</param>
|
|||
/// <returns>
|
|||
/// An array of glyph advances.
|
|||
/// </returns>
|
|||
int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Skia |
|||
{ |
|||
internal readonly struct FontKey : IEquatable<FontKey> |
|||
{ |
|||
public readonly FontStyle Style; |
|||
public readonly FontWeight Weight; |
|||
|
|||
public FontKey(FontWeight weight, FontStyle style) |
|||
{ |
|||
Style = style; |
|||
Weight = weight; |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
var hash = 17; |
|||
hash = hash * 31 + (int)Style; |
|||
hash = hash * 31 + (int)Weight; |
|||
|
|||
return hash; |
|||
} |
|||
|
|||
public override bool Equals(object other) |
|||
{ |
|||
return other is FontKey key && Equals(key); |
|||
} |
|||
|
|||
public bool Equals(FontKey other) |
|||
{ |
|||
return Style == other.Style && |
|||
Weight == other.Weight; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using SkiaSharp; |
|||
|
|||
namespace Avalonia.Skia |
|||
{ |
|||
internal class FontManagerImpl : IFontManagerImpl |
|||
{ |
|||
private SKFontManager _skFontManager = SKFontManager.Default; |
|||
|
|||
public FontManagerImpl() |
|||
{ |
|||
DefaultFontFamilyName = SKTypeface.Default.FamilyName; |
|||
} |
|||
|
|||
public string DefaultFontFamilyName { get; } |
|||
|
|||
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) |
|||
{ |
|||
if (checkForUpdates) |
|||
{ |
|||
_skFontManager = SKFontManager.CreateDefault(); |
|||
} |
|||
|
|||
return _skFontManager.FontFamilies; |
|||
} |
|||
|
|||
public Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) |
|||
{ |
|||
return TypefaceCache.Get(fontFamily.Name, fontWeight, fontStyle).Typeface; |
|||
} |
|||
|
|||
public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default, |
|||
FontFamily fontFamily = null, CultureInfo culture = null) |
|||
{ |
|||
var fontFamilyName = FontFamily.Default.Name; |
|||
|
|||
if (culture == null) |
|||
{ |
|||
culture = CultureInfo.CurrentUICulture; |
|||
} |
|||
|
|||
if (fontFamily != null) |
|||
{ |
|||
foreach (var familyName in fontFamily.FamilyNames) |
|||
{ |
|||
var skTypeface = _skFontManager.MatchCharacter(familyName, (SKFontStyleWeight)fontWeight, |
|||
SKFontStyleWidth.Normal, |
|||
(SKFontStyleSlant)fontStyle, |
|||
new[] { culture.TwoLetterISOLanguageName, culture.ThreeLetterISOLanguageName }, codepoint); |
|||
|
|||
if (skTypeface == null) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
fontFamilyName = familyName; |
|||
|
|||
break; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
var skTypeface = _skFontManager.MatchCharacter(null, (SKFontStyleWeight)fontWeight, SKFontStyleWidth.Normal, |
|||
(SKFontStyleSlant)fontStyle, |
|||
new[] { culture.TwoLetterISOLanguageName, culture.ThreeLetterISOLanguageName }, codepoint); |
|||
|
|||
if (skTypeface != null) |
|||
{ |
|||
fontFamilyName = skTypeface.FamilyName; |
|||
} |
|||
} |
|||
|
|||
return GetTypeface(fontFamilyName, fontWeight, fontStyle); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,179 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using HarfBuzzSharp; |
|||
using SkiaSharp; |
|||
|
|||
namespace Avalonia.Skia |
|||
{ |
|||
public class GlyphTypefaceImpl : IGlyphTypefaceImpl |
|||
{ |
|||
private bool _isDisposed; |
|||
|
|||
public GlyphTypefaceImpl(Typeface typeface) |
|||
{ |
|||
Typeface = TypefaceCache.Get(typeface.FontFamily, typeface.Weight, typeface.Style).SKTypeface; |
|||
|
|||
Face = new Face(GetTable) |
|||
{ |
|||
UnitsPerEm = Typeface.UnitsPerEm |
|||
}; |
|||
|
|||
Font = new Font(Face); |
|||
|
|||
Font.SetFunctionsOpenType(); |
|||
|
|||
Font.GetScale(out var xScale, out _); |
|||
|
|||
DesignEmHeight = (short)xScale; |
|||
|
|||
if (!Font.TryGetHorizontalFontExtents(out var fontExtents)) |
|||
{ |
|||
Font.TryGetVerticalFontExtents(out fontExtents); |
|||
} |
|||
|
|||
Ascent = -fontExtents.Ascender; |
|||
|
|||
Descent = -fontExtents.Descender; |
|||
|
|||
LineGap = fontExtents.LineGap; |
|||
|
|||
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition)) |
|||
{ |
|||
UnderlinePosition = underlinePosition; |
|||
} |
|||
|
|||
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness)) |
|||
{ |
|||
UnderlineThickness = underlineThickness; |
|||
} |
|||
|
|||
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition)) |
|||
{ |
|||
StrikethroughPosition = strikethroughPosition; |
|||
} |
|||
|
|||
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness)) |
|||
{ |
|||
StrikethroughThickness = strikethroughThickness; |
|||
} |
|||
} |
|||
|
|||
public Face Face { get; } |
|||
|
|||
public Font Font { get; } |
|||
|
|||
public SKTypeface Typeface { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public short DesignEmHeight { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int Ascent { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int Descent { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int LineGap { get; } |
|||
|
|||
//ToDo: Get these values from HarfBuzz
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int UnderlinePosition { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int UnderlineThickness { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int StrikethroughPosition { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int StrikethroughThickness { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public ushort GetGlyph(uint codepoint) |
|||
{ |
|||
if (Font.TryGetGlyph(codepoint, out var glyph)) |
|||
{ |
|||
return (ushort)glyph; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) |
|||
{ |
|||
var glyphs = new ushort[codepoints.Length]; |
|||
|
|||
for (var i = 0; i < codepoints.Length; i++) |
|||
{ |
|||
if (Font.TryGetGlyph(codepoints[i], out var glyph)) |
|||
{ |
|||
glyphs[i] = (ushort)glyph; |
|||
} |
|||
} |
|||
|
|||
return glyphs; |
|||
} |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int GetGlyphAdvance(ushort glyph) |
|||
{ |
|||
return Font.GetHorizontalGlyphAdvance(glyph); |
|||
} |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) |
|||
{ |
|||
var glyphIndices = new uint[glyphs.Length]; |
|||
|
|||
for (var i = 0; i < glyphs.Length; i++) |
|||
{ |
|||
glyphIndices[i] = glyphs[i]; |
|||
} |
|||
|
|||
return Font.GetHorizontalGlyphAdvances(glyphIndices); |
|||
} |
|||
|
|||
private Blob GetTable(Face face, Tag tag) |
|||
{ |
|||
var size = Typeface.GetTableSize(tag); |
|||
|
|||
var data = Marshal.AllocCoTaskMem(size); |
|||
|
|||
var releaseDelegate = new ReleaseDelegate(() => Marshal.FreeCoTaskMem(data)); |
|||
|
|||
return Typeface.TryGetTableData(tag, 0, size, data) ? |
|||
new Blob(data, size, MemoryMode.ReadOnly, releaseDelegate) : null; |
|||
} |
|||
|
|||
private void Dispose(bool disposing) |
|||
{ |
|||
if (_isDisposed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_isDisposed = true; |
|||
|
|||
if (!disposing) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
Font?.Dispose(); |
|||
Face?.Dispose(); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
Dispose(true); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Media; |
|||
using SkiaSharp; |
|||
|
|||
namespace Avalonia.Skia |
|||
{ |
|||
internal class TypefaceCollectionEntry |
|||
{ |
|||
public TypefaceCollectionEntry(Typeface typeface, SKTypeface skTypeface) |
|||
{ |
|||
Typeface = typeface; |
|||
SKTypeface = skTypeface; |
|||
} |
|||
public Typeface Typeface { get; } |
|||
public SKTypeface SKTypeface { get; } |
|||
} |
|||
} |
|||
@ -1,62 +1,61 @@ |
|||
using System.Collections.Concurrent; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Fonts; |
|||
using SharpDX.DirectWrite; |
|||
using FontFamily = Avalonia.Media.FontFamily; |
|||
using FontStyle = SharpDX.DirectWrite.FontStyle; |
|||
using FontWeight = SharpDX.DirectWrite.FontWeight; |
|||
|
|||
namespace Avalonia.Direct2D1.Media |
|||
{ |
|||
internal static class Direct2D1FontCollectionCache |
|||
{ |
|||
private static readonly ConcurrentDictionary<FontFamilyKey, SharpDX.DirectWrite.FontCollection> s_cachedCollections; |
|||
internal static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; |
|||
private static readonly ConcurrentDictionary<FontFamilyKey, FontCollection> s_cachedCollections; |
|||
internal static readonly FontCollection InstalledFontCollection; |
|||
|
|||
static Direct2D1FontCollectionCache() |
|||
{ |
|||
s_cachedCollections = new ConcurrentDictionary<FontFamilyKey, SharpDX.DirectWrite.FontCollection>(); |
|||
s_cachedCollections = new ConcurrentDictionary<FontFamilyKey, FontCollection>(); |
|||
|
|||
s_installedFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false); |
|||
InstalledFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false); |
|||
} |
|||
|
|||
public static SharpDX.DirectWrite.TextFormat GetTextFormat(Typeface typeface) |
|||
public static Font GetFont(Typeface typeface) |
|||
{ |
|||
var fontFamily = typeface.FontFamily; |
|||
var fontCollection = GetOrAddFontCollection(fontFamily); |
|||
var fontFamilyName = FontFamily.Default.Name; |
|||
|
|||
// Should this be cached?
|
|||
foreach (var familyName in fontFamily.FamilyNames) |
|||
{ |
|||
if (!fontCollection.FindFamilyName(familyName, out _)) |
|||
if (fontCollection.FindFamilyName(familyName, out var index)) |
|||
{ |
|||
continue; |
|||
return fontCollection.GetFontFamily(index).GetFirstMatchingFont( |
|||
(FontWeight)typeface.Weight, |
|||
FontStretch.Normal, |
|||
(FontStyle)typeface.Style); |
|||
} |
|||
|
|||
fontFamilyName = familyName; |
|||
|
|||
break; |
|||
} |
|||
|
|||
return new SharpDX.DirectWrite.TextFormat( |
|||
Direct2D1Platform.DirectWriteFactory, |
|||
fontFamilyName, |
|||
fontCollection, |
|||
(SharpDX.DirectWrite.FontWeight)typeface.Weight, |
|||
(SharpDX.DirectWrite.FontStyle)typeface.Style, |
|||
SharpDX.DirectWrite.FontStretch.Normal, |
|||
(float)typeface.FontSize); |
|||
InstalledFontCollection.FindFamilyName(FontFamily.Default.Name, out var i); |
|||
|
|||
return InstalledFontCollection.GetFontFamily(i).GetFirstMatchingFont( |
|||
(FontWeight)typeface.Weight, |
|||
FontStretch.Normal, |
|||
(FontStyle)typeface.Style); |
|||
} |
|||
|
|||
private static SharpDX.DirectWrite.FontCollection GetOrAddFontCollection(FontFamily fontFamily) |
|||
private static FontCollection GetOrAddFontCollection(FontFamily fontFamily) |
|||
{ |
|||
return fontFamily.Key == null ? s_installedFontCollection : s_cachedCollections.GetOrAdd(fontFamily.Key, CreateFontCollection); |
|||
return fontFamily.Key == null ? InstalledFontCollection : s_cachedCollections.GetOrAdd(fontFamily.Key, CreateFontCollection); |
|||
} |
|||
|
|||
private static SharpDX.DirectWrite.FontCollection CreateFontCollection(FontFamilyKey key) |
|||
private static FontCollection CreateFontCollection(FontFamilyKey key) |
|||
{ |
|||
var assets = FontFamilyLoader.LoadFontAssets(key); |
|||
|
|||
var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, assets); |
|||
|
|||
return new SharpDX.DirectWrite.FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key); |
|||
return new FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using SharpDX.DirectWrite; |
|||
using FontFamily = Avalonia.Media.FontFamily; |
|||
using FontStyle = Avalonia.Media.FontStyle; |
|||
using FontWeight = Avalonia.Media.FontWeight; |
|||
|
|||
namespace Avalonia.Direct2D1.Media |
|||
{ |
|||
internal class FontManagerImpl : IFontManagerImpl |
|||
{ |
|||
public FontManagerImpl() |
|||
{ |
|||
//ToDo: Implement a real lookup of the system's default font.
|
|||
DefaultFontFamilyName = "segoe ui"; |
|||
} |
|||
|
|||
public string DefaultFontFamilyName { get; } |
|||
|
|||
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) |
|||
{ |
|||
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount; |
|||
|
|||
var fontFamilies = new string[familyCount]; |
|||
|
|||
for (var i = 0; i < familyCount; i++) |
|||
{ |
|||
fontFamilies[i] = Direct2D1FontCollectionCache.InstalledFontCollection.GetFontFamily(i).FamilyNames.GetString(0); |
|||
} |
|||
|
|||
return fontFamilies; |
|||
} |
|||
|
|||
public Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) |
|||
{ |
|||
//ToDo: Implement caching.
|
|||
return new Typeface(fontFamily, fontWeight, fontStyle); |
|||
} |
|||
|
|||
public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default, |
|||
FontFamily fontFamily = null, CultureInfo culture = null) |
|||
{ |
|||
var fontFamilyName = FontFamily.Default.Name; |
|||
|
|||
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount; |
|||
|
|||
for (var i = 0; i < familyCount; i++) |
|||
{ |
|||
var font = Direct2D1FontCollectionCache.InstalledFontCollection.GetFontFamily(i) |
|||
.GetMatchingFonts((SharpDX.DirectWrite.FontWeight)fontWeight, FontStretch.Normal, |
|||
(SharpDX.DirectWrite.FontStyle)fontStyle).GetFont(0); |
|||
|
|||
if (!font.HasCharacter(codepoint)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
fontFamilyName = font.FontFamily.FamilyNames.GetString(0); |
|||
|
|||
break; |
|||
} |
|||
|
|||
return GetTypeface(new FontFamily(fontFamilyName), fontWeight, fontStyle); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,188 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using HarfBuzzSharp; |
|||
using SharpDX.DirectWrite; |
|||
|
|||
namespace Avalonia.Direct2D1.Media |
|||
{ |
|||
public class GlyphTypefaceImpl : IGlyphTypefaceImpl |
|||
{ |
|||
private bool _isDisposed; |
|||
|
|||
public GlyphTypefaceImpl(Typeface typeface) |
|||
{ |
|||
DWFont = Direct2D1FontCollectionCache.GetFont(typeface); |
|||
|
|||
FontFace = new FontFace(DWFont); |
|||
|
|||
Face = new Face(GetTable); |
|||
|
|||
Font = new HarfBuzzSharp.Font(Face); |
|||
|
|||
Font.SetFunctionsOpenType(); |
|||
|
|||
Font.GetScale(out var xScale, out _); |
|||
|
|||
DesignEmHeight = (short)xScale; |
|||
|
|||
if (!Font.TryGetHorizontalFontExtents(out var fontExtents)) |
|||
{ |
|||
Font.TryGetVerticalFontExtents(out fontExtents); |
|||
} |
|||
|
|||
Ascent = -fontExtents.Ascender; |
|||
|
|||
Descent = -fontExtents.Descender; |
|||
|
|||
LineGap = fontExtents.LineGap; |
|||
|
|||
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition)) |
|||
{ |
|||
UnderlinePosition = underlinePosition; |
|||
} |
|||
|
|||
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness)) |
|||
{ |
|||
UnderlineThickness = underlineThickness; |
|||
} |
|||
|
|||
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition)) |
|||
{ |
|||
StrikethroughPosition = strikethroughPosition; |
|||
} |
|||
|
|||
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness)) |
|||
{ |
|||
StrikethroughThickness = strikethroughThickness; |
|||
} |
|||
} |
|||
|
|||
private Blob GetTable(Face face, Tag tag) |
|||
{ |
|||
var dwTag = (int)SwapBytes(tag); |
|||
|
|||
if (FontFace.TryGetFontTable(dwTag, out var tableData, out _)) |
|||
{ |
|||
return new Blob(tableData.Pointer, tableData.Size, MemoryMode.ReadOnly, () => { }); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private static uint SwapBytes(uint x) |
|||
{ |
|||
x = (x >> 16) | (x << 16); |
|||
|
|||
return ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8); |
|||
} |
|||
|
|||
public SharpDX.DirectWrite.Font DWFont { get; } |
|||
|
|||
public FontFace FontFace { get; } |
|||
|
|||
public Face Face { get; } |
|||
|
|||
public HarfBuzzSharp.Font Font { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public short DesignEmHeight { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int Ascent { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int Descent { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int LineGap { get; } |
|||
|
|||
//ToDo: Read font table for these values
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int UnderlinePosition { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int UnderlineThickness { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int StrikethroughPosition { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int StrikethroughThickness { get; } |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public ushort GetGlyph(uint codepoint) |
|||
{ |
|||
if (Font.TryGetGlyph(codepoint, out var glyph)) |
|||
{ |
|||
return (ushort)glyph; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) |
|||
{ |
|||
var glyphs = new ushort[codepoints.Length]; |
|||
|
|||
for (var i = 0; i < codepoints.Length; i++) |
|||
{ |
|||
if (Font.TryGetGlyph(codepoints[i], out var glyph)) |
|||
{ |
|||
glyphs[i] = (ushort)glyph; |
|||
} |
|||
} |
|||
|
|||
return glyphs; |
|||
} |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int GetGlyphAdvance(ushort glyph) |
|||
{ |
|||
return Font.GetHorizontalGlyphAdvance(glyph); |
|||
} |
|||
|
|||
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
|||
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) |
|||
{ |
|||
var glyphIndices = new uint[glyphs.Length]; |
|||
|
|||
for (var i = 0; i < glyphs.Length; i++) |
|||
{ |
|||
glyphIndices[i] = glyphs[i]; |
|||
} |
|||
|
|||
return Font.GetHorizontalGlyphAdvances(glyphIndices); |
|||
} |
|||
|
|||
private void Dispose(bool disposing) |
|||
{ |
|||
if (_isDisposed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_isDisposed = true; |
|||
|
|||
if (!disposing) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
Font?.Dispose(); |
|||
Face?.Dispose(); |
|||
FontFace?.Dispose(); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
Dispose(true); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,109 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Reactive.Disposables; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Threading; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using Moq; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests |
|||
{ |
|||
public class TolTipTests |
|||
{ |
|||
private MouseTestHelper _mouseHelper = new MouseTestHelper(); |
|||
|
|||
[Fact] |
|||
public void Should_Not_Open_On_Detached_Control() |
|||
{ |
|||
//issue #3188
|
|||
var control = new Decorator() |
|||
{ |
|||
[ToolTip.TipProperty] = "Tip", |
|||
[ToolTip.ShowDelayProperty] = 0 |
|||
}; |
|||
|
|||
Assert.False((control as IVisual).IsAttachedToVisualTree); |
|||
|
|||
//here in issue #3188 exception is raised
|
|||
_mouseHelper.Enter(control); |
|||
|
|||
Assert.False(ToolTip.GetIsOpen(control)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Open_On_Pointer_Enter() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
|||
{ |
|||
var window = new Window(); |
|||
|
|||
var target = new Decorator() |
|||
{ |
|||
[ToolTip.TipProperty] = "Tip", |
|||
[ToolTip.ShowDelayProperty] = 0 |
|||
}; |
|||
|
|||
window.Content = target; |
|||
|
|||
window.ApplyTemplate(); |
|||
window.Presenter.ApplyTemplate(); |
|||
|
|||
Assert.True((target as IVisual).IsAttachedToVisualTree); |
|||
|
|||
_mouseHelper.Enter(target); |
|||
|
|||
Assert.True(ToolTip.GetIsOpen(target)); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Open_On_Pointer_Enter_With_Delay() |
|||
{ |
|||
Action timercallback = null; |
|||
var delay = TimeSpan.Zero; |
|||
|
|||
var pti = Mock.Of<IPlatformThreadingInterface>(x => x.CurrentThreadIsLoopThread == true); |
|||
|
|||
Mock.Get(pti) |
|||
.Setup(v => v.StartTimer(It.IsAny<DispatcherPriority>(), It.IsAny<TimeSpan>(), It.IsAny<Action>())) |
|||
.Callback<DispatcherPriority, TimeSpan, Action>((priority, interval, tick) => |
|||
{ |
|||
delay = interval; |
|||
timercallback = tick; |
|||
}) |
|||
.Returns(Disposable.Empty); |
|||
|
|||
using (UnitTestApplication.Start(TestServices.StyledWindow.With(threadingInterface: pti))) |
|||
{ |
|||
var window = new Window(); |
|||
|
|||
var target = new Decorator() |
|||
{ |
|||
[ToolTip.TipProperty] = "Tip", |
|||
[ToolTip.ShowDelayProperty] = 1 |
|||
}; |
|||
|
|||
window.Content = target; |
|||
|
|||
window.ApplyTemplate(); |
|||
window.Presenter.ApplyTemplate(); |
|||
|
|||
Assert.True((target as IVisual).IsAttachedToVisualTree); |
|||
|
|||
_mouseHelper.Enter(target); |
|||
|
|||
Assert.Equal(TimeSpan.FromMilliseconds(1), delay); |
|||
Assert.NotNull(timercallback); |
|||
Assert.False(ToolTip.GetIsOpen(target)); |
|||
|
|||
timercallback(); |
|||
|
|||
Assert.True(ToolTip.GetIsOpen(target)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Input.UnitTests |
|||
{ |
|||
public class PointerTests |
|||
{ |
|||
[Fact] |
|||
public void On_Capture_Transfer_PointerCaptureLost_Should_Propagate_Up_To_The_Common_Parent() |
|||
{ |
|||
Border initialParent, initialCapture, newParent, newCapture; |
|||
var el = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
(initialParent = new Border { Child = initialCapture = new Border() }), |
|||
(newParent = new Border { Child = newCapture = new Border() }) |
|||
} |
|||
}; |
|||
var receivers = new List<object>(); |
|||
var root = new TestRoot(el); |
|||
foreach (InputElement d in root.GetSelfAndVisualDescendants()) |
|||
d.PointerCaptureLost += (s, e) => receivers.Add(s); |
|||
var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); |
|||
|
|||
pointer.Capture(initialCapture); |
|||
pointer.Capture(newCapture); |
|||
Assert.True(receivers.SequenceEqual(new[] { initialCapture, initialParent })); |
|||
|
|||
receivers.Clear(); |
|||
pointer.Capture(null); |
|||
Assert.True(receivers.SequenceEqual(new object[] { newCapture, newParent, el, root })); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue