Browse Source

Merge remote-tracking branch 'origin/master' into repro/popup-transparent-areas

pull/11502/head
Dan Walmsley 3 years ago
parent
commit
b1a685a86c
  1. 18
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  2. 5
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  3. 3
      src/Avalonia.Controls/TextBlock.cs
  4. 91
      src/Avalonia.Controls/TextBox.cs
  5. 19
      src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs
  6. 32
      src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml
  7. 3
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  8. 59
      src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml
  9. 3
      src/Avalonia.Themes.Simple/Controls/TextBox.xaml
  10. 27
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs
  11. 4
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  12. 19
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

18
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@ -60,14 +60,10 @@ namespace Avalonia.Media.TextFormatting
_textTrimming = textTrimming ?? TextTrimming.None; _textTrimming = textTrimming ?? TextTrimming.None;
LineHeight = lineHeight;
MaxWidth = maxWidth; MaxWidth = maxWidth;
MaxHeight = maxHeight; MaxHeight = maxHeight;
LetterSpacing = letterSpacing;
MaxLines = maxLines; MaxLines = maxLines;
_textLines = CreateTextLines(); _textLines = CreateTextLines();
@ -81,8 +77,6 @@ namespace Avalonia.Media.TextFormatting
/// <param name="textTrimming">The text trimming.</param> /// <param name="textTrimming">The text trimming.</param>
/// <param name="maxWidth">The maximum width.</param> /// <param name="maxWidth">The maximum width.</param>
/// <param name="maxHeight">The maximum height.</param> /// <param name="maxHeight">The maximum height.</param>
/// <param name="lineHeight">The height of each line of text.</param>
/// <param name="letterSpacing">The letter spacing that is applied to rendered glyphs.</param>
/// <param name="maxLines">The maximum number of text lines.</param> /// <param name="maxLines">The maximum number of text lines.</param>
public TextLayout( public TextLayout(
ITextSource textSource, ITextSource textSource,
@ -90,8 +84,6 @@ namespace Avalonia.Media.TextFormatting
TextTrimming? textTrimming = null, TextTrimming? textTrimming = null,
double maxWidth = double.PositiveInfinity, double maxWidth = double.PositiveInfinity,
double maxHeight = double.PositiveInfinity, double maxHeight = double.PositiveInfinity,
double lineHeight = double.NaN,
double letterSpacing = 0,
int maxLines = 0) int maxLines = 0)
{ {
_textSource = textSource; _textSource = textSource;
@ -100,14 +92,10 @@ namespace Avalonia.Media.TextFormatting
_textTrimming = textTrimming ?? TextTrimming.None; _textTrimming = textTrimming ?? TextTrimming.None;
LineHeight = lineHeight;
MaxWidth = maxWidth; MaxWidth = maxWidth;
MaxHeight = maxHeight; MaxHeight = maxHeight;
LetterSpacing = letterSpacing;
MaxLines = maxLines; MaxLines = maxLines;
_textLines = CreateTextLines(); _textLines = CreateTextLines();
@ -120,7 +108,7 @@ namespace Avalonia.Media.TextFormatting
/// A value of NaN (equivalent to an attribute value of "Auto") indicates that the line height /// A value of NaN (equivalent to an attribute value of "Auto") indicates that the line height
/// is determined automatically from the current font characteristics. The default is NaN. /// is determined automatically from the current font characteristics. The default is NaN.
/// </remarks> /// </remarks>
public double LineHeight { get; } public double LineHeight => _paragraphProperties.LineHeight;
/// <summary> /// <summary>
/// Gets the maximum width. /// Gets the maximum width.
@ -140,7 +128,7 @@ namespace Avalonia.Media.TextFormatting
/// <summary> /// <summary>
/// Gets the text spacing. /// Gets the text spacing.
/// </summary> /// </summary>
public double LetterSpacing { get; } public double LetterSpacing => _paragraphProperties.LetterSpacing;
/// <summary> /// <summary>
/// Gets the text lines. /// Gets the text lines.
@ -495,7 +483,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="lineHeight">The height of each line of text.</param> /// <param name="lineHeight">The height of each line of text.</param>
/// <param name="letterSpacing">The letter spacing that is applied to rendered glyphs.</param> /// <param name="letterSpacing">The letter spacing that is applied to rendered glyphs.</param>
/// <returns></returns> /// <returns></returns>
private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize, internal static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize,
IBrush? foreground, TextAlignment textAlignment, TextWrapping textWrapping, IBrush? foreground, TextAlignment textAlignment, TextWrapping textWrapping,
TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight, TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight,
double letterSpacing) double letterSpacing)

