Skip to content
Open
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
240 changes: 218 additions & 22 deletions src/Calculator/Views/NumberPad.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
using CalculatorApp.ViewModel.Common;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
using Windows.UI.Xaml.Media;

namespace CalculatorApp
{
public sealed partial class NumberPad : UserControl
{
private bool m_isErrorVisualState;

// Ancestor grid column mapping state
private Grid _ancestorGrid;
private int _ancestorStartColumn = -1;
private int _ancestorSpan = 0;
private bool _applyingColumnMap;

public NumberPad()
{
m_isErrorVisualState = false;
InitializeComponent();

var localizationSettings = LocalizationSettings.GetInstance();

DecimalSeparatorButton.Content = localizationSettings.GetDecimalSeparator();
Num0Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('0');
Num1Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('1');
Expand All @@ -27,31 +37,220 @@ public NumberPad()
Num7Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('7');
Num8Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('8');
Num9Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('9');

Loaded += NumberPad_Loaded;
}

public Windows.UI.Xaml.Style ButtonStyle
private async void NumberPad_Loaded(object sender, RoutedEventArgs e)
{
get => (Windows.UI.Xaml.Style)GetValue(ButtonStyleProperty);
set => SetValue(ButtonStyleProperty, value);
InitializeAncestorColumnBinding();
// Wait a layout pass so ancestor ActualWidth values are valid
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { });
ApplyAncestorColumnWidths();
}

private void InitializeAncestorColumnBinding()
{
var (grid, elementWithIndex, colIdx) = GetAncestorGridAndColumnIndex();
if (grid == null)
return;

_ancestorGrid = grid;
int span = 1;
if (elementWithIndex != null)
span = Math.Max(1, Grid.GetColumnSpan(elementWithIndex));

_ancestorStartColumn = Math.Max(0, colIdx);
_ancestorSpan = span;

_ancestorGrid.SizeChanged -= AncestorGrid_SizeChanged;
_ancestorGrid.SizeChanged += AncestorGrid_SizeChanged;
}

private async void AncestorGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
// Defer to let inner layout settle
await Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => ApplyAncestorColumnWidths());
}

private void ApplyAncestorColumnWidths()
{
if (_ancestorGrid == null || _applyingColumnMap)
return;

int ancestorColumnCount = _ancestorGrid.ColumnDefinitions.Count;
if (ancestorColumnCount == 0 || _ancestorStartColumn >= ancestorColumnCount)
return;

int effectiveSpan = Math.Min(_ancestorSpan, ancestorColumnCount - _ancestorStartColumn);
if (effectiveSpan <= 0)
return;

var slice = new List<double>(effectiveSpan);
for (int i = 0; i < effectiveSpan; i++)
{
slice.Add(_ancestorGrid.ColumnDefinitions[_ancestorStartColumn + i].ActualWidth);
}

// If any width is zero (not measured yet) skip this attempt
if (slice.Any(w => w <= 0.0))
return;

var targetColumns = Root.ColumnDefinitions;
if (targetColumns.Count == 0)
return;

_applyingColumnMap = true;
try
{
if (targetColumns.Count == effectiveSpan)
{
for (int i = 0; i < effectiveSpan; i++)
{
double w = slice[i];
var newWidth = new GridLength(w, GridUnitType.Pixel);
if (!GridLengthEquals(targetColumns[i].Width, newWidth))
targetColumns[i].Width = newWidth;
}
}
else
{
double total = slice.Sum();
if (total <= 0) return;
for (int i = 0; i < targetColumns.Count; i++)
{
double ratio = slice[i % effectiveSpan] / total;
ratio = Math.Max(ratio, 0.0001);
// Scale ratios so sum ~ targetColumns.Count
double starValue = ratio * targetColumns.Count;
// Bound star value
starValue = Math.Max(0.0001, starValue);
var newWidth = new GridLength(starValue, GridUnitType.Star);
if (!GridLengthEquals(targetColumns[i].Width, newWidth))
targetColumns[i].Width = newWidth;
}
}
}
finally
{
_applyingColumnMap = false;
}
}

private static bool GridLengthEquals(GridLength a, GridLength b)
{
if (a.GridUnitType != b.GridUnitType) return false;
if (a.GridUnitType == GridUnitType.Pixel)
return Math.Abs(a.Value - b.Value) < 0.5;
return Math.Abs(a.Value - b.Value) < 0.0001;
}

#region Ancestor Grid Introspection (existing)

public async Task<IReadOnlyList<double>> GetAncestorGridColumnWidthsAsync()
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { });
var grid = FindAncestorGridWithColumns(this);
if (grid == null || grid.ColumnDefinitions.Count == 0)
return Array.Empty<double>();

return grid.ColumnDefinitions.Select(cd => cd.ActualWidth).ToList().AsReadOnly();
}

