Browse Source

Merge branch 'master' into animations-property-path

pull/2661/head
Jumar Macato 7 years ago
committed by GitHub
parent
commit
3133a9db99
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      Avalonia.sln
  2. 4
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  3. 4
      src/Avalonia.Controls/ColumnDefinitions.cs
  4. 323
      src/Avalonia.Controls/DefinitionBase.cs
  5. 7
      src/Avalonia.Controls/DefinitionList.cs
  6. 953
      src/Avalonia.Controls/Grid.cs
  7. 64
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  8. 4
      src/Avalonia.Controls/RowDefinitions.cs
  9. 38
      src/Avalonia.Controls/TextBox.cs
  10. 4
      src/Avalonia.Native/Avalonia.Native.csproj
  11. 2
      src/Avalonia.Themes.Default/Accents/BaseDark.xaml
  12. 2
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  13. 6
      src/Avalonia.Themes.Default/Button.xaml
  14. 7
      src/Avalonia.Themes.Default/TextBox.xaml
  15. 8
      src/Avalonia.Themes.Default/ToggleButton.xaml
  16. 4
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  17. 2
      src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
  18. 23
      src/Skia/Avalonia.Skia/TypefaceCache.cs
  19. 50
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

1
Avalonia.sln

@ -146,7 +146,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\Serilog.props = build\Serilog.props
build\SharpDX.props = build\SharpDX.props
build\SkiaSharp.props = build\SkiaSharp.props
build\Splat.props = build\Splat.props
build\System.Memory.props = build\System.Memory.props
build\XUnit.props = build\XUnit.props
EndProjectSection

4
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -26,6 +26,10 @@
<TextBox Width="200" Text="Left aligned text" TextAlignment="Left" />
<TextBox Width="200" Text="Center aligned text" TextAlignment="Center" />
<TextBox Width="200" Text="Right aligned text" TextAlignment="Right" />
<TextBox Width="200" Text="Custom selection brush"
SelectionStart="5" SelectionEnd="22"
SelectionBrush="Green" SelectionForegroundBrush="Yellow"/>
<TextBox Width="200" Text="Custom caret brush" CaretBrush="DarkOrange"/>
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8">

4
src/Avalonia.Controls/ColumnDefinitions.cs

@ -16,10 +16,8 @@ namespace Avalonia.Controls
/// <summary>
/// Initializes a new instance of the <see cref="ColumnDefinitions"/> class.
/// </summary>
public ColumnDefinitions()
public ColumnDefinitions() : base ()
{
ResetBehavior = ResetBehavior.Remove;
CollectionChanged += OnCollectionChanged;
}
/// <summary>

323
src/Avalonia.Controls/DefinitionBase.cs

