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/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 = "

" + resourceLoader.GetString("GraphImageAltText") + "

"; + + 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 += ""; + } + equationHtml += "
" + + Utilities.EscapeHtmlSpecialCharacters(expression) + "
"; + + 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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; + } +}