Browse Source

Merge branch 'master' into feature/RichTextBlock

pull/8370/head
Benedikt Stebner 4 years ago
committed by GitHub
parent
commit
579468403d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .editorconfig
  2. 1
      src/Avalonia.Base/PropertyStore/PriorityValue.cs
  3. 27
      src/Avalonia.Base/Styling/PropertySetterInstance.cs
  4. 34
      src/Avalonia.Controls/ComboBox.cs
  5. 7
      src/Avalonia.Controls/Control.cs
  6. 74
      src/Avalonia.Controls/TextBox.cs
  7. 15
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs
  8. 33
      tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  9. 42
      tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
  10. 101
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  11. 57
      tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs
  12. 28
      tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
  13. 28
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

1
.editorconfig

@ -21,6 +21,7 @@ csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
trim_trailing_whitespace = true
# Indentation preferences
csharp_indent_block_contents = true

1
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@ -121,6 +121,7 @@ namespace Avalonia.PropertyStore
public void ClearLocalValue()
{
_localValue = default;
UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
Property,

27
src/Avalonia.Base/Styling/PropertySetterInstance.cs

@ -18,7 +18,7 @@ namespace Avalonia.Styling
private readonly DirectPropertyBase<T>? _directProperty;
private readonly T _value;
private IDisposable? _subscription;
private bool _isActive;
private State _state;
public PropertySetterInstance(
IStyleable target,
@ -40,6 +40,8 @@ namespace Avalonia.Styling
_value = value;
}
private bool IsActive => _state == State.Active;
public void Start(bool hasActivator)
{
if (hasActivator)
@ -70,31 +72,35 @@ namespace Avalonia.Styling
public void Activate()
{
if (!_isActive)
if (!IsActive)
{
_isActive = true;
_state = State.Active;
PublishNext();
}
}
public void Deactivate()
{
if (_isActive)
if (IsActive)
{
_isActive = false;
_state = State.Inactive;
PublishNext();
}
}
public override void Dispose()
{
if (_state == State.Disposed)
return;
_state = State.Disposed;
if (_subscription is object)
{
var sub = _subscription;
_subscription = null;
sub.Dispose();
}
else if (_isActive)
else if (IsActive)
{
if (_styledProperty is object)
{
@ -114,7 +120,14 @@ namespace Avalonia.Styling
private void PublishNext()
{
PublishNext(_isActive ? new BindingValue<T>(_value) : default);
PublishNext(IsActive ? new BindingValue<T>(_value) : default);
}
private enum State
{
Inactive,
Active,
Disposed,
}
}
}

34
src/Avalonia.Controls/ComboBox.cs

@ -181,26 +181,13 @@ namespace Avalonia.Controls
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
this.UpdateSelectionBoxItem(SelectedItem);
UpdateSelectionBoxItem(SelectedItem);
}
// Because the SelectedItem isn't connected to the visual tree
public override void InvalidateMirrorTransform()
{
base.InvalidateMirrorTransform();
if (SelectedItem is Control selectedControl)
{
selectedControl.InvalidateMirrorTransform();
foreach (var visual in selectedControl.GetVisualDescendants())
{
if (visual is Control childControl)
{
childControl.InvalidateMirrorTransform();
}
}
}
UpdateFlowDirection();
}
/// <inheritdoc/>
@ -365,6 +352,8 @@ namespace Avalonia.Controls
{
parent.GetObservable(IsVisibleProperty).Subscribe(IsVisibleChanged).DisposeWith(_subscriptionsOnOpen);
}
UpdateFlowDirection();
}
private void IsVisibleChanged(bool isVisible)
@ -432,6 +421,8 @@ namespace Avalonia.Controls
}
};
}
UpdateFlowDirection();
}
else
{
@ -439,6 +430,19 @@ namespace Avalonia.Controls
}
}
private void UpdateFlowDirection()
{
if (SelectionBoxItem is Rectangle rectangle)
{
if ((rectangle.Fill as VisualBrush)?.Visual is Control content)
{
var flowDirection = (((IVisual)content!).VisualParent as Control)?.FlowDirection ??
FlowDirection.LeftToRight;
rectangle.FlowDirection = flowDirection;
}
}
}
private void SelectFocusedItem()
{
foreach (ItemContainerInfo dropdownItem in ItemContainerGenerator.Containers)

7
src/Avalonia.Controls/Control.cs

@ -378,17 +378,12 @@ namespace Avalonia.Controls
bool bypassFlowDirectionPolicies = BypassFlowDirectionPolicies;
bool parentBypassFlowDirectionPolicies = false;
var parent = this.FindAncestorOfType<Control>();
var parent = ((IVisual)this).VisualParent as Control;
if (parent != null)
{
parentFlowDirection = parent.FlowDirection;
parentBypassFlowDirectionPolicies = parent.BypassFlowDirectionPolicies;
}
else if (Parent is Control logicalParent)
{
parentFlowDirection = logicalParent.FlowDirection;
parentBypassFlowDirectionPolicies = logicalParent.BypassFlowDirectionPolicies;
}
bool thisShouldBeMirrored = flowDirection == FlowDirection.RightToLeft && !bypassFlowDirectionPolicies;
bool parentShouldBeMirrored = parentFlowDirection == FlowDirection.RightToLeft && !parentBypassFlowDirectionPolicies;

74
src/Avalonia.Controls/TextBox.cs

@ -53,7 +53,7 @@ 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(SelectionBrush));
@ -196,7 +196,6 @@ namespace Avalonia.Controls
private TextBoxTextInputMethodClient _imClient = new TextBoxTextInputMethodClient();
private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _isUndoingRedoing;
private bool _ignoreTextChanges;
private bool _canCut;
private bool _canCopy;
private bool _canPaste;
@ -276,7 +275,7 @@ namespace Avalonia.Controls
get => GetValue(IsReadOnlyProperty);
set => SetValue(IsReadOnlyProperty, value);
}
public char PasswordChar
{
get => GetValue(PasswordCharProperty);
@ -368,21 +367,17 @@ namespace Avalonia.Controls
get => _text;
set
{
if (!_ignoreTextChanges)
{
var caretIndex = CaretIndex;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
var caretIndex = CaretIndex;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
CaretIndex = CoerceCaretIndex(caretIndex, value);
SelectionStart = CoerceCaretIndex(selectionStart, value);
SelectionEnd = CoerceCaretIndex(selectionEnd, value);
if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing)
{
_undoRedoHelper.Clear();
SnapshotUndoRedo(); // so we always have an initial state
}
CaretIndex = CoerceCaretIndex(caretIndex, value);
SelectionStart = CoerceCaretIndex(selectionStart, value);
SelectionEnd = CoerceCaretIndex(selectionEnd, value);
if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing)
{
_undoRedoHelper.Clear();
SnapshotUndoRedo(); // so we always have an initial state
}
}
}
@ -736,32 +731,23 @@ namespace Avalonia.Controls
{
var oldText = _text;
_ignoreTextChanges = true;
try
{
DeleteSelection(false);
var caretIndex = CaretIndex;
text = Text ?? string.Empty;
SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
ClearSelection();
if (IsUndoEnabled)
{
_undoRedoHelper.DiscardRedo();
}
if (_text != oldText)
{
RaisePropertyChanged(TextProperty, oldText, _text);
}
DeleteSelection(false);
var caretIndex = CaretIndex;
text = Text ?? string.Empty;
SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
ClearSelection();
CaretIndex = caretIndex + input.Length;
if (IsUndoEnabled)
{
_undoRedoHelper.DiscardRedo();
}
finally
if (_text != oldText)
{
_ignoreTextChanges = false;
RaisePropertyChanged(TextProperty, oldText, _text);
}
CaretIndex = caretIndex + input.Length;
}
}
@ -1499,15 +1485,7 @@ namespace Avalonia.Controls
{
if (raiseTextChanged)
{
try
{
_ignoreTextChanges = true;
SetAndRaise(TextProperty, ref _text, value);
}
finally
{
_ignoreTextChanges = false;
}
SetAndRaise(TextProperty, ref _text, value);
}
else
{

15
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs

@ -17,6 +17,21 @@ namespace Avalonia.Base.UnitTests
Assert.Equal("foodefault", target.GetValue(Class1.FooProperty));
}
[Fact]
public void ClearValue_Resets_Value_To_Style_value()
{
Class1 target = new Class1();
target.SetValue(Class1.FooProperty, "style", BindingPriority.Style);
target.SetValue(Class1.FooProperty, "local");
Assert.Equal("local", target.GetValue(Class1.FooProperty));
target.ClearValue(Class1.FooProperty);
Assert.Equal("style", target.GetValue(Class1.FooProperty));
}
[Fact]
public void ClearValue_Raises_PropertyChanged()
{

33
tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@ -349,6 +349,39 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
}
}
[Fact]
public void MirrorTransform_For_Control_With_RenderTransform_Should_Be_Correct()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
Border border;
var tree = new TestRoot
{
Width = 400,
Height = 200,
Child = border = new Border
{
HorizontalAlignment = HorizontalAlignment.Left,
Background = Brushes.Red,
Width = 100,
RenderTransform = new ScaleTransform(0.5, 1),
FlowDirection = FlowDirection.RightToLeft
}
};
tree.Measure(Size.Infinity);
tree.Arrange(new Rect(tree.DesiredSize));
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
var expectedTransform = new Matrix(-1, 0, 0, 1, 100, 0) * Matrix.CreateScale(0.5, 1) * Matrix.CreateTranslation(25, 0);
var borderNode = scene.FindNode(border);
Assert.Equal(expectedTransform, borderNode.Transform);
}
}
[Fact]
public void Should_Update_Border_Background_Node()
{

42
tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs

@ -150,13 +150,43 @@ namespace Avalonia.Base.UnitTests.Styling
Assert.Equal(BindingPriority.StyleTrigger, control.GetDiagnostic(TextBlock.TagProperty).Priority);
}
private IBinding CreateMockBinding(AvaloniaProperty property)
[Fact]
public void Disposing_Setter_Should_Preserve_LocalValue()
{
var subject = new Subject<object>();
var descriptor = InstancedBinding.OneWay(subject);
var binding = Mock.Of<IBinding>(x =>
x.Initiate(It.IsAny<IAvaloniaObject>(), property, null, false) == descriptor);
return binding;
var control = new Canvas();
var setter = new Setter(TextBlock.TagProperty, "foo");
var instance = setter.Instance(control);
instance.Start(true);
instance.Activate();
control.Tag = "bar";
instance.Dispose();
Assert.Equal("bar", control.Tag);
}
[Fact]
public void Disposing_Binding_Setter_Should_Preserve_LocalValue()
{
var control = new Canvas();
var source = new { Foo = "foo" };
var setter = new Setter(TextBlock.TagProperty, new Binding
{
Source = source,
Path = nameof(source.Foo),
});
var instance = setter.Instance(control);
instance.Start(true);
instance.Activate();
control.Tag = "bar";
instance.Dispose();
Assert.Equal("bar", control.Tag);
}
private class TestConverter : IValueConverter