@ -20,49 +20,15 @@ namespace Avalonia.Controls
/// </summary>
public abstract class DefinitionBase : AvaloniaObject
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
/* internal DefinitionBase(bool isColumnDefinition)
{
_isColumnDefinition = isColumnDefinition;
_parentIndex = -1;
}*/
#endregion Constructors
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
/// <summary>
/// SharedSizeGroup property.
/// </summary>
public string SharedSizeGroup
{
get { return (string) GetValue(SharedSizeGroupProperty); }
get { return (string)GetValue(SharedSizeGroupProperty); }
set { SetValue(SharedSizeGroupProperty, value); }
}
#endregion Public Properties
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
/// <summary>
/// Callback to notify about entering model tree.
/// </summary>
@ -86,15 +52,6 @@ namespace Avalonia.Controls
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Property.PropertyType == typeof(GridLength)
|| e.Property.PropertyType == typeof(double))
OnUserSizePropertyChanged(e);
base.OnPropertyChanged(e);
}
/// <summary>
/// Callback to notify about exitting model tree.
/// </summary>
@ -139,119 +96,14 @@ namespace Avalonia.Controls
_minSize = minSize;
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal void OnUserSizePropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (InParentLogicalTree)
{
if (_sharedState != null)
{
_sharedState.Invalidate();
}
else
{
if (((GridLength)e.OldValue).GridUnitType != ((GridLength)e.NewValue).GridUnitType)
{
Parent.Invalidate();
}
else
{
Parent.InvalidateMeasure();
}
}
}
}
/// <summary>
/// <see cref="AvaloniaProperty.ValidateValueCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static bool IsUserSizePropertyValueValid(object value)
{
return (((GridLength)value).Value >= 0);
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static void OnUserMinSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
DefinitionBase definition = (DefinitionBase) d;
if (definition.InParentLogicalTree)
{
Grid parentGrid = (Grid) definition.Parent;
parentGrid.InvalidateMeasure();
}
}
/// <summary>
/// <see cref="AvaloniaProperty.ValidateValueCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static bool IsUserMinSizePropertyValueValid(object value)
{
double v = (double)value;
return (!double.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v));
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static void OnUserMaxSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
DefinitionBase definition = (DefinitionBase) d;
if (definition.InParentLogicalTree)
{
Grid parentGrid = (Grid) definition.Parent;
parentGrid.InvalidateMeasure();
}
}
/// <summary>
/// <see cref="AvaloniaProperty.ValidateValueCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static bool IsUserMaxSizePropertyValueValid(object value)
{
double v = (double)value;
return (!double.IsNaN(v) && v >= 0.0d);
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remarks>
/// This method reflects Grid.SharedScopeProperty state by setting / clearing
/// dynamic property PrivateSharedSizeScopeProperty. Value of PrivateSharedSizeScopeProperty
/// is a collection of SharedSizeState objects for the scope.
/// Also PrivateSharedSizeScopeProperty is FrameworkPropertyMetadataOptions.Inherits property. So that all children
/// elements belonging to a certain scope can easily access SharedSizeState collection. As well
/// as been norified about enter / exit a scope.
/// </remarks>
internal static void OnIsSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
// is it possible to optimize here something like this:
// if ((bool)d.GetValue(Grid.IsSharedSizeScopeProperty) == (d.GetLocalValue(PrivateSharedSizeScopeProperty) != null)
// { /* do nothing */ }
if ((bool) e.NewValue)
if ((bool)e.NewValue)
{
SharedSizeScope sharedStatesCollection = new SharedSizeScope();
d.SetValue(PrivateSharedSizeScopeProperty, sharedStatesCollection);
@ -262,16 +114,6 @@ namespace Avalonia.Controls
}
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
/// <summary>
/// Returns <c>true</c> if this definition is a part of shared group.
/// </summary>
@ -349,8 +191,8 @@ namespace Avalonia.Controls
get
{
double preferredSize = MinSize;
if ( _sizeType != Grid.LayoutTimeSizeType.Auto
&& preferredSize < _measureSize )
if (_sizeType != Grid.LayoutTimeSizeType.Auto
&& preferredSize < _measureSize)
{
preferredSize = _measureSize;
}
@ -375,9 +217,9 @@ namespace Avalonia.Controls
get
{
double minSize = _minSize;
if ( UseSharedMinimum
&& _sharedState != null
&& minSize < _sharedState.MinSize )
if (UseSharedMinimum
&& _sharedState != null
&& minSize < _sharedState.MinSize)
{
minSize = _sharedState.MinSize;
}
@ -393,9 +235,9 @@ namespace Avalonia.Controls
get
{
double minSize = _minSize;
if ( _sharedState != null
&& (UseSharedMinimum || !LayoutWasUpdated)
&& minSize < _sharedState.MinSize )
if (_sharedState != null
&& (UseSharedMinimum || !LayoutWasUpdated)
&& minSize < _sharedState.MinSize)
{
minSize = _sharedState.MinSize;
}
@ -426,27 +268,9 @@ namespace Avalonia.Controls
/// Internal helper to access up-to-date UserMaxSize property value.
/// </summary>
internal abstract double UserMaxSizeValueCache { get; }
/// <summary>
/// Protected. Returns <c>true</c> if this DefinitionBase instance is in parent's logical tree.
/// </summary>
internal bool InParentLogicalTree
{
get { return (_parentIndex != -1); }
}
internal Grid Parent { get; set; }
#endregion Internal Properties
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
/// <summary>
/// SetFlags is used to set or unset one or multiple
/// flags on the object.
@ -465,16 +289,13 @@ namespace Avalonia.Controls
return ((_flags & flags) == flags);
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
private static void OnSharedSizeGroupPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
DefinitionBase definition = (DefinitionBase) d;
if (definition.InParentLogicalTree)
DefinitionBase definition = (DefinitionBase)d;
if (definition.Parent != null)
{
string sharedSizeGroupId = (string) e.NewValue;
string sharedSizeGroupId = (string)e.NewValue;
if (definition._sharedState != null)
{
@ -483,7 +304,7 @@ namespace Avalonia.Controls
definition._sharedState.RemoveMember(definition);
definition._sharedState = null;
}
if ((definition._sharedState == null) && (sharedSizeGroupId != null))
{
SharedSizeScope privateSharedSizeScope = definition.PrivateSharedSizeScope;
@ -498,9 +319,6 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// <see cref="AvaloniaProperty.ValidateValueCallback"/>
/// </summary>
/// <remarks>
/// Verifies that Shared Size Group Property string
/// a) not empty.
@ -520,10 +338,10 @@ namespace Avalonia.Controls
{
bool isDigit = Char.IsDigit(id[i]);
if ( (i == 0 && isDigit)
|| !( isDigit
|| Char.IsLetter(id[i])
|| '_' == id[i] ) )
if ((i == 0 && isDigit)
|| !(isDigit
|| Char.IsLetter(id[i])
|| '_' == id[i]))
{
break;
}
@ -535,12 +353,9 @@ namespace Avalonia.Controls
}
}
throw new ArgumentException("Invalid SharedSizeGroup string.");
throw new ArgumentException("Invalid SharedSizeGroup string.");
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remark>
/// OnPrivateSharedSizeScopePropertyChanged is called when new scope enters or
/// existing scope just left. In both cases if the DefinitionBase object is already registered
@ -550,9 +365,9 @@ namespace Avalonia.Controls
{
DefinitionBase definition = (DefinitionBase)d;
if (definition.InParentLogicalTree)
if (definition.Parent != null)
{
SharedSizeScope privateSharedSizeScope = (SharedSizeScope) e.NewValue;
SharedSizeScope privateSharedSizeScope = (SharedSizeScope)e.NewValue;
if (definition._sharedState != null)
{
@ -576,22 +391,12 @@ namespace Avalonia.Controls
}
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
#region Private Properties
/// <summary>
/// Private getter of shared state collection dynamic property.
/// </summary>
private SharedSizeScope PrivateSharedSizeScope
{
get { return (SharedSizeScope) GetValue(PrivateSharedSizeScopeProperty); }
get { return (SharedSizeScope)GetValue(PrivateSharedSizeScopeProperty); }
}
/// <summary>
@ -612,16 +417,6 @@ namespace Avalonia.Controls
set { SetFlags(value, Flags.LayoutWasUpdated); }
}
#endregion Private Properties
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private readonly bool _isColumnDefinition; // when "true", this is a ColumnDefinition; when "false" this is a RowDefinition (faster than a type check)
private Flags _flags; // flags reflecting various aspects of internal state
internal int _parentIndex = -1; // this instance's index in parent's children collection
@ -633,19 +428,6 @@ namespace Avalonia.Controls
private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case)
private SharedSizeState _sharedState; // reference to shared state object this instance is registered with
internal const bool ThisIsColumnDefinition = true;
internal const bool ThisIsRowDefinition = false;
#endregion Private Fields
//------------------------------------------------------
//
// Private Structures / Classes
//
//------------------------------------------------------
#region Private Structures Classes
[System.Flags]
private enum Flags : byte
@ -653,8 +435,8 @@ namespace Avalonia.Controls
//
// bool flags
//
UseSharedMinimum = 0x00000020, // when "1", definition will take into account shared state's minimum
LayoutWasUpdated = 0x00000040, // set to "1" every time the parent grid is measured
UseSharedMinimum = 0x00000020, // when "1", definition will take into account shared state's minimum
LayoutWasUpdated = 0x00000040, // set to "1" every time the parent grid is measured
}
/// <summary>
@ -799,8 +581,8 @@ namespace Avalonia.Controls
for (int i = 0, count = _registry.Count; i < count; ++i)
{
Debug.Assert( _userSize.GridUnitType == GridUnitType.Auto
|| _userSize.GridUnitType == GridUnitType.Pixel );
Debug.Assert(_userSize.GridUnitType == GridUnitType.Auto
|| _userSize.GridUnitType == GridUnitType.Pixel);
GridLength currentGridLength = _registry[i].UserSizeValueCache;
if (currentGridLength.GridUnitType == GridUnitType.Pixel)
@ -845,7 +627,7 @@ namespace Avalonia.Controls
{
DefinitionBase definitionBase = _registry[i];
if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated)
if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated)
{
// if definition's min size is different, then need to re-measure
if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize))
@ -857,7 +639,7 @@ namespace Avalonia.Controls
else
{
definitionBase.UseSharedMinimum = false;
// if measure is valid then also need to check arrange.
// Note: definitionBase.SizeCache is volatile but at this point
// it contains up-to-date final size
@ -879,27 +661,34 @@ namespace Avalonia.Controls
_broadcastInvalidation = true;
}
private readonly SharedSizeScope _sharedSizeScope; // the scope this state belongs to
private readonly string _sharedSizeGroupId; // Id of the shared size group this object is servicing
private readonly List<DefinitionBase> _registry; // registry of participating definitions
private readonly EventHandler _layoutUpdated; // instance event handler for layout updated event
private Control _layoutUpdatedHost; // Control for which layout updated event handler is registered
private bool _broadcastInvalidation; // "true" when broadcasting of invalidation is needed
private bool _userSizeValid; // "true" when _userSize is up to date
private GridLength _userSize; // shared state
private double _minSize; // shared state
}
#endregion Private Structures Classes
// the scope this state belongs to
private readonly SharedSizeScope _sharedSizeScope;
// Id of the shared size group this object is servicing
private readonly string _sharedSizeGroupId;
// Registry of participating definitions
private readonly List<DefinitionBase> _registry;
//------------------------------------------------------
//
// Properties
//
//------------------------------------------------------
// Instance event handler for layout updated event
private readonly EventHandler _layoutUpdated;
#region Properties
// Control for which layout updated event handler is registered
private Control _layoutUpdatedHost;
// "true" when broadcasting of invalidation is needed
private bool _broadcastInvalidation;
// "true" when _userSize is up to date
private bool _userSizeValid;
// shared state
private GridLength _userSize;
// shared state
private double _minSize;
}
/// <summary>
/// Private shared size scope property holds a collection of shared state objects for the a given shared size scope.
@ -931,7 +720,7 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<string> SharedSizeGroupProperty =
AvaloniaProperty.RegisterAttached<DefinitionBase, Control, string>(
"SharedSizeGroup",
validate:SharedSizeGroupPropertyValueValid);
validate: SharedSizeGroupPropertyValueValid);
/// <summary>
/// Static ctor. Used for static registration of properties.
@ -941,7 +730,5 @@ namespace Avalonia.Controls
SharedSizeGroupProperty.Changed.AddClassHandler<DefinitionBase>(OnSharedSizeGroupPropertyChanged);
PrivateSharedSizeScopeProperty.Changed.AddClassHandler<DefinitionBase>(OnPrivateSharedSizeScopePropertyChanged);
}
#endregion Properties
}
}

