Public Access
Give the rest of the button shapes their content alignment too
The sweep the ghost/accent/danger fix implied: navuser, poprow, panechip, chiptoggle and choice each set VerticalContentAlignment now, because each set everything else about how its content sits and left that one to Avalonia's Stretch default. None of them was misbehaving. Every one is content-sized everywhere it is used today, so Stretch and Center agreed and this moves nothing — 113 buttons across 29 screens and cards measured byte-identical before and after, the strips that have no height of their own included. What it buys is that the day one of them is given a height, it is already right rather than quietly drawing its label in the top third. flat and cat are deliberately NOT swept in, and the reasoning that would sweep them is exactly the trap. flat carries the titlebar's search pill, a Border.searchpill with no height of its own that is meant to fill all 35 pixels of its button — the usage states HorizontalContentAlignment="Stretch" and takes the vertical default to match. cat carries the keychain rail's accent strip, a Border.rowmark whose style sets Width="2" and no height at all, "at full row height" by its own remark. Centring either from the style shrinks a pill and a strip that are correct today. AStretchingShapeStillFillsItsButton pins both, and fails when flat is centred. ButtonCaptionTests covers the five new shapes on the existing rule. Its stretch-fill assertion reads the content slot off the presenter rather than recomputing it from the button's Padding: the shapes differ in whether their presenter also draws a border, and a hand-rolled sum was two pixels out on Button.cat for that reason.
This commit is contained in:
@@ -153,6 +153,7 @@
|
|||||||
<Setter Property="CornerRadius" Value="6" />
|
<Setter Property="CornerRadius" Value="6" />
|
||||||
<Setter Property="Padding" Value="6,2" />
|
<Setter Property="Padding" Value="6,2" />
|
||||||
<Setter Property="MinHeight" Value="0" />
|
<Setter Property="MinHeight" Value="0" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
|
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontWeight" Value="Medium" />
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
@@ -463,6 +464,7 @@
|
|||||||
<Setter Property="Padding" Value="13,7" />
|
<Setter Property="Padding" Value="13,7" />
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
<Setter Property="CornerRadius" Value="12" />
|
<Setter Property="CornerRadius" Value="12" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.navuser /template/ ContentPresenter#PART_ContentPresenter">
|
<Style Selector="Button.navuser /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
@@ -482,6 +484,7 @@
|
|||||||
<Style Selector="Button.poprow">
|
<Style Selector="Button.poprow">
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
<Setter Property="Padding" Value="11,4" />
|
<Setter Property="Padding" Value="11,4" />
|
||||||
<Setter Property="CornerRadius" Value="8" />
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
<Setter Property="MinHeight" Value="20" />
|
<Setter Property="MinHeight" Value="20" />
|
||||||
@@ -671,6 +674,7 @@
|
|||||||
-->
|
-->
|
||||||
<Style Selector="Button.choice">
|
<Style Selector="Button.choice">
|
||||||
<Setter Property="Padding" Value="10,5" />
|
<Setter Property="Padding" Value="10,5" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="10.5" />
|
<Setter Property="FontSize" Value="10.5" />
|
||||||
<Setter Property="LetterSpacing" Value="0.5" />
|
<Setter Property="LetterSpacing" Value="0.5" />
|
||||||
@@ -1120,6 +1124,7 @@
|
|||||||
<Style Selector="Button.panechip">
|
<Style Selector="Button.panechip">
|
||||||
<Setter Property="Padding" Value="8,4" />
|
<Setter Property="Padding" Value="8,4" />
|
||||||
<Setter Property="MinHeight" Value="0" />
|
<Setter Property="MinHeight" Value="0" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="11" />
|
<Setter Property="FontSize" Value="11" />
|
||||||
<Setter Property="FontWeight" Value="Medium" />
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Presenters;
|
||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
using Avalonia.VisualTree;
|
using Avalonia.VisualTree;
|
||||||
|
|
||||||
namespace DodoSSH.Client.App.Layout.Tests;
|
namespace DodoSSH.Client.App.Layout.Tests;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The three button shapes centre their caption inside a button taller than the caption.
|
/// The button shapes that centre their caption do, and the two that deliberately do not still fill.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>
|
/// <para>
|
||||||
@@ -41,6 +42,15 @@ namespace DodoSSH.Client.App.Layout.Tests;
|
|||||||
/// action — so an absolute expectation would be a font metric written down in a test file, and it would move
|
/// action — so an absolute expectation would be a font metric written down in a test file, and it would move
|
||||||
/// the day the face does. "Centred" survives both.
|
/// the day the face does. "Centred" survives both.
|
||||||
/// </para>
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The five shapes beyond the original three were swept in afterwards, and none of them was misbehaving
|
||||||
|
/// when it was: every one is content-sized everywhere it is used today, so <c>Stretch</c> and <c>Center</c>
|
||||||
|
/// agreed and the change moved nothing — 113 buttons across 29 screens measured byte-identical before and
|
||||||
|
/// after. What the sweep buys is that the day any of them is given a height, it is already right. That is
|
||||||
|
/// also why <see cref="AStretchingShapeStillFillsItsButton"/> matters more than it looks: the same
|
||||||
|
/// reasoning applied to <c>flat</c> or <c>cat</c> would break a pill and a strip that are currently
|
||||||
|
/// correct.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class ButtonCaptionTests
|
public sealed class ButtonCaptionTests
|
||||||
{
|
{
|
||||||
@@ -61,6 +71,11 @@ public sealed class ButtonCaptionTests
|
|||||||
[InlineData("ghost")]
|
[InlineData("ghost")]
|
||||||
[InlineData("accent")]
|
[InlineData("accent")]
|
||||||
[InlineData("danger")]
|
[InlineData("danger")]
|
||||||
|
[InlineData("navuser")]
|
||||||
|
[InlineData("poprow")]
|
||||||
|
[InlineData("panechip")]
|
||||||
|
[InlineData("chiptoggle")]
|
||||||
|
[InlineData("choice")]
|
||||||
public async Task ACaptionIsCentredInAButtonTallerThanItself(string shape)
|
public async Task ACaptionIsCentredInAButtonTallerThanItself(string shape)
|
||||||
{
|
{
|
||||||
await MeasureAsync(
|
await MeasureAsync(
|
||||||
@@ -96,6 +111,72 @@ public sealed class ButtonCaptionTests
|
|||||||
"the caption should sit high, which is the defect this suite was written for"));
|
"the caption should sit high, which is the defect this suite was written for"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <c>flat</c> and <c>cat</c> are excluded from the rule above, and must stay excluded.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Both stretch their content on purpose, and both would be silently broken by a later pass that
|
||||||
|
/// "finished" the sweep the rest of these classes belong to — which is exactly why this is a test and
|
||||||
|
/// not a comment.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// <c>flat</c> carries the titlebar's search pill, a <c>Border.searchpill</c> with no height of its own
|
||||||
|
/// that is meant to fill all 35 pixels of the button; the usage states
|
||||||
|
/// <c>HorizontalContentAlignment="Stretch"</c> and relies on the vertical default matching it. Centring
|
||||||
|
/// from the style would shrink that pill to its caption's line box inside a button twice as tall.
|
||||||
|
/// <c>cat</c> carries the keychain rail's accent strip, a <c>Border.rowmark</c> whose style sets
|
||||||
|
/// <c>Width="2"</c> and no height at all — "at full row height", says the rule's own remark — so its
|
||||||
|
/// height is the stretch and nothing else.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Asserted as "the content fills the button", not as "the caption is off-centre": what these two need
|
||||||
|
/// is the fill, and a test phrased the other way would still pass if the fill broke in some new way.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
[Theory]
|
||||||
|
[InlineData("flat")]
|
||||||
|
[InlineData("cat")]
|
||||||
|
public async Task AStretchingShapeStillFillsItsButton(string shape)
|
||||||
|
{
|
||||||
|
await LayoutHarness.OnTheUiThreadAsync(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
// A bare Border is what both of them actually hold: no height, sized only by its parent.
|
||||||
|
var fill = new Border();
|
||||||
|
var button = new Button { Content = fill, Height = FixedHeight };
|
||||||
|
button.Classes.Add(shape);
|
||||||
|
|
||||||
|
var window = LayoutHarness.HostAtMinimumSize(
|
||||||
|
button, LayoutHarness.MinimumWidth, LayoutHarness.MinimumHeight);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// The slot read off the presenter rather than recomputed from the button's Padding:
|
||||||
|
// these shapes differ in whether their presenter also draws a border, and a hand-rolled
|
||||||
|
// sum was two pixels out on Button.cat for exactly that reason.
|
||||||
|
var presenter = button.GetVisualDescendants()
|
||||||
|
.OfType<ContentPresenter>()
|
||||||
|
.Single(p => string.Equals(p.Name, "PART_ContentPresenter", StringComparison.Ordinal));
|
||||||
|
|
||||||
|
var slot = presenter.Bounds.Height
|
||||||
|
- presenter.Padding.Top - presenter.Padding.Bottom
|
||||||
|
- presenter.BorderThickness.Top - presenter.BorderThickness.Bottom;
|
||||||
|
|
||||||
|
fill.Bounds.Height.ShouldBe(
|
||||||
|
slot,
|
||||||
|
Tolerance,
|
||||||
|
$"Button.{shape} must stretch its content — the search pill and the rail's accent "
|
||||||
|
+ "strip have no height of their own");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
window.Close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Token);
|
||||||
|
}
|
||||||
|
|
||||||
private static Task MeasureAsync(
|
private static Task MeasureAsync(
|
||||||
string shape, VerticalAlignment? alignment, Action<double, double> assert) =>
|
string shape, VerticalAlignment? alignment, Action<double, double> assert) =>
|
||||||
LayoutHarness.OnTheUiThreadAsync(
|
LayoutHarness.OnTheUiThreadAsync(
|
||||||
|
|||||||
Reference in New Issue
Block a user