101
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@ -8,7 +8,7 @@ using Avalonia.Data;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Avalonia.UnitTests;
using Xunit;
@ -336,5 +336,104 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, count);
}
}
[Fact]
public void FlowDirection_Of_RectangleContent_Shuold_Be_LeftToRight()
{
var items = new[]
{
new ComboBoxItem()
{
Content = new Control()
}
};
var target = new ComboBox
{
FlowDirection = FlowDirection.RightToLeft,
Items = items,
Template = GetTemplate()
};
var root = new TestRoot(target);
target.ApplyTemplate();
target.SelectedIndex = 0;
var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection);
}
[Fact]
public void FlowDirection_Of_RectangleContent_Updated_After_InvalidateMirrorTransform()
{
var parentContent = new Decorator()
{
Child = new Control()
};
var items = new[]
{
new ComboBoxItem()
{
Content = parentContent.Child
}
};
var target = new ComboBox
{
Items = items,
Template = GetTemplate()
};
var root = new TestRoot(target);
target.ApplyTemplate();
target.SelectedIndex = 0;
var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection);
parentContent.FlowDirection = FlowDirection.RightToLeft;
target.FlowDirection = FlowDirection.RightToLeft;
Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection);
}
[Fact]
public void FlowDirection_Of_RectangleContent_Updated_After_OpenPopup()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parentContent = new Decorator()
{
Child = new Control()
};
var items = new[]
{
new ComboBoxItem()
{
Content = parentContent.Child
}
};
var target = new ComboBox
{
FlowDirection = FlowDirection.RightToLeft,
Items = items,
Template = GetTemplate()
};
var root = new TestRoot(target);
target.ApplyTemplate();
target.SelectedIndex = 0;
var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection);
parentContent.FlowDirection = FlowDirection.RightToLeft;
var popup = target.GetVisualDescendants().OfType<Popup>().First();
popup.PlacementTarget = new Window();
popup.Open();
Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection);
}
}
}
}