7
src/Avalonia.Controls/DefinitionList.cs

@ -10,6 +10,12 @@ namespace Avalonia.Controls
{
public abstract class DefinitionList<T> : AvaloniaList<T> where T : DefinitionBase
{
public DefinitionList()
{
ResetBehavior = ResetBehavior.Remove;
CollectionChanged += OnCollectionChanged;
}
internal bool IsDirty = true;
private Grid _parent;
@ -19,7 +25,6 @@ namespace Avalonia.Controls
set => SetParent(value);
}
private void SetParent(Grid value)
{
_parent = value;

953
src/Avalonia.Controls/Grid.cs

File diff suppressed because it is too large

64
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -19,6 +19,15 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<char> PasswordCharProperty =
AvaloniaProperty.Register<TextPresenter, char>(nameof(PasswordChar));
public static readonly StyledProperty<IBrush> SelectionBrushProperty =
AvaloniaProperty.Register<TextPresenter, IBrush>(nameof(SelectionBrushProperty));
public static readonly StyledProperty<IBrush> SelectionForegroundBrushProperty =
AvaloniaProperty.Register<TextPresenter, IBrush>(nameof(SelectionForegroundBrushProperty));
public static readonly StyledProperty<IBrush> CaretBrushProperty =
AvaloniaProperty.Register<TextPresenter, IBrush>(nameof(CaretBrushProperty));
public static readonly DirectProperty<TextPresenter, int> SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner<TextPresenter>(
o => o.SelectionStart,
@ -34,11 +43,12 @@ namespace Avalonia.Controls.Presenters
private int _selectionStart;
private int _selectionEnd;
private bool _caretBlink;
private IBrush _highlightBrush;
static TextPresenter()
{
AffectsRender<TextPresenter>(PasswordCharProperty);
AffectsRender<TextPresenter>(PasswordCharProperty,
SelectionBrushProperty, SelectionForegroundBrushProperty,
SelectionStartProperty, SelectionEndProperty);
}
public TextPresenter()
@ -79,6 +89,24 @@ namespace Avalonia.Controls.Presenters
set => SetValue(PasswordCharProperty, value);
}
public IBrush SelectionBrush
{
get => GetValue(SelectionBrushProperty);
set => SetValue(SelectionBrushProperty, value);
}
public IBrush SelectionForegroundBrush
{
get => GetValue(SelectionForegroundBrushProperty);
set => SetValue(SelectionForegroundBrushProperty, value);
}
public IBrush CaretBrush
{
get => GetValue(CaretBrushProperty);
set => SetValue(CaretBrushProperty, value);
}
public int SelectionStart
{
get
@ -129,14 +157,9 @@ namespace Avalonia.Controls.Presenters
var rects = FormattedText.HitTestTextRange(start, length);
if (_highlightBrush == null)
{
_highlightBrush = (IBrush)this.FindResource("HighlightBrush");
}
foreach (var rect in rects)
{
context.FillRectangle(_highlightBrush, rect);
context.FillRectangle(SelectionBrush, rect);
}
}
@ -144,16 +167,21 @@ namespace Avalonia.Controls.Presenters
if (selectionStart == selectionEnd)
{
var backgroundColor = (((Control)TemplatedParent).GetValue(BackgroundProperty) as SolidColorBrush)?.Color;
var caretBrush = Brushes.Black;
var caretBrush = CaretBrush;
if (backgroundColor.HasValue)
if (caretBrush is null)
{
byte red = (byte)~(backgroundColor.Value.R);
byte green = (byte)~(backgroundColor.Value.G);
byte blue = (byte)~(backgroundColor.Value.B);
caretBrush = new SolidColorBrush(Color.FromRgb(red, green, blue));
var backgroundColor = (((Control)TemplatedParent).GetValue(BackgroundProperty) as SolidColorBrush)?.Color;
if (backgroundColor.HasValue)
{
byte red = (byte)~(backgroundColor.Value.R);
byte green = (byte)~(backgroundColor.Value.G);
byte blue = (byte)~(backgroundColor.Value.B);
caretBrush = new SolidColorBrush(Color.FromRgb(red, green, blue));
}
else
caretBrush = Brushes.Black;
}
if (_caretBlink)
@ -252,7 +280,7 @@ namespace Avalonia.Controls.Presenters
{
result.Spans = new[]
{
new FormattedTextStyleSpan(start, length, foregroundBrush: Brushes.White),
new FormattedTextStyleSpan(start, length, SelectionForegroundBrush),
};
}

4
src/Avalonia.Controls/RowDefinitions.cs

@ -14,10 +14,8 @@ namespace Avalonia.Controls
/// <summary>
/// Initializes a new instance of the <see cref="RowDefinitions"/> class.
/// </summary>
public RowDefinitions()
public RowDefinitions() : base()
{
ResetBehavior = ResetBehavior.Remove;
CollectionChanged += OnCollectionChanged;
}
/// <summary>

38
src/Avalonia.Controls/TextBox.cs

@ -38,6 +38,15 @@ namespace Avalonia.Controls
public static readonly StyledProperty<char> PasswordCharProperty =
AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar));
public static readonly StyledProperty<IBrush> SelectionBrushProperty =
AvaloniaProperty.Register<TextBox, IBrush>(nameof(SelectionBrushProperty));
public static readonly StyledProperty<IBrush> SelectionForegroundBrushProperty =
AvaloniaProperty.Register<TextBox, IBrush>(nameof(SelectionForegroundBrushProperty));
public static readonly StyledProperty<IBrush> CaretBrushProperty =
AvaloniaProperty.Register<TextBox, IBrush>(nameof(CaretBrushProperty));
public static readonly DirectProperty<TextBox, int> SelectionStartProperty =
AvaloniaProperty.RegisterDirect<TextBox, int>(
nameof(SelectionStart),
@ -169,6 +178,24 @@ namespace Avalonia.Controls
set => SetValue(PasswordCharProperty, value);
}
public IBrush SelectionBrush
{
get => GetValue(SelectionBrushProperty);
set => SetValue(SelectionBrushProperty, value);
}
public IBrush SelectionForegroundBrush
{
get => GetValue(SelectionForegroundBrushProperty);
set => SetValue(SelectionForegroundBrushProperty, value);
}
public IBrush CaretBrush
{
get => GetValue(CaretBrushProperty);
set => SetValue(CaretBrushProperty, value);
}
public int SelectionStart
{
get
@ -287,16 +314,11 @@ namespace Avalonia.Controls
{
DecideCaretVisibility();
}
e.Handled = true;
}
private void DecideCaretVisibility()
{
if (!IsReadOnly)
_presenter?.ShowCaret();
else
_presenter?.HideCaret();
_presenter.ShowCaret();
}
protected override void OnLostFocus(RoutedEventArgs e)
@ -456,7 +478,7 @@ namespace Avalonia.Controls
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfLine))
{
@ -485,7 +507,7 @@ namespace Avalonia.Controls
movement = true;
selection = true;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfLineWithSelection))
{

4
src/Avalonia.Native/Avalonia.Native.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
@ -7,8 +7,6 @@
<CastXmlPath Condition="Exists('/usr/bin/castxml')">/usr/bin/castxml</CastXmlPath>
<CastXmlPath Condition="Exists('/usr/local/bin/castxml')">/usr/local/bin/castxml</CastXmlPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- This is needed because Rider doesn't see generated files in obj for some reason -->
<SharpGenGeneratedCodeFolder>$(MSBuildThisFileDirectory)/Generated</SharpGenGeneratedCodeFolder>
</PropertyGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release' AND '$([MSBuild]::IsOSPlatform(OSX))' == 'true'">

2
src/Avalonia.Themes.Default/Accents/BaseDark.xaml

@ -22,6 +22,7 @@
<Color x:Key="ThemeForegroundLowColor">#FF808080</Color>
<Color x:Key="HighlightColor">#FF119EDA</Color>
<Color x:Key="HighlightForegroundColor">#FFFFFFFF</Color>
<Color x:Key="ErrorColor">#FFFF0000</Color>
<Color x:Key="ErrorLowColor">#10FF0000</Color>
@ -39,6 +40,7 @@
<SolidColorBrush x:Key="ThemeForegroundLowBrush" Color="{DynamicResource ThemeForegroundLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="HighlightBrush" Color="{DynamicResource HighlightColor}"></SolidColorBrush>
<SolidColorBrush x:Key="HighlightForegroundBrush" Color="{DynamicResource HighlightForegroundColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush" Color="{DynamicResource ThemeAccentColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush2" Color="{DynamicResource ThemeAccentColor2}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush3" Color="{DynamicResource ThemeAccentColor3}"></SolidColorBrush>

2
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@ -22,6 +22,7 @@
<Color x:Key="ThemeForegroundLowColor">#FF808080</Color>
<Color x:Key="HighlightColor">#FF086F9E</Color>
<Color x:Key="HighlightForegroundColor">#FFFFFFFF</Color>
<Color x:Key="ErrorColor">#FFFF0000</Color>
<Color x:Key="ErrorLowColor">#10FF0000</Color>
@ -39,6 +40,7 @@
<SolidColorBrush x:Key="ThemeForegroundLowBrush" Color="{DynamicResource ThemeForegroundLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="HighlightBrush" Color="{DynamicResource HighlightColor}"></SolidColorBrush>
<SolidColorBrush x:Key="HighlightForegroundBrush" Color="{DynamicResource HighlightForegroundColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush" Color="{DynamicResource ThemeAccentColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush2" Color="{DynamicResource ThemeAccentColor2}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush3" Color="{DynamicResource ThemeAccentColor3}"></SolidColorBrush>

6
src/Avalonia.Themes.Default/Button.xaml

@ -22,13 +22,13 @@
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Button:pointerover /template/ ContentPresenter">
<Style Selector="Button:pointerover">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
</Style>
<Style Selector="Button:pressed /template/ ContentPresenter">
<Style Selector="Button:pressed">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighBrush}"/>
</Style>
<Style Selector="Button:disabled">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
</Style>
</Styles>
</Styles>