5
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -208,7 +208,7 @@ namespace Avalonia.PropertyStore
IsOverridenCurrentValue = isOverriddenCurrentValue; IsOverridenCurrentValue = isOverriddenCurrentValue;
IsCoercedDefaultValue = isCoercedDefaultValue; IsCoercedDefaultValue = isCoercedDefaultValue;
if (_uncommon?._coerce is { } coerce) if (!isCoercedDefaultValue && _uncommon?._coerce is { } coerce)
v = coerce(owner.Owner, value); v = coerce(owner.Owner, value);
if (priority <= Priority) if (priority <= Priority)
@ -262,7 +262,8 @@ namespace Avalonia.PropertyStore
if (_uncommon?._coerce is { } coerce) if (_uncommon?._coerce is { } coerce)
{ {
v = coerce(owner.Owner, value); v = coerce(owner.Owner, value);
bv = coerce(owner.Owner, baseValue); if (priority != basePriority)
bv = coerce(owner.Owner, baseValue);
} }
if (!EqualityComparer<T>.Default.Equals(Value, v)) if (!EqualityComparer<T>.Default.Equals(Value, v))

3
src/Avalonia.Controls/TextBlock.cs

@ -639,8 +639,7 @@ namespace Avalonia.Controls
TextTrimming, TextTrimming,
_constraint.Width, _constraint.Width,
_constraint.Height, _constraint.Height,
maxLines: MaxLines, MaxLines);
lineHeight: LineHeight);
} }
/// <summary> /// <summary>

91
src/Avalonia.Controls/TextBox.cs