57
tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs

@ -0,0 +1,57 @@
using Avalonia.Media;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class FlowDirectionTests
{
[Fact]
public void HasMirrorTransform_Should_Be_True()
{
var target = new Control
{
FlowDirection = FlowDirection.RightToLeft,
};
Assert.True(target.HasMirrorTransform);
}
[Fact]
public void HasMirrorTransform_Of_LTR_Children_Should_Be_True_For_RTL_Parent()
{
Control child;
var target = new Decorator
{
FlowDirection = FlowDirection.RightToLeft,
Child = child = new Control()
};
child.FlowDirection = FlowDirection.LeftToRight;
Assert.True(target.HasMirrorTransform);
Assert.True(child.HasMirrorTransform);
}
[Fact]
public void HasMirrorTransform_Of_Children_Is_Updated_After_Parent_Changeed()
{
Control child;
var target = new Decorator
{
FlowDirection = FlowDirection.LeftToRight,
Child = child = new Control()
{
FlowDirection = FlowDirection.LeftToRight,
}
};
Assert.False(target.HasMirrorTransform);
Assert.False(child.HasMirrorTransform);
target.FlowDirection = FlowDirection.RightToLeft;
Assert.True(target.HasMirrorTransform);
Assert.True(child.HasMirrorTransform);
}
}
}

