[ImageResizer] Fix issues with blank Width and Height controls (#37373)

* Allow custom preset's dimensions to be blank in the UI while still persisted as 0.

* XAML formatting - reorder namespaces.

* Add "(auto)" text to zero-value Width/Height in Settings. Ensure Width and Height fields in flyout are formatted to empty when their value is 0.
This commit is contained in:
Dave Rayment
2025-02-25 08:23:30 +00:00
committed by GitHub
parent 744316c400
commit a5a354a70f
11 changed files with 309 additions and 27 deletions

View File

@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters;
/// <summary>
/// Converts between double and string for text-based controls bound to Width or Height fields.
/// Optionally returns localized "Auto" text when the underlying value is 0, letting the UI show,
/// for example "(auto) x 1024 pixels".
/// </summary>
public sealed partial class ImageResizerDoubleToAutoConverter : IValueConverter
{
private static readonly string AutoText =
Helpers.ResourceLoaderInstance.ResourceLoader.GetString("ImageResizer_AutoText");
/// <summary>
/// Converts a double to a string, optionally showing "Auto" for 0 values. NaN values are
/// converted to empty strings.
/// </summary>
/// <param name="value">The value to convert from <see cref="double"/> to
/// <see cref="string"/>.</param>
/// <param name="targetType">The conversion target type. <see cref="string"/> here.</param>
/// <param name="parameter">Set to "Auto" to return the localized "Auto" string if the
/// value is 0.</param>
/// <param name="language">Ignored.</param>
/// <returns>The string representation of the passed-in value.</returns>
public object Convert(object value, Type targetType, object parameter, string language) =>
value switch
{
double d => d switch
{
double.NaN => "0",
0 => (string)parameter == "Auto" ? AutoText : "0",
_ => d.ToString(CultureInfo.CurrentCulture),
},
_ => "0",
};
/// <summary>
/// Converts the string representation back to a double, returning 0 if the string is empty,
/// null or not a valid number in the specified culture.
/// </summary>
/// <param name="value">The string value to convert.</param>
/// <param name="targetType">The conversion target type. <see cref="double"/> here.</param>
/// <param name="parameter">Converter parameter. Unused.</param>
/// <param name="language">Ignored.</param>
/// <returns>The corresponding double value.</returns>
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
value switch
{
null or "" => 0.0,
string text when double.TryParse(text, NumberStyles.Any, CultureInfo.CurrentCulture, out double result) => result,
_ => 0.0,
};
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters;
public partial class ImageResizerNumberBoxValueConverter : IValueConverter
{
/// <summary>
/// Converts the underlying double value to a display-friendly format. Ensures that NaN values
/// are not propagated to the UI.
/// </summary>
public object Convert(object value, Type targetType, object parameter, string language) =>
value is double d && double.IsNaN(d) ? 0.0 : value;
/// <summary>
/// Converts the user input back to the underlying double value. If the input is not a valid
/// number, a double with value 0 is returned.
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
value switch
{
null => 0.0,
double d when double.IsNaN(d) => 0.0,
string str when !double.TryParse(str, out _) => 0.0,
_ => value,
};
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Converters;
public partial class ImageResizerZeroToEmptyStringNumberFormatter
{
public string Format(long value) => throw new NotImplementedException();
public string Format(ulong value) => throw new NotImplementedException();
public string Format(double value) => throw new NotImplementedException();
public string FormatDouble(double? value) => value switch
{
null => string.Empty,
0 => string.Empty,
_ => value.Value.ToString(CultureInfo.CurrentCulture),
};
public double? ParseDouble(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return 0.0;
}
return double.TryParse(text, NumberStyles.Any, CultureInfo.CurrentCulture, out double result) ? result : 0.0;
}
public long? ParseInt(string text) => throw new NotImplementedException();
public ulong? ParseUInt(string text) => throw new NotImplementedException();
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Controls;
public partial class ImageResizerDimensionsNumberBox : NumberBox
{
public ImageResizerDimensionsNumberBox()
{
this.Loaded += (_, _) => UpdateDisplayText();
this.ValueChanged += (_, _) => UpdateDisplayText();
this.GotFocus += (s, e) =>
{
// Show "0" in the UI when focused on the empty value. This ensures that the spinbutton
// controls are usable.
if (Value is double.NaN)
{
Value = 0.0;
}
};
this.LostFocus += (_, _) => UpdateDisplayText();
}
private void UpdateDisplayText()
{
if (FocusState == FocusState.Unfocused && Value == 0)
{
Text = string.Empty;
}
}
}

View File

@@ -19,6 +19,9 @@
<converters:ImageResizerUnitToStringConverter x:Key="ImageResizerUnitToStringConverter" />
<converters:ImageResizerUnitToIntConverter x:Key="ImageResizerUnitToIntConverter" />
<converters:ImageResizerSizeToAccessibleTextConverter x:Key="ImageResizerSizeToAccessibleTextConverter" />
<converters:ImageResizerDoubleToAutoConverter x:Key="ImageResizerDoubleToAutoConverter" />
<converters:ImageResizerNumberBoxValueConverter x:Key="ImageResizerNumberBoxValueConverter" />
<converters:ImageResizerZeroToEmptyStringNumberFormatter x:Key="ImageResizerZeroToEmptyStringNumberFormatter" />
<toolkitconverters:BoolToObjectConverter
x:Key="BoolToComboBoxIndexConverter"
FalseValue="1"
@@ -78,7 +81,7 @@
Margin="0,0,4,0"
FontWeight="SemiBold"
Style="{ThemeResource SecondaryTextStyle}"
Text="{x:Bind Width, Mode=OneWay}" />
Text="{x:Bind Width, Mode=OneWay, Converter={StaticResource ImageResizerDoubleToAutoConverter}, ConverterParameter=Auto}" />
<TextBlock
Margin="0,5,4,0"
AutomationProperties.AccessibilityView="Raw"
@@ -91,7 +94,7 @@
Margin="0,0,4,0"
FontWeight="SemiBold"
Style="{ThemeResource SecondaryTextStyle}"
Text="{x:Bind Height, Mode=OneWay}"
Text="{x:Bind Height, Mode=OneWay, Converter={StaticResource ImageResizerDoubleToAutoConverter}, ConverterParameter=Auto}"
Visibility="{x:Bind IsHeightUsed, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock
Margin="0,0,4,0"
@@ -136,20 +139,20 @@
</ComboBox>
<StackPanel Orientation="Horizontal" Spacing="8">
<NumberBox
<controls:ImageResizerDimensionsNumberBox
x:Uid="ImageResizer_Width"
Width="116"
Minimum="0"
SpinButtonPlacementMode="Compact"
Value="{x:Bind Width, Mode=TwoWay}" />
Value="{x:Bind Width, Mode=TwoWay, Converter={StaticResource ImageResizerNumberBoxValueConverter}}" />
<NumberBox
<controls:ImageResizerDimensionsNumberBox
x:Uid="ImageResizer_Height"
Width="116"
Minimum="0"
SpinButtonPlacementMode="Compact"
Visibility="{x:Bind IsHeightUsed, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}"
Value="{x:Bind Height, Mode=TwoWay}" />
Value="{x:Bind Height, Mode=TwoWay, Converter={StaticResource ImageResizerNumberBoxValueConverter}}" />
</StackPanel>
<ComboBox

View File

@@ -1193,6 +1193,10 @@
<value>TIFF compression</value>
<comment>{Locked="TIFF"}</comment>
</data>
<data name="ImageResizer_AutoText" xml:space="preserve">
<value>(auto)</value>
<comment>Displayed on the preset card when the Width or Height property is zero. The same as "Input_Auto" in the ImageResizerUI project's resources.</comment>
</data>
<data name="File.Header" xml:space="preserve">
<value>File</value>
<comment>as in a computer file</comment>