Wire up keyboard in graphing calculator (#863)

* Wire up graphing calculator keyboard with math rich edit control

* CR feedback

* Handle focus bug in flyout
This commit is contained in:
Eric Wong 2019-12-19 09:56:03 -08:00 committed by Pepe Rivera
parent 38da8d7b38
commit 13e31799c9
9 changed files with 1252 additions and 893 deletions

View File

@ -120,11 +120,7 @@ public
RshL = (int)CM::Command::CommandRSHFL,
RolC = (int)CM::Command::CommandROLC,
RorC = (int)CM::Command::CommandRORC,
Plot,
X,
Y,
BINSTART = (int)CM::Command::CommandBINEDITSTART,
BINPOS0 = (int)CM::Command::CommandBINPOS0,
BINPOS1 = (int)CM::Command::CommandBINPOS1,
@ -198,6 +194,14 @@ public
MemoryRecall = (int)CM::Command::CommandRECALL,
MemoryClear = (int)CM::Command::CommandMCLEAR,
BitflipButton = 1000,
FullKeypadButton = 1001
FullKeypadButton = 1001,
// Buttons used in graphing calculator
LessThan,
LessThanOrEqualTo,
GreaterThan,
GreaterThanOrEqualTo,
X,
Y
};
}

View File

@ -115,41 +115,86 @@ void MathRichEditBox::SetMathTextProperty(String ^ newValue)
}
this->IsReadOnly = readOnlyState;
SetValue(MathTextProperty, newValue);
}
void CalculatorApp::Controls::MathRichEditBox::OnLosingFocus(Windows::UI::Xaml::UIElement ^ sender, Windows::UI::Xaml::Input::LosingFocusEventArgs ^ args)
{
auto newVal = GetMathTextProperty();
if (MathText != newVal)
{
SetValue(MathTextProperty, newVal);
EquationSubmitted(this, ref new MathRichEditBoxSubmission(true, EquationSubmissionSource::FOCUS_LOST));
}
else
{
EquationSubmitted(this, ref new MathRichEditBoxSubmission(false, EquationSubmissionSource::FOCUS_LOST));
}
SubmitEquation(EquationSubmissionSource::FOCUS_LOST);
}
void CalculatorApp::Controls::MathRichEditBox::OnKeyUp(Platform::Object ^ sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs ^ e)
{
if (e->Key == VirtualKey::Enter)
{
auto newVal = GetMathTextProperty();
if (MathText != newVal)
{
SetValue(MathTextProperty, newVal);
EquationSubmitted(this, ref new MathRichEditBoxSubmission(true, EquationSubmissionSource::ENTER_KEY));
}
else
{
EquationSubmitted(this, ref new MathRichEditBoxSubmission(true, EquationSubmissionSource::ENTER_KEY));
}
SubmitEquation(EquationSubmissionSource::ENTER_KEY);
}
}
void MathRichEditBox::OnMathTextPropertyChanged(Platform::String ^ oldValue, Platform::String ^ newValue)
{
SetMathTextProperty(newValue);
SetValue(MathTextProperty, newValue);
}
void MathRichEditBox::InsertText(Platform::String ^ text, int cursorOffSet, int selectionLength)
{
// If the rich edit is empty, the math zone may not exist, and so selection (and thus the resulting text) will not be in a math zone.
// If the rich edit has content already, then the mathzone will already be created due to mathonly mode being set and the selection will exist inside the
// math zone. To handle this, we will force a math zone to be created in teh case of the text being empty and then replacing the text inside of the math
// zone with the newly inserted text.
if (GetMathTextProperty() == nullptr)
{
SetMathTextProperty("<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>x</mi></math>");
TextDocument->Selection->StartPosition = 0;
TextDocument->Selection->EndPosition = 1;
}
// insert the text in place of selection
TextDocument->Selection->SetText(Windows::UI::Text::TextSetOptions::FormatRtf, text);
// Move the cursor to the next logical place for users to enter text.
TextDocument->Selection->StartPosition += cursorOffSet;
TextDocument->Selection->EndPosition = TextDocument->Selection->StartPosition + selectionLength;
}
void MathRichEditBox::BackSpace()
{
// if anything is selected, just delete the selection. Note: EndPosition can be before start position.
if (TextDocument->Selection->StartPosition != TextDocument->Selection->EndPosition)
{
TextDocument->Selection->SetText(Windows::UI::Text::TextSetOptions::None, L"");
return;
}
// if we are at the start of the string, do nothing
if (TextDocument->Selection->StartPosition == 0)
{
return;
}
// Select the previous group.
TextDocument->Selection->EndPosition = TextDocument->Selection->StartPosition;
TextDocument->Selection->StartPosition -= 1;
// If the group contains anything complex, we want to give the user a chance to preview the deletion.
// If it's a single character, then just delete it. Otherwise do nothing until the user triggers backspace again.
auto text = TextDocument->Selection->Text;
if (text->Length() == 1)
{
TextDocument->Selection->SetText(Windows::UI::Text::TextSetOptions::None, L"");
}
}
void MathRichEditBox::SubmitEquation(EquationSubmissionSource source)
{
auto newVal = GetMathTextProperty();
if (MathText != newVal)
{
SetValue(MathTextProperty, newVal);
EquationSubmitted(this, ref new MathRichEditBoxSubmission(true, source));
}
else
{
EquationSubmitted(this, ref new MathRichEditBoxSubmission(false, source));
}
}

