Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -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.
-->
<VersionPrefix>1.1.2</VersionPrefix>
<VersionPrefix>1.1.4</VersionPrefix>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
<https://whiteboard.sqlbi.com> 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
Expand Down
115 changes: 106 additions & 9 deletions src/SQLBI.Whiteboard/PreferencesWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,7 @@ private FrameworkElement CreateSampleChoice(
SettingDescriptor setting,
Func<string, FrameworkElement?> sampleFor)
{
var host = new UniformGrid
{
Rows = 1,
Columns = setting.Choices.Count,
};

var host = new ReflowingSegments();
var segments = new List<ToggleButton>();
foreach (var choice in setting.Choices)
{
Expand All @@ -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
Expand All @@ -263,6 +274,7 @@ private FrameworkElement CreateSampleChoice(
IsChecked = choice.Id == CurrentEnumId(setting),
Tag = choice.Id,
ToolTip = choice.Title,
HorizontalContentAlignment = HorizontalAlignment.Stretch,
};
segment.Click += (_, _) =>
{
Expand All @@ -280,6 +292,91 @@ private FrameworkElement CreateSampleChoice(
return host;
}

/// <summary>
/// A row of drawn choices that takes a second row rather than squeezing its
/// labels. Below <see cref="MinimumSegmentWidth"/> 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.
/// </summary>
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);
Expand Down
6 changes: 3 additions & 3 deletions src/SQLBI.Whiteboard/Themes/Settings.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,9 @@
<Style x:Key="SettingsSampleSegment"
TargetType="ToggleButton">
<Setter Property="Margin"
Value="4,0" />
Value="3,0" />
<Setter Property="Padding"
Value="10,12" />
Value="6,12" />
<Setter Property="Focusable"
Value="False" />
<Setter Property="Cursor"
Expand All @@ -319,7 +319,7 @@
BorderBrush="#FFE4E6EA"
Background="{DynamicResource SettingsEditorBrush}"
SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="Center" />
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Expand Down