diff --git a/Directory.Build.props b/Directory.Build.props index 3a08133..1ab2674 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,7 @@ scripts/build-installer.ps1 both read it from here, so releasing is a reviewed change to this line rather than an edit in a pipeline variable group. --> - 1.1.2 + 1.1.4 latest enable enable diff --git a/TODO.md b/TODO.md index 6dc7195..2a54152 100644 --- a/TODO.md +++ b/TODO.md @@ -16,7 +16,7 @@ The delivery chain works end to end: a merge to `main` builds, signs, and publis pre-release to GitHub Releases, and one approval promotes that same build to a release. reads its download links from the release manifest deployed beside it and needs no edit per release. The current product version is `VersionPrefix` in `Directory.Build.props` -(1.1.2). Identity version for the Store package is `VersionPrefix.0` (`1.1.2.0`). +(1.1.4). Identity version for the Store package is `VersionPrefix.0` (`1.1.4.0`). Declaring that number is decision 20 in [docs/decisions.md](docs/decisions.md). What 1.0 was waiting on shipped during 0.9.x: Preferences, `.wimport`, Explorer and VS Code diff --git a/src/SQLBI.Whiteboard/PreferencesWindow.xaml.cs b/src/SQLBI.Whiteboard/PreferencesWindow.xaml.cs index 079d2f7..10b823a 100644 --- a/src/SQLBI.Whiteboard/PreferencesWindow.xaml.cs +++ b/src/SQLBI.Whiteboard/PreferencesWindow.xaml.cs @@ -229,12 +229,7 @@ private FrameworkElement CreateSampleChoice( SettingDescriptor setting, Func sampleFor) { - var host = new UniformGrid - { - Rows = 1, - Columns = setting.Choices.Count, - }; - + var host = new ReflowingSegments(); var segments = new List(); foreach (var choice in setting.Choices) { @@ -243,17 +238,33 @@ private FrameworkElement CreateSampleChoice( continue; } - var content = new StackPanel { HorizontalAlignment = HorizontalAlignment.Center }; + var content = new StackPanel { HorizontalAlignment = HorizontalAlignment.Stretch }; + + // The sample is drawn at a fixed size and then allowed to shrink + // with the column. Left to its own width it overflowed a narrowed + // dialog and was clipped, edges first, which is worse than small. sample.HorizontalAlignment = HorizontalAlignment.Center; - content.Children.Add(sample); + content.Children.Add(new Viewbox + { + Child = sample, + Stretch = Stretch.Uniform, + StretchDirection = StretchDirection.DownOnly, + MaxWidth = sample.Width > 0 ? sample.Width : double.PositiveInfinity, + HorizontalAlignment = HorizontalAlignment.Center, + }); + + // Wrapping runs out at the longest word, and a clipped word reads as + // a different one. Past that point the picture carries the meaning, + // and the tooltip still spells it out. content.Children.Add(new TextBlock { Style = (Style)FindResource("SettingsValueLabel"), Text = choice.Title, Margin = new Thickness(0, 8, 0, 0), TextWrapping = TextWrapping.Wrap, + TextTrimming = TextTrimming.CharacterEllipsis, TextAlignment = TextAlignment.Center, - HorizontalAlignment = HorizontalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Stretch, }); var segment = new ToggleButton @@ -263,6 +274,7 @@ private FrameworkElement CreateSampleChoice( IsChecked = choice.Id == CurrentEnumId(setting), Tag = choice.Id, ToolTip = choice.Title, + HorizontalContentAlignment = HorizontalAlignment.Stretch, }; segment.Click += (_, _) => { @@ -280,6 +292,91 @@ private FrameworkElement CreateSampleChoice( return host; } + /// + /// A row of drawn choices that takes a second row rather than squeezing its + /// labels. Below a segment cannot hold the + /// longest word in a label - "Bottom" is 44px at the 12px label size, and + /// the padding and border take the rest - and a word too long for its line + /// overflows and is clipped rather than wrapped or trimmed, so it reads as a + /// different word. + /// + private sealed class ReflowingSegments : Panel + { + private const double MinimumSegmentWidth = 82; + + // The width a row is measured against and the width it is finally given + // are not the same here: measurement arrives far narrower than the + // arrangement, so a count settled during measure put five choices on + // three columns in a row with room for all five. Arrange has the real + // width, so that is what the count is taken from, and the children are + // measured again if it disagrees with what measure assumed. + protected override Size MeasureOverride(Size availableSize) + { + var count = InternalChildren.Count; + if (count == 0) + { + return default; + } + + var columns = ColumnsFor(availableSize.Width, count); + var cell = MeasureCells(columns, availableSize.Width, count); + return new Size( + double.IsInfinity(availableSize.Width) + ? cell.Width * columns + : availableSize.Width, + cell.Height * RowsFor(count, columns)); + } + + protected override Size ArrangeOverride(Size finalSize) + { + var count = InternalChildren.Count; + if (count == 0) + { + return finalSize; + } + + var columns = ColumnsFor(finalSize.Width, count); + MeasureCells(columns, finalSize.Width, count); + + var cellWidth = finalSize.Width / columns; + var cellHeight = finalSize.Height / RowsFor(count, columns); + for (var index = 0; index < count; index++) + { + InternalChildren[index].Arrange(new Rect( + index % columns * cellWidth, + index / columns * cellHeight, + cellWidth, + cellHeight)); + } + + return finalSize; + } + + private static int ColumnsFor(double width, int count) => Math.Clamp( + double.IsInfinity(width) ? count : (int)(width / MinimumSegmentWidth), + 1, + count); + + private static int RowsFor(int count, int columns) => + ((count - 1) / columns) + 1; + + private Size MeasureCells(int columns, double width, int count) + { + var cellWidth = double.IsInfinity(width) + ? MinimumSegmentWidth + : width / columns; + var tallest = 0d; + for (var index = 0; index < count; index++) + { + var child = InternalChildren[index]; + child.Measure(new Size(cellWidth, double.PositiveInfinity)); + tallest = Math.Max(tallest, child.DesiredSize.Height); + } + + return new Size(cellWidth, tallest); + } + } + private static readonly Brush SampleInkBrush = Frozen(0xFF374151); private static readonly Brush SampleGhostBrush = Frozen(0x59374151); private static readonly Brush SampleBoardBrush = Frozen(0xFFFFFFFF); diff --git a/src/SQLBI.Whiteboard/Themes/Settings.xaml b/src/SQLBI.Whiteboard/Themes/Settings.xaml index f6921f0..7adf003 100644 --- a/src/SQLBI.Whiteboard/Themes/Settings.xaml +++ b/src/SQLBI.Whiteboard/Themes/Settings.xaml @@ -300,9 +300,9 @@