7
src/Avalonia.Themes.Default/TextBox.xaml

@ -3,6 +3,8 @@
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="SelectionBrush" Value="{DynamicResource HighlightBrush}"/>
<Setter Property="SelectionForegroundBrush" Value="{DynamicResource HighlightForegroundBrush}"/>
<Setter Property="Padding" Value="4"/>
<Setter Property="Template">
<ControlTemplate>
@ -44,7 +46,10 @@
SelectionEnd="{TemplateBinding SelectionEnd}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"
PasswordChar="{TemplateBinding PasswordChar}"/>
PasswordChar="{TemplateBinding PasswordChar}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
CaretBrush="{TemplateBinding CaretBrush}"/>
</Panel>
</ScrollViewer>
</DataValidationErrors>

8
src/Avalonia.Themes.Default/ToggleButton.xaml

@ -22,17 +22,17 @@
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ToggleButton:checked /template/ ContentPresenter">
<Style Selector="ToggleButton:checked">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
</Style>
<Style Selector="ToggleButton:pointerover /template/ ContentPresenter">
<Style Selector="ToggleButton:pointerover">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
</Style>
<Style Selector="ToggleButton:pressed /template/ ContentPresenter">
<Style Selector="ToggleButton:pressed">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighBrush}"/>
</Style>
<Style Selector="ToggleButton:disabled">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
</Style>
</Styles>
</Styles>