View File

@ -13,6 +13,7 @@ namespace CalculatorApp
{
FOCUS_LOST,
ENTER_KEY,
PROGRAMMATIC
};
public
@ -40,6 +41,9 @@ namespace CalculatorApp
event Windows::Foundation::EventHandler<MathRichEditBoxSubmission^> ^ EquationSubmitted;
void OnMathTextPropertyChanged(Platform::String ^ oldValue, Platform::String ^ newValue);
void InsertText(Platform::String ^ text, int cursorOffSet, int selectionLength);
void SubmitEquation(EquationSubmissionSource source);
void BackSpace();
private:
Platform::String ^ GetMathTextProperty();

View File

@ -31,7 +31,7 @@ namespace CalculatorApp
static Windows::UI::Xaml::Media::SolidColorBrush
^ ToSolidColorBrush(Windows::UI::Color color) { return ref new Windows::UI::Xaml::Media::SolidColorBrush(color); }
private:
void OnPropertyChanged(Platform::String^ propertyName);
void OnEquationsPropertyChanged();
@ -59,7 +59,6 @@ namespace CalculatorApp
void TextBoxKeyDown(Windows::UI::Xaml::Controls::TextBox ^ textbox, Windows::UI::Xaml::Input::KeyRoutedEventArgs ^ e);
void SubmitTextbox(Windows::UI::Xaml::Controls::TextBox ^ textbox);
private:
Windows::UI::ViewManagement::AccessibilitySettings ^ m_accessibilitySettings;
int m_lastLineColorIndex;
int m_lastFunctionLabelIndex;

View File

