diff --git a/src/Calculator/AboutFlyout.xaml b/src/Calculator/AboutFlyout.xaml new file mode 100644 index 00000000..e9b16a61 --- /dev/null +++ b/src/Calculator/AboutFlyout.xaml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Calculator/AboutFlyout.xaml.cs b/src/Calculator/AboutFlyout.xaml.cs new file mode 100644 index 00000000..e8d32514 --- /dev/null +++ b/src/Calculator/AboutFlyout.xaml.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using CalculatorApp.Common; +using System; +using System.Diagnostics; +using Windows.ApplicationModel; +using Windows.System; +using Windows.UI.Xaml; + +namespace CalculatorApp +{ + public sealed partial class AboutFlyout + { + // CSHARP_MIGRATION: TODO: + // BUILD_YEAR was a C++/CX macro and may update the value from the pipeline + private const string BUILD_YEAR = "2021"; + + public AboutFlyout() + { + var locService = LocalizationService.GetInstance(); + var resourceLoader = AppResourceProvider.GetInstance(); + + InitializeComponent(); + + Language = locService.GetLanguage(); + + SetVersionString(); + + Header.Text = resourceLoader.GetResourceString("AboutButton/Content"); + + var copyrightText = + LocalizationStringUtil.GetLocalizedString(resourceLoader.GetResourceString("AboutControlCopyright"), BUILD_YEAR); + AboutControlCopyrightRun.Text = copyrightText; + + InitializeContributeTextBlock(); + } + + public void SetDefaultFocus() + { + AboutFlyoutEULA.Focus(FocusState.Programmatic); + } + + private void FeedbackButton_Click(object sender, RoutedEventArgs e) + { + PackageVersion version = Package.Current.Id.Version; + string versionNumber = "Version "; + versionNumber = versionNumber + version.Major + "." + version.Minor + "." + version.Build + "." + version.Revision; + _ = Launcher.LaunchUriAsync(new Uri("windows-feedback:?contextid=130&metadata=%7B%22Metadata%22:[%7B%22AppBuild%22:%22" + versionNumber + "%22%7D]%7D")); + } + + private void SetVersionString() + { + PackageVersion version = Package.Current.Id.Version; + string appName = AppResourceProvider.GetInstance().GetResourceString("AppName"); + AboutFlyoutVersion.Text = appName + " " + version.Major + "." + version.Minor + "." + version.Build + "." + version.Revision; + } + + private void InitializeContributeTextBlock() + { + var resProvider = AppResourceProvider.GetInstance(); + string contributeHyperlinkText = resProvider.GetResourceString("AboutFlyoutContribute"); + + // The resource string has the 'GitHub' hyperlink wrapped with '%HL%'. + // Break the string and assign pieces appropriately. + string delimiter = "%HL%"; + int delimiterLength = delimiter.Length; + + // Find the delimiters. + int firstSplitPosition = contributeHyperlinkText.IndexOf(delimiter, 0); + Debug.Assert(firstSplitPosition != -1); + int secondSplitPosition = contributeHyperlinkText.IndexOf(delimiter, firstSplitPosition + 1); + Debug.Assert(secondSplitPosition != -1); + int hyperlinkTextLength = secondSplitPosition - (firstSplitPosition + delimiterLength); + + // Assign pieces. + var contributeTextBeforeHyperlink = contributeHyperlinkText.Substring(0, firstSplitPosition); + var contributeTextLink = contributeHyperlinkText.Substring(firstSplitPosition + delimiterLength, hyperlinkTextLength); + var contributeTextAfterHyperlink = contributeHyperlinkText.Substring(secondSplitPosition + delimiterLength); + + ContributeRunBeforeLink.Text = contributeTextBeforeHyperlink; + ContributeRunLink.Text = contributeTextLink; + ContributeRunAfterLink.Text = contributeTextAfterHyperlink; + } + } +} diff --git a/src/Calculator/Assets/CalculatorSplashScreen.scale-400.png b/src/Calculator/Assets/CalculatorSplashScreen.scale-400.png new file mode 100644 index 00000000..e9dd7b14 Binary files /dev/null and b/src/Calculator/Assets/CalculatorSplashScreen.scale-400.png differ diff --git a/src/Calculator/Controls/CalculationResult.cs b/src/Calculator/Controls/CalculationResult.cs new file mode 100644 index 00000000..471662ee --- /dev/null +++ b/src/Calculator/Controls/CalculationResult.cs @@ -0,0 +1,500 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Controls; +using CalculatorApp.Common; + +using Windows.Devices.Input; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Automation.Peers; +using System.Reflection; + +namespace CalculatorApp +{ + namespace Controls + { + public delegate void SelectedEventHandler(object sender); + + public sealed class CalculationResult : Windows.UI.Xaml.Controls.Control + { + public CalculationResult() + { + m_isScalingText = false; + m_haveCalculatedMax = false; + } + + public double MinFontSize + { + get { return (double)GetValue(MinFontSizeProperty); } + set { SetValue(MinFontSizeProperty, value); } + } + + // Using a DependencyProperty as the backing store for MinFontSize. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MinFontSizeProperty = + DependencyProperty.Register(nameof(MinFontSize), typeof(double), typeof(CalculationResult), new PropertyMetadata(0.0, new PropertyChangedCallback((sender, args) => + { + var self = (CalculationResult)sender; + self.OnMinFontSizePropertyChanged((double)args.OldValue, (double)args.NewValue); + }))); + + public double MaxFontSize + { + get { return (double)GetValue(MaxFontSizeProperty); } + set { SetValue(MaxFontSizeProperty, value); } + } + + // Using a DependencyProperty as the backing store for MaxFontSize. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MaxFontSizeProperty = + DependencyProperty.Register(nameof(MaxFontSize), typeof(double), typeof(CalculationResult), new PropertyMetadata(30.0, new PropertyChangedCallback((sender, args) => + { + var self = (CalculationResult)sender; + self.OnMaxFontSizePropertyChanged((double)args.OldValue, (double)args.NewValue); + }))); + + public Thickness DisplayMargin + { + get { return (Thickness)GetValue(DisplayMarginProperty); } + set { SetValue(DisplayMarginProperty, value); } + } + + // Using a DependencyProperty as the backing store for DisplayMargin. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DisplayMarginProperty = + DependencyProperty.Register(nameof(DisplayMargin), typeof(Thickness), typeof(CalculationResult), new PropertyMetadata(default(Thickness))); + + public bool IsActive + { + get { return (bool)GetValue(IsActiveProperty); } + set { SetValue(IsActiveProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsActive. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsActiveProperty = + DependencyProperty.Register(nameof(IsActive), typeof(bool), typeof(CalculationResult), new PropertyMetadata(default(bool), new PropertyChangedCallback((sender, args) => + { + var self = (CalculationResult)sender; + self.OnIsActivePropertyChanged((bool)args.OldValue, (bool)args.NewValue); + }))); + + public string DisplayValue + { + get { return (string)GetValue(DisplayValueProperty); } + set { SetValue(DisplayValueProperty, value); } + } + + // Using a DependencyProperty as the backing store for DisplayValue. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DisplayValueProperty = + DependencyProperty.Register(nameof(DisplayValue), typeof(string), typeof(CalculationResult), new PropertyMetadata(string.Empty, new PropertyChangedCallback((sender, args) => + { + var self = (CalculationResult)sender; + self.OnDisplayValuePropertyChanged((string)args.OldValue, (string)args.NewValue); + }))); + + public bool IsInError + { + get { return (bool)GetValue(IsInErrorProperty); } + set { SetValue(IsInErrorProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsInError. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsInErrorProperty = + DependencyProperty.Register(nameof(IsInError), typeof(bool), typeof(CalculationResult), new PropertyMetadata(default(bool), new PropertyChangedCallback((sender, args) => + { + var self = (CalculationResult)sender; + self.OnIsInErrorPropertyChanged((bool)args.OldValue, (bool)args.NewValue); + }))); + + public bool IsOperatorCommand + { + get { return (bool)GetValue(IsOperatorCommandProperty); } + set { SetValue(IsOperatorCommandProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsOperatorCommand. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsOperatorCommandProperty = + DependencyProperty.Register(nameof(IsOperatorCommand), typeof(bool), typeof(CalculationResult), new PropertyMetadata(false)); + + public event SelectedEventHandler Selected; + + public void ProgrammaticSelect() + { + RaiseSelectedEvent(); + } + + internal void UpdateTextState() + { + if ((m_textContainer == null) || (m_textBlock == null)) + { + return; + } + + var containerSize = m_textContainer.ActualWidth; + string oldText = m_textBlock.Text; + string newText = DisplayValue; + + // Initiate the scaling operation + // UpdateLayout will keep calling us until we make it through the below 2 if-statements + if (!m_isScalingText || oldText != newText) + { + m_textBlock.Text = newText; + + m_isScalingText = true; + m_haveCalculatedMax = false; + m_textBlock.InvalidateArrange(); + return; + } + if (containerSize > 0) + { + double widthDiff = Math.Abs(m_textBlock.ActualWidth - containerSize); + double fontSizeChange = INCREMENTOFFSET; + + if (widthDiff > WIDTHCUTOFF) + { + fontSizeChange = Math.Min((double)Math.Max((double)Math.Floor(WIDTHTOFONTSCALAR * widthDiff) - WIDTHTOFONTOFFSET, INCREMENTOFFSET), MAXFONTINCREMENT); + } + if (m_textBlock.ActualWidth < containerSize && Math.Abs(m_textBlock.FontSize - MaxFontSize) > FONTTOLERANCE && !m_haveCalculatedMax) + { + ModifyFontAndMargin(m_textBlock, fontSizeChange); + m_textBlock.InvalidateArrange(); + return; + } + if (fontSizeChange < 5) + { + m_haveCalculatedMax = true; + } + if (m_textBlock.ActualWidth >= containerSize && Math.Abs(m_textBlock.FontSize - MinFontSize) > FONTTOLERANCE) + { + ModifyFontAndMargin(m_textBlock, -1 * fontSizeChange); + m_textBlock.InvalidateArrange(); + return; + } + Debug.Assert(m_textBlock.FontSize >= MinFontSize && m_textBlock.FontSize <= MaxFontSize); + m_isScalingText = false; + if (IsOperatorCommand) + { + m_textContainer.ChangeView(0.0, null, null); + } + else + { + m_textContainer.ChangeView(m_textContainer.ExtentWidth - m_textContainer.ViewportWidth, null, null); + } + } + } + public string GetRawDisplayValue() + { + return LocalizationSettings.GetInstance().RemoveGroupSeparators(DisplayValue); + } + + protected override void OnKeyDown(KeyRoutedEventArgs e) + { + switch (e.Key) + { + case Windows.System.VirtualKey.Left: + this.ScrollLeft(); + break; + case Windows.System.VirtualKey.Right: + this.ScrollRight(); + break; + } + } + + protected override void OnApplyTemplate() + { + if (m_textContainer != null) + { + m_textContainer.LayoutUpdated -= OnTextContainerLayoutUpdated; + m_textContainer.SizeChanged -= OnTextContainerSizeChanged; + m_textContainer.ViewChanged -= OnTextContainerOnViewChanged; + } + + if (m_textBlock != null) + { + m_textBlock.SizeChanged -= OnTextBlockSizeChanged; + } + + if (m_scrollLeft != null) + { + m_scrollLeft.Click -= OnScrollLeftClick; + } + + if (m_scrollRight != null) + { + m_scrollRight.Click -= OnScrollRightClick; + } + + m_textContainer = GetTemplateChild("TextContainer") as ScrollViewer; + if (m_textContainer != null) + { + // We want to know when the size of the container changes so + // we can rescale the textbox + m_textContainer.SizeChanged += OnTextContainerSizeChanged; + + m_textContainer.ViewChanged += OnTextContainerOnViewChanged; + + m_textContainer.LayoutUpdated += OnTextContainerLayoutUpdated; + + m_textContainer.ChangeView(m_textContainer.ExtentWidth - m_textContainer.ViewportWidth, null, null); + m_scrollLeft = GetTemplateChild("ScrollLeft") as HyperlinkButton; + if (m_scrollLeft != null) + { + m_scrollLeft.Click += OnScrollLeftClick; + } + m_scrollRight = GetTemplateChild("ScrollRight") as HyperlinkButton; + if (m_scrollRight != null) + { + m_scrollRight.Click += OnScrollRightClick; + } + m_textBlock = GetTemplateChild("NormalOutput") as TextBlock; + if (m_textBlock != null) + { + m_textBlock.Visibility = Visibility.Visible; + m_textBlock.SizeChanged += OnTextBlockSizeChanged; + } + } + UpdateVisualState(); + UpdateTextState(); + } + + protected override void OnTapped(TappedRoutedEventArgs e) + { + this.Focus(FocusState.Programmatic); + RaiseSelectedEvent(); + } + + protected override void OnRightTapped(RightTappedRoutedEventArgs e) + { + var requestedElement = e.OriginalSource; + + if (requestedElement.Equals(m_textBlock as object)) + { + m_textBlock.Focus(FocusState.Programmatic); + } + else + { + this.Focus(FocusState.Programmatic); + } + } + + protected override AutomationPeer OnCreateAutomationPeer() + { + return new CalculationResultAutomationPeer(this); + } + + private void OnIsActivePropertyChanged(bool oldValue, bool newValue) + { + UpdateVisualState(); + } + + private void OnDisplayValuePropertyChanged(string oldValue, string newValue) + { + UpdateTextState(); + } + + private void OnIsInErrorPropertyChanged(bool oldValue, bool newValue) + { + // We need to have a good template for this to work + if (m_textBlock == null) + { + return; + } + + if (newValue) + { + // If there's an error message we need to override the normal display font + // with the font appropriate for this language. This is because the error + // message is localized and therefore can contain characters that are not + // available in the normal font. + // We use UIText as the font type because this is the most common font type to use + m_textBlock.FontFamily = LocalizationService.GetInstance().GetLanguageFontFamilyForType(LanguageFontType.UIText); + } + else + { + // The error result is no longer an error so we will restore the + // value to FontFamily property to the value provided in the style + // for the TextBlock in the template. + m_textBlock.ClearValue(TextBlock.FontFamilyProperty); + } + } + + private void OnMinFontSizePropertyChanged(double oldValue, double newValue) + { + UpdateTextState(); + } + + private void OnMaxFontSizePropertyChanged(double oldValue, double newValue) + { + UpdateTextState(); + } + + private void OnTextContainerSizeChanged(object sender, SizeChangedEventArgs e) + { + UpdateTextState(); + } + + private void OnTextBlockSizeChanged(object sender, SizeChangedEventArgs e) + { + UpdateScrollButtons(); + } + + private void OnTextContainerLayoutUpdated(object sender, object e) + { + if (m_isScalingText) + { + UpdateTextState(); + } + } + + private void OnTextContainerOnViewChanged(object sender, ScrollViewerViewChangedEventArgs e) + { + UpdateScrollButtons(); + } + + private void UpdateVisualState() + { + if (IsActive) + { + VisualStateManager.GoToState(this, "Active", true); + } + else + { + VisualStateManager.GoToState(this, "Normal", true); + } + } + + private void OnScrollLeftClick(object sender, RoutedEventArgs e) + { + ScrollLeft(); + } + + private void OnScrollRightClick(object sender, RoutedEventArgs e) + { + ScrollRight(); + } + + private void ModifyFontAndMargin(TextBlock textBox, double fontChange) + { + double cur = textBox.FontSize; + double newFontSize = 0.0; + double scaleFactor = SCALEFACTOR; + if (m_textContainer.ActualHeight <= HEIGHTCUTOFF) + { + scaleFactor = SMALLHEIGHTSCALEFACTOR; + } + + newFontSize = Math.Min(Math.Max(cur + fontChange, MinFontSize), MaxFontSize); + m_textContainer.Padding = new Thickness(0, 0, 0, scaleFactor * Math.Abs(cur - newFontSize)); + textBox.FontSize = newFontSize; + } + + private void UpdateScrollButtons() + { + if (m_textContainer == null) + { + return; + } + + bool shouldTryFocusScrollRight = false; + if (m_scrollLeft != null) + { + var scrollLeftVisibility = m_textContainer.HorizontalOffset > SCROLL_BUTTONS_APPROXIMATION_RANGE ? Visibility.Visible : Visibility.Collapsed; + + if (scrollLeftVisibility == Visibility.Collapsed) + { + shouldTryFocusScrollRight = m_scrollLeft.Equals(FocusManager.GetFocusedElement()); + } + + m_scrollLeft.Visibility = scrollLeftVisibility; + } + + if (m_scrollRight != null) + { + var scrollRightVisibility = + m_textContainer.HorizontalOffset + m_textContainer.ViewportWidth + SCROLL_BUTTONS_APPROXIMATION_RANGE < m_textContainer.ExtentWidth + ? Visibility.Visible + : Visibility.Collapsed; + + if (scrollRightVisibility == Visibility.Collapsed && m_scrollLeft != null && m_scrollLeft.Visibility == Visibility.Visible + && m_scrollRight.Equals(FocusManager.GetFocusedElement())) + { + // ScrollRight had the focus and will be collapsed, ScrollLeft should get the focus + m_scrollLeft.Focus(FocusState.Programmatic); + } + m_scrollRight.Visibility = scrollRightVisibility; + + if (shouldTryFocusScrollRight && scrollRightVisibility == Visibility.Visible) + { + m_scrollRight.Focus(FocusState.Programmatic); + } + } + } + + private void ScrollLeft() + { + if (m_textContainer == null) + { + return; + } + if (m_textContainer.HorizontalOffset > 0) + { + double offset = m_textContainer.HorizontalOffset - (SCROLL_RATIO * m_textContainer.ViewportWidth); + m_textContainer.ChangeView(offset, null, null); + } + } + + private void ScrollRight() + { + if (m_textContainer == null) + { + return; + } + + if (m_textContainer.HorizontalOffset < m_textContainer.ExtentWidth - m_textContainer.ViewportWidth) + { + double offset = m_textContainer.HorizontalOffset + (SCROLL_RATIO * m_textContainer.ViewportWidth); + m_textContainer.ChangeView(offset, null, null); + } + } + + private void RaiseSelectedEvent() + { + Selected?.Invoke(this); + } + + private const double SCALEFACTOR = 0.357143; + private const double SMALLHEIGHTSCALEFACTOR = 0; + private const double HEIGHTCUTOFF = 100; + private const double INCREMENTOFFSET = 1; + private const double MAXFONTINCREMENT = 5; + private const double WIDTHTOFONTSCALAR = 0.0556513; + private const double WIDTHTOFONTOFFSET = 3; + private const double WIDTHCUTOFF = 50; + private const double FONTTOLERANCE = 0.001; + private const double SCROLL_RATIO = 0.7; + + // We need a safety margin to guarantee we correctly always show/hide ScrollLeft and ScrollRight buttons when necessary. + // In rare cases, ScrollViewer::HorizontalOffset is a little low by a few (sub)pixels when users scroll to one of the extremity + // and no events are launched when they scroll again in the same direction + private const double SCROLL_BUTTONS_APPROXIMATION_RANGE = 4; + + private Windows.UI.Xaml.Controls.ScrollViewer m_textContainer; + private Windows.UI.Xaml.Controls.TextBlock m_textBlock; + private Windows.UI.Xaml.Controls.HyperlinkButton m_scrollLeft; + private Windows.UI.Xaml.Controls.HyperlinkButton m_scrollRight; + private bool m_isScalingText; + private bool m_haveCalculatedMax; + } + } +} diff --git a/src/Calculator/Controls/CalculationResultAutomationPeer.cs b/src/Calculator/Controls/CalculationResultAutomationPeer.cs new file mode 100644 index 00000000..36d1f37a --- /dev/null +++ b/src/Calculator/Controls/CalculationResultAutomationPeer.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Automation.Peers; + +namespace CalculatorApp +{ + namespace Controls + { + public sealed class CalculationResultAutomationPeer : Windows.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer, + Windows.UI.Xaml.Automation.Provider.IInvokeProvider + { + public CalculationResultAutomationPeer(FrameworkElement owner) : base(owner) + { + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Text; + } + + protected override object GetPatternCore(PatternInterface pattern) + { + if (pattern == PatternInterface.Invoke) + { + return this; + } + + return base.GetPatternCore(pattern); + } + + public void Invoke() + { + var owner = (CalculationResult)this.Owner; + owner.ProgrammaticSelect(); + } + } + } +} diff --git a/src/Calculator/Controls/CalculatorButton.cs b/src/Calculator/Controls/CalculatorButton.cs new file mode 100644 index 00000000..fd90f79a --- /dev/null +++ b/src/Calculator/Controls/CalculatorButton.cs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Common; +using CalculatorApp.Controls; +using Windows.System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Data; +using Windows.Foundation.Collections; +using Windows.Storage.Pickers; + +namespace CalculatorApp +{ + namespace Controls + { + public sealed class CalculatorButton : Windows.UI.Xaml.Controls.Button + { + public CalculatorButton() + { + // Set the default bindings for this button, these can be overwritten by Xaml if needed + // These are a replacement for binding in styles + Binding commandBinding = new Binding(); + commandBinding.Path = new PropertyPath("ButtonPressed"); + this.SetBinding(CommandProperty, commandBinding); + } + + public NumbersAndOperatorsEnum ButtonId + { + get { return (NumbersAndOperatorsEnum)GetValue(ButtonIdProperty); } + set { SetValue(ButtonIdProperty, value); } + } + + // Using a DependencyProperty as the backing store for ButtonId. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ButtonIdProperty = + DependencyProperty.Register(nameof(ButtonId), typeof(NumbersAndOperatorsEnum), typeof(CalculatorButton), new PropertyMetadata(default(NumbersAndOperatorsEnum), new PropertyChangedCallback((sender, args) => + { + var self = (CalculatorButton)sender; + self.OnButtonIdPropertyChanged((NumbersAndOperatorsEnum)args.OldValue, (NumbersAndOperatorsEnum)args.NewValue); + }))); + + public string AuditoryFeedback + { + get { return (string)GetValue(AuditoryFeedbackProperty); } + set { SetValue(AuditoryFeedbackProperty, value); } + } + + // Using a DependencyProperty as the backing store for AuditoryFeedback. This enables animation, styling, binding, etc... + public static readonly DependencyProperty AuditoryFeedbackProperty = + DependencyProperty.Register(nameof(AuditoryFeedback), typeof(string), typeof(CalculatorButton), new PropertyMetadata(string.Empty, new PropertyChangedCallback((sender, args) => + { + var self = (CalculatorButton)sender; + self.OnAuditoryFeedbackPropertyChanged((string)args.OldValue, (string)args.NewValue); + }))); + + public Windows.UI.Xaml.Media.Brush HoverBackground + { + get { return (Windows.UI.Xaml.Media.Brush)GetValue(HoverBackgroundProperty); } + set { SetValue(HoverBackgroundProperty, value); } + } + + // Using a DependencyProperty as the backing store for HoverBackground. This enables animation, styling, binding, etc... + public static readonly DependencyProperty HoverBackgroundProperty = + DependencyProperty.Register(nameof(HoverBackground), typeof(Windows.UI.Xaml.Media.Brush), typeof(CalculatorButton), new PropertyMetadata(default(Windows.UI.Xaml.Media.Brush))); + + public Windows.UI.Xaml.Media.Brush HoverForeground + { + get { return (Windows.UI.Xaml.Media.Brush)GetValue(HoverForegroundProperty); } + set { SetValue(HoverForegroundProperty, value); } + } + + // Using a DependencyProperty as the backing store for HoverForeground. This enables animation, styling, binding, etc... + public static readonly DependencyProperty HoverForegroundProperty = + DependencyProperty.Register(nameof(HoverForeground), typeof(Windows.UI.Xaml.Media.Brush), typeof(CalculatorButton), new PropertyMetadata(default(Windows.UI.Xaml.Media.Brush))); + + public Windows.UI.Xaml.Media.Brush PressBackground + { + get { return (Windows.UI.Xaml.Media.Brush)GetValue(PressBackgroundProperty); } + set { SetValue(PressBackgroundProperty, value); } + } + + // Using a DependencyProperty as the backing store for PressBackground. This enables animation, styling, binding, etc... + public static readonly DependencyProperty PressBackgroundProperty = + DependencyProperty.Register(nameof(PressBackground), typeof(Windows.UI.Xaml.Media.Brush), typeof(CalculatorButton), new PropertyMetadata(default(Windows.UI.Xaml.Media.Brush))); + + public Windows.UI.Xaml.Media.Brush PressForeground + { + get { return (Windows.UI.Xaml.Media.Brush)GetValue(PressForegroundProperty); } + set { SetValue(PressForegroundProperty, value); } + } + + // Using a DependencyProperty as the backing store for PressForeground. This enables animation, styling, binding, etc... + public static readonly DependencyProperty PressForegroundProperty = + DependencyProperty.Register(nameof(PressForeground), typeof(Windows.UI.Xaml.Media.Brush), typeof(CalculatorButton), new PropertyMetadata(default(Windows.UI.Xaml.Media.Brush))); + + protected override void OnKeyDown(KeyRoutedEventArgs e) + { + // Ignore the Enter key + if (e.Key == VirtualKey.Enter) + { + return; + } + + base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyRoutedEventArgs e) + { + // Ignore the Enter key + if (e.Key == VirtualKey.Enter) + { + return; + } + + base.OnKeyUp(e); + } + + private void OnButtonIdPropertyChanged(NumbersAndOperatorsEnum oldValue, NumbersAndOperatorsEnum newValue) + { + this.CommandParameter = new CalculatorButtonPressedEventArgs(AuditoryFeedback, newValue); + } + + private void OnAuditoryFeedbackPropertyChanged(string oldValue, string newValue) + { + this.CommandParameter = new CalculatorButtonPressedEventArgs(newValue, ButtonId); + } + } + } +} diff --git a/src/Calculator/Controls/OperatorPanelButton.cs b/src/Calculator/Controls/OperatorPanelButton.cs new file mode 100644 index 00000000..b6be9236 --- /dev/null +++ b/src/Calculator/Controls/OperatorPanelButton.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; + +using Windows.Foundation; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +using CalculatorApp; +using CalculatorApp.Common; +using CalculatorApp.Controls; + +namespace CalculatorApp +{ + namespace Controls + { + public sealed class OperatorPanelButton : Windows.UI.Xaml.Controls.Primitives.ToggleButton + { + public OperatorPanelButton() + { + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + // Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register(nameof(Text), typeof(string), typeof(OperatorPanelButton), new PropertyMetadata(string.Empty)); + + public string Glyph + { + get { return (string)GetValue(GlyphProperty); } + set { SetValue(GlyphProperty, value); } + } + + // Using a DependencyProperty as the backing store for Glyph. This enables animation, styling, binding, etc... + public static readonly DependencyProperty GlyphProperty = + DependencyProperty.Register(nameof(Glyph), typeof(string), typeof(OperatorPanelButton), new PropertyMetadata(string.Empty)); + + public double GlyphFontSize + { + get { return (double)GetValue(GlyphFontSizeProperty); } + set { SetValue(GlyphFontSizeProperty, value); } + } + + // Using a DependencyProperty as the backing store for GlyphFontSize. This enables animation, styling, binding, etc... + public static readonly DependencyProperty GlyphFontSizeProperty = + DependencyProperty.Register(nameof(GlyphFontSize), typeof(double), typeof(OperatorPanelButton), new PropertyMetadata(default(double))); + + public double ChevronFontSize + { + get { return (double)GetValue(ChevronFontSizeProperty); } + set { SetValue(ChevronFontSizeProperty, value); } + } + + // Using a DependencyProperty as the backing store for ChevronFontSize. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ChevronFontSizeProperty = + DependencyProperty.Register(nameof(ChevronFontSize), typeof(double), typeof(OperatorPanelButton), new PropertyMetadata(default(double))); + + public Flyout FlyoutMenu + { + get { return (Flyout)GetValue(FlyoutMenuProperty); } + set { SetValue(FlyoutMenuProperty, value); } + } + + // Using a DependencyProperty as the backing store for FlyoutMenu. This enables animation, styling, binding, etc... + public static readonly DependencyProperty FlyoutMenuProperty = + DependencyProperty.Register(nameof(FlyoutMenu), typeof(Flyout), typeof(OperatorPanelButton), new PropertyMetadata(default(Flyout))); + + protected override void OnApplyTemplate() + { + if (FlyoutMenu != null) + { + FlyoutMenu.Closed += FlyoutClosed; + } + } + + protected override void OnToggle() + { + base.OnToggle(); + + if (IsChecked == true) + { + FlyoutMenu.ShowAt(this); + } + } + + private void FlyoutClosed(object sender, object args) + { + IsChecked = false; + } + } + } +} diff --git a/src/Calculator/Controls/OperatorPanelListView.cs b/src/Calculator/Controls/OperatorPanelListView.cs new file mode 100644 index 00000000..ac56cc65 --- /dev/null +++ b/src/Calculator/Controls/OperatorPanelListView.cs @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Controls; + +using Windows.Foundation; +using Windows.Devices.Input; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Controls; + +namespace CalculatorApp +{ + namespace Controls + { + public sealed class OperatorPanelListView : Windows.UI.Xaml.Controls.ListView + { + public OperatorPanelListView() + { + } + + protected override void OnApplyTemplate() + { + m_scrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer; + m_scrollLeft = GetTemplateChild("ScrollLeft") as Button; + m_scrollRight = GetTemplateChild("ScrollRight") as Button; + m_content = GetTemplateChild("Content") as ItemsPresenter; + + if (m_scrollLeft != null) + { + m_scrollLeft.Click += OnScrollClick; + m_scrollLeft.PointerExited += OnButtonPointerExited; + } + + if (m_scrollRight != null) + { + m_scrollRight.Click += OnScrollClick; + m_scrollRight.PointerExited += OnButtonPointerExited; + } + + if (m_scrollViewer != null) + { + m_scrollViewer.ViewChanged += ScrollViewChanged; + } + + this.PointerEntered += OnPointerEntered; + this.PointerExited += OnPointerExited; + + base.OnApplyTemplate(); + } + + private void OnScrollClick(object sender, RoutedEventArgs e) + { + var clicked = sender as Button; + if (clicked == m_scrollLeft) + { + ScrollLeft(); + } + else + { + ScrollRight(); + } + } + + private void OnPointerEntered(object sender, PointerRoutedEventArgs e) + { + if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse) + { + UpdateScrollButtons(); + m_isPointerEntered = true; + } + } + + private void OnPointerExited(object sender, PointerRoutedEventArgs e) + { + m_scrollLeft.Visibility = Visibility.Collapsed; + m_scrollRight.Visibility = Visibility.Collapsed; + m_isPointerEntered = false; + } + + private void OnButtonPointerExited(object sender, PointerRoutedEventArgs e) + { + var button = sender as Button; + + // Do not bubble up the pointer exit event to the control if the button being exited was not visible + if (button.Visibility == Visibility.Collapsed) + { + e.Handled = true; + } + } + + private void ScrollViewChanged(object sender, ScrollViewerViewChangedEventArgs e) + { + if (m_isPointerEntered && !e.IsIntermediate) + { + UpdateScrollButtons(); + } + } + + private void ShowHideScrollButtons(Visibility vLeft, Visibility vRight) + { + if (m_scrollLeft != null && m_scrollRight != null) + { + m_scrollLeft.Visibility = vLeft; + m_scrollRight.Visibility = vRight; + } + } + + private void UpdateScrollButtons() + { + if (m_content == null || m_scrollViewer == null) + { + return; + } + + // When the width is smaller than the container, don't show any + if (m_content.ActualWidth <= m_scrollViewer.ActualWidth) + { + ShowHideScrollButtons(Visibility.Collapsed, Visibility.Collapsed); + } + // We have more number on both side. Show both arrows + else if ((m_scrollViewer.HorizontalOffset > 0) && (m_scrollViewer.HorizontalOffset < (m_scrollViewer.ExtentWidth - m_scrollViewer.ViewportWidth))) + { + ShowHideScrollButtons(Visibility.Visible, Visibility.Visible); + } + // Width is larger than the container and left most part of the number is shown. Should be able to scroll left. + else if (m_scrollViewer.HorizontalOffset == 0) + { + ShowHideScrollButtons(Visibility.Collapsed, Visibility.Visible); + } + else // Width is larger than the container and right most part of the number is shown. Should be able to scroll left. + { + ShowHideScrollButtons(Visibility.Visible, Visibility.Collapsed); + } + } + + private void ScrollLeft() + { + double offset = m_scrollViewer.HorizontalOffset - (scrollRatio * m_scrollViewer.ViewportWidth); + m_scrollViewer.ChangeView(offset, null, null); + } + + private void ScrollRight() + { + double offset = m_scrollViewer.HorizontalOffset + (scrollRatio * m_scrollViewer.ViewportWidth); + m_scrollViewer.ChangeView(offset, null, null); + } + + private double scrollRatio = 0.7; + + private bool m_isPointerEntered; + + private Windows.UI.Xaml.Controls.ItemsPresenter m_content; + private Windows.UI.Xaml.Controls.ScrollViewer m_scrollViewer; + private Windows.UI.Xaml.Controls.Button m_scrollLeft; + private Windows.UI.Xaml.Controls.Button m_scrollRight; + } + } +} diff --git a/src/Calculator/Controls/OverflowTextBlock.cs b/src/Calculator/Controls/OverflowTextBlock.cs new file mode 100644 index 00000000..2d7ee443 --- /dev/null +++ b/src/Calculator/Controls/OverflowTextBlock.cs @@ -0,0 +1,334 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Common; +using CalculatorApp.Controls; + +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.Devices.Input; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Automation.Peers; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; +using Windows.ApplicationModel.Store; + +namespace CalculatorApp +{ + namespace Controls + { + public enum OverflowButtonPlacement : int + { + InLine, + Above + }; + + public sealed class OverflowTextBlock : Windows.UI.Xaml.Controls.Control + { + public OverflowTextBlock() + { + m_isAccessibilityViewControl = false; + m_ignoreViewChanged = false; + m_expressionContent = null; + m_itemsControl = null; + m_expressionContainer = null; + m_scrollLeft = null; + m_scrollRight = null; + } + + public bool TokensUpdated + { + get { return (bool)GetValue(TokensUpdatedProperty); } + set { SetValue(TokensUpdatedProperty, value); } + } + + // Using a DependencyProperty as the backing store for TokensUpdated. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TokensUpdatedProperty = + DependencyProperty.Register(nameof(TokensUpdated), typeof(bool), typeof(OverflowTextBlock), new PropertyMetadata(default(bool), new PropertyChangedCallback((sender, args) => + { + var self = (OverflowTextBlock)sender; + self.OnTokensUpdatedPropertyChanged((bool)args.OldValue, (bool)args.NewValue); + }))); + + public OverflowButtonPlacement ScrollButtonsPlacement + { + get { return (OverflowButtonPlacement)GetValue(ScrollButtonsPlacementProperty); } + set { SetValue(ScrollButtonsPlacementProperty, value); } + } + + // Using a DependencyProperty as the backing store for ScrollButtonsPlacement. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ScrollButtonsPlacementProperty = + DependencyProperty.Register(nameof(ScrollButtonsPlacement), typeof(OverflowButtonPlacement), typeof(OverflowTextBlock), new PropertyMetadata(default(OverflowButtonPlacement), new PropertyChangedCallback((sender, args) => + { + var self = (OverflowTextBlock)sender; + self.OnScrollButtonsPlacementPropertyChanged((OverflowButtonPlacement)args.OldValue, (OverflowButtonPlacement)args.NewValue); + }))); + + public bool IsActive + { + get { return (bool)GetValue(IsActiveProperty); } + set { SetValue(IsActiveProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsActive. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsActiveProperty = + DependencyProperty.Register(nameof(IsActive), typeof(bool), typeof(OverflowTextBlock), new PropertyMetadata(default(bool))); + + public Windows.UI.Xaml.Style TextStyle + { + get { return (Style)GetValue(TextStyleProperty); } + set { SetValue(TextStyleProperty, value); } + } + + // Using a DependencyProperty as the backing store for TextStyle. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TextStyleProperty = + DependencyProperty.Register(nameof(TextStyle), typeof(Style), typeof(OverflowTextBlock), new PropertyMetadata(default(Style))); + + public double ScrollButtonsWidth + { + get { return (double)GetValue(ScrollButtonsWidthProperty); } + set { SetValue(ScrollButtonsWidthProperty, value); } + } + + // Using a DependencyProperty as the backing store for ScrollButtonsWidth. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ScrollButtonsWidthProperty = + DependencyProperty.Register(nameof(ScrollButtonsWidth), typeof(double), typeof(OverflowTextBlock), new PropertyMetadata(default(double))); + + public double ScrollButtonsFontSize + { + get { return (double)GetValue(ScrollButtonsFontSizeProperty); } + set { SetValue(ScrollButtonsFontSizeProperty, value); } + } + + // Using a DependencyProperty as the backing store for ScrollButtonsFontSize. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ScrollButtonsFontSizeProperty = + DependencyProperty.Register(nameof(ScrollButtonsFontSize), typeof(double), typeof(OverflowTextBlock), new PropertyMetadata(default(double))); + + public void OnTokensUpdatedPropertyChanged(bool oldValue, bool newValue) + { + if (m_expressionContainer != null && newValue) + { + m_expressionContainer.UpdateLayout(); + m_expressionContainer.ChangeView(m_expressionContainer.ScrollableWidth, null, null, true); + } + var newIsAccessibilityViewControl = m_itemsControl != null && m_itemsControl.Items.Count > 0; + if (m_isAccessibilityViewControl != newIsAccessibilityViewControl) + { + m_isAccessibilityViewControl = newIsAccessibilityViewControl; + AutomationProperties.SetAccessibilityView(this, newIsAccessibilityViewControl ? AccessibilityView.Control : AccessibilityView.Raw); + } + UpdateScrollButtons(); + } + + public void OnScrollButtonsPlacementPropertyChanged(OverflowButtonPlacement oldValue, OverflowButtonPlacement newValue) + { + if (newValue == OverflowButtonPlacement.InLine) + { + m_expressionContainer.Padding = new Thickness(0); + m_expressionContent.Margin = new Thickness(0); + } + UpdateScrollButtons(); + } + + public void UpdateScrollButtons() + { + if (m_expressionContainer == null || m_scrollLeft == null || m_scrollRight == null) + { + return; + } + + var realOffset = m_expressionContainer.HorizontalOffset + m_expressionContainer.Padding.Left + m_expressionContent.Margin.Left; + var scrollLeftVisibility = realOffset > SCROLL_BUTTONS_APPROXIMATION_RANGE ? Visibility.Visible : Visibility.Collapsed; + var scrollRightVisibility = realOffset + m_expressionContainer.ActualWidth + SCROLL_BUTTONS_APPROXIMATION_RANGE < m_expressionContent.ActualWidth + ? Visibility.Visible + : Visibility.Collapsed; + + bool shouldTryFocusScrollRight = false; + if (m_scrollLeft.Visibility != scrollLeftVisibility) + { + if (scrollLeftVisibility == Visibility.Collapsed) + { + shouldTryFocusScrollRight = m_scrollLeft.Equals(FocusManager.GetFocusedElement()); + } + + m_scrollLeft.Visibility = scrollLeftVisibility; + } + + if (m_scrollRight.Visibility != scrollRightVisibility) + { + if (scrollRightVisibility == Visibility.Collapsed && m_scrollLeft.Visibility == Visibility.Visible + && m_scrollRight.Equals(FocusManager.GetFocusedElement())) + { + m_scrollLeft.Focus(FocusState.Programmatic); + } + m_scrollRight.Visibility = scrollRightVisibility; + } + + if (shouldTryFocusScrollRight && scrollRightVisibility == Visibility.Visible) + { + m_scrollRight.Focus(FocusState.Programmatic); + } + + if (ScrollButtonsPlacement == OverflowButtonPlacement.Above && m_expressionContent != null) + { + double left = m_scrollLeft != null && m_scrollLeft.Visibility == Visibility.Visible ? ScrollButtonsWidth : 0; + double right = m_scrollRight != null && m_scrollRight.Visibility == Visibility.Visible ? ScrollButtonsWidth : 0; + if (m_expressionContainer.Padding.Left != left || m_expressionContainer.Padding.Right != right) + { + m_expressionContainer.ViewChanged -= OnViewChanged; + + m_expressionContainer.Padding = new Thickness(left, 0, right, 0); + m_expressionContent.Margin = new Thickness(-left, 0, -right, 0); + m_expressionContainer.UpdateLayout(); + m_expressionContainer.Measure(m_expressionContainer.RenderSize); + + m_expressionContainer.ViewChanged += OnViewChanged; + } + } + } + + public void UnregisterEventHandlers() + { + // Unregister the event handlers + if (m_scrollLeft != null) + { + m_scrollLeft.Click -= OnScrollLeftClick; + } + + if (m_scrollRight != null) + { + m_scrollRight.Click -= OnScrollRightClick; + } + + if (m_expressionContainer != null) + { + m_expressionContainer.ViewChanged -= OnViewChanged; + } + } + + protected override void OnApplyTemplate() + { + UnregisterEventHandlers(); + + var uiElement = GetTemplateChild("ExpressionContainer"); + if (uiElement != null) + { + m_expressionContainer = (ScrollViewer)uiElement; + m_expressionContainer.ChangeView(m_expressionContainer.ExtentWidth - m_expressionContainer.ViewportWidth, null, null); + m_expressionContainer.ViewChanged += OnViewChanged; + } + + uiElement = GetTemplateChild("ExpressionContent"); + if (uiElement != null) + { + m_expressionContent = (FrameworkElement)uiElement; + } + + uiElement = GetTemplateChild("ScrollLeft"); + if (uiElement != null) + { + m_scrollLeft = (Button)uiElement; + m_scrollLeft.Click += OnScrollLeftClick; + } + + uiElement = GetTemplateChild("ScrollRight"); + if (uiElement != null) + { + m_scrollRight = (Button)uiElement; + m_scrollRight.Click += OnScrollRightClick; + } + + uiElement = GetTemplateChild("TokenList"); + if (uiElement != null) + { + m_itemsControl = (ItemsControl)uiElement; + } + + UpdateAllState(); + + } + + protected override AutomationPeer OnCreateAutomationPeer() + { + return new OverflowTextBlockAutomationPeer(this); + } + + private void OnScrollLeftClick(object sender, RoutedEventArgs e) + { + ScrollLeft(); + } + + private void OnScrollRightClick(object sender, RoutedEventArgs e) + { + ScrollRight(); + } + + private void OnViewChanged(object sender, ScrollViewerViewChangedEventArgs args) + { + UpdateScrollButtons(); + } + + private void UpdateVisualState() + { + if (IsActive) + { + VisualStateManager.GoToState(this, "Active", true); + } + else + { + VisualStateManager.GoToState(this, "Normal", true); + } + } + + private void UpdateAllState() + { + UpdateVisualState(); + } + + private void ScrollLeft() + { + if (m_expressionContainer != null && m_expressionContainer.HorizontalOffset > 0) + { + double offset = m_expressionContainer.HorizontalOffset - (SCROLL_RATIO * m_expressionContainer.ViewportWidth); + m_expressionContainer.ChangeView(offset, null, null); + m_expressionContainer.UpdateLayout(); + UpdateScrollButtons(); + } + } + + private void ScrollRight() + { + var realOffset = m_expressionContainer.HorizontalOffset + m_expressionContainer.Padding.Left + m_expressionContent.Margin.Left; + if (m_expressionContainer != null && realOffset + m_expressionContainer.ActualWidth < m_expressionContent.ActualWidth) + { + double offset = m_expressionContainer.HorizontalOffset + (SCROLL_RATIO * m_expressionContainer.ViewportWidth); + m_expressionContainer.ChangeView(offset, null, null); + m_expressionContainer.UpdateLayout(); + UpdateScrollButtons(); + } + } + + private const uint SCROLL_BUTTONS_APPROXIMATION_RANGE = 4; + private const double SCROLL_RATIO = 0.7; + + private bool m_isAccessibilityViewControl; + private bool m_ignoreViewChanged; + private Windows.UI.Xaml.FrameworkElement m_expressionContent; + private Windows.UI.Xaml.Controls.ItemsControl m_itemsControl; + private Windows.UI.Xaml.Controls.ScrollViewer m_expressionContainer; + private Windows.UI.Xaml.Controls.Button m_scrollLeft; + private Windows.UI.Xaml.Controls.Button m_scrollRight; + } + + } +} diff --git a/src/Calculator/Controls/OverflowTextBlockAutomationPeer.cs b/src/Calculator/Controls/OverflowTextBlockAutomationPeer.cs new file mode 100644 index 00000000..0b9d1300 --- /dev/null +++ b/src/Calculator/Controls/OverflowTextBlockAutomationPeer.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using Windows.UI.Xaml.Automation.Peers; +using Windows.Foundation.Collections; + +namespace CalculatorApp +{ + namespace Controls + { + public sealed class OverflowTextBlockAutomationPeer : Windows.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer + { + public OverflowTextBlockAutomationPeer(OverflowTextBlock owner) : base(owner) + { + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Text; + } + + protected override IList GetChildrenCore() + { + return null; + } + } + } +} diff --git a/src/Calculator/Controls/SupplementaryItemsControl.cs b/src/Calculator/Controls/SupplementaryItemsControl.cs new file mode 100644 index 00000000..6fc2b303 --- /dev/null +++ b/src/Calculator/Controls/SupplementaryItemsControl.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Controls; +using CalculatorApp.ViewModel; +using Windows.System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Automation.Peers; + +using Windows.Foundation.Collections; + +namespace CalculatorApp +{ + namespace Controls + { + public sealed class SupplementaryItemsControl : ItemsControl + { + public SupplementaryItemsControl() + { + } + + protected override DependencyObject GetContainerForItemOverride() + { + return new SupplementaryContentPresenter(); + } + + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + base.PrepareContainerForItemOverride(element, item); + + var supplementaryResult = item as SupplementaryResult; + if (supplementaryResult != null) + { + AutomationProperties.SetName(element, supplementaryResult.GetLocalizedAutomationName()); + } + } + } + + public sealed class SupplementaryContentPresenter : ContentPresenter + { + public SupplementaryContentPresenter() + { + } + + protected override AutomationPeer OnCreateAutomationPeer() + { + return new SupplementaryContentPresenterAP(this); + } + } + + sealed class SupplementaryContentPresenterAP : FrameworkElementAutomationPeer + { + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Text; + } + + protected override IList GetChildrenCore() + { + return null; + } + + internal SupplementaryContentPresenterAP(SupplementaryContentPresenter owner) + : base(owner) + { + } + } + } +} diff --git a/src/Calculator/KeyGraphFeaturesTemplateSelector.cs b/src/Calculator/KeyGraphFeaturesTemplateSelector.cs new file mode 100644 index 00000000..ebf67527 --- /dev/null +++ b/src/Calculator/KeyGraphFeaturesTemplateSelector.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using CalculatorApp.ViewModel; + +using Windows.UI.Xaml; + +namespace CalculatorApp +{ + namespace TemplateSelectors + { + public sealed class KeyGraphFeaturesTemplateSelector : Windows.UI.Xaml.Controls.DataTemplateSelector + { + public KeyGraphFeaturesTemplateSelector() + { + } + + public Windows.UI.Xaml.DataTemplate RichEditTemplate { get; set; } + public Windows.UI.Xaml.DataTemplate GridTemplate { get; set; } + public Windows.UI.Xaml.DataTemplate TextBlockTemplate { get; set; } + + protected override DataTemplate SelectTemplateCore(object item) + { + var kgfItem = (KeyGraphFeaturesItem)item; + + if (!kgfItem.IsText) + { + if (kgfItem.DisplayItems.Count != 0) + { + return RichEditTemplate; + } + else if (kgfItem.GridItems.Count != 0) + { + return GridTemplate; + } + } + + return TextBlockTemplate; + } + + protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) + { + return SelectTemplateCore(item); + } + } + } +} diff --git a/src/Calculator/Views/Calculator.xaml b/src/Calculator/Views/Calculator.xaml new file mode 100644 index 00000000..b08b5810 --- /dev/null +++ b/src/Calculator/Views/Calculator.xaml @@ -0,0 +1,1278 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Calculator/Views/Calculator.xaml.cs b/src/Calculator/Views/Calculator.xaml.cs new file mode 100644 index 00000000..dfcdf296 --- /dev/null +++ b/src/Calculator/Views/Calculator.xaml.cs @@ -0,0 +1,873 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Common; +using CalculatorApp.Converters; +using CalculatorApp.Controls; +using CalculatorApp.ViewModel; + +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.Globalization.NumberFormatting; +using Windows.System; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Automation.Peers; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; +using Windows.System.Threading; +using Windows.UI.ViewManagement; + +namespace CalculatorApp +{ + public class FontTable + { + public string numericSystem; + public double fullFont; + public double fullFontMin; + public double portraitMin; + public double snapFont; + public double fullNumPadFont; + public double snapScientificNumPadFont; + public double portraitScientificNumPadFont; + } + + public delegate void FullscreenFlyoutClosedEventHandler(); + + [Windows.Foundation.Metadata.WebHostHidden] + public sealed partial class Calculator + { + public event FullscreenFlyoutClosedEventHandler FullscreenFlyoutClosed; + + public Calculator() + { + m_doAnimate = false; + m_isLastAnimatedInScientific = false; + m_isLastAnimatedInProgrammer = false; + m_resultAnimate = false; + + SetFontSizeResources(); + InitializeComponent(); + LoadResourceStrings(); + + if (LocalizationService.GetInstance().IsRtlLayout()) + { + HistoryButton.HorizontalAlignment = HorizontalAlignment.Left; + } + + m_displayFlyout = (MenuFlyout)Resources["DisplayContextMenu"]; + var resLoader = AppResourceProvider.GetInstance(); + CopyMenuItem.Text = resLoader.GetResourceString("copyMenuItem"); + PasteMenuItem.Text = resLoader.GetResourceString("pasteMenuItem"); + + this.SizeChanged += Calculator_SizeChanged; + } + + public CalculatorApp.ViewModel.StandardCalculatorViewModel Model + { + get => (StandardCalculatorViewModel)this.DataContext; + } + + public bool IsStandard + { + get { return (bool)GetValue(IsStandardProperty); } + set { SetValue(IsStandardProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsStandard. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsStandardProperty = + DependencyProperty.Register(nameof(IsStandard), typeof(bool), typeof(Calculator), new PropertyMetadata(false, (sender, args) => + { + var self = (Calculator)sender; + self.OnIsStandardPropertyChanged((bool)args.OldValue, (bool)args.NewValue); + })); + + public bool IsScientific + { + get { return (bool)GetValue(IsScientificProperty); } + set { SetValue(IsScientificProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsScientific. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsScientificProperty = + DependencyProperty.Register(nameof(IsScientific), typeof(bool), typeof(Calculator), new PropertyMetadata(false, (sender, args) => + { + var self = (Calculator)sender; + self.OnIsScientificPropertyChanged((bool)args.OldValue, (bool)args.NewValue); + })); + + public bool IsProgrammer + { + get { return (bool)GetValue(IsProgrammerProperty); } + set { SetValue(IsProgrammerProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsProgrammer. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsProgrammerProperty = + DependencyProperty.Register(nameof(IsProgrammer), typeof(bool), typeof(Calculator), new PropertyMetadata(false, (sender, args) => + { + var self = (Calculator)sender; + self.OnIsProgrammerPropertyChanged((bool)args.OldValue, (bool)args.NewValue); + })); + + public bool IsAlwaysOnTop + { + get { return (bool)GetValue(IsAlwaysOnTopProperty); } + set { SetValue(IsAlwaysOnTopProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsAlwaysOnTop. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsAlwaysOnTopProperty = + DependencyProperty.Register(nameof(IsAlwaysOnTop), typeof(bool), typeof(Calculator), new PropertyMetadata(false, (sender, args) => + { + var self = (Calculator)sender; + self.OnIsAlwaysOnTopPropertyChanged((bool)args.OldValue, (bool)args.NewValue); + })); + + public System.Windows.Input.ICommand HistoryButtonPressed + { + get + { + if (donotuse_HistoryButtonPressed == null) + { + donotuse_HistoryButtonPressed = new DelegateCommand(this, ToggleHistoryFlyout); + } + return donotuse_HistoryButtonPressed; + } + } + private System.Windows.Input.ICommand donotuse_HistoryButtonPressed; + + private static UISettings uiSettings = new UISettings(); + public void AnimateCalculator(bool resultAnimate) + { + if (uiSettings.AnimationsEnabled) + { + m_doAnimate = true; + m_resultAnimate = resultAnimate; + if (((m_isLastAnimatedInScientific && IsScientific) || (!m_isLastAnimatedInScientific && !IsScientific)) + & ((m_isLastAnimatedInProgrammer && IsProgrammer) || (!m_isLastAnimatedInProgrammer && !IsProgrammer))) + { + // We are forcing the animation here + // It's because if last animation was in standard, then go to unit converter, then comes back to standard + // The state for the calculator does not change and the animation would not get run. + this.OnModeVisualStateCompleted(null, null); + } + } + } + + public void InitializeHistoryView(CalculatorApp.ViewModel.HistoryViewModel historyVM) + { + if (m_historyList == null) + { + m_historyList = new HistoryList(); + m_historyList.DataContext = historyVM; + historyVM.HideHistoryClicked += OnHideHistoryClicked; + historyVM.HistoryItemClicked += OnHistoryItemClicked; + } + } + + public void UpdatePanelViewState() + { + UpdateHistoryState(); + UpdateMemoryState(); + } + + public void UnregisterEventHandlers() + { + ExpressionText.UnregisterEventHandlers(); + AlwaysOnTopResults.UnregisterEventHandlers(); + } + + public void CloseHistoryFlyout() + { + if (m_fIsHistoryFlyoutOpen) + { + HistoryFlyout.Hide(); + } + } + + public void CloseMemoryFlyout() + { + if (m_fIsMemoryFlyoutOpen) + { + MemoryFlyout.Hide(); + } + } + + public void SetDefaultFocus() + { + if (!IsAlwaysOnTop) + { + Results.Focus(FocusState.Programmatic); + } + else + { + AlwaysOnTopResults.Focus(FocusState.Programmatic); + } + } + + // Methods used by native bindings + public static Visibility ShouldDisplayHistoryButton(bool isAlwaysOnTop, bool isProgrammer, Visibility dockPanelVisibility) + { + return !isAlwaysOnTop && !isProgrammer && dockPanelVisibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + Model.PropertyChanged += OnCalcPropertyChanged; + Model.HideMemoryClicked += OnHideMemoryClicked; + + InitializeHistoryView(Model.HistoryVM); + string historyPaneName = AppResourceProvider.GetInstance().GetResourceString("HistoryPane"); + HistoryFlyout.FlyoutPresenterStyle.Setters.Add(new Setter(AutomationProperties.NameProperty, historyPaneName)); + string memoryPaneName = AppResourceProvider.GetInstance().GetResourceString("MemoryPane"); + MemoryFlyout.FlyoutPresenterStyle.Setters.Add(new Setter(AutomationProperties.NameProperty, memoryPaneName)); + + if (Windows.Foundation.Metadata.ApiInformation.IsEventPresent("Windows.UI.Xaml.Controls.Primitives.FlyoutBase", "Closing")) + { + HistoryFlyout.Closing += HistoryFlyout_Closing; + MemoryFlyout.Closing += OnMemoryFlyoutClosing; + } + + // Delay load things later when we get a chance. + WeakReference weakThis = new WeakReference(this); + _ = this.Dispatcher.RunAsync( + CoreDispatcherPriority.Normal, new DispatchedHandler(() => { + if (TraceLogger.GetInstance().IsWindowIdInLog(ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread()))) + { + var refThis = weakThis.Target as Calculator; + if (refThis != null) + { + refThis.GetMemory(); + } + } + })); + } + + private void LoadResourceStrings() + { + var resProvider = AppResourceProvider.GetInstance(); + m_openMemoryFlyoutAutomationName = resProvider.GetResourceString("MemoryButton_Open"); + m_closeMemoryFlyoutAutomationName = resProvider.GetResourceString("MemoryButton_Close"); + m_openHistoryFlyoutAutomationName = resProvider.GetResourceString("HistoryButton_Open"); + m_closeHistoryFlyoutAutomationName = resProvider.GetResourceString("HistoryButton_Close"); + m_dockPanelHistoryMemoryLists = resProvider.GetResourceString("DockPanel_HistoryMemoryLists"); + m_dockPanelMemoryList = resProvider.GetResourceString("DockPanel_MemoryList"); + AutomationProperties.SetName(MemoryButton, m_openMemoryFlyoutAutomationName); + AutomationProperties.SetName(HistoryButton, m_openHistoryFlyoutAutomationName); + AutomationProperties.SetName(DockPanel, m_dockPanelHistoryMemoryLists); + } + + private void UpdateViewState() + { + string state; + if (IsProgrammer) + { + state = "Programmer"; + Model.IsDecimalEnabled = false; + ResultsMVisualStateTrigger.MinWindowHeight = 640; + } + else if (IsScientific) + { + state = "Scientific"; + Model.IsDecimalEnabled = true; + ResultsMVisualStateTrigger.MinWindowHeight = 544; + } + else + { + state = "Standard"; + Model.IsDecimalEnabled = true; + ResultsMVisualStateTrigger.MinWindowHeight = 1; + } + + CloseHistoryFlyout(); + CloseMemoryFlyout(); + + VisualStateManager.GoToState(this, state, true); + } + + private void UpdateMemoryState() + { + if (!IsAlwaysOnTop) + { + if (!Model.IsMemoryEmpty) + { + MemRecall.IsEnabled = true; + ClearMemoryButton.IsEnabled = true; + } + else + { + MemRecall.IsEnabled = false; + ClearMemoryButton.IsEnabled = false; + } + + if (DockPanel.Visibility == Visibility.Visible) + { + CloseMemoryFlyout(); + SetChildAsMemory(); + MemoryButton.Visibility = Visibility.Collapsed; + + if (m_IsLastFlyoutMemory && !IsProgrammer) + { + DockPivot.SelectedIndex = 1; + } + } + else + { + MemoryButton.Visibility = Visibility.Visible; + DockMemoryHolder.Child = null; + } + } + } + + private void UpdateHistoryState() + { + if (DockPanel.Visibility == Visibility.Visible) + { + // docked view + CloseHistoryFlyout(); + SetChildAsHistory(); + + if (!IsProgrammer && m_IsLastFlyoutHistory) + { + DockPivot.SelectedIndex = 0; + } + } + else + { + // flyout view + DockHistoryHolder.Child = null; + } + } + + private void OnContextRequested(UIElement sender, ContextRequestedEventArgs e) + { + var requestedElement = (FrameworkElement)e.OriginalSource; + + PasteMenuItem.IsEnabled = CopyPasteManager.HasStringToPaste(); + + Point point; + if (e.TryGetPosition(requestedElement, out point)) + { + m_displayFlyout.ShowAt(requestedElement, point); + } + else + { + // Not invoked via pointer, so let XAML choose a default location. + m_displayFlyout.ShowAt(requestedElement); + } + + e.Handled = true; + } + + private void OnContextCanceled(UIElement sender, RoutedEventArgs e) + { + m_displayFlyout.Hide(); + } + + private void OnIsScientificPropertyChanged(bool oldValue, bool newValue) + { + if (newValue) + { + EnsureScientific(); + } + + UpdateViewState(); + UpdatePanelViewState(); + } + + private void OnIsProgrammerPropertyChanged(bool oldValue, bool newValue) + { + if (newValue) + { + EnsureProgrammer(); + m_pivotItem = (PivotItem)DockPivot.Items[0]; + DockPivot.Items.RemoveAt(0); + } + else + { + if (m_pivotItem != null && DockPivot.Items.Count == 1) + { + DockPivot.Items.Insert(0, m_pivotItem); + } + } + + DockPivot.SelectedIndex = 0; + UpdateViewState(); + UpdatePanelViewState(); + } + + private void OnIsStandardPropertyChanged(bool oldValue, bool newValue) + { + UpdateViewState(); + UpdatePanelViewState(); + } + + private void OnIsAlwaysOnTopPropertyChanged(bool oldValue, bool newValue) + { + if (newValue) + { + VisualStateManager.GoToState(this, "DisplayModeAlwaysOnTop", false); + AlwaysOnTopResults.UpdateScrollButtons(); + } + else + { + VisualStateManager.GoToState(this, "DisplayModeNormal", false); + if (!Model.IsInError) + { + EnableMemoryControls(true); + } + Results.UpdateTextState(); + } + + Model.IsMemoryEmpty = (Model.MemorizedNumbers.Count == 0) || IsAlwaysOnTop; + + UpdateViewState(); + UpdatePanelViewState(); + } + + private void OnIsInErrorPropertyChanged() + { + bool isError = Model.IsInError; + + string newState = isError ? "ErrorLayout" : "NoErrorLayout"; + VisualStateManager.GoToState(this, newState, false); + + if (m_memory != null) + { + m_memory.IsErrorVisualState = isError; + } + + OpsPanel.IsErrorVisualState = isError; + if (IsScientific && ScientificAngleButtons != null) + { + ScientificAngleButtons.IsErrorVisualState = isError; + } + // CSHARP_MIGRATION: TODO: + // ScientificAngleButtons works but ProgrammerDisplayPanel doesn't + else if (IsProgrammer && ProgrammerDisplayPanel != null) + { + ProgrammerDisplayPanel.IsErrorVisualState = isError; + } + } + + private void OnCalcPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + string prop = e.PropertyName; + if (prop == StandardCalculatorViewModel.IsMemoryEmptyPropertyName) + { + UpdateMemoryState(); + } + else if (prop == StandardCalculatorViewModel.IsInErrorPropertyName) + { + OnIsInErrorPropertyChanged(); + } + } + + private void OnLayoutVisualStateCompleted(object sender, object e) + { + UpdatePanelViewState(); + } + + private void OnModeVisualStateCompleted(object sender, object e) + { + m_isLastAnimatedInScientific = IsScientific; + m_isLastAnimatedInProgrammer = IsProgrammer; + if (m_doAnimate) + { + m_doAnimate = false; + if (m_resultAnimate) + { + m_resultAnimate = false; + Animate.Begin(); + } + else + { + AnimateWithoutResult.Begin(); + } + } + if (IsProgrammer) + { + AutomationProperties.SetName(DockPanel, m_dockPanelMemoryList); + } + else + { + AutomationProperties.SetName(DockPanel, m_dockPanelHistoryMemoryLists); + } + } + + private void OnErrorVisualStateCompleted(object sender, object e) + { + SetDefaultFocus(); + } + + private void OnDisplayVisualStateCompleted(object sender, object e) + { + SetDefaultFocus(); + } + + private void EnsureScientific() + { + OpsPanel.EnsureScientificOps(); + + if (ScientificAngleButtons == null) + { + this.FindName("ScientificAngleButtons"); + } + } + + private void EnsureProgrammer() + { + if (ProgrammerOperators == null) + { + this.FindName("ProgrammerOperators"); + } + + if (ProgrammerDisplayPanel == null) + { + this.FindName("ProgrammerDisplayPanel"); + } + + OpsPanel.EnsureProgrammerRadixOps(); + ProgrammerOperators.SetRadixButton(Model.CurrentRadixType); + } + + // Since we need different font sizes for different numeric system, + // we use a table of optimal font sizes for each numeric system. + private static readonly FontTable[] fontTables = new FontTable[] { + new FontTable { numericSystem = "Arab", fullFont = 104, fullFontMin = 29.333, portraitMin = 23, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "ArabExt", fullFont = 104, fullFontMin = 29.333, portraitMin = 23, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "Beng", fullFont = 104, fullFontMin = 26, portraitMin = 17, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "Deva", fullFont = 104, fullFontMin = 29.333, portraitMin = 20.5, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "Gujr", fullFont = 104, fullFontMin = 29.333, portraitMin = 18.5, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "Khmr", fullFont = 104, fullFontMin = 29.333, portraitMin = 19.5, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "Knda", fullFont = 104, fullFontMin = 25, portraitMin = 17, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "Laoo", fullFont = 104, fullFontMin = 28, portraitMin = 18, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "Latn", fullFont = 104, fullFontMin = 29.333, portraitMin = 23, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "Mlym", fullFont = 80, fullFontMin = 22, portraitMin = 15.5, snapFont = 30, + fullNumPadFont = 56, snapScientificNumPadFont = 35, portraitScientificNumPadFont = 48 }, + new FontTable { numericSystem = "Mymr", fullFont = 104, fullFontMin = 29.333, portraitMin = 20, snapFont = 35, + fullNumPadFont = 48, snapScientificNumPadFont = 36, portraitScientificNumPadFont = 48 }, + new FontTable { numericSystem = "Orya", fullFont = 88, fullFontMin = 26, portraitMin = 20, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "TamlDec", fullFont = 77, fullFontMin = 25, portraitMin = 16, snapFont = 28, + fullNumPadFont = 48, snapScientificNumPadFont = 34, portraitScientificNumPadFont = 48 }, + new FontTable { numericSystem = "Telu", fullFont = 104, fullFontMin = 25, portraitMin = 16.5, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "Thai", fullFont = 104, fullFontMin = 28, portraitMin = 18, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "Tibt", fullFont = 104, fullFontMin = 29.333, portraitMin = 20, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 }, + new FontTable { numericSystem = "Default", fullFont = 104, fullFontMin = 29.333, portraitMin = 23, snapFont = 40, + fullNumPadFont = 56, snapScientificNumPadFont = 40, portraitScientificNumPadFont = 56 } + }; + + private void SetFontSizeResources() + { + DecimalFormatter formatter = LocalizationService.GetInstance().GetRegionalSettingsAwareDecimalFormatter(); + + int currentItemIdx = 0; + while (!fontTables[currentItemIdx].numericSystem.Equals("Default") && + !fontTables[currentItemIdx].numericSystem.Equals(formatter.NumeralSystem)) + { + ++currentItemIdx; + } + + var currentItem = fontTables[currentItemIdx]; + this.Resources.Add("ResultFullFontSize", currentItem.fullFont); + this.Resources.Add("ResultFullMinFontSize", currentItem.fullFontMin); + this.Resources.Add("ResultPortraitMinFontSize", currentItem.portraitMin); + this.Resources.Add("ResultSnapFontSize", currentItem.snapFont); + this.Resources.Add("CalcButtonCaptionSizeOverride", currentItem.fullNumPadFont); + this.Resources.Add("CalcButtonScientificSnapCaptionSizeOverride", currentItem.snapScientificNumPadFont); + this.Resources.Add("CalcButtonScientificPortraitCaptionSizeOverride", currentItem.portraitScientificNumPadFont); + } + + private string GetCurrentLayoutState() + { + if (IsProgrammer) + { + return "Programmer"; + } + else if (IsScientific) + { + return "Scientific"; + } + else + { + return "Standard"; + } + } + + private void Calculator_SizeChanged(object sender, SizeChangedEventArgs e) + { + if (Model.IsAlwaysOnTop) + { + AlwaysOnTopResults.UpdateScrollButtons(); + } + } + + private Windows.UI.Xaml.Controls.ListView m_tokenList; + private Windows.UI.Xaml.Controls.MenuFlyout m_displayFlyout; + private bool m_doAnimate; + private bool m_resultAnimate; + private bool m_isLastAnimatedInScientific; + private bool m_isLastAnimatedInProgrammer; + private bool m_IsLastFlyoutMemory = false; + private bool m_IsLastFlyoutHistory = false; + + private string m_openMemoryFlyoutAutomationName; + private string m_closeMemoryFlyoutAutomationName; + private string m_openHistoryFlyoutAutomationName; + private string m_closeHistoryFlyoutAutomationName; + private string m_dockPanelHistoryMemoryLists; + private string m_dockPanelMemoryList; + + private Windows.UI.Xaml.Controls.PivotItem m_pivotItem; + private bool m_IsDigit = false; + private Memory m_memory; + + private void HistoryFlyout_Opened(object sender, object args) + { + m_fIsHistoryFlyoutOpen = true; + m_IsLastFlyoutMemory = false; + m_IsLastFlyoutHistory = true; + EnableControls(false); + AutomationProperties.SetName(HistoryButton, m_closeHistoryFlyoutAutomationName); + } + + private void HistoryFlyout_Closing(FlyoutBase sender, FlyoutBaseClosingEventArgs args) + { + // Set in the Closing event so the new name is available when the Flyout has Closed. + AutomationProperties.SetName(HistoryButton, m_openHistoryFlyoutAutomationName); + } + + private void HistoryFlyout_Closed(object sender, object args) + { + // Ideally, this would be renamed in the Closing event because the Closed event is too late. + // Closing is not available until RS1+ so we set the name again here for TH2 support. + AutomationProperties.SetName(HistoryButton, m_openHistoryFlyoutAutomationName); + m_fIsHistoryFlyoutOpen = false; + EnableControls(true); + if (HistoryButton.IsEnabled && HistoryButton.Visibility == Visibility.Visible) + { + HistoryButton.Focus(FocusState.Programmatic); + } + + FullscreenFlyoutClosed?.Invoke(); + } + + private void OnHideHistoryClicked() + { + ToggleHistoryFlyout(null); + } + + private void OnHideMemoryClicked() + { + if (!m_fIsMemoryFlyoutOpen) + { + this.Focus(FocusState.Programmatic); + } + + MemoryFlyout.Hide(); + } + + private void OnHistoryItemClicked(HistoryItemViewModel e) + { + Model.SelectHistoryItem(e); + + CloseHistoryFlyout(); + this.Focus(FocusState.Programmatic); + } + + private void ToggleHistoryFlyout(object parameter) + { + if (Model.IsProgrammer || DockPanel.Visibility == Visibility.Visible) + { + return; + } + + if (m_fIsHistoryFlyoutOpen) + { + HistoryFlyout.Hide(); + } + else + { + HistoryFlyout.Content = m_historyList; + m_historyList.RowHeight = new GridLength(NumpadPanel.ActualHeight); + FlyoutBase.ShowAttachedFlyout(HistoryButton); + } + } + + private void ToggleMemoryFlyout(object sender, RoutedEventArgs e) + { + if (DockPanel.Visibility == Visibility.Visible) + { + return; + } + + if (m_fIsMemoryFlyoutOpen) + { + MemoryFlyout.Hide(); + } + else + { + MemoryFlyout.Content = GetMemory(); + m_memory.RowHeight = new GridLength(NumpadPanel.ActualHeight); + FlyoutBase.ShowAttachedFlyout(MemoryButton); + } + } + + private CalculatorApp.HistoryList m_historyList; + private bool m_fIsHistoryFlyoutOpen; + private bool m_fIsMemoryFlyoutOpen; + + private void OnMemoryFlyoutOpened(object sender, object args) + { + m_IsLastFlyoutMemory = true; + m_IsLastFlyoutHistory = false; + m_fIsMemoryFlyoutOpen = true; + AutomationProperties.SetName(MemoryButton, m_closeMemoryFlyoutAutomationName); + EnableControls(false); + } + + private void OnMemoryFlyoutClosing(FlyoutBase sender, FlyoutBaseClosingEventArgs args) + { + // Set in the Closing event so the new name is available when the Flyout has Closed. + AutomationProperties.SetName(MemoryButton, m_openMemoryFlyoutAutomationName); + } + + private void OnMemoryFlyoutClosed(object sender, object args) + { + // Ideally, this would be renamed in the Closing event because the Closed event is too late. + // Closing is not available until RS1+ so we set the name again here for TH2 support. + AutomationProperties.SetName(MemoryButton, m_openMemoryFlyoutAutomationName); + m_fIsMemoryFlyoutOpen = false; + EnableControls(true); + if (MemoryButton.IsEnabled) + { + MemoryButton.Focus(FocusState.Programmatic); + } + + FullscreenFlyoutClosed?.Invoke(); + } + + private void SetChildAsMemory() + { + DockMemoryHolder.Child = GetMemory(); + } + + private void SetChildAsHistory() + { + if (m_historyList == null) + { + InitializeHistoryView(Model.HistoryVM); + } + + DockHistoryHolder.Child = m_historyList; + } + + private Memory GetMemory() + { + if (m_memory == null) + { + m_memory = new Memory(); + VisualStateManager.GoToState(m_memory, GetCurrentLayoutState(), true); + } + + return m_memory; + } + + private void EnableControls(bool enable) + { + OpsPanel.IsEnabled = enable; + EnableMemoryControls(enable); + } + + private void EnableMemoryControls(bool enable) + { + MemButton.IsEnabled = enable; + MemMinus.IsEnabled = enable; + MemPlus.IsEnabled = enable; + if (!Model.IsMemoryEmpty) + { + MemRecall.IsEnabled = enable; + ClearMemoryButton.IsEnabled = enable; + } + } + + private void OnMemoryFlyOutTapped(object sender, TappedRoutedEventArgs e) + { + Grid grid = (Grid)sender; + Point point = e.GetPosition(null); + + if (point.Y < (grid.ActualHeight - NumpadPanel.ActualHeight)) + { + MemoryFlyout.Hide(); + } + } + + private void OnHistoryFlyOutTapped(object sender, TappedRoutedEventArgs e) + { + Grid grid = (Grid)sender; + Point point = e.GetPosition(null); + + if (point.Y < (grid.ActualHeight - NumpadPanel.ActualHeight)) + { + HistoryFlyout.Hide(); + } + } + + private void DockPanelTapped(object sender, TappedRoutedEventArgs e) + { + int index = DockPivot.SelectedIndex; + if (index == 1 && !IsProgrammer) + { + SetChildAsMemory(); + } + + m_IsLastFlyoutMemory = false; + m_IsLastFlyoutHistory = false; + } + + private void OnHistoryAccessKeyInvoked(UIElement sender, AccessKeyInvokedEventArgs args) + { + DockPivot.SelectedItem = HistoryPivotItem; + } + + private void OnMemoryAccessKeyInvoked(UIElement sender, AccessKeyInvokedEventArgs args) + { + DockPivot.SelectedItem = MemoryPivotItem; + } + + private void OnVisualStateChanged(object sender, VisualStateChangedEventArgs e) + { + if (!IsStandard && !IsScientific && !IsProgrammer) + { + return; + } + + var mode = IsStandard ? ViewMode.Standard : IsScientific ? ViewMode.Scientific : ViewMode.Programmer; + TraceLogger.GetInstance().LogVisualStateChanged(mode, e.NewState.Name, IsAlwaysOnTop); + } + } +} diff --git a/src/Calculator/Views/CalculatorProgrammerBitFlipPanel.xaml b/src/Calculator/Views/CalculatorProgrammerBitFlipPanel.xaml new file mode 100644 index 00000000..f88b6bc1 --- /dev/null +++ b/src/Calculator/Views/CalculatorProgrammerBitFlipPanel.xaml @@ -0,0 +1,1057 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 63 + + + + + 62 + + + + + 61 + + + + + 60 + + + + + + 59 + + + + + 58 + + + + + 57 + + + + + 56 + + + + + + 55 + + + + + 54 + + + + + 53 + + + + + 52 + + + + + + 51 + + + + + 50 + + + + + 49 + + + + + 48 + + + + + + 47 + + + + + 46 + + + + + 45 + + + + + 44 + + + + + + 43 + + + + + 42 + + + + + 41 + + + + + 40 + + + + + + 39 + + + + + 38 + + + + + 37 + + + + + 36 + + + + + + 35 + + + + + 34 + + + + + 33 + + + + + 32 + + + + + + 31 + + + + + 30 + + + + + 29 + + + + + 28 + + + + + + 27 + + + + + 26 + + + + + 25 + + + + + 24 + + + + + + 23 + + + + + 22 + + + + + 21 + + + + + 20 + + + + + + 19 + + + + + 18 + + + + + 17 + + + + + 16 + + + + + + 15 + + + + + 14 + + + + + 13 + + + + + 12 + + + + + + 11 + + + + + 10 + + + + + 9 + + + + + 8 + + + + + + 7 + + + + + 6 + + + + + 5 + + + + + 4 + + + + + + 3 + + + + + 2 + + + + + 1 + + + + + 0 + + + + diff --git a/src/Calculator/Views/CalculatorProgrammerBitFlipPanel.xaml.cs b/src/Calculator/Views/CalculatorProgrammerBitFlipPanel.xaml.cs new file mode 100644 index 00000000..bf519252 --- /dev/null +++ b/src/Calculator/Views/CalculatorProgrammerBitFlipPanel.xaml.cs @@ -0,0 +1,286 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// +// CalculatorProgrammerBitFlipPanel.xaml.h +// Declaration of the CalculatorProgrammerBitFlipPanel class +// + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Common; +using CalculatorApp.Controls; +using CalculatorApp.ViewModel; + +using Windows.UI.Xaml; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; + + +namespace CalculatorApp +{ + [Windows.Foundation.Metadata.WebHostHidden] + public sealed partial class CalculatorProgrammerBitFlipPanel + { + public CalculatorProgrammerBitFlipPanel() + { + m_updatingCheckedStates = false; + InitializeComponent(); + AssignFlipButtons(); + } + + public bool ShouldEnableBit(BitLength length, int index) + { + return index <= GetIndexOfLastBit(length); + } + + public StandardCalculatorViewModel Model + { + get { return (StandardCalculatorViewModel)this.DataContext; } + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + SubscribePropertyChanged(); + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + UnsubscribePropertyChanged(); + } + + private void SubscribePropertyChanged() + { + if (Model != null) + { + Model.PropertyChanged += OnPropertyChanged; + m_currentValueBitLength = Model.ValueBitLength; + UpdateCheckedStates(true); + } + } + + private void UnsubscribePropertyChanged() + { + if (Model != null) + { + Model.PropertyChanged -= OnPropertyChanged; + } + } + + private void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == StandardCalculatorViewModel.BinaryDigitsPropertyName) + { + UpdateCheckedStates(false); + m_currentValueBitLength = Model.ValueBitLength; + } + else if ( + e.PropertyName == StandardCalculatorViewModel.IsBitFlipCheckedPropertyName + || e.PropertyName == StandardCalculatorViewModel.IsProgrammerPropertyName) + { + if (Model.IsBitFlipChecked && Model.IsProgrammer) + { + // OnBitToggle won't update the automation properties when this control isn't displayed + // We need to update all automation properties names manually when the BitFlipPanel is displayed again + UpdateAutomationPropertiesNames(); + } + } + } + + private void AssignFlipButtons() + { + Debug.Assert(m_flipButtons.Length == 64); + + m_flipButtons[0] = this.Bit0; + m_flipButtons[1] = this.Bit1; + m_flipButtons[2] = this.Bit2; + m_flipButtons[3] = this.Bit3; + m_flipButtons[4] = this.Bit4; + m_flipButtons[5] = this.Bit5; + m_flipButtons[6] = this.Bit6; + m_flipButtons[7] = this.Bit7; + m_flipButtons[8] = this.Bit8; + m_flipButtons[9] = this.Bit9; + m_flipButtons[10] = this.Bit10; + m_flipButtons[11] = this.Bit11; + m_flipButtons[12] = this.Bit12; + m_flipButtons[13] = this.Bit13; + m_flipButtons[14] = this.Bit14; + m_flipButtons[15] = this.Bit15; + m_flipButtons[16] = this.Bit16; + m_flipButtons[17] = this.Bit17; + m_flipButtons[18] = this.Bit18; + m_flipButtons[19] = this.Bit19; + m_flipButtons[20] = this.Bit20; + m_flipButtons[21] = this.Bit21; + m_flipButtons[22] = this.Bit22; + m_flipButtons[23] = this.Bit23; + m_flipButtons[24] = this.Bit24; + m_flipButtons[25] = this.Bit25; + m_flipButtons[26] = this.Bit26; + m_flipButtons[27] = this.Bit27; + m_flipButtons[28] = this.Bit28; + m_flipButtons[29] = this.Bit29; + m_flipButtons[30] = this.Bit30; + m_flipButtons[31] = this.Bit31; + m_flipButtons[32] = this.Bit32; + m_flipButtons[33] = this.Bit33; + m_flipButtons[34] = this.Bit34; + m_flipButtons[35] = this.Bit35; + m_flipButtons[36] = this.Bit36; + m_flipButtons[37] = this.Bit37; + m_flipButtons[38] = this.Bit38; + m_flipButtons[39] = this.Bit39; + m_flipButtons[40] = this.Bit40; + m_flipButtons[41] = this.Bit41; + m_flipButtons[42] = this.Bit42; + m_flipButtons[43] = this.Bit43; + m_flipButtons[44] = this.Bit44; + m_flipButtons[45] = this.Bit45; + m_flipButtons[46] = this.Bit46; + m_flipButtons[47] = this.Bit47; + m_flipButtons[48] = this.Bit48; + m_flipButtons[49] = this.Bit49; + m_flipButtons[50] = this.Bit50; + m_flipButtons[51] = this.Bit51; + m_flipButtons[52] = this.Bit52; + m_flipButtons[53] = this.Bit53; + m_flipButtons[54] = this.Bit54; + m_flipButtons[55] = this.Bit55; + m_flipButtons[56] = this.Bit56; + m_flipButtons[57] = this.Bit57; + m_flipButtons[58] = this.Bit58; + m_flipButtons[59] = this.Bit59; + m_flipButtons[60] = this.Bit60; + m_flipButtons[61] = this.Bit61; + m_flipButtons[62] = this.Bit62; + m_flipButtons[63] = this.Bit63; + } + + private void OnBitToggled(object sender, RoutedEventArgs e) + { + if (m_updatingCheckedStates) + { + return; + } + + // Handle this the bit toggled event only if it is coming from BitFlip mode. + // Any input from the Numpad may also result in toggling the bit as their state is bound to the BinaryDisplayValue. + // Also, if the mode is switched to other Calculator modes when the BitFlip panel is open, + // a race condition exists in which the IsProgrammerMode property is still true and the UpdatePrimaryResult() is called, + // which continuously alters the Display Value and the state of the Bit Flip buttons. + if (Model.IsBitFlipChecked && Model.IsProgrammer) + { + var flipButton = (FlipButtons)sender; + int index = (int)flipButton.Tag; + flipButton.SetValue(AutomationProperties.NameProperty, GenerateAutomationPropertiesName(index, flipButton.IsChecked.Value)); + Model.ButtonPressed.Execute(flipButton.ButtonId); + } + } + + private string GenerateAutomationPropertiesName(int position, bool value) + { + var resourceLoader = AppResourceProvider.GetInstance(); + string automationNameTemplate = resourceLoader.GetResourceString("BitFlipItemAutomationName"); + string bitPosition; + if (position == 0) + { + bitPosition = resourceLoader.GetResourceString("LeastSignificantBit"); + } + else + { + int lastPosition = -1; + if (Model != null) + { + lastPosition = GetIndexOfLastBit(Model.ValueBitLength); + } + + if (position == lastPosition) + { + bitPosition = resourceLoader.GetResourceString("MostSignificantBit"); + } + else + { + string indexName = resourceLoader.GetResourceString(position.ToString()); + string bitPositionTemplate = resourceLoader.GetResourceString("BitPosition"); + bitPosition = LocalizationStringUtil.GetLocalizedString(bitPositionTemplate, indexName); + } + } + return LocalizationStringUtil.GetLocalizedString(automationNameTemplate, bitPosition, value ? "1" : "0"); + } + + private void UpdateCheckedStates(bool updateAutomationPropertiesNames) + { + Debug.Assert(!m_updatingCheckedStates); + Debug.Assert(m_flipButtons.Length == s_numBits); + + if (Model == null) + { + return; + } + + m_updatingCheckedStates = true; + // CSHARP_MIGRATION: + // iterator and index move at the same time with same step + // add index validation in the loop + int index = 0; + bool mustUpdateTextOfMostSignificantDigits = m_currentValueBitLength != Model.ValueBitLength; + int previousMSDPosition = GetIndexOfLastBit(m_currentValueBitLength); + int newMSDPosition = GetIndexOfLastBit(Model.ValueBitLength); + foreach (bool val in Model.BinaryDigits) + { + if (index < m_flipButtons.Length) + { + bool hasValueChanged = m_flipButtons[index].IsChecked.Value != val; + m_flipButtons[index].IsChecked = val; + if (updateAutomationPropertiesNames + || hasValueChanged + || (mustUpdateTextOfMostSignificantDigits && (index == previousMSDPosition || index == newMSDPosition))) + { + m_flipButtons[index].SetValue(AutomationProperties.NameProperty, GenerateAutomationPropertiesName(index, val)); + } + ++index; + } + } + + m_updatingCheckedStates = false; + } + + private void UpdateAutomationPropertiesNames() + { + foreach (FlipButtons flipButton in m_flipButtons) + { + int index = (int)flipButton.Tag; + flipButton.SetValue(AutomationProperties.NameProperty, GenerateAutomationPropertiesName(index, flipButton.IsChecked.Value)); + } + } + + private int GetIndexOfLastBit(BitLength length) + { + switch (length) + { + case BitLength.BitLengthQWord: + return 63; + case BitLength.BitLengthDWord: + return 31; + case BitLength.BitLengthWord: + return 15; + case BitLength.BitLengthByte: + return 7; + } + return -1; + } + + private static readonly uint s_numBits = 64; + private FlipButtons[] m_flipButtons = new FlipButtons[s_numBits]; + private bool m_updatingCheckedStates; + private BitLength m_currentValueBitLength; + } +} diff --git a/src/Calculator/Views/CalculatorProgrammerOperators.xaml b/src/Calculator/Views/CalculatorProgrammerOperators.xaml new file mode 100644 index 00000000..4fe2fcd1 --- /dev/null +++ b/src/Calculator/Views/CalculatorProgrammerOperators.xaml @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Calculator/Views/CalculatorProgrammerOperators.xaml.cs b/src/Calculator/Views/CalculatorProgrammerOperators.xaml.cs new file mode 100644 index 00000000..bda5a7f4 --- /dev/null +++ b/src/Calculator/Views/CalculatorProgrammerOperators.xaml.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Common; +using CalculatorApp.Controls; +using CalculatorApp.ViewModel; +using Windows.Devices.Input; + +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Automation.Peers; +using Windows.UI.ViewManagement; +using Windows.UI.Core; + +namespace CalculatorApp +{ + [Windows.Foundation.Metadata.WebHostHidden] + public sealed partial class CalculatorProgrammerOperators + { + public CalculatorProgrammerOperators() + { + InitializeComponent(); + + CopyMenuItem.Text = AppResourceProvider.GetInstance().GetResourceString("copyMenuItem"); + } + + public StandardCalculatorViewModel Model + { + get { return (StandardCalculatorViewModel)this.DataContext; } + } + + public Style SymbolButtonStyle + { + get { return (Style)GetValue(SymbolButtonStyleProperty); } + set { SetValue(SymbolButtonStyleProperty, value); } + } + + // Using a DependencyProperty as the backing store for SymbolButtonStyle. This enables animation, styling, binding, etc... + public static readonly DependencyProperty SymbolButtonStyleProperty = + DependencyProperty.Register(nameof(SymbolButtonStyle), typeof(Style), typeof(CalculatorProgrammerOperators), new PropertyMetadata(default(Style))); + + internal void SetRadixButton(NumberBase numberBase) + { + switch (numberBase) + { + case NumberBase.DecBase: + DecimalButton.IsChecked = true; + break; + case NumberBase.HexBase: + HexButton.IsChecked = true; + break; + case NumberBase.OctBase: + OctButton.IsChecked = true; + break; + case NumberBase.BinBase: + BinaryButton.IsChecked = true; + break; + default: + Debug.Assert(false); + break; + } + } + + private void DecButtonChecked(object sender, RoutedEventArgs e) + { + TraceLogger.GetInstance().UpdateButtonUsage(NumbersAndOperatorsEnum.DecButton, ViewMode.Programmer); + if (Model != null) + { + Model.SwitchProgrammerModeBase(NumberBase.DecBase); + } + } + + private void HexButtonChecked(object sender, RoutedEventArgs e) + { + TraceLogger.GetInstance().UpdateButtonUsage(NumbersAndOperatorsEnum.HexButton, ViewMode.Programmer); + if (Model != null) + { + Model.SwitchProgrammerModeBase(NumberBase.HexBase); + } + } + + private void BinButtonChecked(object sender, RoutedEventArgs e) + { + TraceLogger.GetInstance().UpdateButtonUsage(NumbersAndOperatorsEnum.BinButton, ViewMode.Programmer); + if (Model != null) + { + Model.SwitchProgrammerModeBase(NumberBase.BinBase); + } + } + + private void OctButtonChecked(object sender, RoutedEventArgs e) + { + TraceLogger.GetInstance().UpdateButtonUsage(NumbersAndOperatorsEnum.OctButton, ViewMode.Programmer); + if (Model != null) + { + Model.SwitchProgrammerModeBase(NumberBase.OctBase); + } + } + + private void OnCopyMenuItemClicked(object sender, RoutedEventArgs e) + { + var source = (RadixButton)ProgrammerOperatorsContextMenu.Target; + + CopyPasteManager.CopyToClipboard(source.GetRawDisplayValue()); + } + } +} diff --git a/src/Calculator/Views/CalculatorProgrammerRadixOperators.xaml b/src/Calculator/Views/CalculatorProgrammerRadixOperators.xaml new file mode 100644 index 00000000..8b3a1409 --- /dev/null +++ b/src/Calculator/Views/CalculatorProgrammerRadixOperators.xaml @@ -0,0 +1,693 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Calculator/Views/CalculatorProgrammerRadixOperators.xaml.cs b/src/Calculator/Views/CalculatorProgrammerRadixOperators.xaml.cs new file mode 100644 index 00000000..7e4c6439 --- /dev/null +++ b/src/Calculator/Views/CalculatorProgrammerRadixOperators.xaml.cs @@ -0,0 +1,238 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; + +using CalculatorApp; +using CalculatorApp.ViewModel; + +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using CalculatorApp.Common; +using Windows.UI.Xaml.Media; + +namespace CalculatorApp +{ + [Windows.Foundation.Metadata.WebHostHidden] + public sealed partial class CalculatorProgrammerRadixOperators + { + public CalculatorProgrammerRadixOperators() + { + m_isErrorVisualState = false; + InitializeComponent(); + LoadResourceStrings(); + } + + public StandardCalculatorViewModel Model + { + get { return (StandardCalculatorViewModel)this.DataContext; } + } + + public bool IsErrorVisualState { get; set; } + + public string ParenthesisCountToString(uint count) + { + return (count == 0) ? "" : count.ToString(); + } + + public void checkDefaultBitShift() + { + LoadDeferredLoadButtons(); + + if (IsButtonLoaded()) + { + return; + } + + CollapseBitshiftButtons(); + + m_selectedShiftButtonMode = BitShiftMode.Arithmetic; + LshButton.Visibility = Visibility.Visible; + RshButton.Visibility = Visibility.Visible; + LshButton.IsEnabled = true; + RshButton.IsEnabled = true; + } + + private enum BitShiftMode + { + Arithmetic, + LogicalShift, + RotateCircular, + RotateCarry + }; + + private void BitshiftFlyout_Checked(object sender, RoutedEventArgs e) + { + // Load deferred load buttons + LoadDeferredLoadButtons(); + + if (IsButtonLoaded()) + { + return; + } + + CollapseBitshiftButtons(); + + var radioButton = (RadioButton)sender; + string announcementString = ""; + BitShiftMode selectedButtonMode = m_selectedShiftButtonMode; + + if (radioButton == ArithmeticShiftButton) + { + LshButton.Visibility = Visibility.Visible; + RshButton.Visibility = Visibility.Visible; + LshButton.IsEnabled = true; + RshButton.IsEnabled = true; + announcementString = m_arithmeticShiftButtonContent; + selectedButtonMode = BitShiftMode.Arithmetic; + } + else if (radioButton == LogicalShiftButton) + { + LshLogicalButton.Visibility = Visibility.Visible; + RshLogicalButton.Visibility = Visibility.Visible; + LshLogicalButton.IsEnabled = true; + RshLogicalButton.IsEnabled = true; + announcementString = m_logicalShiftButtonContent; + selectedButtonMode = BitShiftMode.LogicalShift; + } + else if (radioButton == RotateCircularButton) + { + RolButton.Visibility = Visibility.Visible; + RorButton.Visibility = Visibility.Visible; + RolButton.IsEnabled = true; + RorButton.IsEnabled = true; + announcementString = m_rotateCircularButtonContent; + selectedButtonMode = BitShiftMode.RotateCircular; + } + else if (radioButton == RotateCarryShiftButton) + { + RolCarryButton.Visibility = Visibility.Visible; + RorCarryButton.Visibility = Visibility.Visible; + RolCarryButton.IsEnabled = true; + RorCarryButton.IsEnabled = true; + announcementString = m_rotateCarryShiftButtonContent; + selectedButtonMode = BitShiftMode.RotateCarry; + } + + if (selectedButtonMode != m_selectedShiftButtonMode) + { + this.BitShiftFlyout.Hide(); + m_selectedShiftButtonMode = selectedButtonMode; + } + + Model.SetBitshiftRadioButtonCheckedAnnouncement(announcementString); + } + + private void FlyoutButton_Clicked(object sender, RoutedEventArgs e) + { + this.BitwiseFlyout.Hide(); + } + + private void CollapseBitshiftButtons() + { + RolButton.Visibility = Visibility.Collapsed; + RorButton.Visibility = Visibility.Collapsed; + RolCarryButton.Visibility = Visibility.Collapsed; + RorCarryButton.Visibility = Visibility.Collapsed; + LshButton.Visibility = Visibility.Collapsed; + RshButton.Visibility = Visibility.Collapsed; + LshLogicalButton.Visibility = Visibility.Collapsed; + RshLogicalButton.Visibility = Visibility.Collapsed; + + // We need to set the collapsed buttons to disabled so that the KeyboardShortcutManager can skip the keybinds for the disabled buttons + RolButton.IsEnabled = false; + RorButton.IsEnabled = false; + RolCarryButton.IsEnabled = false; + RorCarryButton.IsEnabled = false; + LshButton.IsEnabled = false; + RshButton.IsEnabled = false; + LshLogicalButton.IsEnabled = false; + RshLogicalButton.IsEnabled = false; + } + + private void LoadResourceStrings() + { + var resProvider = AppResourceProvider.GetInstance(); + m_arithmeticShiftButtonContent = resProvider.GetResourceString("arithmeticShiftButtonSelected"); + m_logicalShiftButtonContent = resProvider.GetResourceString("logicalShiftButtonSelected"); + m_rotateCircularButtonContent = resProvider.GetResourceString("rotateCircularButtonSelected"); + m_rotateCarryShiftButtonContent = resProvider.GetResourceString("rotateCarryShiftButtonSelected"); + } + + private void LoadDeferredLoadButtons() + { + // Load deferred load buttons + if (RolButton == null) + { + FindName("RolButton"); + FindName("RorButton"); + FindName("RolCarryButton"); + FindName("RorCarryButton"); + FindName("LshLogicalButton"); + FindName("RshLogicalButton"); + } + } + + private bool IsButtonLoaded() + { + // Since arithmeticShiftButton defaults to IsChecked = true, this event an fire before we can load the deferred loaded controls. If that is the case, just + // return and do nothing. + return RolButton == null || RorButton == null || RolCarryButton == null || RorCarryButton == null || LshLogicalButton == null + || RshLogicalButton == null; + } + + private bool m_isErrorVisualState; + + private void OpenParenthesisButton_GotFocus(object sender, RoutedEventArgs e) + { + Model.SetOpenParenthesisCountNarratorAnnouncement(); + } + + private void ClearEntryButton_LostFocus(object sender, RoutedEventArgs e) + { + if (ClearEntryButton.Visibility == Visibility.Collapsed && ClearButton.Visibility == Visibility.Visible) + { + ClearButton.Focus(FocusState.Programmatic); + } + } + + private void ClearButton_LostFocus(object sender, RoutedEventArgs e) + { + if (ClearEntryButton.Visibility == Visibility.Visible && ClearButton.Visibility == Visibility.Collapsed) + { + ClearEntryButton.Focus(FocusState.Programmatic); + } + } + + private void BitShiftFlyout_Opened(object sender, object e) + { + if (m_selectedShiftButtonMode == BitShiftMode.Arithmetic) + { + ArithmeticShiftButton.IsChecked = true; + } + else if (m_selectedShiftButtonMode == BitShiftMode.LogicalShift) + { + LogicalShiftButton.IsChecked = true; + } + else if (m_selectedShiftButtonMode == BitShiftMode.RotateCircular) + { + RotateCircularButton.IsChecked = true; + } + else if (m_selectedShiftButtonMode == BitShiftMode.RotateCarry) + { + RotateCarryShiftButton.IsChecked = true; + } + } + + private BitShiftMode m_selectedShiftButtonMode; + private string m_arithmeticShiftButtonContent; + private string m_logicalShiftButtonContent; + private string m_rotateCircularButtonContent; + private string m_rotateCarryShiftButtonContent; + } +} diff --git a/src/Calculator/Views/CalculatorScientificAngleButtons.xaml b/src/Calculator/Views/CalculatorScientificAngleButtons.xaml new file mode 100644 index 00000000..5659e823 --- /dev/null +++ b/src/Calculator/Views/CalculatorScientificAngleButtons.xaml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FToE + + + + diff --git a/src/Calculator/Views/CalculatorScientificAngleButtons.xaml.cs b/src/Calculator/Views/CalculatorScientificAngleButtons.xaml.cs new file mode 100644 index 00000000..bee6445d --- /dev/null +++ b/src/Calculator/Views/CalculatorScientificAngleButtons.xaml.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// +// CalculatorScientificAngleButtons.xaml.h +// Declaration of the CalculatorScientificAngleButtons class +// + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.ViewModel; + +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; +using Windows.UI.ViewManagement; +using Windows.UI.Core; + +namespace CalculatorApp +{ + [Windows.Foundation.Metadata.WebHostHidden] + public sealed partial class CalculatorScientificAngleButtons + { + public CalculatorScientificAngleButtons() + { + m_isErrorVisualState = false; + InitializeComponent(); + } + + public StandardCalculatorViewModel Model + { + get { return (StandardCalculatorViewModel)this.DataContext; } + } + + public System.Windows.Input.ICommand ButtonPressed + { + get + { + if (donotuse_ButtonPressed == null) + { + donotuse_ButtonPressed = new CalculatorApp.Common.DelegateCommand(this, OnAngleButtonPressed); + } + return donotuse_ButtonPressed; + } + } + private System.Windows.Input.ICommand donotuse_ButtonPressed; + + public bool IsErrorVisualState { get; set; } + + private void OnAngleButtonPressed(object commandParameter) + { + string buttonId = (string)commandParameter; + + DegreeButton.Visibility = Visibility.Collapsed; + RadianButton.Visibility = Visibility.Collapsed; + GradsButton.Visibility = Visibility.Collapsed; + + if (buttonId == "0") + { + Model.SwitchAngleType(NumbersAndOperatorsEnum.Radians); + RadianButton.Visibility = Visibility.Visible; + RadianButton.Focus(FocusState.Programmatic); + } + else if (buttonId == "1") + { + Model.SwitchAngleType(NumbersAndOperatorsEnum.Grads); + GradsButton.Visibility = Visibility.Visible; + GradsButton.Focus(FocusState.Programmatic); + } + else if (buttonId == "2") + { + Model.SwitchAngleType(NumbersAndOperatorsEnum.Degree); + DegreeButton.Visibility = Visibility.Visible; + DegreeButton.Focus(FocusState.Programmatic); + } + } + + private void FToEButton_Toggled(object sender, RoutedEventArgs e) + { + var viewModel = (StandardCalculatorViewModel)this.DataContext; + viewModel.FtoEButtonToggled(); + } + + private bool m_isErrorVisualState; + } +} diff --git a/src/Calculator/Views/CalculatorScientificOperators.xaml b/src/Calculator/Views/CalculatorScientificOperators.xaml new file mode 100644 index 00000000..2e3701a6 --- /dev/null +++ b/src/Calculator/Views/CalculatorScientificOperators.xaml @@ -0,0 +1,1212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Calculator/Views/CalculatorScientificOperators.xaml.cs b/src/Calculator/Views/CalculatorScientificOperators.xaml.cs new file mode 100644 index 00000000..b1ece8d0 --- /dev/null +++ b/src/Calculator/Views/CalculatorScientificOperators.xaml.cs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// +// CalculatorScientificOperators.xaml.h +// Declaration of the CalculatorScientificOperators class +// + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Common; +using CalculatorApp.ViewModel; + +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Core; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; +using Windows.UI.Core; + +namespace CalculatorApp +{ + [Windows.Foundation.Metadata.WebHostHidden] + public sealed partial class CalculatorScientificOperators + { + public CalculatorScientificOperators() + { + InitializeComponent(); + + ExpButton.SetValue(Common.KeyboardShortcutManager.VirtualKeyProperty, Common.MyVirtualKey.E); + } + + public StandardCalculatorViewModel Model + { + get { return (StandardCalculatorViewModel)this.DataContext; } + } + + public bool IsErrorVisualState + { + get { return (bool)GetValue(IsErrorVisualStateProperty); } + set { SetValue(IsErrorVisualStateProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsErrorVisualState. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsErrorVisualStateProperty = + DependencyProperty.Register(nameof(IsErrorVisualState), typeof(bool), typeof(CalculatorScientificOperators), new PropertyMetadata(false, (sender, args) => + { + var self = (CalculatorScientificOperators)sender; + self.OnIsErrorVisualStatePropertyChanged((bool)args.OldValue, (bool)args.NewValue); + })); + + public void OpenParenthesisButton_GotFocus(object sender, RoutedEventArgs e) + { + Model.SetOpenParenthesisCountNarratorAnnouncement(); + } + + public string ParenthesisCountToString(uint count) + { + return (count == 0) ? "" : count.ToString(); + } + + private void OnIsErrorVisualStatePropertyChanged(bool oldValue, bool newValue) + { + string newState = newValue ? "ErrorLayout" : "NoErrorLayout"; + VisualStateManager.GoToState(this, newState, false); + NumberPad.IsErrorVisualState = newValue; + } + + private void ShiftButton_Check(object sender, RoutedEventArgs e) + { + SetOperatorRowVisibility(); + } + + private void ShiftButton_Uncheck(object sender, RoutedEventArgs e) + { + ShiftButton.IsChecked = false; + SetOperatorRowVisibility(); + ShiftButton.Focus(FocusState.Programmatic); + } + + private void TrigFlyoutShift_Toggle(object sender, RoutedEventArgs e) + { + SetTrigRowVisibility(); + } + + private void TrigFlyoutHyp_Toggle(object sender, RoutedEventArgs e) + { + SetTrigRowVisibility(); + } + + private void FlyoutButton_Clicked(object sender, RoutedEventArgs e) + { + HypButton.IsChecked = false; + TrigShiftButton.IsChecked = false; + Trigflyout.Hide(); + FuncFlyout.Hide(); + } + + private void ShiftButton_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + SetOperatorRowVisibility(); + } + + private void SetOperatorRowVisibility() + { + Visibility rowVis, invRowVis; + if (ShiftButton.IsChecked.Value) + { + rowVis = Visibility.Collapsed; + invRowVis = Visibility.Visible; + } + else + { + rowVis = Visibility.Visible; + invRowVis = Visibility.Collapsed; + } + + Row1.Visibility = rowVis; + InvRow1.Visibility = invRowVis; + } + + private void SetTrigRowVisibility() + { + bool isShiftChecked = TrigShiftButton.IsChecked.Value; + bool isHypeChecked = HypButton.IsChecked.Value; + + InverseHyperbolicTrigFunctions.Visibility = Visibility.Collapsed; + InverseTrigFunctions.Visibility = Visibility.Collapsed; + HyperbolicTrigFunctions.Visibility = Visibility.Collapsed; + TrigFunctions.Visibility = Visibility.Collapsed; + + if (isShiftChecked && isHypeChecked) + { + InverseHyperbolicTrigFunctions.Visibility = Visibility.Visible; + } + else if (isShiftChecked && !isHypeChecked) + { + InverseTrigFunctions.Visibility = Visibility.Visible; + } + else if (!isShiftChecked && isHypeChecked) + { + HyperbolicTrigFunctions.Visibility = Visibility.Visible; + } + else + { + TrigFunctions.Visibility = Visibility.Visible; + } + } + + private void ClearEntryButton_LostFocus(object sender, RoutedEventArgs e) + { + if (ClearEntryButton.Visibility == Visibility.Collapsed && ClearButton.Visibility == Visibility.Visible) + { + ClearButton.Focus(FocusState.Programmatic); + } + } + + private void ClearButton_LostFocus(object sender, RoutedEventArgs e) + { + if (ClearEntryButton.Visibility == Visibility.Visible && ClearButton.Visibility == Visibility.Collapsed) + { + ClearEntryButton.Focus(FocusState.Programmatic); + } + } + } +} diff --git a/src/Calculator/Views/CalculatorStandardOperators.xaml b/src/Calculator/Views/CalculatorStandardOperators.xaml new file mode 100644 index 00000000..6907e8b4 --- /dev/null +++ b/src/Calculator/Views/CalculatorStandardOperators.xaml @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Calculator/Views/CalculatorStandardOperators.xaml.cs b/src/Calculator/Views/CalculatorStandardOperators.xaml.cs new file mode 100644 index 00000000..f03bc7af --- /dev/null +++ b/src/Calculator/Views/CalculatorStandardOperators.xaml.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// +// CalculatorStandardOperators.xaml.h +// Declaration of the CalculatorStandardOperators class +// + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; + +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +namespace CalculatorApp +{ + [Windows.Foundation.Metadata.WebHostHidden] + public sealed partial class CalculatorStandardOperators + { + public CalculatorStandardOperators() + { + m_isErrorVisualState = false; + InitializeComponent(); + } + + public bool IsErrorVisualState + { + get { return m_isErrorVisualState; } + set + { + if (m_isErrorVisualState != value) + { + m_isErrorVisualState = value; + string newState = m_isErrorVisualState ? "ErrorLayout" : "NoErrorLayout"; + VisualStateManager.GoToState(this, newState, false); + NumberPad.IsErrorVisualState = m_isErrorVisualState; + } + } + } + private bool m_isErrorVisualState; + } +} diff --git a/src/Calculator/Views/DateCalculator.xaml b/src/Calculator/Views/DateCalculator.xaml new file mode 100644 index 00000000..43ae80a8 --- /dev/null +++ b/src/Calculator/Views/DateCalculator.xaml @@ -0,0 +1,589 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Calculator/Views/DateCalculator.xaml.cs b/src/Calculator/Views/DateCalculator.xaml.cs new file mode 100644 index 00000000..0b0e8c7c --- /dev/null +++ b/src/Calculator/Views/DateCalculator.xaml.cs @@ -0,0 +1,269 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// +// DateCalculator.xaml.h +// Declaration of the DateCalculator class +// + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Common; +using CalculatorApp.ViewModel; + +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.Globalization; +using Windows.Globalization.DateTimeFormatting; +using Windows.System.UserProfile; +using Windows.UI.Core; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Automation.Peers; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +namespace CalculatorApp +{ + [Windows.Foundation.Metadata.WebHostHidden] + public sealed partial class DateCalculator + { + public DateCalculator() + { + InitializeComponent(); + + // Set Calendar Identifier + DateDiff_FromDate.CalendarIdentifier = localizationSettings.GetCalendarIdentifier(); + DateDiff_ToDate.CalendarIdentifier = localizationSettings.GetCalendarIdentifier(); + + // Setting the FirstDayofWeek + DateDiff_FromDate.FirstDayOfWeek = localizationSettings.GetFirstDayOfWeek(); + DateDiff_ToDate.FirstDayOfWeek = localizationSettings.GetFirstDayOfWeek(); + + // Setting the Language explicitly is not required, + // this is a workaround for the bug in the control due to which + // the displayed date is incorrect for non Gregorian Calendar Systems + // The displayed date doesn't honor the shortdate format, on setting the Language the format is refreshed + DateDiff_FromDate.Language = localizationSettings.GetLocaleName(); + DateDiff_ToDate.Language = localizationSettings.GetLocaleName(); + + // Set Min and Max Dates according to the Gregorian Calendar(1601 & 9999) + var calendar = new Calendar(); + var today = calendar.GetDateTime(); + + calendar.ChangeCalendarSystem(CalendarIdentifiers.Gregorian); + calendar.Day = 1; + calendar.Month = 1; + calendar.Year = c_minYear; + var minYear = calendar.GetDateTime(); // 1st January, 1601 + DateDiff_FromDate.MinDate = minYear; + DateDiff_ToDate.MinDate = minYear; + + calendar.Day = 31; + calendar.Month = 12; + calendar.Year = c_maxYear; + var maxYear = calendar.GetDateTime(); // 31st December, 9878 + DateDiff_FromDate.MaxDate = maxYear; + DateDiff_ToDate.MaxDate = maxYear; + + // Set the PlaceHolderText for CalendarDatePicker + DateTimeFormatter dateTimeFormatter = LocalizationService.GetInstance().GetRegionalSettingsAwareDateTimeFormatter( + "day month year", + localizationSettings.GetCalendarIdentifier(), + ClockIdentifiers.TwentyFourHour); // Clock Identifier is not used + + DateDiff_FromDate.DateFormat = "day month year"; + DateDiff_ToDate.DateFormat = "day month year"; + + var placeholderText = dateTimeFormatter.Format(today); + + DateDiff_FromDate.PlaceholderText = placeholderText; + DateDiff_ToDate.PlaceholderText = placeholderText; + + CopyMenuItem.Text = AppResourceProvider.GetInstance().GetResourceString("copyMenuItem"); + DateCalculationOption.SelectionChanged += DateCalcOption_Changed; + } + + public void CloseCalendarFlyout() + { + if (DateDiff_FromDate.IsCalendarOpen) + { + DateDiff_FromDate.IsCalendarOpen = false; + } + + if (DateDiff_ToDate.IsCalendarOpen) + { + DateDiff_ToDate.IsCalendarOpen = false; + } + + if ((AddSubtract_FromDate != null) && (AddSubtract_FromDate.IsCalendarOpen)) + { + AddSubtract_FromDate.IsCalendarOpen = false; + } + } + + public void SetDefaultFocus() + { + DateCalculationOption.Focus(FocusState.Programmatic); + } + + private void FromDate_DateChanged(CalendarDatePicker sender, CalendarDatePickerDateChangedEventArgs e) + { + if (e.NewDate != null) + { + var dateCalcViewModel = (DateCalculatorViewModel)DataContext; + dateCalcViewModel.FromDate = e.NewDate.Value; + TraceLogger.GetInstance().LogDateCalculationModeUsed(false); + } + else + { + ReselectCalendarDate(sender, e.OldDate.Value); + } + } + + private void ToDate_DateChanged(CalendarDatePicker sender, CalendarDatePickerDateChangedEventArgs e) + { + if (e.NewDate != null) + { + var dateCalcViewModel = (DateCalculatorViewModel)this.DataContext; + dateCalcViewModel.ToDate = e.NewDate.Value; + TraceLogger.GetInstance().LogDateCalculationModeUsed(false); + } + else + { + ReselectCalendarDate(sender, e.OldDate.Value); + } + } + + private void AddSubtract_DateChanged(CalendarDatePicker sender, CalendarDatePickerDateChangedEventArgs e) + { + if (e.NewDate != null) + { + var dateCalcViewModel = (DateCalculatorViewModel)this.DataContext; + dateCalcViewModel.StartDate = e.NewDate.Value; + TraceLogger.GetInstance().LogDateCalculationModeUsed(true); + } + else + { + ReselectCalendarDate(sender, e.OldDate.Value); + } + } + + private void OffsetValue_Changed(object sender, SelectionChangedEventArgs e) + { + var dateCalcViewModel = (DateCalculatorViewModel)this.DataContext; + // do not log diagnostics for no-ops and initialization of combo boxes + if (dateCalcViewModel.DaysOffset != 0 || dateCalcViewModel.MonthsOffset != 0 || dateCalcViewModel.YearsOffset != 0) + { + TraceLogger.GetInstance().LogDateCalculationModeUsed(true); + } + } + + private void OnCopyMenuItemClicked(object sender, RoutedEventArgs e) + { + var calcResult = (TextBlock)ResultsContextMenu.Target; + + CopyPasteManager.CopyToClipboard(calcResult.Text); + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + } + + private void DateCalcOption_Changed(object sender, SelectionChangedEventArgs e) + { + FindName("AddSubtractDateGrid"); + var dateCalcViewModel = (DateCalculatorViewModel)this.DataContext; + + // From Date Field needs to persist across Date Difference and Add Substract Date Mode. + // So when the mode dropdown changes, update the other datepicker with the latest date. + if (dateCalcViewModel.IsDateDiffMode) + { + if (AddSubtract_FromDate.Date == null) + { + return; + } + DateDiff_FromDate.Date = AddSubtract_FromDate.Date.Value; + } + else + { + if (DateDiff_FromDate.Date == null) + { + // If no date has been picked, then this can be null. + return; + } + AddSubtract_FromDate.Date = DateDiff_FromDate.Date.Value; + } + } + + private void AddSubtractDateGrid_Loaded(object sender, RoutedEventArgs e) + { + AddSubtract_FromDate.PlaceholderText = DateDiff_FromDate.PlaceholderText; + AddSubtract_FromDate.CalendarIdentifier = localizationSettings.GetCalendarIdentifier(); + AddSubtract_FromDate.FirstDayOfWeek = localizationSettings.GetFirstDayOfWeek(); + AddSubtract_FromDate.Language = localizationSettings.GetLocaleName(); + + AddSubtract_FromDate.MinDate = DateDiff_FromDate.MinDate; + AddSubtract_FromDate.MaxDate = DateDiff_FromDate.MaxDate; + AddSubtract_FromDate.DateFormat = "day month year"; + } + + private void AddSubtractOption_Checked(object sender, RoutedEventArgs e) + { + RaiseLiveRegionChangedAutomationEvent(false); + } + + // CSHARP_MIGRATION: TODO: + // can we change the calendarDatePicker.Date of in arg? + private void ReselectCalendarDate(CalendarDatePicker calendarDatePicker, DateTimeOffset? dateTime) + { + // Reselect the unselected Date + calendarDatePicker.Date = dateTime; + + // Dismiss the Calendar flyout + calendarDatePicker.IsCalendarOpen = false; + } + + private void OffsetDropDownClosed(object sender, object e) + { + RaiseLiveRegionChangedAutomationEvent(false); + } + + private void CalendarFlyoutClosed(object sender, object e) + { + var dateCalcViewModel = (DateCalculatorViewModel)this.DataContext; + RaiseLiveRegionChangedAutomationEvent(dateCalcViewModel.IsDateDiffMode); + } + + private void RaiseLiveRegionChangedAutomationEvent(bool isDateDiffMode) + { + TextBlock resultTextBlock = isDateDiffMode ? DateDiffAllUnitsResultLabel : DateResultLabel; + string automationName = AutomationProperties.GetName(resultTextBlock); + TextBlockAutomationPeer.FromElement(resultTextBlock).RaiseAutomationEvent(AutomationEvents.LiveRegionChanged); + } + + private void OnVisualStateChanged(object sender, VisualStateChangedEventArgs e) + { + TraceLogger.GetInstance().LogVisualStateChanged(ViewMode.Date, e.NewState.Name, false); + } + + // We choose 2550 as the max year because CalendarDatePicker experiences clipping + // issues just after 2558. We would like 9999 but will need to wait for a platform + // fix before we use a higher max year. This platform issue is tracked by + // TODO: MSFT-9273247 + private const int c_maxYear = 2550; + private const int c_minYear = 1601; + + private static readonly LocalizationSettings localizationSettings = LocalizationSettings.GetInstance(); + } +} diff --git a/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml new file mode 100644 index 00000000..84472f07 --- /dev/null +++ b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml @@ -0,0 +1,950 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 32 + + + + + + + + + + + + + + + + + + + + + 18 + + + + + + + + + + + + 18 + + + + + + + + + + + + 18 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.cs b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.cs new file mode 100644 index 00000000..71aeea97 --- /dev/null +++ b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.cs @@ -0,0 +1,654 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Common; +using CalculatorApp.Common.Automation; +using GraphControl; +using CalculatorApp.ViewModel; +using CalculatorApp.Controls; +using Windows.Foundation; +using Windows.System; +using Windows.UI; +using Windows.UI.Core; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Input; +using Calculator.Utils; + +namespace CalculatorApp +{ + public sealed partial class EquationInputArea : System.ComponentModel.INotifyPropertyChanged + { + public EquationInputArea() + { + m_lastLineColorIndex = -1; + m_AvailableColors = new ObservableCollection(); + m_accessibilitySettings = new AccessibilitySettings(); + m_equationToFocus = null; + + m_accessibilitySettings.HighContrastChanged += OnHighContrastChanged; + m_isHighContrast = m_accessibilitySettings.HighContrast; + + m_uiSettings = new UISettings(); + m_uiSettings.ColorValuesChanged += OnColorValuesChanged; + + ReloadAvailableColors(m_accessibilitySettings.HighContrast, true); + + InitializeComponent(); + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + internal void RaisePropertyChanged(string p) + { + PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(p)); + OnPropertyChanged(p); + } + + public Windows.Foundation.Collections.IObservableVector Equations + { + get { return m_Equations; } + set + { + if (m_Equations != value) + { + m_Equations = value; + RaisePropertyChanged("m_Equations"); + } + } + } + private Windows.Foundation.Collections.IObservableVector m_Equations; + + public Windows.Foundation.Collections.IObservableVector Variables + { + get { return m_Variables; } + set + { + if (m_Variables != value) + { + m_Variables = value; + RaisePropertyChanged("m_Variables"); + } + } + } + private Windows.Foundation.Collections.IObservableVector m_Variables; + + public ObservableCollection AvailableColors + { + get { return m_AvailableColors; } + set + { + if (m_AvailableColors != value) + { + m_AvailableColors = value; + RaisePropertyChanged("m_AvailableColors"); + } + } + } + private ObservableCollection m_AvailableColors; + + + public bool IsMatchAppTheme + { + get { return m_IsMatchAppTheme; } + set + { + if (m_IsMatchAppTheme != value) + { + m_IsMatchAppTheme = value; + RaisePropertyChanged("m_IsMatchAppTheme"); + } + } + } + private bool m_IsMatchAppTheme; + + public event System.EventHandler KeyGraphFeaturesRequested; + public event System.EventHandler EquationFormatRequested; + + public static Visibility ManageEditVariablesButtonVisibility(uint numberOfVariables) + { + return numberOfVariables == 0 ? Visibility.Collapsed : Visibility.Visible; + } + + public static bool ManageEditVariablesButtonLoaded(int numberOfVariables) + { + return numberOfVariables != 0; + } + + public static string GetChevronIcon(bool isCollapsed) + { + return isCollapsed ? "\uE70E" : "\uE70D"; + } + + public static SolidColorBrush ToSolidColorBrush(Color color) + { + return new SolidColorBrush(color); + } + + public static SolidColorBrush GetForegroundColor(Color lineColor) + { + return Utilities.GetContrastColor(lineColor); + } + + public void FocusEquationTextBox(EquationViewModel equation) + { + int index = Equations.IndexOf(equation); + if (index < 0) + { + return; + } + var container = (UIElement)EquationInputList.ContainerFromIndex(index); + if (container != null) + { + container.StartBringIntoView(); + + var equationInput = VisualTree.FindDescendantByName(container, "EquationInputButton"); + if (equationInput == null) + { + return; + } + var equationTextBox = equationInput as EquationTextBox; + if (equationTextBox != null) + { + equationTextBox.FocusTextBox(); + } + } + } + + private void OnPropertyChanged(string propertyName) + { + if (propertyName == EquationsPropertyName) + { + OnEquationsPropertyChanged(); + } + + else if (propertyName == IsMatchAppThemePropertyName) + { + ReloadAvailableColors(m_accessibilitySettings.HighContrast, false); + } + + } + + private void OnEquationsPropertyChanged() + { + if (Equations != null && Equations.Count == 0) + { + AddNewEquation(); + } + } + + private void AddNewEquation() + { + if (Equations.Count > 0) + { + Equations[Equations.Count - 1].IsLastItemInList = false; + } + + // Cap equations at 14 + if (Equations.Count >= maxEquationSize) + { + return; + } + + int colorIndex; + + if (m_accessibilitySettings.HighContrast) + { + m_lastLineColorIndex = (m_lastLineColorIndex + 1) % AvailableColors.Count; + colorIndex = m_lastLineColorIndex; + } + else + { + bool[] colorAssignmentUsed = new bool[colorCount]; + foreach (var equation in Equations) + { + colorAssignmentUsed[equation.LineColorIndex] = true; + } + + colorIndex = 0; + // If for some reason all of the values in colorAssignmentUsed are true, the check for colorIndex < colorCount - 1 will + // set it to the last color in the list + while (colorIndex < colorCount - 1 && colorAssignmentUsed[colorAssignmentMapping[colorIndex]]) + { + colorIndex++; + } + + colorIndex = colorAssignmentMapping[colorIndex]; + } + + var eq = new EquationViewModel(new Equation(), ++m_lastFunctionLabelIndex, AvailableColors[colorIndex].Color, colorIndex); + eq.IsLastItemInList = true; + m_equationToFocus = eq; + Equations.Add(eq); + } + + private void EquationTextBox_GotFocus(object sender, RoutedEventArgs e) + { + var eq = GetViewModelFromEquationTextBox(sender); + if (eq != null) + { + eq.GraphEquation.IsSelected = true; + } + } + + private void EquationTextBox_LostFocus(object sender, RoutedEventArgs e) + { + var eq = GetViewModelFromEquationTextBox(sender); + if (eq != null) + { + eq.GraphEquation.IsSelected = false; + } + } + + private void EquationTextBox_Submitted(object sender, MathRichEditBoxSubmission submission) + { + var eq = GetViewModelFromEquationTextBox(sender); + if (eq == null) + { + return; + } + + if (submission.Source == EquationSubmissionSource.ENTER_KEY + || (submission.Source == EquationSubmissionSource.FOCUS_LOST && submission.HasTextChanged && eq.Expression != null + && eq.Expression.Length > 0)) + { + if (submission.Source == EquationSubmissionSource.ENTER_KEY) + { + eq.IsLineEnabled = true; + } + + int index = Equations.IndexOf(eq); + if (index >= 0) + { + if (index == Equations.Count - 1) + { + // If it's the last equation of the list + AddNewEquation(); + } + else + { + if (submission.Source == EquationSubmissionSource.ENTER_KEY) + { + var nextEquation = Equations[index + 1]; + FocusEquationTextBox(nextEquation); + } + } + } + } + } + + private void OnHighContrastChanged(AccessibilitySettings sender, object args) + { + ReloadAvailableColors(sender.HighContrast, true); + m_isHighContrast = sender.HighContrast; + } + + private void ReloadAvailableColors(bool isHighContrast, bool reassignColors) + { + m_AvailableColors.Clear(); + if (isHighContrast) + { + m_AvailableColors.Add((SolidColorBrush)Application.Current.Resources["EquationBrush1"]); + m_AvailableColors.Add((SolidColorBrush)Application.Current.Resources["EquationBrush2"]); + m_AvailableColors.Add((SolidColorBrush)Application.Current.Resources["EquationBrush3"]); + m_AvailableColors.Add((SolidColorBrush)Application.Current.Resources["EquationBrush4"]); + } + + // If this is not high contrast, we have all 16 colors, otherwise we will restrict this to a subset of high contrast colors + else + { + object themeDictionaryName = "Light"; + if (IsMatchAppTheme && Application.Current.RequestedTheme == ApplicationTheme.Dark) + { + themeDictionaryName = "Default"; + } + var themeDictionary = (ResourceDictionary)Application.Current.Resources.ThemeDictionaries[themeDictionaryName]; + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush1"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush2"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush3"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush4"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush5"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush6"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush7"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush8"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush9"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush10"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush11"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush12"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush13"]); + m_AvailableColors.Add((SolidColorBrush)themeDictionary["EquationBrush14"]); + } + + // If there are no equations to reload, quit early + if (Equations == null || Equations.Count == 0) + { + return; + } + + // Reassign colors for each equation + if (reassignColors) + { + m_lastLineColorIndex = -1; + } + + foreach (var equationViewModel in Equations) + { + if (reassignColors) + { + m_lastLineColorIndex = (m_lastLineColorIndex + 1) % AvailableColors.Count; + equationViewModel.LineColorIndex = m_lastLineColorIndex; + } + equationViewModel.LineColor = AvailableColors[equationViewModel.LineColorIndex].Color; + } + } + + private void OnColorValuesChanged(UISettings sender, object args) + { + + WeakReference weakThis = new WeakReference(this); + _ = this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() => { + var refThis = weakThis.Target as EquationInputArea; + if (refThis != null && refThis.m_isHighContrast == refThis.m_accessibilitySettings.HighContrast) + { + refThis.ReloadAvailableColors(false, false); + } + })); + } + + private void EquationTextBox_RemoveButtonClicked(object sender, RoutedEventArgs e) + { + var eq = GetViewModelFromEquationTextBox(sender); + int index = Equations.IndexOf(eq); + + if (index >= 0) + { + // Prevent removing the last equation + if (index == Equations.Count - 1) + { + return; + } + + Equations.RemoveAt(index); + + var narratorNotifier = new NarratorNotifier(); + var announcement = + CalculatorAnnouncement.GetFunctionRemovedAnnouncement(AppResourceProvider.GetInstance().GetResourceString("FunctionRemovedAnnouncement")); + narratorNotifier.Announce(announcement); + + int lastIndex = Equations.Count - 1; + + if (Equations.Count <= 1) + { + m_lastFunctionLabelIndex = 1; + } + else + { + m_lastFunctionLabelIndex = Equations[lastIndex - 1].FunctionLabelIndex + 1; + } + + Equations[lastIndex].FunctionLabelIndex = m_lastFunctionLabelIndex; + + // Focus the next equation after the one we just removed. There should always be at least one ghost equation, + // but check to make sure that there is an equation we can focus in the index where we just removed an equation. + if (index < Equations.Count) + { + FocusEquationTextBox(Equations[index]); + } + } + } + + private void EquationTextBox_KeyGraphFeaturesButtonClicked(object sender, RoutedEventArgs e) + { + KeyGraphFeaturesRequested(this, GetViewModelFromEquationTextBox(sender)); + } + + private void EquationTextBox_EquationButtonClicked(object sender, RoutedEventArgs e) + { + var eq = GetViewModelFromEquationTextBox(sender); + eq.IsLineEnabled = !eq.IsLineEnabled; + + TraceLogger.GetInstance().LogShowHideButtonClicked(eq.IsLineEnabled ? false : true); + } + + private void EquationTextBox_Loaded(object sender, RoutedEventArgs e) + { + var tb = (EquationTextBox)sender; + + var colorChooser = (EquationStylePanelControl)tb.ColorChooserFlyout.Content; + colorChooser.AvailableColors = AvailableColors; + + if (m_equationToFocus != null && tb.DataContext == m_equationToFocus) + { + var copyEquationToFocus = m_equationToFocus; + m_equationToFocus = null; + tb.FocusTextBox(); + + int index = Equations.IndexOf(copyEquationToFocus); + if (index >= 0) + { + var container = (UIElement)EquationInputList.ContainerFromIndex(index); + if (container != null) + { + container.StartBringIntoView(); + } + } + } + } + + private void EquationTextBox_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) + { + var tb = (EquationTextBox)sender; + if (!tb.IsLoaded) + { + return; + } + + FocusEquationIfNecessary(tb); + } + + private void FocusEquationIfNecessary(EquationTextBox textBox) + { + if (m_equationToFocus != null && textBox.DataContext == m_equationToFocus) + { + m_equationToFocus = null; + textBox.FocusTextBox(); + + int index = Equations.IndexOf(m_equationToFocus); + if (index >= 0) + { + var container = (UIElement)EquationInputList.ContainerFromIndex(index); + if (container != null) + { + container.StartBringIntoView(); + } + } + } + } + + private double validateDouble(string value, double defaultValue) + { + double resultValue = 0; + if (double.TryParse(value, out resultValue)) + { + return resultValue; + } + + return defaultValue; + } + + private void TextBoxGotFocus(object sender, RoutedEventArgs e) + { + ((TextBox)sender).SelectAll(); + } + + private void TextBoxLosingFocus(object sender, LosingFocusEventArgs e) + { + SubmitTextbox((TextBox)sender); + } + + private void TextBoxKeyDown(object sender, KeyRoutedEventArgs e) + { + if (e.Key == VirtualKey.Enter) + { + SubmitTextbox((TextBox)sender); + } + } + + private void SubmitTextbox(TextBox sender) + { + var variableViewModel = (VariableViewModel)sender.DataContext; + double val; + if (sender.Name == "ValueTextBox") + { + val = validateDouble(sender.Text, variableViewModel.Value); + variableViewModel.Value = val; + TraceLogger.GetInstance().LogVariableChanged("ValueTextBox", variableViewModel.Name); + } + else if (sender.Name == "MinTextBox") + { + val = validateDouble(sender.Text, variableViewModel.Min); + + variableViewModel.Min = val; + TraceLogger.GetInstance().LogVariableSettingsChanged("MinTextBox"); + } + else if (sender.Name == "MaxTextBox") + { + val = validateDouble(sender.Text, variableViewModel.Max); + variableViewModel.Max = val; + TraceLogger.GetInstance().LogVariableSettingsChanged("MaxTextBox"); + } + else if (sender.Name == "StepTextBox") + { + val = validateDouble(sender.Text, variableViewModel.Step); + + // Don't allow a value less than or equal to 0 as the step + if (val <= 0) + { + val = variableViewModel.Step; + } + + variableViewModel.Step = val; + TraceLogger.GetInstance().LogVariableSettingsChanged("StepTextBox"); + } + else + { + return; + } + + // CSHARP_MIGRATION: TODO: + // Due to different culture, some regions use comma instead of dot as the decimal point + sender.Text = val.ToString("0", System.Globalization.CultureInfo.InvariantCulture); + } + + private void VariableAreaClicked(object sender, RoutedEventArgs e) + { + ToggleVariableArea((VariableViewModel)((ToggleButton)sender).DataContext); + } + + private void VariableAreaButtonTapped(object sender, TappedRoutedEventArgs e) + { + e.Handled = true; + } + + private void VariableAreaTapped(object sender, TappedRoutedEventArgs e) + { + ToggleVariableArea((VariableViewModel)((FrameworkElement)sender).DataContext); + } + + private void EquationTextBox_EquationFormatRequested(object sender, MathRichEditBoxFormatRequest e) + { + EquationFormatRequested(sender, e); + } + + private void Slider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e) + { + if (variableSliders == null) + { + variableSliders = new SortedDictionary(); + } + + var slider = (Slider)sender; + + // The slider value updates when the user uses the TextBox to change the variable value. + // Check the focus state so that we don't trigger the event when the user used the textbox to change the variable value. + if (slider.FocusState == Windows.UI.Xaml.FocusState.Unfocused) + { + return; + } + + var variableVM = (VariableViewModel)slider.DataContext; + if (variableVM == null) + { + return; + } + + var name = variableVM.Name; + + if (!variableSliders.ContainsKey(name)) + { + TimeSpan timeSpan = new TimeSpan(10000000); // 1 tick = 100 nanoseconds, and 10000000 ticks = 1 second. + DispatcherTimerDelayer delayer = new DispatcherTimerDelayer(timeSpan); + delayer.Action += new EventHandler((object s, object arg) => { + TraceLogger.GetInstance().LogVariableChanged("Slider", name); + variableSliders.Remove(name); + }); + delayer.Start(); + variableSliders.Add(name, delayer); + } + else + { + DispatcherTimerDelayer delayer = variableSliders[name]; + delayer.ResetAndStart(); + } + } + + private EquationViewModel GetViewModelFromEquationTextBox(object sender) + { + var tb = (EquationTextBox)sender; + if (tb == null) + { + return null; + } + + var eq = (EquationViewModel)tb.DataContext; + + return eq; + } + + private void ToggleVariableArea(VariableViewModel selectedVariableViewModel) + { + selectedVariableViewModel.SliderSettingsVisible = !selectedVariableViewModel.SliderSettingsVisible; + + // Collapse all other slider settings that are open + foreach (var variableViewModel in Variables) + { + if (variableViewModel != selectedVariableViewModel) + { + variableViewModel.SliderSettingsVisible = false; + } + } + } + + private const int maxEquationSize = 14; + private const int colorCount = 14; + private static readonly int[] colorAssignmentMapping = { 0, 3, 7, 10, 1, 4, 8, 11, 2, 5, 9, 12, 6, 13 }; + private const string EquationsPropertyName = "Equations"; + private const string IsMatchAppThemePropertyName = "IsMatchAppTheme"; + + private Windows.UI.ViewManagement.AccessibilitySettings m_accessibilitySettings; + private Windows.UI.ViewManagement.UISettings m_uiSettings; + private int m_lastLineColorIndex; + private int m_lastFunctionLabelIndex; + private bool m_isHighContrast; + private ViewModel.EquationViewModel m_equationToFocus; + private SortedDictionary variableSliders; + } +} diff --git a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml new file mode 100644 index 00000000..d18534a2 --- /dev/null +++ b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml @@ -0,0 +1,783 @@ + + + + + + + + + + + + + + + + #000000 + #FFFFFF + #C6C6C6 + #FFFFFF + #1F1F1F + #4F4F4F + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4,4,0,0 + 0,0,4,4 + 4,0,0,4 + 0,4,4,0 + + + 4,4,0,0 + 0,0,4,4 + 4,0,0,4 + 0,4,4,0 + + + 0 + 0 + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cs b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cs new file mode 100644 index 00000000..9c5a4aa2 --- /dev/null +++ b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cs @@ -0,0 +1,895 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Common; +using CalculatorApp.Common.Automation; +using CalculatorApp.Controls; +using CalculatorApp.ViewModel; +//using CalcManager.NumberFormattingUtils; +using GraphControl; +//using Utils; +using Windows.ApplicationModel.DataTransfer; +using Windows.ApplicationModel.Resources; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.Storage; +using Windows.Storage.Streams; +using Windows.System; +using Windows.UI.Core; +using Windows.UI.Input; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Automation.Peers; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Imaging; +using Windows.UI.Popups; + +namespace CalculatorApp +{ + public sealed partial class GraphingCalculator : System.ComponentModel.INotifyPropertyChanged + { + public GraphingCalculator() + { + InitializeComponent(); + + m_accessibilitySettings = new AccessibilitySettings(); + DataTransferManager dataTransferManager = DataTransferManager.GetForCurrentView(); + + // Register the current control as a share source. + dataTransferManager.DataRequested += OnDataRequested; + + // Request notifications when we should be showing the trace values + GraphingControl.TracingChangedEvent += OnShowTracePopupChanged; + + // And when the actual trace value changes + GraphingControl.TracingValueChangedEvent += OnTracePointChanged; + + // Update where the pointer value is (ie: where the user cursor from keyboard inputs moves the point to) + GraphingControl.PointerValueChangedEvent += OnPointerPointChanged; + + GraphingControl.Loaded += OnGraphingCalculatorLoaded; + + GraphingControl.UseCommaDecimalSeperator = LocalizationSettings.GetInstance().GetDecimalSeparator() == ','; + + // OemMinus and OemAdd aren't declared in the VirtualKey enum, we can't add this accelerator XAML-side + var virtualKey = new KeyboardAccelerator(); + virtualKey.Key = (VirtualKey)189; // OemPlus key + virtualKey.Modifiers = VirtualKeyModifiers.Control; + ZoomOutButton.KeyboardAccelerators.Add(virtualKey); + + virtualKey = new KeyboardAccelerator(); + virtualKey.Key = (VirtualKey)187; // OemAdd key + virtualKey.Modifiers = VirtualKeyModifiers.Control; + ZoomInButton.KeyboardAccelerators.Add(virtualKey); + + if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.UI.Xaml.Media.ThemeShadow")) + { + SharedShadow.Receivers.Add(GraphingControl); + } + + m_accessibilitySettings.HighContrastChanged += OnHighContrastChanged; + + m_uiSettings = new UISettings(); + m_uiSettings.ColorValuesChanged += OnColorValuesChanged; + + ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; + + if (localSettings != null && localSettings.Values.ContainsKey(sc_IsGraphThemeMatchApp)) + { + var isMatchAppLocalSetting = (bool)(localSettings.Values[sc_IsGraphThemeMatchApp]); + if (isMatchAppLocalSetting) + { + IsMatchAppTheme = true; + TraceLogger.GetInstance().LogGraphTheme("IsMatchAppTheme"); + } + } + else + { + IsMatchAppTheme = false; + TraceLogger.GetInstance().LogGraphTheme("IsAlwaysLightTheme"); + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + internal void RaisePropertyChanged(string p) + { +#if !UNIT_TESTS + PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(p)); +#endif + } + + public System.Windows.Input.ICommand ZoomOutButtonPressed + { + get + { + if (donotuse_ZoomOutButtonPressed == null) + { + donotuse_ZoomOutButtonPressed = new DelegateCommand(this, OnZoomOutCommand); + } + return donotuse_ZoomOutButtonPressed; + } + } + private System.Windows.Input.ICommand donotuse_ZoomOutButtonPressed; + + public System.Windows.Input.ICommand ZoomInButtonPressed + { + get + { + if (donotuse_ZoomInButtonPressed == null) + { + donotuse_ZoomInButtonPressed = new DelegateCommand(this, OnZoomInCommand); + } + return donotuse_ZoomInButtonPressed; + } + } + private System.Windows.Input.ICommand donotuse_ZoomInButtonPressed; + + public bool IsKeyGraphFeaturesVisible + { + get { return m_IsKeyGraphFeaturesVisible; } + private set + { + if (m_IsKeyGraphFeaturesVisible != value) + { + m_IsKeyGraphFeaturesVisible = value; + RaisePropertyChanged("IsKeyGraphFeaturesVisible"); + } + } + } + private bool m_IsKeyGraphFeaturesVisible; + + public bool IsSmallState + { + get { return (bool)GetValue(IsSmallStateProperty); } + set { SetValue(IsSmallStateProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsSmallState. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsSmallStateProperty = + DependencyProperty.Register(nameof(IsSmallState), typeof(bool), typeof(GraphingCalculator), new PropertyMetadata(default(bool))); + + public string GraphControlAutomationName + { + get { return (string)GetValue(GraphControlAutomationNameProperty); } + set { SetValue(GraphControlAutomationNameProperty, value); } + } + + // Using a DependencyProperty as the backing store for GraphControlAutomationName. This enables animation, styling, binding, etc... + public static readonly DependencyProperty GraphControlAutomationNameProperty = + DependencyProperty.Register(nameof(GraphControlAutomationName), typeof(string), typeof(GraphingCalculator), new PropertyMetadata(string.Empty)); + + public bool IsMatchAppTheme + { + get { return m_IsMatchAppTheme; } + private set + { + if (m_IsMatchAppTheme != value) + { + m_IsMatchAppTheme = value; + RaisePropertyChanged("IsMatchAppTheme"); + } + } + } + private bool m_IsMatchAppTheme; + + public bool IsManualAdjustment + { + get { return m_IsManualAdjustment; } + set + { + if (m_IsManualAdjustment != value) + { + m_IsManualAdjustment = value; + RaisePropertyChanged("IsManualAdjustment"); + } + } + } + private bool m_IsManualAdjustment; + + public CalculatorApp.ViewModel.GraphingCalculatorViewModel ViewModel + { + get { return m_viewModel; } + set + { + if (m_viewModel != value) + { + m_viewModel = value; + RaisePropertyChanged(sc_ViewModelPropertyName); + } + } + } + + public static Visibility ShouldDisplayPanel(bool isSmallState, bool isEquationModeActivated, bool isGraphPanel) + { + return (!isSmallState || isEquationModeActivated ^ isGraphPanel) ? Visibility.Visible : Visibility.Collapsed; + } + + public static string GetInfoForSwitchModeToggleButton(bool isChecked) + { + if (isChecked) + { + return AppResourceProvider.GetInstance().GetResourceString("GraphSwitchToGraphMode"); + } + else + { + return AppResourceProvider.GetInstance().GetResourceString("GraphSwitchToEquationMode"); + } + } + + public static Windows.UI.Xaml.Visibility ManageEditVariablesButtonVisibility(uint numberOfVariables) + { + return numberOfVariables == 0 ? Visibility.Collapsed : Visibility.Visible; + } + + public static String GetTracingLegend(bool? isTracing) + { + var resProvider = AppResourceProvider.GetInstance(); + return isTracing != null && isTracing.Value ? resProvider.GetResourceString("disableTracingButtonToolTip") + : resProvider.GetResourceString("enableTracingButtonToolTip"); + } + + public void SetDefaultFocus() + { + if (IsSmallState) + { + SwitchModeToggleButton.Focus(FocusState.Programmatic); + } + else + { + EquationInputAreaControl.Focus(FocusState.Programmatic); + } + } + + private void GraphingCalculator_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) + { + if (ViewModel != null) + { + ViewModel.Equations.VectorChanged -= OnEquationsVectorChanged; + ViewModel.VariableUpdated -= OnVariableChanged; + } + + ViewModel = args.NewValue as GraphingCalculatorViewModel; + ViewModel.Equations.VectorChanged += OnEquationsVectorChanged; + ViewModel.VariableUpdated += OnVariableChanged; + + UpdateGraphAutomationName(); + } + + private void OnVariableChanged(object sender, VariableChangedEventArgs args) + { + GraphingControl.SetVariable(args.variableName, args.newValue); + } + + private void OnEquationsVectorChanged(IObservableVector sender, IVectorChangedEventArgs e) + { + // If an item is already added to the graph, changing it should automatically trigger a graph update + if (e.CollectionChange == CollectionChange.ItemChanged) + { + return; + } + + // Do not plot the graph if we are removing an empty equation, just remove it + if (e.CollectionChange == CollectionChange.ItemRemoved) + { + var itemToRemove = GraphingControl.Equations[(int)e.Index]; + + if (string.IsNullOrEmpty(itemToRemove.Expression)) + { + GraphingControl.Equations.RemoveAt((int)e.Index); + + if (GraphingControl.Equations.Count == 1 && string.IsNullOrEmpty(GraphingControl.Equations[0].Expression)) + { + IsManualAdjustment = false; + } + + return; + } + } + + // Do not plot the graph if we are adding an empty equation, just add it + if (e.CollectionChange == CollectionChange.ItemInserted) + { + var itemToAdd = sender[(int)e.Index]; + + if (string.IsNullOrEmpty(itemToAdd.Expression)) + { + GraphingControl.Equations.Add(itemToAdd.GraphEquation); + + return; + } + } + + // We are either adding or removing a valid equation, or resetting the collection. We will need to plot the graph + GraphingControl.Equations.Clear(); + + foreach (var equationViewModel in ViewModel.Equations) + { + GraphingControl.Equations.Add(equationViewModel.GraphEquation); + } + + GraphingControl.PlotGraph(false); + } + + private void OnZoomInCommand(object parameter) + { + GraphingControl.ZoomFromCenter(zoomInScale); + TraceLogger.GetInstance().LogGraphButtonClicked(GraphButton.ZoomIn, GraphButtonValue.None); + } + + private void OnZoomOutCommand(object parameter) + { + GraphingControl.ZoomFromCenter(zoomOutScale); + TraceLogger.GetInstance().LogGraphButtonClicked(GraphButton.ZoomOut, GraphButtonValue.None); + } + + private void OnShareClick(object sender, RoutedEventArgs e) + { + // Ask the OS to start a share action. + try + { + DataTransferManager.ShowShareUI(); + TraceLogger.GetInstance().LogGraphButtonClicked(GraphButton.Share, GraphButtonValue.None); + } + catch (System.Runtime.InteropServices.COMException ex) + { + // COMException and HResult, long RPC_E_SERVERCALL_RETRYLATER is out of range of int + // LogPlatformException is internal + long rpc_e_servercall_retrylater = 0x8001010A; + if (ex.HResult == unchecked(rpc_e_servercall_retrylater)) + { + ShowShareError(); + TraceLogger.GetInstance().LogPlatformException(ViewMode.Graphing, System.Reflection.MethodBase.GetCurrentMethod().Name, ex); + } + else + { + throw; + } + } + } + + private void OnShowTracePopupChanged(bool newValue) + { + if ((TraceValuePopup.Visibility == Visibility.Visible) != newValue) + { + TraceValuePopup.Visibility = newValue ? Visibility.Visible : Visibility.Collapsed; + } + } + + private void OnTracePointChanged(double xPointValue, double yPointValue) + { + double xAxisMin, xAxisMax, yAxisMin, yAxisMax; + GraphingControl.GetDisplayRanges(out xAxisMin, out xAxisMax, out yAxisMin, out yAxisMax); + + TraceValue.Text = "(" + xPointValue.ToString("R") + ", " + yPointValue.ToString("N15") + ")"; + + var peer = FrameworkElementAutomationPeer.FromElement(TraceValue); + + if (peer != null) + { + peer.RaiseAutomationEvent(AutomationEvents.LiveRegionChanged); + } + + PositionGraphPopup(); + } + + private void OnPointerPointChanged(Point newPoint) + { + if (TracePointer != null) + { + // Move the pointer glyph to where it is supposed to be. + Canvas.SetLeft(TracePointer, newPoint.X); + Canvas.SetTop(TracePointer, newPoint.Y); + } + } + + // When share is invoked (by the user or programmatically) the event handler we registered will be called to populate the data package with the + // data to be shared. We will request the current graph image from the grapher as a stream that will pass to the share request. + private void OnDataRequested(DataTransferManager sender, DataRequestedEventArgs args) + { + var resourceLoader = ResourceLoader.GetForCurrentView(); + + try + { + string rawHtml; + string equationHtml; + + rawHtml = ""; + + var equations = ViewModel.Equations; + bool hasEquations = false; + + if (equations.Count > 0) + { + equationHtml = "" + + resourceLoader.GetString("EquationsShareHeader") + "" + + ""; + + foreach (var equation in equations) + { + var expression = equation.Expression; + if (string.IsNullOrEmpty(expression)) + { + continue; + } + + var color = equation.LineColor; + hasEquations = true; + + expression = GraphingControl.ConvertToLinear(expression); + + string equationColorHtml; + equationColorHtml = "color:rgb(" + color.R.ToString() + "," + color.G.ToString() + "," + color.B.ToString() + ");"; + + equationHtml += "■" + + Utilities.EscapeHtmlSpecialCharacters(expression) + ""; + } + equationHtml += ""; + + if (hasEquations) + { + rawHtml += equationHtml; + } + } + + var variables = ViewModel.Variables; + + if (variables.Count > 0) + { + var localizedSeperator = LocalizationSettings.GetInstance().GetListSeparatorWinRT() + " "; + + rawHtml += "" + + resourceLoader.GetString("VariablesShareHeader") + + ""; + + for (int i = 0; i < variables.Count; i++) + { + var name = variables[i].Name; + var value = variables[i].Value; + + rawHtml += name + "="; + var formattedValue = value.ToString("R"); + formattedValue = Utilities.TrimTrailingZeros(formattedValue); + rawHtml += formattedValue; + + if (variables.Count - 1 != i) + { + rawHtml += localizedSeperator; + } + } + + rawHtml += ""; + } + + rawHtml += ""; + + // Shortcut to the request data + var requestData = args.Request.Data; + + DataPackage dataPackage = new DataPackage(); + var html = HtmlFormatHelper.CreateHtmlFormat(rawHtml); + + requestData.Properties.Title = resourceLoader.GetString("ShareActionTitle"); + + requestData.SetHtmlFormat(html); + + var bitmapStream = GraphingControl.GetGraphBitmapStream(); + + requestData.ResourceMap.Add("graph.png", bitmapStream); + requestData.SetBitmap(bitmapStream); + + // Set the thumbnail image (in case the share target can't handle HTML) + requestData.Properties.Thumbnail = bitmapStream; + } + catch (Exception ex) + { + ShowShareError(); + + TraceLogger.GetInstance().LogPlatformException(ViewMode.Graphing, + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); + } + } + + private void GraphingControl_LostFocus(object sender, RoutedEventArgs e) + { + // If the graph is losing focus while we are in active tracing we need to turn it off so we don't try to eat keys in other controls. + if (GraphingControl.ActiveTracing) + { + if (ActiveTracing.Equals(FocusManager.GetFocusedElement()) && ActiveTracing.IsPressed) + { + ActiveTracing.PointerCaptureLost += ActiveTracing_PointerCaptureLost; + } + else + { + GraphingControl.ActiveTracing = false; + OnShowTracePopupChanged(false); + } + } + } + + private void GraphingControl_LosingFocus(UIElement sender, LosingFocusEventArgs args) + { + var newFocusElement = args.NewFocusedElement as FrameworkElement; + if (newFocusElement == null || newFocusElement.Name == null) + { + // Because clicking on the swap chain panel will try to move focus to a control that can't actually take focus + // we will get a null destination. So we are going to try and cancel that request. + // If the destination is not in our application we will also get a null destination but the cancel will fail so it doesn't hurt to try. + args.TryCancel(); + } + } + + private void GraphingControl_VariablesUpdated(object sender, object args) + { + m_viewModel.UpdateVariables(GraphingControl.Variables); + } + + private void GraphingControl_GraphViewChangedEvent(object sender, GraphViewChangedReason reason) + { + if (reason == GraphViewChangedReason.Manipulation) + { + IsManualAdjustment = true; + } + else + { + IsManualAdjustment = false; + } + + UpdateGraphAutomationName(); + + var announcement = CalculatorAnnouncement.GetGraphViewChangedAnnouncement(GraphControlAutomationName); + var peer = FrameworkElementAutomationPeer.FromElement(GraphingControl); + if (peer != null) + { + peer.RaiseNotificationEvent(announcement.Kind, announcement.Processing, announcement.Announcement, announcement.ActivityId); + } + } + + private void GraphingControl_GraphPlottedEvent(object sender, RoutedEventArgs e) + { + UpdateGraphAutomationName(); + } + + private void OnEquationKeyGraphFeaturesRequested(object sender, EquationViewModel equationViewModel) + { + ViewModel.SetSelectedEquation(equationViewModel); + if (equationViewModel != null) + { + var keyGraphFeatureInfo = GraphingControl.AnalyzeEquation(equationViewModel.GraphEquation); + equationViewModel.PopulateKeyGraphFeatures(keyGraphFeatureInfo); + IsKeyGraphFeaturesVisible = true; + equationViewModel.GraphEquation.IsSelected = true; + } + } + + private void OnKeyGraphFeaturesClosed(object sender, RoutedEventArgs e) + { + IsKeyGraphFeaturesVisible = false; + EquationInputAreaControl.FocusEquationTextBox(ViewModel.SelectedEquation); + } + + private void TraceValuePopup_SizeChanged(object sender, SizeChangedEventArgs e) + { + PositionGraphPopup(); + } + + private void PositionGraphPopup() + { + if (GraphingControl.TraceLocation.X + 15 + TraceValuePopup.ActualWidth >= GraphingControl.ActualWidth) + { + TraceValuePopupTransform.X = (int)GraphingControl.TraceLocation.X - 15 - TraceValuePopup.ActualWidth; + } + else + { + TraceValuePopupTransform.X = (int)GraphingControl.TraceLocation.X + 15; + } + + if (GraphingControl.TraceLocation.Y >= 30) + { + TraceValuePopupTransform.Y = (int)GraphingControl.TraceLocation.Y - 30; + } + else + { + TraceValuePopupTransform.Y = (int)GraphingControl.TraceLocation.Y; + } + } + + private void ActiveTracing_Checked(object sender, RoutedEventArgs e) + { + if (!m_cursorShadowInitialized) + { + this.FindName("TraceCanvas"); + + // add shadow to the trace pointer + AddTracePointerShadow(); + + // hide the shadow in high contrast mode + CursorShadow.Visibility = m_accessibilitySettings.HighContrast ? Visibility.Collapsed : Visibility.Visible; + + Canvas.SetLeft(TracePointer, TraceCanvas.ActualWidth / 2 + 40); + Canvas.SetTop(TracePointer, TraceCanvas.ActualHeight / 2 - 40); + + m_cursorShadowInitialized = true; + } + + _ = FocusManager.TryFocusAsync(GraphingControl, FocusState.Programmatic); + + Window.Current.CoreWindow.KeyUp += ActiveTracing_KeyUp; + + KeyboardShortcutManager.IgnoreEscape(false); + + TracePointer.Visibility = Visibility.Visible; + TraceLogger.GetInstance().LogGraphButtonClicked(GraphButton.ActiveTracingChecked, GraphButtonValue.None); + } + + private void ActiveTracing_Unchecked(object sender, RoutedEventArgs e) + { + ActiveTracing.PointerCaptureLost -= ActiveTracing_PointerCaptureLost; + Window.Current.CoreWindow.KeyUp -= ActiveTracing_KeyUp; + KeyboardShortcutManager.HonorEscape(); + + TracePointer.Visibility = Visibility.Collapsed; + TraceLogger.GetInstance().LogGraphButtonClicked(GraphButton.ActiveTracingUnchecked, GraphButtonValue.None); + } + + private void ActiveTracing_KeyUp(CoreWindow sender, KeyEventArgs args) + { + if (args.VirtualKey == VirtualKey.Escape) + { + GraphingControl.ActiveTracing = false; + ActiveTracing.Focus(FocusState.Programmatic); + args.Handled = true; + } + } + + private void ActiveTracing_PointerCaptureLost(object sender, PointerRoutedEventArgs e) + { + ActiveTracing.PointerCaptureLost -= ActiveTracing_PointerCaptureLost; + + if (GraphingControl.ActiveTracing) + { + GraphingControl.ActiveTracing = false; + OnShowTracePopupChanged(false); + } + } + + private void GraphSettingsButton_Click(object sender, RoutedEventArgs e) + { + DisplayGraphSettings(); + TraceLogger.GetInstance().LogGraphButtonClicked(GraphButton.GraphSettings, GraphButtonValue.None); + } + + private void SwitchModeToggleButton_Toggled(object sender, RoutedEventArgs e) + { + var narratorNotifier = new NarratorNotifier(); + String announcementText; + if (SwitchModeToggleButton.IsOn) + { + announcementText = AppResourceProvider.GetInstance().GetResourceString("GraphSwitchedToEquationModeAnnouncement"); + } + else + { + announcementText = AppResourceProvider.GetInstance().GetResourceString("GraphSwitchedToGraphModeAnnouncement"); + } + + var announcement = CalculatorAnnouncement.GetGraphModeChangedAnnouncement(announcementText); + narratorNotifier.Announce(announcement); + } + + private void DisplayGraphSettings() + { + if (m_graphSettings == null) + { + m_graphSettings = new GraphingSettings(); + m_graphSettings.GraphThemeSettingChanged += OnGraphThemeSettingChanged; + } + + if (m_graphFlyout == null) + { + m_graphFlyout = new Flyout(); + m_graphFlyout.Content = m_graphSettings; + } + + m_graphSettings.SetGrapher(this.GraphingControl); + m_graphSettings.IsMatchAppTheme = IsMatchAppTheme; + + var options = new FlyoutShowOptions(); + options.Placement = FlyoutPlacementMode.BottomEdgeAlignedRight; + m_graphFlyout.ShowAt(GraphSettingsButton, options); + } + + private void AddTracePointerShadow() + { + var compositor = Windows.UI.Xaml.Hosting.ElementCompositionPreview.GetElementVisual(CursorPath).Compositor; + var dropShadow = compositor.CreateDropShadow(); + dropShadow.BlurRadius = 6; + dropShadow.Opacity = 0.33f; + dropShadow.Offset = new System.Numerics.Vector3(2, 2, 0); + dropShadow.Mask = CursorPath.GetAlphaMask(); + + var shadowSpriteVisual = compositor.CreateSpriteVisual(); + shadowSpriteVisual.Size = new System.Numerics.Vector2(18, 18); + shadowSpriteVisual.Shadow = dropShadow; + Windows.UI.Xaml.Hosting.ElementCompositionPreview.SetElementChildVisual(CursorShadow, shadowSpriteVisual); + } + + private void UpdateGraphAutomationName() + { + int numEquations = 0; + double xAxisMin, xAxisMax, yAxisMin, yAxisMax; + + // Only count equations that are graphed + foreach (var equation in ViewModel.Equations) + { + if (equation.GraphEquation.IsValidated) + { + numEquations++; + } + } + + GraphingControl.GetDisplayRanges(out xAxisMin, out xAxisMax, out yAxisMin, out yAxisMax); + + GraphControlAutomationName = LocalizationStringUtil.GetLocalizedString( + AppResourceProvider.GetInstance().GetResourceString("graphAutomationName"), + xAxisMin.ToString(), + xAxisMax.ToString(), + yAxisMin.ToString(), + yAxisMax.ToString(), + numEquations.ToString()); + } + + private void OnColorValuesChanged(UISettings sender, object args) + { + WeakReference weakThis = new WeakReference(this); + _ = this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() => { + GraphingCalculator refThis = weakThis.Target as GraphingCalculator; + if (refThis != null && IsMatchAppTheme) + { + refThis.UpdateGraphTheme(); + } + })); + } + + private void UpdateGraphTheme() + { + if (m_accessibilitySettings.HighContrast) + { + VisualStateManager.GoToState(this, "GrapherHighContrast", true); + return; + } + + if (IsMatchAppTheme && Application.Current.RequestedTheme == ApplicationTheme.Dark) + { + VisualStateManager.GoToState(this, "GrapherDarkTheme", true); + } + else + { + VisualStateManager.GoToState(this, "GrapherLightTheme", true); + } + } + + private void OnGraphThemeSettingChanged(object sender, bool isMatchAppTheme) + { + if (IsMatchAppTheme == isMatchAppTheme) + { + return; + } + + IsMatchAppTheme = isMatchAppTheme; + WeakReference weakThis = new WeakReference(this); + _ = this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() => + { + var refThis = weakThis.Target as GraphingCalculator; + if (refThis != null) + { + refThis.UpdateGraphTheme(); + } + })); + } + + private const double zoomInScale = 1 / 1.0625; + private const double zoomOutScale = 1.0625; + private const string sc_ViewModelPropertyName = "ViewModel"; + private const string sc_IsGraphThemeMatchApp = "IsGraphThemeMatchApp"; + + private CalculatorApp.ViewModel.GraphingCalculatorViewModel m_viewModel; + private Windows.UI.ViewManagement.AccessibilitySettings m_accessibilitySettings; + private bool m_cursorShadowInitialized; + private Windows.UI.ViewManagement.UISettings m_uiSettings; + private Windows.UI.Xaml.Controls.Flyout m_graphFlyout; + private CalculatorApp.GraphingSettings m_graphSettings; + + private void Canvas_SizeChanged(object sender, SizeChangedEventArgs e) + { + // Initialize the pointer to the correct location to match initial value in GraphControl\DirectX\RenderMain + if (TracePointer != null) + { + Canvas.SetLeft(TracePointer, e.NewSize.Width / 2 + 40); + Canvas.SetTop(TracePointer, e.NewSize.Height / 2 - 40); + } + } + + private void OnHighContrastChanged(AccessibilitySettings sender, object args) + { + if (CursorShadow != null) + { + CursorShadow.Visibility = sender.HighContrast ? Visibility.Collapsed : Visibility.Visible; + } + + UpdateGraphTheme(); + } + + private void OnEquationFormatRequested(object sender, MathRichEditBoxFormatRequest e) + { + if (!string.IsNullOrEmpty(e.OriginalText)) + { + e.FormattedText = GraphingControl.FormatMathML(e.OriginalText); + } + } + + private void GraphMenuFlyoutItem_Click(object sender, RoutedEventArgs e) + { + var dataPackage = new DataPackage(); + dataPackage.RequestedOperation = DataPackageOperation.Copy; + + var bitmapStream = GraphingControl.GetGraphBitmapStream(); + dataPackage.SetBitmap(bitmapStream); + Clipboard.SetContent(dataPackage); + } + + private void OnVisualStateChanged(object sender, VisualStateChangedEventArgs e) + { + TraceLogger.GetInstance().LogVisualStateChanged(ViewMode.Graphing, e.NewState.Name, false); + } + + private void GraphViewButton_Click(object sender, RoutedEventArgs e) + { + var narratorNotifier = new NarratorNotifier(); + string announcementText; + if (IsManualAdjustment) + { + announcementText = AppResourceProvider.GetInstance().GetResourceString("GraphViewManualAdjustmentAnnouncement"); + } + else + { + announcementText = AppResourceProvider.GetInstance().GetResourceString("GraphViewAutomaticBestFitAnnouncement"); + announcementText += AppResourceProvider.GetInstance().GetResourceString("GridResetAnnouncement"); + GraphingControl.ResetGrid(); + } + + var announcement = CalculatorAnnouncement.GetGraphViewBestFitChangedAnnouncement(announcementText); + narratorNotifier.Announce(announcement); + + TraceLogger.GetInstance().LogGraphButtonClicked( + GraphButton.GraphView, IsManualAdjustment ? GraphButtonValue.ManualAdjustment : GraphButtonValue.AutomaticBestFit); + } + + private void ShowShareError() + { + // Something went wrong, notify the user. + var resourceLoader = ResourceLoader.GetForCurrentView(); + var errDialog = new ContentDialog(); + errDialog.Content = resourceLoader.GetString("ShareActionErrorMessage"); + errDialog.CloseButtonText = resourceLoader.GetString("ShareActionErrorOk"); + _ = errDialog.ShowAsync(); + } + + private void OnGraphingCalculatorLoaded(object sender, RoutedEventArgs e) + { + GraphingControl.Loaded -= OnGraphingCalculatorLoaded; + + // The control needs to be loaded, else the control will override GridLinesColor and ignore the value passed + UpdateGraphTheme(); + } + } +} diff --git a/src/Calculator/Views/GraphingCalculator/GraphingNumPad.xaml b/src/Calculator/Views/GraphingCalculator/GraphingNumPad.xaml new file mode 100644 index 00000000..6f6ff2e1 --- /dev/null +++ b/src/Calculator/Views/GraphingCalculator/GraphingNumPad.xaml @@ -0,0 +1,1390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Calculator/Views/GraphingCalculator/GraphingNumPad.xaml.cs b/src/Calculator/Views/GraphingCalculator/GraphingNumPad.xaml.cs new file mode 100644 index 00000000..060f83f9 --- /dev/null +++ b/src/Calculator/Views/GraphingCalculator/GraphingNumPad.xaml.cs @@ -0,0 +1,283 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.Common; + +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +namespace CalculatorApp +{ + [Windows.Foundation.Metadata.WebHostHidden] + public sealed partial class GraphingNumPad + { + public GraphingNumPad() + { + InitializeComponent(); + DecimalSeparatorButton.Content = localizationSettings.GetDecimalSeparator(); + Num0Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('0'); + Num1Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('1'); + Num2Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('2'); + Num3Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('3'); + Num4Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('4'); + Num5Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('5'); + Num6Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('6'); + Num7Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('7'); + Num8Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('8'); + Num9Button.Content = localizationSettings.GetDigitSymbolFromEnUsDigit('9'); + } + + private void ShiftButton_Check(object sender, RoutedEventArgs e) + { + SetOperatorRowVisibility(); + } + + private void ShiftButton_Uncheck(object sender, RoutedEventArgs e) + { + ShiftButton.IsChecked = false; + SetOperatorRowVisibility(); + + Button_Clicked(sender, null); + } + + private void TrigFlyoutShift_Toggle(object sender, RoutedEventArgs e) + { + SetTrigRowVisibility(); + } + + private void TrigFlyoutHyp_Toggle(object sender, RoutedEventArgs e) + { + SetTrigRowVisibility(); + } + + private void FlyoutButton_Clicked(object sender, RoutedEventArgs e) + { + this.HypButton.IsChecked = false; + this.TrigShiftButton.IsChecked = false; + this.Trigflyout.Hide(); + this.FuncFlyout.Hide(); + this.InequalityFlyout.Hide(); + + Button_Clicked(sender, null); + } + + private void ShiftButton_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + SetOperatorRowVisibility(); + } + + private void SetOperatorRowVisibility() + { + Visibility rowVis, invRowVis; + if (ShiftButton.IsChecked.Value) + { + rowVis = Visibility.Collapsed; + invRowVis = Visibility.Visible; + } + else + { + rowVis = Visibility.Visible; + invRowVis = Visibility.Collapsed; + } + + Row1.Visibility = rowVis; + InvRow1.Visibility = invRowVis; + } + + private void SetTrigRowVisibility() + { + bool isShiftChecked = TrigShiftButton.IsChecked.Value; + bool isHypeChecked = HypButton.IsChecked.Value; + + InverseHyperbolicTrigFunctions.Visibility = Visibility.Collapsed; + InverseTrigFunctions.Visibility = Visibility.Collapsed; + HyperbolicTrigFunctions.Visibility = Visibility.Collapsed; + TrigFunctions.Visibility = Visibility.Collapsed; + + if (isShiftChecked && isHypeChecked) + { + InverseHyperbolicTrigFunctions.Visibility = Visibility.Visible; + } + else if (isShiftChecked && !isHypeChecked) + { + InverseTrigFunctions.Visibility = Visibility.Visible; + } + else if (!isShiftChecked && isHypeChecked) + { + HyperbolicTrigFunctions.Visibility = Visibility.Visible; + } + else + { + TrigFunctions.Visibility = Visibility.Visible; + } + } + + private void Button_Clicked(object sender, RoutedEventArgs e) + { + var mathRichEdit = GetActiveRichEdit(); + var button = sender as CalculatorApp.Controls.CalculatorButton; + if (mathRichEdit != null && sender != null) + { + var id = button.ButtonId; + TraceLogger.GetInstance().UpdateButtonUsage(id, CalculatorApp.Common.ViewMode.Graphing); + Tuple output = GraphingNumPad.GetButtonOutput(id); + mathRichEdit.InsertText(output.Item1, output.Item2, output.Item3); + } + } + + private void SubmitButton_Clicked(object sender, RoutedEventArgs e) + { + var mathRichEdit = GetActiveRichEdit(); + if (mathRichEdit != null) + { + mathRichEdit.SubmitEquation(CalculatorApp.Controls.EquationSubmissionSource.ENTER_KEY); + TraceLogger.GetInstance().UpdateButtonUsage(NumbersAndOperatorsEnum.Submit, CalculatorApp.Common.ViewMode.Graphing); + } + } + + private void ClearButton_Clicked(object sender, RoutedEventArgs e) + { + var mathRichEdit = GetActiveRichEdit(); + if (mathRichEdit != null) + { + string text; + mathRichEdit.TextDocument.GetText(Windows.UI.Text.TextGetOptions.NoHidden, out text); + + if (!string.IsNullOrEmpty(text)) + { + mathRichEdit.MathText = ""; + mathRichEdit.SubmitEquation(CalculatorApp.Controls.EquationSubmissionSource.PROGRAMMATIC); + } + + TraceLogger.GetInstance().UpdateButtonUsage(NumbersAndOperatorsEnum.Clear, CalculatorApp.Common.ViewMode.Graphing); + } + } + + private void BackSpaceButton_Clicked(object sender, RoutedEventArgs e) + { + var mathRichEdit = GetActiveRichEdit(); + if (mathRichEdit != null) + { + mathRichEdit.BackSpace(); + TraceLogger.GetInstance().UpdateButtonUsage(NumbersAndOperatorsEnum.Backspace, CalculatorApp.Common.ViewMode.Graphing); + } + } + + // To avoid focus moving when the space between buttons is clicked, handle click events that make it through the keypad. + private void GraphingNumPad_PointerPressed(object sender, PointerRoutedEventArgs e) + { + e.Handled = true; + } + + private CalculatorApp.Controls.MathRichEditBox GetActiveRichEdit() + { + return FocusManager.GetFocusedElement() as Controls.MathRichEditBox; + } + + // Adding event because the ShowMode property is ignored in xaml. + private void Flyout_Opening(object sender, object e) + { + var flyout = sender as Flyout; + if (flyout != null) + { + flyout.ShowMode = FlyoutShowMode.Transient; + } + } + + private static readonly Dictionary> buttonOutput = new Dictionary>() + { + { NumbersAndOperatorsEnum.Sin, Tuple.Create("sin()", 4, 0) }, + { NumbersAndOperatorsEnum.Cos, Tuple.Create("cos()", 4, 0) }, + { NumbersAndOperatorsEnum.Tan, Tuple.Create("tan()", 4, 0) }, + { NumbersAndOperatorsEnum.Sec, Tuple.Create("sec()", 4, 0) }, + { NumbersAndOperatorsEnum.Csc, Tuple.Create("csc()", 4, 0) }, + { NumbersAndOperatorsEnum.Cot, Tuple.Create("cot()", 4, 0) }, + { NumbersAndOperatorsEnum.InvSin, Tuple.Create("arcsin()", 7, 0) }, + { NumbersAndOperatorsEnum.InvCos, Tuple.Create("arccos()", 7, 0) }, + { NumbersAndOperatorsEnum.InvTan, Tuple.Create("arctan()", 7, 0) }, + { NumbersAndOperatorsEnum.InvSec, Tuple.Create("arcsec()", 7, 0) }, + { NumbersAndOperatorsEnum.InvCsc, Tuple.Create("arccsc()", 7, 0) }, + { NumbersAndOperatorsEnum.InvCot, Tuple.Create("arccot()", 7, 0) }, + { NumbersAndOperatorsEnum.Sinh, Tuple.Create("sinh()", 5, 0) }, + { NumbersAndOperatorsEnum.Cosh, Tuple.Create("cosh()", 5, 0) }, + { NumbersAndOperatorsEnum.Tanh, Tuple.Create("tanh()", 5, 0) }, + { NumbersAndOperatorsEnum.Sech, Tuple.Create("sech()", 5, 0) }, + { NumbersAndOperatorsEnum.Csch, Tuple.Create("csch()", 5, 0) }, + { NumbersAndOperatorsEnum.Coth, Tuple.Create("coth()", 5, 0) }, + { NumbersAndOperatorsEnum.InvSinh, Tuple.Create("arcsinh()", 8, 0) }, + { NumbersAndOperatorsEnum.InvCosh, Tuple.Create("arccosh()", 8, 0) }, + { NumbersAndOperatorsEnum.InvTanh, Tuple.Create("arctanh()", 8, 0) }, + { NumbersAndOperatorsEnum.InvSech, Tuple.Create("arcsech()", 8, 0) }, + { NumbersAndOperatorsEnum.InvCsch, Tuple.Create("arccsch()", 8, 0) }, + { NumbersAndOperatorsEnum.InvCoth, Tuple.Create("arccoth()", 8, 0) }, + { NumbersAndOperatorsEnum.Abs, Tuple.Create("abs()", 4, 0) }, + { NumbersAndOperatorsEnum.Floor, Tuple.Create("floor()", 6, 0) }, + { NumbersAndOperatorsEnum.Ceil, Tuple.Create("ceiling()", 8, 0) }, + { NumbersAndOperatorsEnum.Pi, Tuple.Create("\u03C0", 1, 0) }, + { NumbersAndOperatorsEnum.Euler, Tuple.Create("e", 1, 0) }, + { NumbersAndOperatorsEnum.XPower2, Tuple.Create("^2", 2, 0) }, + { NumbersAndOperatorsEnum.Cube, Tuple.Create("^3", 2, 0) }, + { NumbersAndOperatorsEnum.XPowerY, Tuple.Create("^", 1, 0) }, + { NumbersAndOperatorsEnum.TenPowerX, Tuple.Create("10^", 3, 0) }, + { NumbersAndOperatorsEnum.LogBase10, Tuple.Create("log()", 4, 0) }, + { NumbersAndOperatorsEnum.LogBaseE, Tuple.Create("ln()", 3, 0) }, + { NumbersAndOperatorsEnum.Sqrt, Tuple.Create("sqrt()", 5, 0) }, + { NumbersAndOperatorsEnum.CubeRoot, Tuple.Create("cbrt()", 5, 0) }, + { NumbersAndOperatorsEnum.YRootX, Tuple.Create("root(x" + LocalizationSettings.GetInstance().GetListSeparatorWinRT().TrimEnd('\0') + "n)", 7, 1) }, + { NumbersAndOperatorsEnum.TwoPowerX, Tuple.Create("2^", 2, 0) }, + { NumbersAndOperatorsEnum.LogBaseY, Tuple.Create("log(b" + LocalizationSettings.GetInstance().GetListSeparatorWinRT().TrimEnd('\0') + " x)", 4, 1) }, + { NumbersAndOperatorsEnum.EPowerX, Tuple.Create("e^", 4, 0) }, + { NumbersAndOperatorsEnum.X, Tuple.Create("x", 1, 0) }, + { NumbersAndOperatorsEnum.Y, Tuple.Create("y", 1, 0) }, + { NumbersAndOperatorsEnum.OpenParenthesis, Tuple.Create("(", 1, 0) }, + { NumbersAndOperatorsEnum.CloseParenthesis, Tuple.Create(")", 1, 0) }, + { NumbersAndOperatorsEnum.Equals, Tuple.Create("=", 1, 0) }, + { NumbersAndOperatorsEnum.Divide, Tuple.Create("/", 1, 0) }, + { NumbersAndOperatorsEnum.Multiply, Tuple.Create("*", 1, 0) }, + { NumbersAndOperatorsEnum.Subtract, Tuple.Create("-", 1, 0) }, + { NumbersAndOperatorsEnum.Add, Tuple.Create("+", 1, 0) }, + { NumbersAndOperatorsEnum.Invert, Tuple.Create("1/", 2, 0) }, + { NumbersAndOperatorsEnum.Negate, Tuple.Create("-", 1, 0) }, + { NumbersAndOperatorsEnum.GreaterThan, Tuple.Create(">", 1, 0) }, + { NumbersAndOperatorsEnum.GreaterThanOrEqualTo, Tuple.Create("\u2265", 1, 0) }, + { NumbersAndOperatorsEnum.LessThan, Tuple.Create("<", 1, 0) }, + { NumbersAndOperatorsEnum.LessThanOrEqualTo, Tuple.Create("\u2264", 1, 0) }, + { NumbersAndOperatorsEnum.Zero, Tuple.Create("0", 1, 0) }, + { NumbersAndOperatorsEnum.One, Tuple.Create("1", 1, 0) }, + { NumbersAndOperatorsEnum.Two, Tuple.Create("2", 1, 0) }, + { NumbersAndOperatorsEnum.Three, Tuple.Create("3", 1, 0) }, + { NumbersAndOperatorsEnum.Four, Tuple.Create("4", 1, 0) }, + { NumbersAndOperatorsEnum.Five, Tuple.Create("5", 1, 0) }, + { NumbersAndOperatorsEnum.Six, Tuple.Create("6", 1, 0) }, + { NumbersAndOperatorsEnum.Seven, Tuple.Create("7", 1, 0) }, + { NumbersAndOperatorsEnum.Eight, Tuple.Create("8", 1, 0) }, + { NumbersAndOperatorsEnum.Nine, Tuple.Create("9", 1, 0) }, + { NumbersAndOperatorsEnum.Decimal, Tuple.Create(LocalizationSettings.GetInstance().GetDecimalSeparatorStrWinRT().TrimEnd('\0'), 1, 0) }, + }; + + private static Tuple GetButtonOutput(NumbersAndOperatorsEnum id) + { + Tuple output; + if (buttonOutput.TryGetValue(id, out output)) + { + return output; + } + + return null; + } + + private static readonly LocalizationSettings localizationSettings = LocalizationSettings.GetInstance(); + } +} diff --git a/src/Calculator/Views/GraphingCalculator/GraphingSettings.xaml b/src/Calculator/Views/GraphingCalculator/GraphingSettings.xaml new file mode 100644 index 00000000..e2982068 --- /dev/null +++ b/src/Calculator/Views/GraphingCalculator/GraphingSettings.xaml @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.0 + 2.0 + 3.0 + 4.0 + + + + + + + + + + + + + + + + + + diff --git a/src/Calculator/Views/GraphingCalculator/GraphingSettings.xaml.cs b/src/Calculator/Views/GraphingCalculator/GraphingSettings.xaml.cs new file mode 100644 index 00000000..318151de --- /dev/null +++ b/src/Calculator/Views/GraphingCalculator/GraphingSettings.xaml.cs @@ -0,0 +1,139 @@ +// +// MyUserControl.xaml.h +// Declaration of the MyUserControl class +// + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; + +//using Graphing; +using GraphControl; +using CalculatorApp; +using CalculatorApp.ViewModel; + +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.Storage; +using Windows.System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +namespace CalculatorApp +{ + [Windows.Foundation.Metadata.WebHostHidden] + public sealed partial class GraphingSettings + { + public GraphingSettings() + { + m_ViewModel = new GraphingSettingsViewModel(); + m_IsMatchAppTheme = false; + InitializeComponent(); + } + + public CalculatorApp.ViewModel.GraphingSettingsViewModel ViewModel + { + get { return m_ViewModel; } + set { m_ViewModel = value; } + } + + public bool IsMatchAppTheme + { + get { return m_IsMatchAppTheme; } + set + { + if (m_IsMatchAppTheme == value) + { + return; + } + + m_IsMatchAppTheme = value; + SetGraphTheme(m_IsMatchAppTheme); + } + } + + public Style SelectTextBoxStyle(bool incorrectRange, bool error) + { + if ((incorrectRange || error) && this.Resources.ContainsKey("ErrorTextBoxStyle")) + { + return (Style)(this.Resources["ErrorTextBoxStyle"]); + } + else + { + return null; + } + } + + public void SetGrapher(GraphControl.Grapher grapher) + { + m_ViewModel.SetGrapher(grapher); + } + + public void RefreshRanges() + { + ViewModel.ResetView(); + } + + public static string GetLineWidthAutomationName(double width) + { + var resourceLoader = AppResourceProvider.GetInstance(); + + if (width == 1.0) + { + return resourceLoader.GetResourceString("SmallLineWidthAutomationName"); + } + else if (width == 2.0) + { + return resourceLoader.GetResourceString("MediumLineWidthAutomationName"); + } + else if (width == 3.0) + { + return resourceLoader.GetResourceString("LargeLineWidthAutomationName"); + } + else + { + return resourceLoader.GetResourceString("ExtraLargeLineWidthAutomationName"); + } + } + + // Event sends the if the IsMatchAppTheme is selected + public event System.EventHandler GraphThemeSettingChanged; + + private void GridSettingsTextBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + if (e.Key == VirtualKey.Enter) + { + // CSHARP_MIGRATION: TODO: + // check if condition + if (FocusManager.TryMoveFocusAsync(FocusNavigationDirection.Next).Status != AsyncStatus.Completed) + { + IAsyncOperation result = FocusManager.TryMoveFocusAsync(FocusNavigationDirection.Previous); + } + e.Handled = true; + } + } + + private void ResetViewButton_Clicked(object sender, RoutedEventArgs e) + { + ViewModel.ResetView(); + } + + private void SetGraphTheme(bool isMatchAppTheme) + { + string propertyName = isMatchAppTheme ? "IsMatchAppTheme" : "IsAlwaysLightTheme"; + ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; + localSettings.Values.Add("IsGraphThemeMatchApp", isMatchAppTheme); + GraphThemeSettingChanged?.Invoke(this, isMatchAppTheme); + TraceLogger.GetInstance().LogGraphSettingsChanged(GraphSettingsType.Theme, propertyName); + } + + private bool m_IsMatchAppTheme; + private CalculatorApp.ViewModel.GraphingSettingsViewModel m_ViewModel; + }; +} diff --git a/src/Calculator/Views/GraphingCalculator/KeyGraphFeaturesPanel.xaml b/src/Calculator/Views/GraphingCalculator/KeyGraphFeaturesPanel.xaml new file mode 100644 index 00000000..c37f81d8 --- /dev/null +++ b/src/Calculator/Views/GraphingCalculator/KeyGraphFeaturesPanel.xaml @@ -0,0 +1,293 @@ + + + + + + + + + 0.3 + 0.5 + + + + + + 0.2 + 0.4 + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Calculator/Views/GraphingCalculator/KeyGraphFeaturesPanel.xaml.cs b/src/Calculator/Views/GraphingCalculator/KeyGraphFeaturesPanel.xaml.cs new file mode 100644 index 00000000..f81e5070 --- /dev/null +++ b/src/Calculator/Views/GraphingCalculator/KeyGraphFeaturesPanel.xaml.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Diagnostics; +using CalculatorApp; +using CalculatorApp.ViewModel; + +using Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using System.ComponentModel; + +namespace CalculatorApp +{ + public sealed partial class KeyGraphFeaturesPanel : System.ComponentModel.INotifyPropertyChanged + { + public KeyGraphFeaturesPanel() + { + InitializeComponent(); + this.Loaded += KeyGraphFeaturesPanel_Loaded; + } + + public event PropertyChangedEventHandler PropertyChanged; + internal void RaisePropertyChanged(string p) + { +#if !UNIT_TESTS + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p)); +#endif + } + + public CalculatorApp.ViewModel.EquationViewModel ViewModel + { + get { return m_viewModel; } + set + { + if (m_viewModel != value) + { + m_viewModel = value; + RaisePropertyChanged("ViewModel"); + } + } + } + + public event Windows.UI.Xaml.RoutedEventHandler KeyGraphFeaturesClosed; + + public static Windows.UI.Xaml.Media.SolidColorBrush + ToSolidColorBrush(Windows.UI.Color color) + { + return new Windows.UI.Xaml.Media.SolidColorBrush(color); + } + + private void KeyGraphFeaturesPanel_Loaded(object sender, RoutedEventArgs e) + { + BackButton.Focus(FocusState.Programmatic); + } + + private void BackButton_Click(object sender, RoutedEventArgs e) + { + KeyGraphFeaturesClosed?.Invoke(this, new RoutedEventArgs()); + } + + private CalculatorApp.ViewModel.EquationViewModel m_viewModel; + } +}