diff --git a/src/Calculator/Views/NumberPad.xaml.cs b/src/Calculator/Views/NumberPad.xaml.cs index 0f766bdc2..f98f5a207 100644 --- a/src/Calculator/Views/NumberPad.xaml.cs +++ b/src/Calculator/Views/NumberPad.xaml.cs @@ -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'); @@ -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(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> GetAncestorGridColumnWidthsAsync() + { + await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { }); + var grid = FindAncestorGridWithColumns(this); + if (grid == null || grid.ColumnDefinitions.Count == 0) + return Array.Empty(); + + 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 GetAncestorButtonColumnWidths() + { + var (grid, _, _) = GetAncestorGridAndColumnIndex(); + if (grid == null) + return new Dictionary(); + + 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(); + foreach (var btn in grid.Children.OfType()) + { + 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 { @@ -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); } } } @@ -97,7 +295,5 @@ private void OnCurrentRadixTypePropertyChanged(NumberBase oldValue, NumberBase n Num9Button.IsEnabled = false; } } - - private bool m_isErrorVisualState; } }