@ -514,7 +514,8 @@
Variables="{x:Bind ViewModel.Variables}"
Visibility="{x:Bind IsKeyGraphFeaturesVisible, Converter={StaticResource BooleanToVisibilityNegationConverter}, Mode=OneWay}"/>
<local:GraphingNumPad Grid.Row="1"
<local:GraphingNumPad x:Name="GraphingNumberPad"
Grid.Row="1"
Margin="2,0,2,2"
Visibility="{x:Bind IsKeyGraphFeaturesVisible, Converter={StaticResource BooleanToVisibilityNegationConverter}, Mode=OneWay}"/>

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,9 @@
#include "pch.h"
#include "GraphingNumPad.xaml.h"
#include "Views/NumberPad.xaml.h"
#include "Controls/CalculatorButton.h"
#include "CalcViewModel/Common/LocalizationSettings.h"
#include "Controls/MathRichEditBox.h"
using namespace CalculatorApp;
@ -18,43 +21,130 @@ using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Navigation;
// Dictionary of the enum of the button clicked mapped to an object with the string to enter into the rich edit, and the start and end of the selection after text has been entered.
static const std::unordered_map<NumbersAndOperatorsEnum, std::tuple<Platform::String ^, int, int>> buttonOutput = {
{ NumbersAndOperatorsEnum::Sin, { L"sin()", 4, 0 } },
{ NumbersAndOperatorsEnum::Cos, { L"cos()", 4, 0 } },
{ NumbersAndOperatorsEnum::Tan, { L"tan()", 4, 0 } },
{ NumbersAndOperatorsEnum::Sec, { L"sec()", 4, 0 } },
{ NumbersAndOperatorsEnum::Csc, { L"csc()", 4, 0 } },
{ NumbersAndOperatorsEnum::Cot, { L"cot()", 4, 0 } },
{ NumbersAndOperatorsEnum::InvSin, { L"arcsin()", 7, 0 } },
{ NumbersAndOperatorsEnum::InvCos, { L"arccos()", 7, 0 } },
{ NumbersAndOperatorsEnum::InvTan, { L"arctan()", 7, 0 } },
{ NumbersAndOperatorsEnum::InvSec, { L"arcsec()", 7, 0 } },
{ NumbersAndOperatorsEnum::InvCsc, { L"arccsc()", 7, 0 } },
{ NumbersAndOperatorsEnum::InvCot, { L"arccot()", 7, 0 } },
{ NumbersAndOperatorsEnum::Sinh, { L"sinh()", 5, 0 } },
{ NumbersAndOperatorsEnum::Cosh, { L"cosh()", 5, 0 } },
{ NumbersAndOperatorsEnum::Tanh, { L"tanh()", 5, 0 } },
{ NumbersAndOperatorsEnum::Sech, { L"sech()", 5, 0 } },
{ NumbersAndOperatorsEnum::Csch, { L"csch()", 5, 0 } },
{ NumbersAndOperatorsEnum::Coth, { L"coth()", 5, 0 } },
{ NumbersAndOperatorsEnum::InvSinh, { L"arcsinh()", 8, 0 } },
{ NumbersAndOperatorsEnum::InvCosh, { L"arccosh()", 8, 0 } },
{ NumbersAndOperatorsEnum::InvTanh, { L"arctanh()", 8, 0 } },
{ NumbersAndOperatorsEnum::InvSech, { L"arcsech()", 8, 0 } },
{ NumbersAndOperatorsEnum::InvCsch, { L"arccsch()", 8, 0 } },
{ NumbersAndOperatorsEnum::InvCoth, { L"arccoth()", 8, 0 } },
{ NumbersAndOperatorsEnum::Abs, { L"abs()", 4, 0 } },
{ NumbersAndOperatorsEnum::Floor, { L"floor()", 6, 0 } },
{ NumbersAndOperatorsEnum::Ceil, { L"ceiling()", 8, 0 } },
{ NumbersAndOperatorsEnum::Pi, { L"\u03C0", 1, 0 } },
{ NumbersAndOperatorsEnum::Euler, { L"e", 1, 0 } },
{ NumbersAndOperatorsEnum::XPower2, { L"^2", 2, 0 } },
{ NumbersAndOperatorsEnum::Cube, { L"^3", 2, 0 } },
{ NumbersAndOperatorsEnum::XPowerY, { L"^", 1, 0 } },
{ NumbersAndOperatorsEnum::TenPowerX, { L"10^", 3, 0 } },
{ NumbersAndOperatorsEnum::LogBase10, { L"log()", 4, 0 } },
{ NumbersAndOperatorsEnum::LogBaseE, { L"ln()", 3, 0 } },
{ NumbersAndOperatorsEnum::Sqrt, { L"sqrt()", 5, 0 } },
{ NumbersAndOperatorsEnum::CubeRoot, { L"cbrt()", 5, 0 } },
{ NumbersAndOperatorsEnum::YRootX, { L"root(x,n)", 7, 1 } },
{ NumbersAndOperatorsEnum::TwoPowerX, { L"2^", 2, 0 } },
{ NumbersAndOperatorsEnum::LogBaseX, { L"log(b, x)", 4, 1 } },
{ NumbersAndOperatorsEnum::EPowerX, { L"e^", 4, 0 } },
{ NumbersAndOperatorsEnum::Abs, { L"abs()", 4, 0 } },
{ NumbersAndOperatorsEnum::X, { L"x", 1, 0 } },
{ NumbersAndOperatorsEnum::Y, { L"y", 1, 0 } },
{ NumbersAndOperatorsEnum::OpenParenthesis, { L"(", 1, 0 } },
{ NumbersAndOperatorsEnum::CloseParenthesis, { L")", 1, 0 } },
{ NumbersAndOperatorsEnum::Equals, { L"=", 1, 0 } },
{ NumbersAndOperatorsEnum::Divide, { L"/", 1, 0 } },
{ NumbersAndOperatorsEnum::Multiply, { L"*", 1, 0 } },
{ NumbersAndOperatorsEnum::Subtract, { L"-", 1, 0 } },
{ NumbersAndOperatorsEnum::Add, { L"+", 1, 0 } },
{ NumbersAndOperatorsEnum::Invert, { L"1/", 2, 0 } },
{ NumbersAndOperatorsEnum::Negate, { L"-", 1, 0 } },
{ NumbersAndOperatorsEnum::GreaterThan, { L">", 1, 0 } },
{ NumbersAndOperatorsEnum::GreaterThanOrEqualTo, { L"\u2265", 1, 0 } },
{ NumbersAndOperatorsEnum::LessThan, { L"<", 1, 0 } },
{ NumbersAndOperatorsEnum::LessThanOrEqualTo, { L"\u2264", 1, 0 } },
{ NumbersAndOperatorsEnum::Zero, { L"0", 1, 0 } },
{ NumbersAndOperatorsEnum::One, { L"1", 1, 0 } },
{ NumbersAndOperatorsEnum::Two, { L"2", 1, 0 } },
{ NumbersAndOperatorsEnum::Three, { L"3", 1, 0 } },
{ NumbersAndOperatorsEnum::Four, { L"4", 1, 0 } },
{ NumbersAndOperatorsEnum::Five, { L"5", 1, 0 } },
{ NumbersAndOperatorsEnum::Six, { L"6", 1, 0 } },
{ NumbersAndOperatorsEnum::Seven, { L"7", 1, 0 } },
{ NumbersAndOperatorsEnum::Eight, { L"8", 1, 0 } },
{ NumbersAndOperatorsEnum::Nine, { L"9", 1, 0 } },
{ NumbersAndOperatorsEnum::Decimal, { L".", 1, 0 } },
};
GraphingNumPad::GraphingNumPad()
{
InitializeComponent();
const auto& localizationSettings = CalculatorApp::Common::LocalizationSettings::GetInstance();
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');
}
void GraphingNumPad::ShiftButton_Check(_In_ Platform::Object ^ /*sender*/, _In_ Windows::UI::Xaml::RoutedEventArgs ^ /*e*/)
void GraphingNumPad::ShiftButton_Check(_In_ Platform::Object ^ /*sender*/, _In_ RoutedEventArgs ^ /*e*/)
{
SetOperatorRowVisibility();
}
void GraphingNumPad::ShiftButton_Uncheck(_In_ Platform::Object ^ /*sender*/, _In_ Windows::UI::Xaml::RoutedEventArgs ^ /*e*/)
void GraphingNumPad::ShiftButton_Uncheck(_In_ Platform::Object ^ sender, _In_ RoutedEventArgs ^ /*e*/)
{
ShiftButton->IsChecked = false;
SetOperatorRowVisibility();
FocusManager::TryFocusAsync(ShiftButton, ::FocusState::Programmatic);
GraphingNumPad::Button_Clicked(sender, nullptr);
}
void GraphingNumPad::TrigFlyoutShift_Toggle(_In_ Platform::Object ^ /*sender*/, _In_ Windows::UI::Xaml::RoutedEventArgs ^ /*e*/)
void GraphingNumPad::TrigFlyoutShift_Toggle(_In_ Platform::Object ^ /*sender*/, _In_ RoutedEventArgs ^ /*e*/)
{
SetTrigRowVisibility();
}
void GraphingNumPad::TrigFlyoutHyp_Toggle(_In_ Platform::Object ^ /*sender*/, _In_ Windows::UI::Xaml::RoutedEventArgs ^ /*e*/)
void GraphingNumPad::TrigFlyoutHyp_Toggle(_In_ Platform::Object ^ /*sender*/, _In_ RoutedEventArgs ^ /*e*/)
{
SetTrigRowVisibility();
}
void GraphingNumPad::FlyoutButton_Clicked(_In_ Platform::Object ^ /*sender*/, _In_ Windows::UI::Xaml::RoutedEventArgs ^ /*e*/)
void GraphingNumPad::FlyoutButton_Clicked(_In_ Platform::Object ^ sender, _In_ RoutedEventArgs ^ /*e*/)
{
this->HypButton->IsChecked = false;
this->TrigShiftButton->IsChecked = false;
this->Trigflyout->Hide();
this->FuncFlyout->Hide();
this->InequalityFlyout->Hide();
GraphingNumPad::Button_Clicked(sender, nullptr);
}
void GraphingNumPad::ShiftButton_IsEnabledChanged(_In_ Platform::Object ^ /*sender*/, _In_ Windows::UI::Xaml::DependencyPropertyChangedEventArgs ^ /*e*/)
void GraphingNumPad::ShiftButton_IsEnabledChanged(_In_ Platform::Object ^ /*sender*/, _In_ DependencyPropertyChangedEventArgs ^ /*e*/)
{
SetOperatorRowVisibility();
}
@ -104,3 +194,64 @@ void GraphingNumPad::SetOperatorRowVisibility()
Row1->Visibility = rowVis;
InvRow1->Visibility = invRowVis;
}
void GraphingNumPad::Button_Clicked(Platform::Object ^ sender, DependencyPropertyChangedEventArgs ^ /*e*/)
{
auto mathRichEdit = GetActiveRichEdit();
auto button = dynamic_cast<CalculatorApp::Controls::CalculatorButton ^>(sender);
if (mathRichEdit != nullptr && sender != nullptr)
{
auto id = button->ButtonId;
auto output = buttonOutput.find(id);
mathRichEdit->InsertText(std::get<0>(output->second), std::get<1>(output->second), std::get<2>(output->second));
}
}
void GraphingNumPad::SubmitButton_Clicked(Platform::Object ^ /*sender*/, RoutedEventArgs ^ /*e*/)
{
auto mathRichEdit = GetActiveRichEdit();
if (mathRichEdit != nullptr)
{
mathRichEdit->SubmitEquation(CalculatorApp::Controls::EquationSubmissionSource::ENTER_KEY);
}
}
void GraphingNumPad::ClearButton_Clicked(Platform::Object ^ /*sender*/, RoutedEventArgs ^ /*e*/)
{
auto mathRichEdit = GetActiveRichEdit();
if (mathRichEdit != nullptr)
{
mathRichEdit->MathText = L"<math xmlns=\"http://www.w3.org/1998/Math/MathML\"></math>";
mathRichEdit->SubmitEquation(CalculatorApp::Controls::EquationSubmissionSource::PROGRAMMATIC);
}
}
void GraphingNumPad::BackSpaceButton_Clicked(Platform::Object ^ /*sender*/, RoutedEventArgs ^ /*e*/)
{
auto mathRichEdit = GetActiveRichEdit();
if (mathRichEdit != nullptr)
{
mathRichEdit->BackSpace();
}
}
// To avoid focus moving when the space between buttons is clicked, handle click events that make it through the keypad.
void GraphingNumPad::GraphingNumPad_PointerPressed(Platform::Object ^ /*sender*/, PointerRoutedEventArgs ^ e)
{
e->Handled = true;
}
CalculatorApp::Controls::MathRichEditBox ^ GraphingNumPad::GetActiveRichEdit()
{
return dynamic_cast<Controls::MathRichEditBox ^>(FocusManager::GetFocusedElement());
}
// Adding event because the ShowMode property is ignored in xaml.
void GraphingNumPad::Flyout_Opening(Platform::Object ^ sender, Platform::Object ^ /*e*/)
{
auto flyout = dynamic_cast<Flyout ^>(sender);
if (flyout)
{
flyout->ShowMode = FlyoutShowMode::Transient;
}
}