28
tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs

@ -179,34 +179,6 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int()
{
using (Start())
{
var source = new Class1();
var target = new MaskedTextBox
{
DataContext = source,
Template = CreateTemplate(),
};
target.ApplyTemplate();
target.Bind(TextBox.TextProperty, new Binding(nameof(Class1.Foo), BindingMode.TwoWay));
Assert.Equal("0", target.Text);
target.CaretIndex = 1;
target.RaiseEvent(new TextInputEventArgs
{
RoutedEvent = InputElement.TextInputEvent,
Text = "2",
});
Assert.Equal("02", target.Text);
}
}
[Fact]
public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection()
{

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

@ -180,34 +180,6 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int()
{
using (UnitTestApplication.Start(Services))
{
var source = new Class1();
var target = new TextBox
{
DataContext = source,
Template = CreateTemplate(),
};
target.ApplyTemplate();
target.Bind(TextBox.TextProperty, new Binding(nameof(Class1.Foo), BindingMode.TwoWay));
Assert.Equal("0", target.Text);
target.CaretIndex = 1;
target.RaiseEvent(new TextInputEventArgs
{
RoutedEvent = InputElement.TextInputEvent,
Text = "2",
});
Assert.Equal("02", target.Text);
}
}
[Fact]
public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection()
{

Loading…
Cancel
Save