4
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -28,7 +28,7 @@ namespace Avalonia.Skia
// Replace 0 characters with zero-width spaces (200B)
Text = Text.Replace((char)0, (char)0x200B);
SKTypeface skiaTypeface = TypefaceCache.Default;
SKTypeface skiaTypeface = null;
if (typeface.FontFamily.Key != null)
{
@ -45,7 +45,7 @@ namespace Avalonia.Skia
familyName,
typeface.Style,
typeface.Weight);
if (skiaTypeface != TypefaceCache.Default) break;
if (skiaTypeface.FamilyName != TypefaceCache.DefaultFamilyName) break;
}
}
else

2
src/Skia/Avalonia.Skia/SKTypefaceCollection.cs

@ -47,7 +47,7 @@ namespace Avalonia.Skia
if (!_fontFamilies.TryGetValue(typeface.FontFamily.Name, out var fontFamily))
{
return TypefaceCache.Default;
return TypefaceCache.GetTypeface(TypefaceCache.DefaultFamilyName, typeface.Style, typeface.Weight);
}
var weight = (SKFontStyleWeight)typeface.Weight;

23
src/Skia/Avalonia.Skia/TypefaceCache.cs

@ -12,8 +12,10 @@ namespace Avalonia.Skia
/// </summary>
internal static class TypefaceCache
{
public static SKTypeface Default = CreateDefaultTypeface();
static readonly Dictionary<string, Dictionary<FontKey, SKTypeface>> Cache = new Dictionary<string, Dictionary<FontKey, SKTypeface>>();
public static readonly string DefaultFamilyName = CreateDefaultFamilyName();
private static readonly Dictionary<string, Dictionary<FontKey, SKTypeface>> s_cache =
new Dictionary<string, Dictionary<FontKey, SKTypeface>>();
struct FontKey
{
@ -49,26 +51,26 @@ namespace Avalonia.Skia
// Equals and GetHashCode ommitted
}
private static SKTypeface CreateDefaultTypeface()
private static string CreateDefaultFamilyName()
{
var defaultTypeface = SKTypeface.FromFamilyName(FontFamily.Default.Name) ?? SKTypeface.FromFamilyName(null);
var defaultTypeface = SKTypeface.CreateDefault();
return defaultTypeface;
return defaultTypeface.FamilyName;
}
private static SKTypeface GetTypeface(string name, FontKey key)
{
var familyKey = name;
if (!Cache.TryGetValue(familyKey, out var entry))
if (!s_cache.TryGetValue(familyKey, out var entry))
{
Cache[familyKey] = entry = new Dictionary<FontKey, SKTypeface>();
s_cache[familyKey] = entry = new Dictionary<FontKey, SKTypeface>();
}
if (!entry.TryGetValue(key, out var typeface))
{
typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant)
?? Default;
typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant) ??
GetTypeface(DefaultFamilyName, key);
entry[key] = typeface;
}
@ -78,7 +80,7 @@ namespace Avalonia.Skia
public static SKTypeface GetTypeface(string name, FontStyle style, FontWeight weight)
{
SKFontStyleSlant skStyle = SKFontStyleSlant.Upright;
var skStyle = SKFontStyleSlant.Upright;
switch (style)
{
@ -93,6 +95,5 @@ namespace Avalonia.Skia
return GetTypeface(name, new FontKey((SKFontStyleWeight)weight, skStyle));
}
}
}

50
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -401,6 +401,56 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void TextBox_GotFocus_And_LostFocus_Work_Properly()
{
using (UnitTestApplication.Start(FocusServices))
{
var target1 = new TextBox
{
Template = CreateTemplate(),
Text = "1234"
};
var target2 = new TextBox
{
Template = CreateTemplate(),
Text = "5678"
};
var sp = new StackPanel();
sp.Children.Add(target1);
sp.Children.Add(target2);
target1.ApplyTemplate();
target2.ApplyTemplate();
var root = new TestRoot { Child = sp };
var gfcount = 0;
var lfcount = 0;
target1.GotFocus += (s, e) => gfcount++;
target2.LostFocus += (s, e) => lfcount++;
target2.Focus();
Assert.False(target1.IsFocused);
Assert.True(target2.IsFocused);
target1.Focus();
Assert.False(target2.IsFocused);
Assert.True(target1.IsFocused);
Assert.Equal(1, gfcount);
Assert.Equal(1, lfcount);
}
}
private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice(),
keyboardNavigation: new KeyboardNavigationHandler(),
inputManager: new InputManager(),
standardCursorFactory: Mock.Of<IStandardCursorFactory>());
private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<IStandardCursorFactory>());

Loading…
Cancel
Save