View File

@ -5,6 +5,8 @@
#include "Views\GraphingCalculator\GraphingNumPad.g.h"
#include "CalcViewModel/GraphingCalculator/GraphingCalculatorViewModel.h"
#include "Views/GraphingCalculator/EquationInputArea.xaml.h"
#include "CalcViewModel/Common/CalculatorButtonUser.h"
namespace CalculatorApp
{
@ -23,5 +25,12 @@ namespace CalculatorApp
void ShiftButton_IsEnabledChanged(_In_ Platform::Object ^ sender, _In_ Windows::UI::Xaml::DependencyPropertyChangedEventArgs ^ e);
void SetOperatorRowVisibility();
void SetTrigRowVisibility();
};
void Button_Clicked(_In_ Platform::Object ^ sender, _In_ Windows::UI::Xaml::DependencyPropertyChangedEventArgs ^ e);
void SubmitButton_Clicked(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e);
void ClearButton_Clicked(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e);
void BackSpaceButton_Clicked(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e);
void GraphingNumPad_PointerPressed(Platform::Object ^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs ^ e);
Controls::MathRichEditBox^ GetActiveRichEdit();
void Flyout_Opening(Platform::Object ^ sender, Platform::Object ^ e);
};
}

View File

@ -27,6 +27,7 @@
#include <concrt.h>
#include <regex>
#include <string>
#include <tuple>
#include <iomanip>
// C++\WinRT Headers