// Using a DependencyProperty as the backing store for ButtonStyle. This enables animation, styling, binding, etc...
public (Grid grid, FrameworkElement elementWithIndex, int columnIndex) GetAncestorGridAndColumnIndex()
{
FrameworkElement elementWithIndex = null;
int columnIndex = -1;

Grid targetGrid = FindAncestorGridWithColumns(this);
if (targetGrid == null)
return (null, null, -1);

DependencyObject current = this;
while (current != null && current != targetGrid)
{
if (current is FrameworkElement fe)
{
int idx = Grid.GetColumn(fe);
elementWithIndex = fe;
columnIndex = idx;
}
current = VisualTreeHelper.GetParent(current);
}

return (targetGrid, elementWithIndex, columnIndex);
}

public IDictionary<int, double> GetAncestorButtonColumnWidths()
{
var (grid, _, _) = GetAncestorGridAndColumnIndex();
if (grid == null)
return new Dictionary<int, double>();

if (grid.ColumnDefinitions.Count > 0)
{
return grid.ColumnDefinitions
.Select((cd, i) => new { i, cd.ActualWidth })
.ToDictionary(p => p.i, p => p.ActualWidth);
}

var result = new Dictionary<int, double>();
foreach (var btn in grid.Children.OfType<Controls.CalculatorButton>())
{
int col = Grid.GetColumn(btn);
double w = btn.ActualWidth;
if (!result.ContainsKey(col) || w > result[col])
result[col] = w;
}
return result;
}

private static Grid FindAncestorGridWithColumns(DependencyObject start)
{
var current = start;
while (current != null)
{
current = VisualTreeHelper.GetParent(current);
if (current is Grid g && g.ColumnDefinitions.Count > 0)
return g;
}
return null;
}

private static bool IsDescendant(FrameworkElement root, FrameworkElement target)
{
if (root == target) return true;
int count = VisualTreeHelper.GetChildrenCount(root);
for (int i = 0; i < count; i++)
{
if (VisualTreeHelper.GetChild(root, i) is FrameworkElement fe && IsDescendant(fe, target))
return true;
}
return false;
}

#endregion

public Style ButtonStyle
{
get => (Style)GetValue(ButtonStyleProperty);
set => SetValue(ButtonStyleProperty, value);
}
public static readonly DependencyProperty ButtonStyleProperty =
DependencyProperty.Register(nameof(ButtonStyle), typeof(Windows.UI.Xaml.Style), typeof(NumberPad), new PropertyMetadata(default(Windows.UI.Xaml.Style)));
DependencyProperty.Register(nameof(ButtonStyle), typeof(Style), typeof(NumberPad), new PropertyMetadata(default(Style)));

public CalculatorApp.ViewModel.Common.NumberBase CurrentRadixType
public NumberBase CurrentRadixType
{
get => (CalculatorApp.ViewModel.Common.NumberBase)GetValue(CurrentRadixTypeProperty);
get => (NumberBase)GetValue(CurrentRadixTypeProperty);
set => SetValue(CurrentRadixTypeProperty, value);
}

// Using a DependencyProperty as the backing store for CurrentRadixType. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentRadixTypeProperty =
DependencyProperty.Register(nameof(CurrentRadixType), typeof(CalculatorApp.ViewModel.Common.NumberBase), typeof(NumberPad), new PropertyMetadata(CalculatorApp.ViewModel.Common.NumberBase.DecBase, (sender, args) =>
{
var self = (NumberPad)sender;
self.OnCurrentRadixTypePropertyChanged((NumberBase)args.OldValue, (NumberBase)args.NewValue);
}));
DependencyProperty.Register(nameof(CurrentRadixType), typeof(NumberBase), typeof(NumberPad),
new PropertyMetadata(NumberBase.DecBase, (sender, args) =>
{
var self = (NumberPad)sender;
self.OnCurrentRadixTypePropertyChanged((NumberBase)args.OldValue, (NumberBase)args.NewValue);
}));

public bool IsErrorVisualState
{
Expand All @@ -61,8 +260,7 @@ public bool IsErrorVisualState
if (m_isErrorVisualState != value)
{
m_isErrorVisualState = value;
string newState = m_isErrorVisualState ? "ErrorLayout" : "NoErrorLayout";
VisualStateManager.GoToState(this, newState, false);
VisualStateManager.GoToState(this, m_isErrorVisualState ? "ErrorLayout" : "NoErrorLayout", false);
}
}
}
Expand Down Expand Up @@ -97,7 +295,5 @@ private void OnCurrentRadixTypePropertyChanged(NumberBase oldValue, NumberBase n
Num9Button.IsEnabled = false;
}
}

private bool m_isErrorVisualState;
}
}