@ -18,9 +18,6 @@ using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Platform;
using System.Reflection;
using static System.Net.Mime.MediaTypeNames;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -28,6 +25,7 @@ namespace Avalonia.Controls
/// Represents a control that can be used to display or edit unformatted text. /// Represents a control that can be used to display or edit unformatted text.
/// </summary> /// </summary>
[TemplatePart("PART_TextPresenter", typeof(TextPresenter))] [TemplatePart("PART_TextPresenter", typeof(TextPresenter))]
[TemplatePart("PART_ScrollViewer", typeof(ScrollViewer))]
[PseudoClasses(":empty")] [PseudoClasses(":empty")]
public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
{ {
@ -158,7 +156,7 @@ namespace Avalonia.Controls
/// Defines see <see cref="TextPresenter.LineHeight"/> property. /// Defines see <see cref="TextPresenter.LineHeight"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<double> LineHeightProperty = public static readonly StyledProperty<double> LineHeightProperty =
TextBlock.LineHeightProperty.AddOwner<TextBox>(); TextBlock.LineHeightProperty.AddOwner<TextBox>(new(defaultValue: double.NaN));
/// <summary> /// <summary>
/// Defines see <see cref="TextBlock.LetterSpacing"/> property. /// Defines see <see cref="TextBlock.LetterSpacing"/> property.
@ -310,6 +308,7 @@ namespace Avalonia.Controls
} }
private TextPresenter? _presenter; private TextPresenter? _presenter;
private ScrollViewer? _scrollViewer;
private readonly TextBoxTextInputMethodClient _imClient = new(); private readonly TextBoxTextInputMethodClient _imClient = new();
private readonly UndoRedoHelper<UndoRedoState> _undoRedoHelper; private readonly UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _isUndoingRedoing; private bool _isUndoingRedoing;
@ -490,7 +489,7 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Gets or sets the maximum character length of the TextBox /// Gets or sets the maximum number of visible lines.
/// </summary> /// </summary>
public int MaxLength public int MaxLength
{ {
@ -803,6 +802,8 @@ namespace Avalonia.Controls
{ {
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter"); _presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
_scrollViewer = e.NameScope.Find<ScrollViewer>("PART_ScrollViewer");
_imClient.SetPresenter(_presenter, this); _imClient.SetPresenter(_presenter, this);
if (IsFocused) if (IsFocused)
@ -855,6 +856,10 @@ namespace Avalonia.Controls
{ {
OnSelectionEndChanged(change); OnSelectionEndChanged(change);
} }
else if (change.Property == MaxLinesProperty)
{
InvalidateMeasure();
}
else if (change.Property == UndoLimitProperty) else if (change.Property == UndoLimitProperty)
{ {
OnUndoLimitChanged(change.GetNewValue<int>()); OnUndoLimitChanged(change.GetNewValue<int>());
@ -942,40 +947,10 @@ namespace Avalonia.Controls
{ {
return; return;
} }
_selectedTextChangesMadeSinceLastUndoSnapshot++; _selectedTextChangesMadeSinceLastUndoSnapshot++;
SnapshotUndoRedo(ignoreChangeCount: false); SnapshotUndoRedo(ignoreChangeCount: false);
if (_presenter != null && MaxLines > 0)
{
var lineCount = _presenter.TextLayout.TextLines.Count;
var length = 0;
var graphemeEnumerator = new GraphemeEnumerator(input.AsSpan());
while (graphemeEnumerator.MoveNext(out var grapheme))
{
if (grapheme.FirstCodepoint.IsBreakChar)
{
if (lineCount + 1 > MaxLines)
{
break;
}
else
{
lineCount++;
}
}
length += grapheme.Length;
}
if (length < input.Length)
{
input = input.Remove(Math.Max(0, length));
}
}
var currentText = Text ?? string.Empty; var currentText = Text ?? string.Empty;
var selectionLength = Math.Abs(SelectionStart - SelectionEnd); var selectionLength = Math.Abs(SelectionStart - SelectionEnd);
var newLength = input.Length + currentText.Length - selectionLength; var newLength = input.Length + currentText.Length - selectionLength;
@ -1518,7 +1493,7 @@ namespace Avalonia.Controls
_presenter.MoveCaretToPoint(point); _presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex; var caretIndex = _presenter.CaretIndex;
var selectionStart = SelectionStart; var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd; var selectionEnd = SelectionEnd;
@ -1976,5 +1951,47 @@ namespace Avalonia.Controls
{ {
CanRedo = _undoRedoHelper.CanRedo; CanRedo = _undoRedoHelper.CanRedo;
} }
protected override Size MeasureOverride(Size availableSize)
{
if(_scrollViewer != null)
{
var maxHeight = double.PositiveInfinity;
if (MaxLines > 0 && double.IsNaN(Height))
{
var fontSize = FontSize;
var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
var paragraphProperties = TextLayout.CreateTextParagraphProperties(typeface, fontSize, null, default, default, null, default, LineHeight, default);
var textLayout = new TextLayout(new MaxLinesTextSource(MaxLines), paragraphProperties);
maxHeight = Math.Ceiling(textLayout.Height);
}
_scrollViewer.SetCurrentValue(MaxHeightProperty, maxHeight);
}
return base.MeasureOverride(availableSize);
}
private class MaxLinesTextSource : ITextSource
{
private readonly int _maxLines;
public MaxLinesTextSource(int maxLines)
{
_maxLines = maxLines;
}
public TextRun? GetTextRun(int textSourceIndex)
{
if (textSourceIndex >= _maxLines)
{
return null;
}
return new TextEndOfLine(1);
}
}
} }
} }

19
src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs

@ -4,7 +4,6 @@ using System.Threading.Tasks;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading;
using Tmds.DBus.Protocol; using Tmds.DBus.Protocol;
using Tmds.DBus.SourceGenerator; using Tmds.DBus.SourceGenerator;
@ -57,15 +56,6 @@ namespace Avalonia.FreeDesktop
WatchAsync(); WatchAsync();
} }
private void InitializeSNWService()
{
if (_connection is null || _isDisposed)
return;
_statusNotifierWatcher = new OrgKdeStatusNotifierWatcher(_connection, "org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher");
_serviceConnected = true;
}
private async void WatchAsync() private async void WatchAsync()
{ {
try try
@ -84,13 +74,13 @@ namespace Avalonia.FreeDesktop
private void OnNameChange(string? newOwner) private void OnNameChange(string? newOwner)
{ {
if (_isDisposed) if (_isDisposed || _connection is null)
return; return;
if (!_serviceConnected & newOwner is not null) if (!_serviceConnected & newOwner is not null)
{ {
_serviceConnected = true; _serviceConnected = true;
InitializeSNWService(); _statusNotifierWatcher = new OrgKdeStatusNotifierWatcher(_connection, "org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher");
DestroyTrayIcon(); DestroyTrayIcon();
@ -182,8 +172,11 @@ namespace Avalonia.FreeDesktop
public void SetIsVisible(bool visible) public void SetIsVisible(bool visible)
{ {
if (_isDisposed) if (_isDisposed || !_serviceConnected)
{
_isVisible = visible;
return; return;
}
switch (visible) switch (visible)
{ {

32
src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml

@ -179,8 +179,8 @@
</DockPanel> </DockPanel>
<CheckBox IsChecked="{Binding ShowHiddenFiles}" Content="Show hidden files" DockPanel.Dock="Left"/> <CheckBox IsChecked="{Binding ShowHiddenFiles}" Content="Show hidden files" DockPanel.Dock="Left"/>
<UniformGrid x:Name="Finalize" HorizontalAlignment="Right" Rows="1"> <UniformGrid x:Name="Finalize" HorizontalAlignment="Right" Rows="1">
<Button Command="{Binding Ok}">OK</Button> <Button Command="{Binding Ok}" MinWidth="80">OK</Button>
<Button Command="{Binding Cancel}">Cancel</Button> <Button Command="{Binding Cancel}" MinWidth="80">Cancel</Button>
</UniformGrid> </UniformGrid>
</DockPanel> </DockPanel>
</DockPanel> </DockPanel>
@ -189,7 +189,6 @@
<Grid DockPanel.Dock="Top" Margin="15 5 0 0" HorizontalAlignment="Stretch"> <Grid DockPanel.Dock="Top" Margin="15 5 0 0" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="20" SharedSizeGroup="Icon" /> <ColumnDefinition Width="20" SharedSizeGroup="Icon" />
<ColumnDefinition Width="16" SharedSizeGroup="Splitter" />
<ColumnDefinition Width="275" SharedSizeGroup="Name" /> <ColumnDefinition Width="275" SharedSizeGroup="Name" />
<ColumnDefinition Width="16" SharedSizeGroup="Splitter" /> <ColumnDefinition Width="16" SharedSizeGroup="Splitter" />
<ColumnDefinition Width="200" SharedSizeGroup="Modified" /> <ColumnDefinition Width="200" SharedSizeGroup="Modified" />
@ -197,6 +196,7 @@
<ColumnDefinition Width="150" SharedSizeGroup="Type" /> <ColumnDefinition Width="150" SharedSizeGroup="Type" />
<ColumnDefinition Width="16" SharedSizeGroup="Splitter" /> <ColumnDefinition Width="16" SharedSizeGroup="Splitter" />
<ColumnDefinition Width="200" SharedSizeGroup="Size" /> <ColumnDefinition Width="200" SharedSizeGroup="Size" />
<ColumnDefinition Width="16" SharedSizeGroup="Splitter" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.Styles> <Grid.Styles>
<Style Selector="GridSplitter"> <Style Selector="GridSplitter">
@ -210,14 +210,14 @@
</Setter> </Setter>
</Style> </Style>
</Grid.Styles> </Grid.Styles>
<GridSplitter Grid.Column="1" /> <TextBlock Grid.Column="1" Text="Name" />
<TextBlock Grid.Column="2" Text="Name" /> <GridSplitter Grid.Column="2" />
<GridSplitter Grid.Column="3" /> <TextBlock Grid.Column="3" Text="Date Modified" />
<TextBlock Grid.Column="4" Text="Date Modified" /> <GridSplitter Grid.Column="4" />
<GridSplitter Grid.Column="5" /> <TextBlock Grid.Column="5" Text="Type" />
<TextBlock Grid.Column="6" Text="Type" /> <GridSplitter Grid.Column="6" />
<GridSplitter Grid.Column="7" /> <TextBlock Grid.Column="7" Text="Size" />
<TextBlock Grid.Column="8" Text="Size" /> <GridSplitter Grid.Column="8" />
</Grid> </Grid>
<ListBox x:Name="PART_Files" <ListBox x:Name="PART_Files"
ItemsSource="{Binding Items}" ItemsSource="{Binding Items}"
@ -231,7 +231,6 @@
<Grid Background="Transparent"> <Grid Background="Transparent">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Icon" /> <ColumnDefinition SharedSizeGroup="Icon" />
<ColumnDefinition SharedSizeGroup="Splitter" />
<ColumnDefinition SharedSizeGroup="Name" /> <ColumnDefinition SharedSizeGroup="Name" />
<ColumnDefinition SharedSizeGroup="Splitter" /> <ColumnDefinition SharedSizeGroup="Splitter" />
<ColumnDefinition SharedSizeGroup="Modified" /> <ColumnDefinition SharedSizeGroup="Modified" />
@ -239,16 +238,17 @@
<ColumnDefinition SharedSizeGroup="Type" /> <ColumnDefinition SharedSizeGroup="Type" />
<ColumnDefinition SharedSizeGroup="Splitter" /> <ColumnDefinition SharedSizeGroup="Splitter" />
<ColumnDefinition SharedSizeGroup="Size" /> <ColumnDefinition SharedSizeGroup="Size" />
<ColumnDefinition SharedSizeGroup="Splitter" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Image Width="16" Height="16"> <Image Width="16" Height="16">
<Image.Source> <Image.Source>
<DrawingImage Drawing="{Binding IconKey, Converter={StaticResource Icons}}"/> <DrawingImage Drawing="{Binding IconKey, Converter={StaticResource Icons}}"/>
</Image.Source> </Image.Source>
</Image> </Image>
<TextBlock Grid.Column="2" Text="{Binding DisplayName}"/> <TextBlock Grid.Column="1" Text="{Binding DisplayName}"/>
<TextBlock Grid.Column="4" Text="{Binding Modified}" /> <TextBlock Grid.Column="3" Text="{Binding Modified}" />
<TextBlock Grid.Column="6" Text="{Binding Type}" /> <TextBlock Grid.Column="5" Text="{Binding Type}" />
<TextBlock Grid.Column="8"> <TextBlock Grid.Column="7" HorizontalAlignment="Right">
<TextBlock.Text> <TextBlock.Text>
<Binding Path="Size"> <Binding Path="Size">
<Binding.Converter> <Binding.Converter>

3
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@ -133,7 +133,8 @@
IsVisible="False" IsVisible="False"
Text="{TemplateBinding Watermark}" Text="{TemplateBinding Watermark}"
DockPanel.Dock="Top" /> DockPanel.Dock="Top" />
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" <ScrollViewer Name="PART_ScrollViewer"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}" AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"

59
src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml

@ -49,7 +49,7 @@
TargetType="dialogs:ManagedFileChooser"> TargetType="dialogs:ManagedFileChooser">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate x:DataType="internal:ManagedFileChooserViewModel"> <ControlTemplate x:DataType="internal:ManagedFileChooserViewModel">
<DockPanel> <DockPanel Margin="5">
<DockPanel Margin="0,0,0,5" <DockPanel Margin="0,0,0,5"
DockPanel.Dock="Top"> DockPanel.Dock="Top">
<internal:ChildFitter Width="{Binding ElementName=Location, Path=Bounds.Height}" <internal:ChildFitter Width="{Binding ElementName=Location, Path=Bounds.Height}"
@ -93,8 +93,8 @@
<Setter Property="Margin" Value="4" /> <Setter Property="Margin" Value="4" />
</Style> </Style>
</StackPanel.Styles> </StackPanel.Styles>
<Button Command="{Binding Ok}">OK</Button> <Button Command="{Binding Ok}" MinWidth="60">OK</Button>
<Button Command="{Binding Cancel}">Cancel</Button> <Button Command="{Binding Cancel}" MinWidth="60">Cancel</Button>
</StackPanel> </StackPanel>
</DockPanel> </DockPanel>
@ -136,7 +136,6 @@
DockPanel.Dock="Top"> DockPanel.Dock="Top">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="20" SharedSizeGroup="Icon" /> <ColumnDefinition Width="20" SharedSizeGroup="Icon" />
<ColumnDefinition Width="16" SharedSizeGroup="Splitter" />
<ColumnDefinition Width="400" SharedSizeGroup="Name" /> <ColumnDefinition Width="400" SharedSizeGroup="Name" />
<ColumnDefinition Width="16" SharedSizeGroup="Splitter" /> <ColumnDefinition Width="16" SharedSizeGroup="Splitter" />
<ColumnDefinition Width="200" SharedSizeGroup="Modified" /> <ColumnDefinition Width="200" SharedSizeGroup="Modified" />
@ -144,19 +143,45 @@
<ColumnDefinition Width="150" SharedSizeGroup="Type" /> <ColumnDefinition Width="150" SharedSizeGroup="Type" />
<ColumnDefinition Width="16" SharedSizeGroup="Splitter" /> <ColumnDefinition Width="16" SharedSizeGroup="Splitter" />
<ColumnDefinition Width="200" SharedSizeGroup="Size" /> <ColumnDefinition Width="200" SharedSizeGroup="Size" />
<ColumnDefinition Width="16" SharedSizeGroup="Splitter" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<GridSplitter Grid.Column="1" /> <TextBlock Grid.Column="1"
<TextBlock Grid.Column="2"
Text="Name" /> Text="Name" />
<GridSplitter Grid.Column="3" /> <GridSplitter Grid.Column="2"
<TextBlock Grid.Column="4" ResizeDirection="Columns"
Background="Transparent" />
<Rectangle HorizontalAlignment="Left" Grid.Column="2" VerticalAlignment="Stretch" Width="1" Fill="{DynamicResource ThemeControlMidBrush}"/>
<TextBlock Grid.Column="3"
Text="Date Modified" /> Text="Date Modified" />
<GridSplitter Grid.Column="5" /> <GridSplitter Grid.Column="4"
<TextBlock Grid.Column="6" ResizeDirection="Columns"
Background="Transparent" />
<Rectangle HorizontalAlignment="Left"
Grid.Column="4"
VerticalAlignment="Stretch"
Width="1"
Fill="{DynamicResource ThemeControlMidBrush}"/>
<TextBlock Grid.Column="5"
Text="Type" /> Text="Type" />
<GridSplitter Grid.Column="7" /> <GridSplitter Grid.Column="6" ResizeDirection="Columns"
<TextBlock Grid.Column="8" Background="Transparent" />
<Rectangle HorizontalAlignment="Left"
Grid.Column="6"
VerticalAlignment="Stretch"
Width="1"
Fill="{DynamicResource ThemeControlMidBrush}"/>
<TextBlock Grid.Column="7"
Text="Size" /> Text="Size" />
<GridSplitter Grid.Column="8"
ResizeDirection="Columns"
Background="Transparent" />
<Rectangle HorizontalAlignment="Left"
Grid.Column="8"
VerticalAlignment="Stretch"
Width="1"
Fill="{DynamicResource ThemeControlMidBrush}"/>
</Grid> </Grid>
<ListBox x:Name="PART_Files" <ListBox x:Name="PART_Files"
Margin="0,5" Margin="0,5"
@ -169,7 +194,6 @@
<Grid Background="Transparent"> <Grid Background="Transparent">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Icon" /> <ColumnDefinition SharedSizeGroup="Icon" />
<ColumnDefinition SharedSizeGroup="Splitter" />
<ColumnDefinition SharedSizeGroup="Name" /> <ColumnDefinition SharedSizeGroup="Name" />
<ColumnDefinition SharedSizeGroup="Splitter" /> <ColumnDefinition SharedSizeGroup="Splitter" />
<ColumnDefinition SharedSizeGroup="Modified" /> <ColumnDefinition SharedSizeGroup="Modified" />
@ -177,19 +201,20 @@
<ColumnDefinition SharedSizeGroup="Type" /> <ColumnDefinition SharedSizeGroup="Type" />
<ColumnDefinition SharedSizeGroup="Splitter" /> <ColumnDefinition SharedSizeGroup="Splitter" />
<ColumnDefinition SharedSizeGroup="Size" /> <ColumnDefinition SharedSizeGroup="Size" />
<ColumnDefinition SharedSizeGroup="Splitter" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Image Grid.Column="0" <Image Grid.Column="0"
Width="16" Width="16"
Height="16"> Height="16">
<DrawingImage Drawing="{Binding IconKey, Converter={StaticResource Icons}}" /> <DrawingImage Drawing="{Binding IconKey, Converter={StaticResource Icons}}" />
</Image> </Image>
<TextBlock Grid.Column="2" <TextBlock Grid.Column="1"
Text="{Binding DisplayName}" /> Text="{Binding DisplayName}" />
<TextBlock Grid.Column="4" <TextBlock Grid.Column="3"
Text="{Binding Modified}" /> Text="{Binding Modified}" />
<TextBlock Grid.Column="6" <TextBlock Grid.Column="5"
Text="{Binding Type}" /> Text="{Binding Type}" />
<TextBlock Grid.Column="8"> <TextBlock Grid.Column="7" HorizontalAlignment="Right">
<TextBlock.Text> <TextBlock.Text>
<Binding Path="Size"> <Binding Path="Size">
<Binding.Converter> <Binding.Converter>

3
src/Avalonia.Themes.Simple/Controls/TextBox.xaml

@ -123,7 +123,8 @@
<ContentPresenter Grid.Column="0" <ContentPresenter Grid.Column="0"
Grid.ColumnSpan="1" Grid.ColumnSpan="1"
Content="{TemplateBinding InnerLeftContent}" /> Content="{TemplateBinding InnerLeftContent}" />
<ScrollViewer Grid.Column="1" <ScrollViewer Name="PART_ScrollViewer"
Grid.Column="1"
Grid.ColumnSpan="1" Grid.ColumnSpan="1"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}" AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}" BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"

27
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs

@ -124,6 +124,19 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(2, target.CoreChanges.Count); Assert.Equal(2, target.CoreChanges.Count);
} }
[Fact]
public void CoerceValue_Calls_Coerce_Callback_Only_Once()
{
var target = new Class1 { Foo = 99 };
target.MaxFoo = 50;
target.CoerceFooInvocations.Clear();
target.CoerceValue(Class1.FooProperty);
Assert.Equal(new[] { 99 }, target.CoerceFooInvocations);
}
[Fact] [Fact]
public void Coerced_Value_Can_Be_Restored_If_Limit_Changed() public void Coerced_Value_Can_Be_Restored_If_Limit_Changed()
{ {
@ -218,6 +231,18 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(1, raised); Assert.Equal(1, raised);
} }
[Fact]
public void Default_Value_Is_Coerced_Only_Once()
{
var target = new Class1();
target.MinFoo = 20;
target.CoerceFooInvocations.Clear();
target.CoerceValue(Class1.FooProperty);
Assert.Equal(new[] { 11 }, target.CoerceFooInvocations);
}
[Fact] [Fact]
public void ClearValue_Respects_Coerced_Default_Value() public void ClearValue_Respects_Coerced_Default_Value()
{ {
@ -338,10 +363,12 @@ namespace Avalonia.Base.UnitTests
public int MinFoo { get; set; } = 0; public int MinFoo { get; set; } = 0;
public int MaxFoo { get; set; } = 100; public int MaxFoo { get; set; } = 100;
public List<int> CoerceFooInvocations { get; } = new();
public List<AvaloniaPropertyChangedEventArgs> CoreChanges { get; } = new(); public List<AvaloniaPropertyChangedEventArgs> CoreChanges { get; } = new();
public static int CoerceFoo(AvaloniaObject instance, int value) public static int CoerceFoo(AvaloniaObject instance, int value)
{ {
(instance as Class1)?.CoerceFooInvocations.Add(value);
return instance is Class1 o ? return instance is Class1 o ?
Math.Clamp(value, o.MinFoo, o.MaxFoo) : Math.Clamp(value, o.MinFoo, o.MaxFoo) :
Math.Clamp(value, 0, 100); Math.Clamp(value, 0, 100);

4
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@ -440,7 +440,7 @@ namespace Avalonia.Controls.UnitTests
throw new InvalidOperationException("Could not get the point in root coordinates."); throw new InvalidOperationException("Could not get the point in root coordinates.");
} }
private Control CreateTemplate(ScrollViewer control, INameScope scope) internal static Control CreateTemplate(ScrollViewer control, INameScope scope)
{ {
return new Grid return new Grid
{ {
@ -480,7 +480,7 @@ namespace Avalonia.Controls.UnitTests
}; };
} }
private Control CreateScrollBarTemplate(ScrollBar scrollBar, INameScope scope) private static Control CreateScrollBarTemplate(ScrollBar scrollBar, INameScope scope)
{ {
return new Border return new Border
{ {

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

@ -884,7 +884,7 @@ namespace Avalonia.Controls.UnitTests
Template = CreateTemplate(), Template = CreateTemplate(),
Text = "ABC", Text = "ABC",
MaxLines = 1, MaxLines = 1,
AcceptsReturn= true AcceptsReturn = true
}; };
var impl = CreateMockTopLevelImpl(); var impl = CreateMockTopLevelImpl();
@ -896,8 +896,11 @@ namespace Avalonia.Controls.UnitTests
topLevel.ApplyTemplate(); topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass(); topLevel.LayoutManager.ExecuteInitialLayoutPass();
target.ApplyTemplate();
target.Measure(Size.Infinity); target.Measure(Size.Infinity);
var initialHeight = target.DesiredSize.Height;
topLevel.Clipboard?.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult(); topLevel.Clipboard?.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult();
RaiseKeyEvent(target, Key.V, KeyModifiers.Control); RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
@ -905,7 +908,10 @@ namespace Avalonia.Controls.UnitTests
RaiseTextEvent(target, Environment.NewLine); RaiseTextEvent(target, Environment.NewLine);
Assert.Equal("ABC", target.Text); target.InvalidateMeasure();
target.Measure(Size.Infinity);
Assert.Equal(initialHeight, target.DesiredSize.Height);
} }
} }
@ -1116,7 +1122,11 @@ namespace Avalonia.Controls.UnitTests
private IControlTemplate CreateTemplate() private IControlTemplate CreateTemplate()
{ {
return new FuncControlTemplate<TextBox>((control, scope) => return new FuncControlTemplate<TextBox>((control, scope) =>
new TextPresenter new ScrollViewer
{
Name = "Part_ScrollViewer",
Template = new FuncControlTemplate<ScrollViewer>(ScrollViewerTests.CreateTemplate),
Content = new TextPresenter
{ {
Name = "PART_TextPresenter", Name = "PART_TextPresenter",
[!!TextPresenter.TextProperty] = new Binding [!!TextPresenter.TextProperty] = new Binding
@ -1133,7 +1143,8 @@ namespace Avalonia.Controls.UnitTests
Priority = BindingPriority.Template, Priority = BindingPriority.Template,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
} }
}.RegisterInNameScope(scope)); }.RegisterInNameScope(scope)
}.RegisterInNameScope(scope));
} }
private static void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers) private static void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers)

Loading…
Cancel
Save