diff --git a/.gitignore b/.gitignore
index 2100dfc1..1088272d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -289,6 +289,7 @@ __pycache__/
# Calculator specific
Generated Files/
+src/GraphControl/GraphingImplOverrides.props
!/build/config/TRexDefs/**
!src/Calculator/TemporaryKey.pfx
!src/CalculatorUnitTests/CalculatorUnitTests_TemporaryKey.pfx
\ No newline at end of file
diff --git a/README.md b/README.md
index d967b175..3da1f4ac 100644
--- a/README.md
+++ b/README.md
@@ -45,6 +45,10 @@ We also welcome [issues submitted on GitHub](https://github.com/Microsoft/calcul
## Roadmap
For information regarding Windows Calculator plans and release schedule, please see the [Windows Calculator Roadmap](docs/Roadmap.md).
+### Graphing Mode
+Adding graphing calculator functionality [is on the project roadmap](https://github.com/Microsoft/calculator/issues/338) and we hope that this project can create a great end-user experience around graphing. To that end, the UI from the official in-box Windows Calculator is currently part of this repository, although the proprietary Microsoft-built graphing engine, which also drives graphing in Microsoft Mathematics and OneNote, is not. Community members can still be involved in the creation of the UI, however developer builds will not have graphing functionality due to the use of a [mock implementation of the engine](/src/MockGraphingImpl) built on top of a
+[common graphing API](/src/GraphingInterfaces).
+
## Data / Telemetry
This project collects usage data and sends it to Microsoft to help improve our products and services.
Read our [privacy statement](https://go.microsoft.com/fwlink/?LinkId=521839) to learn more.
diff --git a/src/CalcViewModel/ApplicationViewModel.cpp b/src/CalcViewModel/ApplicationViewModel.cpp
index f99ce036..ecffca3d 100644
--- a/src/CalcViewModel/ApplicationViewModel.cpp
+++ b/src/CalcViewModel/ApplicationViewModel.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "pch.h"
@@ -42,6 +42,7 @@ namespace
ApplicationViewModel::ApplicationViewModel() :
m_CalculatorViewModel(nullptr),
m_DateCalcViewModel(nullptr),
+ m_GraphingCalcViewModel(nullptr),
m_ConverterViewModel(nullptr),
m_PreviousMode(ViewMode::None),
m_mode(ViewMode::None),
@@ -132,6 +133,13 @@ void ApplicationViewModel::OnModeChanged()
}
m_CalculatorViewModel->SetCalculatorType(m_mode);
}
+ else if (NavCategory::IsGraphingCalculatorViewMode(m_mode))
+ {
+ if (!m_GraphingCalcViewModel)
+ {
+ m_GraphingCalcViewModel = ref new GraphingCalculatorViewModel();
+ }
+ }
else if (NavCategory::IsDateCalculatorViewMode(m_mode))
{
TraceLogger::GetInstance().LogDateCalculatorModeViewed(m_mode, ApplicationView::GetApplicationViewIdForWindow(CoreWindow::GetForCurrentThread()));
diff --git a/src/CalcViewModel/ApplicationViewModel.h b/src/CalcViewModel/ApplicationViewModel.h
index f13e00ad..5c787390 100644
--- a/src/CalcViewModel/ApplicationViewModel.h
+++ b/src/CalcViewModel/ApplicationViewModel.h
@@ -5,6 +5,7 @@
#include "StandardCalculatorViewModel.h"
#include "DateCalculatorViewModel.h"
+#include "GraphingCalculator/GraphingCalculatorViewModel.h"
#include "UnitConverterViewModel.h"
namespace CalculatorApp
@@ -22,6 +23,7 @@ namespace CalculatorApp
OBSERVABLE_OBJECT();
OBSERVABLE_PROPERTY_RW(StandardCalculatorViewModel^, CalculatorViewModel);
OBSERVABLE_PROPERTY_RW(DateCalculatorViewModel^, DateCalcViewModel);
+ OBSERVABLE_PROPERTY_RW(GraphingCalculatorViewModel^, GraphingCalcViewModel);
OBSERVABLE_PROPERTY_RW(UnitConverterViewModel^, ConverterViewModel);
OBSERVABLE_PROPERTY_RW(CalculatorApp::Common::ViewMode, PreviousMode);
OBSERVABLE_NAMED_PROPERTY_RW(Platform::String^, CategoryName);
diff --git a/src/CalcViewModel/CalcViewModel.vcxproj b/src/CalcViewModel/CalcViewModel.vcxproj
index e2e98ab9..82c74f39 100644
--- a/src/CalcViewModel/CalcViewModel.vcxproj
+++ b/src/CalcViewModel/CalcViewModel.vcxproj
@@ -350,6 +350,8 @@
+
+
@@ -386,6 +388,8 @@
+
+
diff --git a/src/CalcViewModel/CalcViewModel.vcxproj.filters b/src/CalcViewModel/CalcViewModel.vcxproj.filters
index d05aca0b..1e85ec88 100644
--- a/src/CalcViewModel/CalcViewModel.vcxproj.filters
+++ b/src/CalcViewModel/CalcViewModel.vcxproj.filters
@@ -10,6 +10,9 @@
{0184f727-b8aa-4af8-a699-63f1b56e7853}
+
+ {f7519cec-2ebd-432b-9d59-9647de131d50}
+
@@ -93,6 +96,12 @@
DataLoaders
+
+ GraphingCalculator
+
+
+ GraphingCalculator
+
@@ -213,6 +222,12 @@
Common
+
+ GraphingCalculator
+
+
+ GraphingCalculator
+
diff --git a/src/CalcViewModel/Common/CalculatorButtonUser.h b/src/CalcViewModel/Common/CalculatorButtonUser.h
index cc6647d9..cda4477e 100644
--- a/src/CalcViewModel/Common/CalculatorButtonUser.h
+++ b/src/CalcViewModel/Common/CalculatorButtonUser.h
@@ -62,10 +62,6 @@ namespace CalculatorApp
IsStandardMode = (int) CM::Command::ModeBasic,
None = (int) CM::Command::CommandNULL,
IsProgrammerMode = (int) CM::Command::ModeProgrammer,
- DecButton = (int) CM::Command::CommandDec,
- OctButton = (int) CM::Command::CommandOct,
- HexButton = (int) CM::Command::CommandHex,
- BinButton = (int) CM::Command::CommandBin,
And = (int) CM::Command::CommandAnd,
Ror = (int) CM::Command::CommandROR,
Rol = (int) CM::Command::CommandROL,
@@ -87,12 +83,21 @@ namespace CalculatorApp
InvSinh = (int) CM::Command::CommandASINH,
InvCosh = (int) CM::Command::CommandACOSH,
InvTanh = (int) CM::Command::CommandATANH,
- Qword = (int) CM::Command::CommandQword,
- Dword = (int) CM::Command::CommandDword,
- Word = (int) CM::Command::CommandWord,
- Byte = (int) CM::Command::CommandByte,
Cube = (int) CM::Command::CommandCUB,
DMS = (int) CM::Command::CommandDMS,
+ Hyp = (int)CM::Command::CommandHYP,
+ HexButton = (int)CM::Command::CommandHex,
+ DecButton = (int)CM::Command::CommandDec,
+ OctButton = (int)CM::Command::CommandOct,
+ BinButton = (int)CM::Command::CommandBin,
+ Qword = (int)CM::Command::CommandQword,
+ Dword = (int)CM::Command::CommandDword,
+ Word = (int)CM::Command::CommandWord,
+ Byte = (int)CM::Command::CommandByte,
+
+ Plot,
+ X,
+ Y,
BINSTART = (int) CM::Command::CommandBINEDITSTART,
BINPOS0 = (int) CM::Command::CommandBINPOS0,
@@ -159,8 +164,7 @@ namespace CalculatorApp
BINPOS61 = (int) CM::Command::CommandBINPOS61,
BINPOS62 = (int) CM::Command::CommandBINPOS62,
BINPOS63 = (int) CM::Command::CommandBINPOS63,
- BINEND = (int) CM::Command::CommandBINEDITEND,
- Hyp = (int) CM::Command::CommandHYP
+ BINEND = (int) CM::Command::CommandBINEDITEND
};
// This contains list of functions whose usage we are tracelogging
diff --git a/src/CalcViewModel/Common/KeyboardShortcutManager.cpp b/src/CalcViewModel/Common/KeyboardShortcutManager.cpp
index d7b87c96..33d2697b 100644
--- a/src/CalcViewModel/Common/KeyboardShortcutManager.cpp
+++ b/src/CalcViewModel/Common/KeyboardShortcutManager.cpp
@@ -10,6 +10,7 @@
using namespace Concurrency;
using namespace Platform;
using namespace std;
+using namespace std::chrono;
using namespace Windows::ApplicationModel::Resources;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
@@ -70,7 +71,7 @@ namespace CalculatorApp
}
}
- void LightUpButton(ButtonBase^ button)
+ winrt::fire_and_forget LightUpButton(ButtonBase^ button)
{
// If the button is a toggle button then we don't need
// to change the UI of the button
@@ -82,33 +83,15 @@ namespace CalculatorApp
// The button will go into the visual Pressed state with this call
VisualStateManager::GoToState(button, "Pressed", true);
- // This timer will fire after lightUpTime and make the button
- // go back to the normal state.
- // This timer will only fire once after which it will be destroyed
- auto timer = ref new DispatcherTimer();
- TimeSpan lightUpTime{};
- lightUpTime.Duration = 500000L; // Half second (in 100-ns units)
- timer->Interval = lightUpTime;
+ winrt::apartment_context uiThreadContext;
- WeakReference timerWeakReference(timer);
- WeakReference buttonWeakReference(button);
- timer->Tick += ref new EventHandler
diff --git a/src/Calculator/Calculator.vcxproj.filters b/src/Calculator/Calculator.vcxproj.filters
index cc25a224..bc22d788 100644
--- a/src/Calculator/Calculator.vcxproj.filters
+++ b/src/Calculator/Calculator.vcxproj.filters
@@ -218,6 +218,9 @@
{0120c344-0bc0-4a1d-b82c-df7945f46189}
+
+ {e23e2a6e-491b-4200-9bf7-d355a1ee695b}
+
@@ -480,6 +483,12 @@
Views
+
+ Views\GraphingCalculator
+
+
+ Views\GraphingCalculator
+
diff --git a/src/Calculator/Package.appxmanifest b/src/Calculator/Package.appxmanifest
index 93d1d7f6..83bfc947 100644
--- a/src/Calculator/Package.appxmanifest
+++ b/src/Calculator/Package.appxmanifest
@@ -1,6 +1,6 @@
-
+
-
+
ms-resource:DevAppStoreName
diff --git a/src/Calculator/Resources/en-US/Resources.resw b/src/Calculator/Resources/en-US/Resources.resw
index a9f3682f..9233a5ae 100644
--- a/src/Calculator/Resources/en-US/Resources.resw
+++ b/src/Calculator/Resources/en-US/Resources.resw
@@ -118,7 +118,7 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
- Calculator
+ Graphing Calculator
{@Appx_ShortDisplayName@}{StringCategory="Feature Title"} This is the title of the official application when published through Windows Store.
@@ -1271,7 +1271,7 @@
Equals
- Screen reader prompt for the invert button on the scientific operator keypad
+ Screen reader prompt for the equals button on the scientific operator keypad
Inverse Function
@@ -3379,4 +3379,40 @@
Microsoft Services Agreement
Displayed on a link to the Microsoft Services Agreement in the about this app information
+
+ Graphing
+ Name of the Graphing mode of the Calculator app. Displayed in the navigation menu.
+
+
+ =
+ {Locked}This is the character that should trigger this button. Note that it is a character and not a key, so it does not come from the Windows::System::VirtualKey enum.
+
+
+ Equals
+ Screen reader prompt for the equal button on the graphing calculator operator keypad
+
+
+ Enter
+ {Locked}This is the value from the VirtualKey enum that maps to this button
+
+
+ Plot
+ Screen reader prompt for the plot button on the graphing calculator operator keypad
+
+
+ X
+ {Locked}This is the value that comes from the VirtualKey enum that represents the button. This value is not localized and must be one value that comes from the Windows::System::VirtualKey enum.
+
+
+ Y
+ {Locked}This is the value that comes from the VirtualKey enum that represents the button. This value is not localized and must be one value that comes from the Windows::System::VirtualKey enum.
+
+
+ Equations
+ The text that shows as the header for the equation input area in Graphing Calculator mode.
+
+
+ ^
+ {Locked}This is the character that should trigger this button. Note that it is a character and not a key, so it does not come from the Windows::System::VirtualKey enum.
+
diff --git a/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml
new file mode 100644
index 00000000..5da0818f
--- /dev/null
+++ b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.cpp b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.cpp
new file mode 100644
index 00000000..5c577ce9
--- /dev/null
+++ b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.cpp
@@ -0,0 +1,108 @@
+#include "pch.h"
+#include "EquationInputArea.xaml.h"
+#include "CalcViewModel/Common/KeyboardShortcutManager.h"
+
+using namespace CalculatorApp;
+using namespace CalculatorApp::Common;
+using namespace CalculatorApp::ViewModel;
+using namespace Platform;
+using namespace std;
+using namespace Windows::System;
+using namespace Windows::UI;
+using namespace Windows::UI::ViewManagement;
+using namespace Windows::UI::Xaml;
+using namespace Windows::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Input;
+
+namespace
+{
+ const Color accentColor = (ref new UISettings())->GetColorValue(UIColorType::Accent);
+ const Color lineColors[] = {
+ accentColor,
+ Colors::DarkOrange,
+ Colors::MediumPurple,
+ Colors::ForestGreen,
+ Colors::BlueViolet,
+ Colors::DarkRed,
+ Colors::LightGoldenrodYellow,
+ Colors::DarkOliveGreen
+ };
+ const size_t lineColorsSize = std::size(lineColors);
+
+ StringReference EquationsPropertyName(L"Equations");
+}
+
+EquationInputArea::EquationInputArea()
+ : m_lastLineColorIndex{ -1 }
+{
+ InitializeComponent();
+}
+
+void EquationInputArea::OnPropertyChanged(String^ propertyName)
+{
+ if (propertyName == EquationsPropertyName)
+ {
+ OnEquationsPropertyChanged();
+ }
+}
+
+void EquationInputArea::OnEquationsPropertyChanged()
+{
+ if (Equations != nullptr && Equations->Size == 0)
+ {
+ AddNewEquation();
+
+ // For now, the first equation needs to be y = 0.
+ // We can remove this when we can create empty graphs.
+ if (EquationViewModel^ eqvm = Equations->GetAt(0))
+ {
+ eqvm->Expression = L"0";
+ }
+ }
+}
+
+void EquationInputArea::AddEquationButton_Click(Object^ sender, RoutedEventArgs^ e)
+{
+ AddNewEquation();
+}
+
+void EquationInputArea::AddNewEquation()
+{
+ auto eq = ref new EquationViewModel();
+ eq->LineColor = GetNextLineColor();
+
+ Equations->Append(eq);
+ EquationInputList->ScrollIntoView(eq);
+}
+
+void EquationInputArea::InputTextBox_GotFocus(Object^ sender, RoutedEventArgs^ e)
+{
+ KeyboardShortcutManager::HonorShortcuts(false);
+}
+
+void EquationInputArea::InputTextBox_LostFocus(Object^ sender, RoutedEventArgs^ e)
+{
+ KeyboardShortcutManager::HonorShortcuts(true);
+
+ auto tb = static_cast(sender);
+ auto eq = static_cast(tb->DataContext);
+ tb->Text = eq->Expression;
+}
+
+void EquationInputArea::InputTextBox_KeyUp(Object^ sender, KeyRoutedEventArgs^ e)
+{
+ if (e->Key == VirtualKey::Enter)
+ {
+ auto tb = static_cast(sender);
+ auto eq = static_cast(tb->DataContext);
+ eq->Expression = tb->Text;
+
+ e->Handled = true;
+ }
+}
+
+Color EquationInputArea::GetNextLineColor()
+{
+ m_lastLineColorIndex = (m_lastLineColorIndex + 1) % lineColorsSize;
+ return lineColors[m_lastLineColorIndex];
+}
diff --git a/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.h b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.h
new file mode 100644
index 00000000..ad5dee73
--- /dev/null
+++ b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "Views/GraphingCalculator/EquationInputArea.g.h"
+#include "CalcViewModel/Common/Utils.h"
+#include "CalcViewModel/GraphingCalculator/EquationViewModel.h"
+
+namespace CalculatorApp
+{
+ public ref class EquationInputArea sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged
+ {
+ public:
+ EquationInputArea();
+
+ OBSERVABLE_OBJECT_CALLBACK(OnPropertyChanged);
+ OBSERVABLE_PROPERTY_RW(Windows::Foundation::Collections::IObservableVector< ViewModel::EquationViewModel^ >^, Equations);
+
+ private:
+ void OnPropertyChanged(Platform::String^ propertyName);
+ void OnEquationsPropertyChanged();
+
+ void AddEquationButton_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
+ void AddNewEquation();
+
+ void InputTextBox_GotFocus(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
+ void InputTextBox_LostFocus(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
+ void InputTextBox_KeyUp(Platform::Object^ sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs^ e);
+
+ Windows::UI::Color GetNextLineColor();
+
+ private:
+ int m_lastLineColorIndex;
+ };
+}
diff --git a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml
new file mode 100644
index 00000000..24f096f2
--- /dev/null
+++ b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cpp b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cpp
new file mode 100644
index 00000000..d83fcaf3
--- /dev/null
+++ b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cpp
@@ -0,0 +1,51 @@
+
+#include "pch.h"
+#include "GraphingCalculator.xaml.h"
+#include "CalcViewModel/Common/KeyboardShortcutManager.h"
+#include "Controls/CalculationResult.h"
+
+using namespace CalculatorApp;
+using namespace CalculatorApp::Common;
+using namespace CalculatorApp::Controls;
+using namespace CalculatorApp::ViewModel;
+using namespace concurrency;
+using namespace GraphControl;
+using namespace Platform;
+using namespace std::chrono;
+using namespace Utils;
+using namespace Windows::Foundation::Collections;
+using namespace Windows::Storage::Streams;
+using namespace Windows::System;
+using namespace Windows::UI::Xaml;
+using namespace Windows::UI::Xaml::Data;
+using namespace Windows::UI::Xaml::Input;
+using namespace Windows::UI::Xaml::Media;
+using namespace Windows::UI::Xaml::Media::Imaging;
+
+constexpr auto sc_ViewModelPropertyName = L"ViewModel";
+
+GraphingCalculator::GraphingCalculator()
+{
+ Equation::RegisterDependencyProperties();
+ Grapher::RegisterDependencyProperties();
+ InitializeComponent();
+}
+
+void GraphingCalculator::GraphingCalculator_DataContextChanged(FrameworkElement^ sender, DataContextChangedEventArgs^ args)
+{
+ ViewModel = dynamic_cast(args->NewValue);
+}
+
+GraphingCalculatorViewModel^ GraphingCalculator::ViewModel::get()
+{
+ return m_viewModel;
+}
+
+void GraphingCalculator::ViewModel::set(GraphingCalculatorViewModel^ vm)
+{
+ if (m_viewModel != vm)
+ {
+ m_viewModel = vm;
+ RaisePropertyChanged(StringReference(sc_ViewModelPropertyName));
+ }
+}
diff --git a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.h b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.h
new file mode 100644
index 00000000..fc095ae8
--- /dev/null
+++ b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "Views\GraphingCalculator\GraphingCalculator.g.h"
+#include "CalcViewModel/GraphingCalculator/GraphingCalculatorViewModel.h"
+#include "Views\NumberPad.xaml.h"
+
+namespace CalculatorApp
+{
+ public ref class GraphingCalculator sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged
+ {
+ public:
+ GraphingCalculator();
+
+ OBSERVABLE_OBJECT();
+
+ property CalculatorApp::ViewModel::GraphingCalculatorViewModel^ ViewModel
+ {
+ CalculatorApp::ViewModel::GraphingCalculatorViewModel^ get();
+ void set(CalculatorApp::ViewModel::GraphingCalculatorViewModel^ vm);
+ }
+
+ private:
+ void GraphingCalculator_DataContextChanged(Windows::UI::Xaml::FrameworkElement^ sender, Windows::UI::Xaml::DataContextChangedEventArgs^ args);
+
+ private:
+ CalculatorApp::ViewModel::GraphingCalculatorViewModel^ m_viewModel;
+ };
+}
diff --git a/src/Calculator/Views/MainPage.xaml b/src/Calculator/Views/MainPage.xaml
index 6136f42f..0d542e84 100644
--- a/src/Calculator/Views/MainPage.xaml
+++ b/src/Calculator/Views/MainPage.xaml
@@ -154,6 +154,9 @@
+
+
+
diff --git a/src/Calculator/Views/MainPage.xaml.cpp b/src/Calculator/Views/MainPage.xaml.cpp
index 14c406a0..2209f9f2 100644
--- a/src/Calculator/Views/MainPage.xaml.cpp
+++ b/src/Calculator/Views/MainPage.xaml.cpp
@@ -167,6 +167,10 @@ void MainPage::OnAppPropertyChanged(_In_ Platform::Object^ sender, _In_ Windows:
}
EnsureDateCalculator();
}
+ else if (newValue == ViewMode::Graphing)
+ {
+ EnsureGraphingCalculator();
+ }
else if (NavCategory::IsConverterViewMode(newValue))
{
if (m_model->CalculatorViewModel)
@@ -196,9 +200,10 @@ void MainPage::OnAppPropertyChanged(_In_ Platform::Object^ sender, _In_ Windows:
void MainPage::ShowHideControls(ViewMode mode)
{
- auto isCalcViewMode = NavCategory::IsCalculatorViewMode(mode);
- auto isDateCalcViewMode = NavCategory::IsDateCalculatorViewMode(mode);
- auto isConverterViewMode = NavCategory::IsConverterViewMode(mode);
+ bool isCalcViewMode = NavCategory::IsCalculatorViewMode(mode);
+ bool isDateCalcViewMode = NavCategory::IsDateCalculatorViewMode(mode);
+ bool isGraphingCalcViewMode = NavCategory::IsGraphingCalculatorViewMode(mode);
+ bool isConverterViewMode = NavCategory::IsConverterViewMode(mode);
if (m_calculator)
{
@@ -212,6 +217,12 @@ void MainPage::ShowHideControls(ViewMode mode)
m_dateCalculator->IsEnabled = isDateCalcViewMode;
}
+ if (m_graphingCalculator)
+ {
+ m_graphingCalculator->Visibility = BooleanToVisibilityConverter::Convert(isGraphingCalcViewMode);
+ m_graphingCalculator->IsEnabled = isGraphingCalcViewMode;
+ }
+
if (m_converter)
{
m_converter->Visibility = BooleanToVisibilityConverter::Convert(isConverterViewMode);
@@ -239,7 +250,7 @@ void MainPage::UpdatePanelViewState()
void MainPage::OnPageLoaded(_In_ Object^, _In_ RoutedEventArgs^ args)
{
- if (!m_converter && !m_calculator && !m_dateCalculator)
+ if (!m_converter && !m_calculator && !m_dateCalculator && !m_graphingCalculator)
{
// We have just launched into our default mode (standard calc) so ensure calc is loaded
EnsureCalculator();
@@ -292,6 +303,10 @@ void MainPage::SetDefaultFocus()
{
m_dateCalculator->SetDefaultFocus();
}
+ if (m_graphingCalculator != nullptr && m_graphingCalculator->Visibility == ::Visibility::Visible)
+ {
+ m_graphingCalculator->Focus(::FocusState::Programmatic);
+ }
if (m_converter != nullptr && m_converter->Visibility == ::Visibility::Visible)
{
m_converter->SetDefaultFocus();
@@ -354,6 +369,18 @@ void MainPage::EnsureDateCalculator()
}
}
+void MainPage::EnsureGraphingCalculator()
+{
+ if (!m_graphingCalculator)
+ {
+ m_graphingCalculator = ref new GraphingCalculator();
+ m_graphingCalculator->Name = L"GraphingCalculator";
+ m_graphingCalculator->DataContext = m_model->GraphingCalcViewModel;
+
+ GraphingCalcHolder->Child = m_graphingCalculator;
+ }
+}
+
void MainPage::EnsureConverter()
{
if (!m_converter)
@@ -571,7 +598,7 @@ void MainPage::SetHeaderAutomationName()
else
{
wstring full;
- if (NavCategory::IsCalculatorViewMode(mode))
+ if (NavCategory::IsCalculatorViewMode(mode) || NavCategory::IsGraphingCalculatorViewMode(mode))
{
full = resProvider.GetResourceString(L"HeaderAutomationName_Calculator")->Data();
}
@@ -581,6 +608,7 @@ void MainPage::SetHeaderAutomationName()
}
string::size_type found = full.find(L"%1");
+ assert(found != wstring::npos);
wstring strMode = m_model->CategoryName->Data();
full = full.replace(found, 2, strMode);
diff --git a/src/Calculator/Views/MainPage.xaml.h b/src/Calculator/Views/MainPage.xaml.h
index 96a5ff76..1bbe51e4 100644
--- a/src/Calculator/Views/MainPage.xaml.h
+++ b/src/Calculator/Views/MainPage.xaml.h
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
@@ -6,6 +6,7 @@
#include "Views/Calculator.xaml.h"
#include "Views/MainPage.g.h"
#include "Views/DateCalculator.xaml.h"
+#include "Views/GraphingCalculator/GraphingCalculator.xaml.h"
#include "Views/UnitConverter.xaml.h"
#include "CalcViewModel/ApplicationViewModel.h"
#include "Views/TitleBar.xaml.h"
@@ -24,9 +25,9 @@ namespace CalculatorApp
{
public:
MainPage();
- property CalculatorApp::ViewModel::ApplicationViewModel^ Model
+ property ViewModel::ApplicationViewModel^ Model
{
- CalculatorApp::ViewModel::ApplicationViewModel^ get(){
+ ViewModel::ApplicationViewModel^ get(){
return m_model;
}
}
@@ -36,7 +37,7 @@ namespace CalculatorApp
void SetDefaultFocus();
void SetHeaderAutomationName();
- Windows::Foundation::Collections::IObservableVector^ CreateUIElementsForCategories(_In_ Windows::Foundation::Collections::IObservableVector^ categories);
+ Windows::Foundation::Collections::IObservableVector^ CreateUIElementsForCategories(_In_ Windows::Foundation::Collections::IObservableVector^ categories);
protected:
void OnNavigatedTo(_In_ Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override;
@@ -57,13 +58,13 @@ namespace CalculatorApp
void OnAboutFlyoutOpened(_In_ Platform::Object^ sender, _In_ Platform::Object^ e);
void OnAboutFlyoutClosed(_In_ Platform::Object^ sender, _In_ Platform::Object^ e);
- Microsoft::UI::Xaml::Controls::NavigationViewItemHeader^ CreateNavViewHeaderFromGroup(CalculatorApp::Common::NavCategoryGroup^ group);
- Microsoft::UI::Xaml::Controls::NavigationViewItem^ CreateNavViewItemFromCategory(CalculatorApp::Common::NavCategory^ category);
+ Microsoft::UI::Xaml::Controls::NavigationViewItemHeader^ CreateNavViewHeaderFromGroup(Common::NavCategoryGroup^ group);
+ Microsoft::UI::Xaml::Controls::NavigationViewItem^ CreateNavViewItemFromCategory(Common::NavCategory^ category);
Windows::Foundation::EventRegistrationToken m_fullscreenFlyoutClosedToken;
void OnFullscreenFlyoutClosed();
- void ShowHideControls(CalculatorApp::Common::ViewMode mode);
+ void ShowHideControls(Common::ViewMode mode);
void UpdateViewState();
void UpdatePanelViewState();
@@ -73,21 +74,23 @@ namespace CalculatorApp
void PinUnpinAppBarButtonOnClicked(_In_ Platform::Object^ sender, _In_ Windows::UI::Xaml::RoutedEventArgs^ e);
void EnsureCalculator();
- void EnsureConverter();
void EnsureDateCalculator();
+ void EnsureGraphingCalculator();
+ void EnsureConverter();
void ShowAboutPage();
void AnnounceCategoryName();
- CalculatorApp::Calculator^ m_calculator;
- CalculatorApp::UnitConverter^ m_converter;
- CalculatorApp::DateCalculator^ m_dateCalculator;
+ Calculator^ m_calculator;
+ GraphingCalculator^ m_graphingCalculator;
+ UnitConverter^ m_converter;
+ DateCalculator^ m_dateCalculator;
Windows::Foundation::EventRegistrationToken _windowSizeEventToken;
Windows::Foundation::EventRegistrationToken m_hardwareButtonsBackPressedToken;
Windows::Foundation::EventRegistrationToken m_colorValuesChangedToken;
- CalculatorApp::ViewModel::ApplicationViewModel^ m_model;
+ ViewModel::ApplicationViewModel^ m_model;
Windows::UI::ViewManagement::UISettings^ m_uiSettings;
- std::unique_ptr m_titleBarHelper;
+ std::unique_ptr m_titleBarHelper;
};
}
diff --git a/src/CalculatorUnitTests/NavCategoryUnitTests.cpp b/src/CalculatorUnitTests/NavCategoryUnitTests.cpp
index 3c7babf2..9d2b9fa4 100644
--- a/src/CalculatorUnitTests/NavCategoryUnitTests.cpp
+++ b/src/CalculatorUnitTests/NavCategoryUnitTests.cpp
@@ -97,7 +97,7 @@ namespace CalculatorUnitTests
// Boundary testing
VERIFY_ARE_EQUAL(ViewMode::None, NavCategory::Deserialize(ref new Box(-1)));
- VERIFY_ARE_EQUAL(ViewMode::None, NavCategory::Deserialize(ref new Box(17)));
+ VERIFY_ARE_EQUAL(ViewMode::None, NavCategory::Deserialize(ref new Box(18)));
}
void NavCategoryUnitTests::IsValidViewMode_AllValid()
@@ -106,6 +106,7 @@ namespace CalculatorUnitTests
VERIFY_IS_TRUE(NavCategory::IsValidViewMode(ViewMode::Scientific));
VERIFY_IS_TRUE(NavCategory::IsValidViewMode(ViewMode::Programmer));
VERIFY_IS_TRUE(NavCategory::IsValidViewMode(ViewMode::Date));
+ VERIFY_IS_TRUE(NavCategory::IsValidViewMode(ViewMode::Graphing));
VERIFY_IS_TRUE(NavCategory::IsValidViewMode(ViewMode::Currency));
VERIFY_IS_TRUE(NavCategory::IsValidViewMode(ViewMode::Volume));
VERIFY_IS_TRUE(NavCategory::IsValidViewMode(ViewMode::Length));
@@ -125,9 +126,9 @@ namespace CalculatorUnitTests
{
VERIFY_IS_FALSE(NavCategory::IsValidViewMode(ViewMode::None));
- // There are 17 total options so int 17 should be the first invalid
- VERIFY_IS_TRUE(NavCategory::IsValidViewMode(static_cast(16)));
- VERIFY_IS_FALSE(NavCategory::IsValidViewMode(static_cast(17)));
+ // There are 18 total options so int 18 should be the first invalid
+ VERIFY_IS_TRUE(NavCategory::IsValidViewMode(static_cast(17)));
+ VERIFY_IS_FALSE(NavCategory::IsValidViewMode(static_cast(18)));
// Also verify the lower bound
VERIFY_IS_TRUE(NavCategory::IsValidViewMode(static_cast(0)));
@@ -141,6 +142,7 @@ namespace CalculatorUnitTests
VERIFY_IS_TRUE(NavCategory::IsCalculatorViewMode(ViewMode::Programmer));
VERIFY_IS_FALSE(NavCategory::IsCalculatorViewMode(ViewMode::Date));
+ VERIFY_IS_FALSE(NavCategory::IsCalculatorViewMode(ViewMode::Graphing));
VERIFY_IS_FALSE(NavCategory::IsCalculatorViewMode(ViewMode::Currency));
VERIFY_IS_FALSE(NavCategory::IsCalculatorViewMode(ViewMode::Volume));
@@ -165,6 +167,8 @@ namespace CalculatorUnitTests
VERIFY_IS_TRUE(NavCategory::IsDateCalculatorViewMode(ViewMode::Date));
+ VERIFY_IS_FALSE(NavCategory::IsDateCalculatorViewMode(ViewMode::Graphing));
+
VERIFY_IS_FALSE(NavCategory::IsDateCalculatorViewMode(ViewMode::Currency));
VERIFY_IS_FALSE(NavCategory::IsDateCalculatorViewMode(ViewMode::Volume));
VERIFY_IS_FALSE(NavCategory::IsDateCalculatorViewMode(ViewMode::Length));
@@ -185,8 +189,8 @@ namespace CalculatorUnitTests
VERIFY_IS_FALSE(NavCategory::IsConverterViewMode(ViewMode::Standard));
VERIFY_IS_FALSE(NavCategory::IsConverterViewMode(ViewMode::Scientific));
VERIFY_IS_FALSE(NavCategory::IsConverterViewMode(ViewMode::Programmer));
-
VERIFY_IS_FALSE(NavCategory::IsConverterViewMode(ViewMode::Date));
+ VERIFY_IS_FALSE(NavCategory::IsConverterViewMode(ViewMode::Graphing));
VERIFY_IS_TRUE(NavCategory::IsConverterViewMode(ViewMode::Currency));
VERIFY_IS_TRUE(NavCategory::IsConverterViewMode(ViewMode::Volume));
@@ -209,6 +213,7 @@ namespace CalculatorUnitTests
VERIFY_ARE_EQUAL(StringReference(L"Scientific"), NavCategory::GetFriendlyName(ViewMode::Scientific));
VERIFY_ARE_EQUAL(StringReference(L"Programmer"), NavCategory::GetFriendlyName(ViewMode::Programmer));
VERIFY_ARE_EQUAL(StringReference(L"Date"), NavCategory::GetFriendlyName(ViewMode::Date));
+ VERIFY_ARE_EQUAL(StringReference(L"Graphing"), NavCategory::GetFriendlyName(ViewMode::Graphing));
VERIFY_ARE_EQUAL(StringReference(L"Currency"), NavCategory::GetFriendlyName(ViewMode::Currency));
VERIFY_ARE_EQUAL(StringReference(L"Volume"), NavCategory::GetFriendlyName(ViewMode::Volume));
VERIFY_ARE_EQUAL(StringReference(L"Length"), NavCategory::GetFriendlyName(ViewMode::Length));
@@ -232,6 +237,7 @@ namespace CalculatorUnitTests
VERIFY_ARE_EQUAL(CategoryGroupType::Calculator, NavCategory::GetGroupType(ViewMode::Scientific));
VERIFY_ARE_EQUAL(CategoryGroupType::Calculator, NavCategory::GetGroupType(ViewMode::Programmer));
VERIFY_ARE_EQUAL(CategoryGroupType::Calculator, NavCategory::GetGroupType(ViewMode::Date));
+ VERIFY_ARE_EQUAL(CategoryGroupType::Calculator, NavCategory::GetGroupType(ViewMode::Graphing));
VERIFY_ARE_EQUAL(CategoryGroupType::Converter, NavCategory::GetGroupType(ViewMode::Currency));
VERIFY_ARE_EQUAL(CategoryGroupType::Converter, NavCategory::GetGroupType(ViewMode::Volume));
@@ -251,11 +257,12 @@ namespace CalculatorUnitTests
void NavCategoryUnitTests::GetIndex()
{
// Index is the 0-based ordering of modes
- vector orderedModes = {
+ ViewMode orderedModes[] = {
ViewMode::Standard,
ViewMode::Scientific,
ViewMode::Programmer,
ViewMode::Date,
+ ViewMode::Graphing,
ViewMode::Currency,
ViewMode::Volume,
ViewMode::Length,
@@ -271,7 +278,8 @@ namespace CalculatorUnitTests
ViewMode::Angle
};
- for (size_t index = 0; index < orderedModes.size(); index++)
+ auto orderedModesSize = size(orderedModes);
+ for (size_t index = 0; index < orderedModesSize; index++)
{
ViewMode mode = orderedModes[index];
VERIFY_ARE_EQUAL(index, (size_t)NavCategory::GetIndex(mode));
@@ -283,11 +291,12 @@ namespace CalculatorUnitTests
void NavCategoryUnitTests::GetPosition()
{
// Position is the 1-based ordering of modes
- vector orderedModes = {
+ ViewMode orderedModes[] = {
ViewMode::Standard,
ViewMode::Scientific,
ViewMode::Programmer,
ViewMode::Date,
+ ViewMode::Graphing,
ViewMode::Currency,
ViewMode::Volume,
ViewMode::Length,
@@ -303,7 +312,8 @@ namespace CalculatorUnitTests
ViewMode::Angle
};
- for (size_t pos = 1; pos <= orderedModes.size(); pos++)
+ auto orderedModesSize = size(orderedModes);
+ for (size_t pos = 1; pos <= orderedModesSize; pos++)
{
ViewMode mode = orderedModes[pos - 1];
VERIFY_ARE_EQUAL(pos, (size_t)NavCategory::GetPosition(mode));
@@ -327,6 +337,7 @@ namespace CalculatorUnitTests
VERIFY_ARE_EQUAL(1, NavCategory::GetIndexInGroup(ViewMode::Scientific, CategoryGroupType::Calculator));
VERIFY_ARE_EQUAL(2, NavCategory::GetIndexInGroup(ViewMode::Programmer, CategoryGroupType::Calculator));
VERIFY_ARE_EQUAL(3, NavCategory::GetIndexInGroup(ViewMode::Date, CategoryGroupType::Calculator));
+ VERIFY_ARE_EQUAL(4, NavCategory::GetIndexInGroup(ViewMode::Graphing, CategoryGroupType::Calculator));
VERIFY_ARE_EQUAL(0, NavCategory::GetIndexInGroup(ViewMode::Currency, CategoryGroupType::Converter));
VERIFY_ARE_EQUAL(1, NavCategory::GetIndexInGroup(ViewMode::Volume, CategoryGroupType::Converter));
@@ -351,6 +362,8 @@ namespace CalculatorUnitTests
VERIFY_ARE_EQUAL(ViewMode::Standard, NavCategory::GetViewModeForVirtualKey(MyVirtualKey::Number1));
VERIFY_ARE_EQUAL(ViewMode::Scientific, NavCategory::GetViewModeForVirtualKey(MyVirtualKey::Number2));
VERIFY_ARE_EQUAL(ViewMode::Programmer, NavCategory::GetViewModeForVirtualKey(MyVirtualKey::Number3));
+ VERIFY_ARE_EQUAL(ViewMode::Date, NavCategory::GetViewModeForVirtualKey(MyVirtualKey::Number4));
+ VERIFY_ARE_EQUAL(ViewMode::Graphing, NavCategory::GetViewModeForVirtualKey(MyVirtualKey::Number5));
}
TEST_CLASS(NavCategoryGroupUnitTests)
@@ -380,29 +393,30 @@ namespace CalculatorUnitTests
VERIFY_ARE_EQUAL(CategoryGroupType::Calculator, calculatorGroup->GroupType);
IObservableVector^ calculatorCategories = calculatorGroup->Categories;
- VERIFY_ARE_EQUAL(4, calculatorCategories->Size);
+ VERIFY_ARE_EQUAL(5, calculatorCategories->Size);
ValidateNavCategory(calculatorCategories, 0u, ViewMode::Standard, 1);
ValidateNavCategory(calculatorCategories, 1u, ViewMode::Scientific, 2);
ValidateNavCategory(calculatorCategories, 2u, ViewMode::Programmer, 3);
ValidateNavCategory(calculatorCategories, 3u, ViewMode::Date, 4);
+ ValidateNavCategory(calculatorCategories, 4u, ViewMode::Graphing, 5);
NavCategoryGroup^ converterGroup = menuOptions->GetAt(1);
VERIFY_ARE_EQUAL(CategoryGroupType::Converter, converterGroup->GroupType);
IObservableVector^ converterCategories = converterGroup->Categories;
VERIFY_ARE_EQUAL(13, converterCategories->Size);
- ValidateNavCategory(converterCategories, 0u, ViewMode::Currency, 5);
- ValidateNavCategory(converterCategories, 1u, ViewMode::Volume, 6);
- ValidateNavCategory(converterCategories, 2u, ViewMode::Length, 7);
- ValidateNavCategory(converterCategories, 3u, ViewMode::Weight, 8);
- ValidateNavCategory(converterCategories, 4u, ViewMode::Temperature, 9);
- ValidateNavCategory(converterCategories, 5u, ViewMode::Energy, 10);
- ValidateNavCategory(converterCategories, 6u, ViewMode::Area, 11);
- ValidateNavCategory(converterCategories, 7u, ViewMode::Speed, 12);
- ValidateNavCategory(converterCategories, 8u, ViewMode::Time, 13);
- ValidateNavCategory(converterCategories, 9u, ViewMode::Power, 14);
- ValidateNavCategory(converterCategories, 10u, ViewMode::Data, 15);
- ValidateNavCategory(converterCategories, 11u, ViewMode::Pressure, 16);
- ValidateNavCategory(converterCategories, 12u, ViewMode::Angle, 17);
+ ValidateNavCategory(converterCategories, 0u, ViewMode::Currency, 6);
+ ValidateNavCategory(converterCategories, 1u, ViewMode::Volume, 7);
+ ValidateNavCategory(converterCategories, 2u, ViewMode::Length, 8);
+ ValidateNavCategory(converterCategories, 3u, ViewMode::Weight, 9);
+ ValidateNavCategory(converterCategories, 4u, ViewMode::Temperature, 10);
+ ValidateNavCategory(converterCategories, 5u, ViewMode::Energy, 11);
+ ValidateNavCategory(converterCategories, 6u, ViewMode::Area, 12);
+ ValidateNavCategory(converterCategories, 7u, ViewMode::Speed, 13);
+ ValidateNavCategory(converterCategories, 8u, ViewMode::Time, 14);
+ ValidateNavCategory(converterCategories, 9u, ViewMode::Power, 15);
+ ValidateNavCategory(converterCategories, 10u, ViewMode::Data, 16);
+ ValidateNavCategory(converterCategories, 11u, ViewMode::Pressure, 17);
+ ValidateNavCategory(converterCategories, 12u, ViewMode::Angle, 18);
}
}
diff --git a/src/GraphControl/Control/Equation.cpp b/src/GraphControl/Control/Equation.cpp
new file mode 100644
index 00000000..93710643
--- /dev/null
+++ b/src/GraphControl/Control/Equation.cpp
@@ -0,0 +1,104 @@
+#include "pch.h"
+#include "Equation.h"
+
+using namespace Platform;
+using namespace std;
+using namespace Windows::UI;
+using namespace Windows::UI::ViewManagement;
+using namespace Windows::UI::Xaml;
+
+namespace GraphControl
+{
+ DependencyProperty^ Equation::s_expressionProperty;
+ static constexpr auto s_propertyName_Expression = L"Expression";
+
+ DependencyProperty^ Equation::s_lineColorProperty;
+ static constexpr auto s_propertyName_LineColor = L"LineColor";
+
+ namespace EquationProperties
+ {
+ String^ Expression = StringReference(s_propertyName_Expression);
+ String^ LineColor = StringReference(s_propertyName_LineColor);
+ }
+
+ void Equation::RegisterDependencyProperties()
+ {
+ if (!s_expressionProperty)
+ {
+ s_expressionProperty = DependencyProperty::Register(
+ EquationProperties::Expression,
+ String::typeid,
+ Equation::typeid,
+ ref new PropertyMetadata(
+ nullptr,
+ ref new PropertyChangedCallback(&Equation::OnCustomDependencyPropertyChanged)));
+ }
+
+ if (!s_lineColorProperty)
+ {
+ // Default line color should be the user's accent color
+ auto uiSettings = ref new UISettings();
+ Color accentColor = uiSettings->GetColorValue(UIColorType::Accent);
+
+ s_lineColorProperty = DependencyProperty::Register(
+ EquationProperties::LineColor,
+ Color::typeid,
+ Equation::typeid,
+ ref new PropertyMetadata(
+ accentColor,
+ ref new PropertyChangedCallback(&Equation::OnCustomDependencyPropertyChanged)));
+ }
+ }
+
+ void Equation::OnCustomDependencyPropertyChanged(DependencyObject^ obj, DependencyPropertyChangedEventArgs^ args)
+ {
+ if (auto eq = static_cast(obj))
+ {
+ String^ propertyName = nullptr;
+ if (args->Property == s_expressionProperty)
+ {
+ propertyName = EquationProperties::Expression;
+ }
+ else if (args->Property == s_lineColorProperty)
+ {
+ propertyName = EquationProperties::LineColor;
+ }
+
+ eq->PropertyChanged(eq, propertyName);
+ }
+ }
+
+ wstring Equation::GetRequest()
+ {
+ wstringstream ss{};
+ ss << GetRequestHeader()
+ << GetExpression()
+ << GetLineColor()
+ << L")";
+
+ return ss.str();
+ }
+
+ wstring Equation::GetRequestHeader()
+ {
+ wstring expr{ Expression->Data() };
+ if (expr.find(L"=") != wstring::npos)
+ {
+ return L"plotEq2d("s;
+ }
+ else
+ {
+ return L"plot2d("s;
+ }
+ }
+
+ wstring Equation::GetExpression()
+ {
+ return Expression->Data();
+ }
+
+ wstring Equation::GetLineColor()
+ {
+ return L""s;
+ }
+}
diff --git a/src/GraphControl/Control/Equation.h b/src/GraphControl/Control/Equation.h
new file mode 100644
index 00000000..29f05924
--- /dev/null
+++ b/src/GraphControl/Control/Equation.h
@@ -0,0 +1,80 @@
+#pragma once
+
+namespace GraphControl
+{
+ namespace EquationProperties
+ {
+ extern Platform::String^ Expression;
+ extern Platform::String^ LineColor;
+ }
+
+ ref class Equation;
+ delegate void PropertyChangedEventHandler(Equation^ sender, Platform::String^ propertyName);
+
+ [Windows::UI::Xaml::Data::Bindable]
+ public ref class Equation sealed : public Windows::UI::Xaml::FrameworkElement
+ {
+ public:
+ Equation() {}
+
+ static void RegisterDependencyProperties();
+
+ #pragma region Platform::String^ Expression DependencyProperty
+ static property Windows::UI::Xaml::DependencyProperty^ ExpressionProperty
+ {
+ Windows::UI::Xaml::DependencyProperty^ get()
+ {
+ return s_expressionProperty;
+ }
+ }
+ property Platform::String^ Expression
+ {
+ Platform::String^ get()
+ {
+ return static_cast(GetValue(s_expressionProperty));
+ }
+ void set(Platform::String^ value)
+ {
+ SetValue(s_expressionProperty, value);
+ }
+ }
+ #pragma endregion
+
+ #pragma region Windows::UI::Color LineColor DependencyProperty
+ static property Windows::UI::Xaml::DependencyProperty^ LineColorProperty
+ {
+ Windows::UI::Xaml::DependencyProperty^ get()
+ {
+ return s_lineColorProperty;
+ }
+ }
+ property Windows::UI::Color LineColor
+ {
+ Windows::UI::Color get()
+ {
+ return static_cast(GetValue(s_lineColorProperty));
+ }
+ void set(Windows::UI::Color value)
+ {
+ SetValue(s_lineColorProperty, value);
+ }
+ }
+ #pragma endregion
+
+ internal:
+ event PropertyChangedEventHandler^ PropertyChanged;
+
+ std::wstring GetRequest();
+
+ private:
+ static void OnCustomDependencyPropertyChanged(Windows::UI::Xaml::DependencyObject^ obj, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args);
+
+ std::wstring GetRequestHeader();
+ std::wstring GetExpression();
+ std::wstring GetLineColor();
+
+ private:
+ static Windows::UI::Xaml::DependencyProperty^ s_expressionProperty;
+ static Windows::UI::Xaml::DependencyProperty^ s_lineColorProperty;
+ };
+}
diff --git a/src/GraphControl/Control/EquationCollection.h b/src/GraphControl/Control/EquationCollection.h
new file mode 100644
index 00000000..d048eeb0
--- /dev/null
+++ b/src/GraphControl/Control/EquationCollection.h
@@ -0,0 +1,176 @@
+#pragma once
+
+#include "Equation.h"
+
+namespace GraphControl
+{
+ delegate void EquationChangedEventHandler();
+
+ public ref class EquationCollection sealed : public Windows::Foundation::Collections::IObservableVector< GraphControl::Equation^ >
+ {
+ public:
+ virtual ~EquationCollection()
+ {
+ }
+
+ #pragma region IIterable
+ virtual Windows::Foundation::Collections::IIterator< GraphControl::Equation^ >^ First()
+ {
+ return m_vector->First();
+ }
+ #pragma endregion
+
+ #pragma region IVector
+ virtual property unsigned int Size
+ {
+ unsigned int get()
+ {
+ return m_vector->Size;
+ }
+ }
+
+ virtual void Append(GraphControl::Equation^ value)
+ {
+ m_vector->Append(value);
+ m_tokens.emplace_back(
+ value->PropertyChanged += ref new GraphControl::PropertyChangedEventHandler(this, &EquationCollection::OnEquationPropertyChanged)
+ );
+
+ EquationChanged();
+ }
+
+ virtual void Clear()
+ {
+ auto numEqs = m_vector->Size;
+ for (auto i = 0u; i < numEqs; i++)
+ {
+ m_vector->GetAt(i)->PropertyChanged -= m_tokens[i];
+ }
+
+ m_vector->Clear();
+ m_tokens.clear();
+
+ EquationChanged();
+ }
+
+ virtual GraphControl::Equation^ GetAt(unsigned int index)
+ {
+ return m_vector->GetAt(index);
+ }
+
+ virtual unsigned int GetMany(unsigned int startIndex, Platform::WriteOnlyArray< GraphControl::Equation^ >^ items)
+ {
+ return m_vector->GetMany(startIndex, items);
+ }
+
+ virtual Windows::Foundation::Collections::IVectorView< GraphControl::Equation^ >^ GetView()
+ {
+ return m_vector->GetView();
+ }
+
+ virtual Platform::Boolean IndexOf(GraphControl::Equation^ value, unsigned int *index)
+ {
+ return m_vector->IndexOf(value, index);
+ }
+
+ virtual void InsertAt(unsigned int index, GraphControl::Equation^ value)
+ {
+ m_vector->InsertAt(index, value);
+ m_tokens.insert(
+ m_tokens.begin() + index,
+ value->PropertyChanged += ref new PropertyChangedEventHandler(this, &EquationCollection::OnEquationPropertyChanged)
+ );
+
+ EquationChanged();
+ }
+
+ virtual void RemoveAt(unsigned int index)
+ {
+ m_vector->GetAt(index)->PropertyChanged -= m_tokens[index];
+
+ m_vector->RemoveAt(index);
+ m_tokens.erase(m_tokens.begin() + index);
+
+ EquationChanged();
+ }
+
+ virtual void RemoveAtEnd()
+ {
+ auto size = m_vector->Size;
+ if (size > 0)
+ {
+ m_vector->GetAt(size - 1)->PropertyChanged -= *m_tokens.rbegin();
+ m_tokens.erase(m_tokens.end() - 1);
+ }
+
+ m_vector->RemoveAtEnd();
+
+ EquationChanged();
+ }
+
+ virtual void ReplaceAll(const Platform::Array< GraphControl::Equation^ >^ items)
+ {
+ auto size = m_vector->Size;
+ for (auto i = 0u; i < size; i++)
+ {
+ m_vector->GetAt(i)->PropertyChanged -= m_tokens[i];
+ }
+
+ size = items->Length;
+ m_tokens.resize(size);
+ for (auto i = 0u; i < size; i++)
+ {
+ m_tokens[i] = items[i]->PropertyChanged += ref new PropertyChangedEventHandler(this, &EquationCollection::OnEquationPropertyChanged);
+ }
+
+ m_vector->ReplaceAll(items);
+
+ EquationChanged();
+ }
+
+ virtual void SetAt(unsigned int index, GraphControl::Equation^ value)
+ {
+ m_vector->GetAt(index)->PropertyChanged -= m_tokens[index];
+
+ m_vector->SetAt(index, value);
+ m_tokens[index] =
+ value->PropertyChanged += ref new PropertyChangedEventHandler(this, &EquationCollection::OnEquationPropertyChanged);
+
+ EquationChanged();
+ }
+ #pragma endregion
+
+ #pragma region IObservableVector
+ virtual event Windows::Foundation::Collections::VectorChangedEventHandler< GraphControl::Equation^ >^ VectorChanged
+ {
+ Windows::Foundation::EventRegistrationToken add(Windows::Foundation::Collections::VectorChangedEventHandler< GraphControl::Equation^ >^ handler)
+ {
+ return m_vector->VectorChanged += handler;
+ }
+
+ void remove(Windows::Foundation::EventRegistrationToken token)
+ {
+ m_vector->VectorChanged -= token;
+ }
+ }
+ #pragma endregion
+
+ internal:
+ EquationCollection() :
+ m_vector(ref new Platform::Collections::Vector< GraphControl::Equation^ >())
+ {
+ }
+
+ event EquationChangedEventHandler^ EquationChanged;
+
+ private:
+ void OnEquationPropertyChanged(GraphControl::Equation^, Platform::String^ propertyName)
+ {
+ EquationChanged();
+ }
+
+ private:
+ Platform::Collections::Vector< GraphControl::Equation^ >^ m_vector;
+ std::vector m_tokens;
+ };
+}
diff --git a/src/GraphControl/Control/Grapher.cpp b/src/GraphControl/Control/Grapher.cpp
new file mode 100644
index 00000000..3a434cb1
--- /dev/null
+++ b/src/GraphControl/Control/Grapher.cpp
@@ -0,0 +1,392 @@
+#include "pch.h"
+#include "Grapher.h"
+
+using namespace Graphing;
+using namespace GraphControl;
+using namespace GraphControl::DX;
+using namespace Platform;
+using namespace Platform::Collections;
+using namespace std;
+using namespace Windows::Foundation;
+using namespace Windows::Foundation::Collections;
+using namespace Windows::UI;
+using namespace Windows::UI::Input;
+using namespace Windows::UI::Xaml;
+using namespace Windows::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Input;
+using namespace Windows::UI::Xaml::Media;
+
+namespace GraphControl
+{
+ constexpr auto s_defaultStyleKey = L"GraphControl.Grapher";
+ constexpr auto s_templateKey_SwapChainPanel = L"GraphSurface";
+
+ DependencyProperty^ Grapher::s_equationTemplateProperty;
+ constexpr auto s_propertyName_EquationTemplate = L"EquationTemplate";
+
+ DependencyProperty^ Grapher::s_equationsProperty;
+ constexpr auto s_propertyName_Equations = L"Equations";
+
+ DependencyProperty^ Grapher::s_equationsSourceProperty;
+ constexpr auto s_propertyName_EquationsSource = L"EquationsSource";
+
+ DependencyProperty^ Grapher::s_forceProportionalAxesTemplateProperty;
+ constexpr auto s_propertyName_ForceProportionalAxes = L"ForceProportionalAxes";
+
+ Grapher::Grapher()
+ : m_solver{ IMathSolver::CreateMathSolver() }
+ , m_graph{ m_solver->CreateGrapher() }
+ {
+ m_solver->ParsingOptions().SetFormatType(FormatType::Linear);
+
+ DefaultStyleKey = StringReference(s_defaultStyleKey);
+
+ this->SetValue(EquationsProperty, ref new EquationCollection());
+
+ this->Loaded += ref new RoutedEventHandler(this, &Grapher::OnLoaded);
+ this->Unloaded += ref new RoutedEventHandler(this, &Grapher::OnUnloaded);
+ }
+
+ void Grapher::OnLoaded(Object^ sender, RoutedEventArgs^ args)
+ {
+ if (auto backgroundBrush = safe_cast(this->Background))
+ {
+ m_tokenBackgroundColorChanged.Value =
+ backgroundBrush->RegisterPropertyChangedCallback(SolidColorBrush::ColorProperty, ref new DependencyPropertyChangedCallback(this, &Grapher::OnDependencyPropertyChanged));
+
+ OnBackgroundColorChanged(backgroundBrush->Color);
+ }
+ }
+
+ void Grapher::OnUnloaded(Object^ sender, RoutedEventArgs^ args)
+ {
+ if (auto backgroundBrush = safe_cast(this->Background))
+ {
+ this->UnregisterPropertyChangedCallback(BackgroundProperty, m_tokenBackgroundColorChanged.Value);
+ }
+ }
+
+ void Grapher::OnApplyTemplate()
+ {
+ auto swapChainPanel = dynamic_cast(GetTemplateChild(StringReference(s_templateKey_SwapChainPanel)));
+ if (swapChainPanel)
+ {
+ m_renderMain = ref new RenderMain(swapChainPanel);
+ }
+
+ UpdateGraph();
+ }
+
+ void Grapher::RegisterDependencyProperties()
+ {
+ if (!s_equationsProperty)
+ {
+ s_equationsProperty = DependencyProperty::Register(
+ StringReference(s_propertyName_Equations),
+ EquationCollection::typeid ,
+ Grapher::typeid,
+ ref new PropertyMetadata(
+ nullptr,
+ ref new PropertyChangedCallback(&Grapher::OnCustomDependencyPropertyChanged)));
+ }
+
+ if (!s_equationsSourceProperty)
+ {
+ s_equationsSourceProperty = DependencyProperty::Register(
+ StringReference(s_propertyName_EquationsSource),
+ Object::typeid,
+ Grapher::typeid,
+ ref new PropertyMetadata(
+ nullptr,
+ ref new PropertyChangedCallback(&Grapher::OnCustomDependencyPropertyChanged)));
+ }
+
+ if (!s_equationTemplateProperty)
+ {
+ s_equationTemplateProperty = DependencyProperty::Register(
+ StringReference(s_propertyName_EquationTemplate),
+ DataTemplate::typeid,
+ Grapher::typeid,
+ ref new PropertyMetadata(
+ nullptr,
+ ref new PropertyChangedCallback(&Grapher::OnCustomDependencyPropertyChanged)));
+ }
+
+ if (!s_forceProportionalAxesTemplateProperty)
+ {
+ s_forceProportionalAxesTemplateProperty = DependencyProperty::Register(
+ StringReference(s_propertyName_ForceProportionalAxes),
+ bool::typeid,
+ Grapher::typeid,
+ ref new PropertyMetadata(
+ true,
+ ref new PropertyChangedCallback(&Grapher::OnCustomDependencyPropertyChanged)));
+ }
+ }
+
+ void Grapher::OnCustomDependencyPropertyChanged(DependencyObject^ obj, DependencyPropertyChangedEventArgs^ args)
+ {
+ auto self = static_cast(obj);
+ if (self)
+ {
+ if (args->Property == EquationsProperty)
+ {
+ self->OnEquationsChanged(args);
+ }
+ else if (args->Property == EquationsSourceProperty)
+ {
+ self->OnEquationsSourceChanged(args);
+ }
+ else if (args->Property == EquationTemplateProperty)
+ {
+ self->OnEquationTemplateChanged(args);
+ }
+ else if (args->Property == ForceProportionalAxesTemplateProperty)
+ {
+ self->OnForceProportionalAxesChanged(args);
+ }
+ }
+ }
+
+ void Grapher::OnDependencyPropertyChanged(DependencyObject^ obj, DependencyProperty^ p)
+ {
+ if (p == SolidColorBrush::ColorProperty)
+ {
+ auto brush = static_cast(obj);
+ OnBackgroundColorChanged(brush->Color);
+ }
+ }
+
+ void Grapher::OnEquationTemplateChanged(DependencyPropertyChangedEventArgs^ args)
+ {
+ SyncEquationsWithItemsSource();
+ }
+
+ void Grapher::OnEquationsSourceChanged(DependencyPropertyChangedEventArgs^ args)
+ {
+ if (m_dataSource && m_tokenDataSourceChanged.Value != 0)
+ {
+ m_dataSource->DataSourceChanged -= m_tokenDataSourceChanged;
+ }
+
+ m_dataSource = args->NewValue ? ref new InspectingDataSource(args->NewValue) : nullptr;
+ if (m_dataSource)
+ {
+ m_tokenDataSourceChanged =
+ m_dataSource->DataSourceChanged += ref new TypedEventHandler(this, &Grapher::OnDataSourceChanged);
+ }
+
+ SyncEquationsWithItemsSource();
+ }
+
+ void Grapher::OnDataSourceChanged(InspectingDataSource^ sender, DataSourceChangedEventArgs args)
+ {
+ switch (args.Action)
+ {
+ case DataSourceChangedAction::Insert:
+ OnItemsAdded(args.NewStartingIndex, args.NewItemsCount);
+ break;
+
+ case DataSourceChangedAction::Remove:
+ OnItemsRemoved(args.OldStartingIndex, args.OldItemsCount);
+ break;
+
+ case DataSourceChangedAction::Reset:
+ SyncEquationsWithItemsSource();
+ break;
+
+ case DataSourceChangedAction::Replace:
+ OnItemsRemoved(args.OldStartingIndex, args.OldItemsCount);
+ OnItemsAdded(args.NewStartingIndex, args.NewItemsCount);
+ break;
+ }
+ }
+
+ void Grapher::OnItemsAdded(int index, int count)
+ {
+ for (int i = index + count - 1; i >= index; i--)
+ {
+ auto eq = safe_cast(EquationTemplate->LoadContent());
+ eq->DataContext = m_dataSource->GetAt(i);
+
+ Equations->InsertAt(index, eq);
+ }
+ }
+
+ void Grapher::OnItemsRemoved(int index, int count)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ Equations->RemoveAt(index);
+ }
+ }
+
+ void Grapher::SyncEquationsWithItemsSource()
+ {
+ Equations->Clear();
+ if (m_dataSource)
+ {
+ auto size = m_dataSource->GetSize();
+ for (auto i = 0u; i < size; i++)
+ {
+ auto eq = safe_cast(EquationTemplate->LoadContent());
+ eq->DataContext = m_dataSource->GetAt(i);
+
+ Equations->Append(eq);
+ }
+ }
+ }
+
+ void Grapher::OnEquationsChanged(DependencyPropertyChangedEventArgs^ args)
+ {
+ if (auto older = static_cast(args->OldValue))
+ {
+ if (m_tokenEquationsChanged.Value != 0)
+ {
+ older->VectorChanged -= m_tokenEquationsChanged;
+ m_tokenEquationsChanged.Value = 0;
+ }
+ if (m_tokenEquationChanged.Value != 0)
+ {
+ older->EquationChanged -= m_tokenEquationChanged;
+ m_tokenEquationChanged.Value = 0;
+ }
+ }
+
+ if (auto newer = static_cast(args->NewValue))
+ {
+ m_tokenEquationsChanged =
+ newer->VectorChanged += ref new VectorChangedEventHandler(this, &Grapher::OnEquationsVectorChanged);
+
+ m_tokenEquationChanged =
+ newer->EquationChanged += ref new EquationChangedEventHandler(this, &Grapher::OnEquationChanged);
+ }
+
+ UpdateGraph();
+ }
+
+ void Grapher::OnEquationsVectorChanged(IObservableVector^ sender, IVectorChangedEventArgs^ event)
+ {
+ UpdateGraph();
+ }
+
+ void Grapher::OnEquationChanged()
+ {
+ UpdateGraph();
+ }
+
+ void Grapher::UpdateGraph()
+ {
+ if (m_renderMain && m_graph != nullptr)
+ {
+ auto validEqs = GetValidEquations();
+ if (!validEqs.empty())
+ {
+
+ wstringstream ss{};
+ ss << L"show2d(";
+
+ int numValidEquations = 0;
+ for (Equation^ eq : validEqs)
+ {
+ if (numValidEquations++ > 0)
+ {
+ ss << L",";
+ }
+
+ ss << eq->GetRequest();
+ }
+
+ ss << L")";
+
+ wstring request = ss.str();
+ if (auto graphExpression = m_solver->ParseInput(request))
+ {
+ if (m_graph->TryInitialize(graphExpression.get()))
+ {
+ UpdateGraphOptions(m_graph->GetOptions(), validEqs);
+
+ m_renderMain->Graph = m_graph;
+ }
+ }
+ }
+ }
+ }
+
+ void Grapher::UpdateGraphOptions(IGraphingOptions& options, const vector& validEqs)
+ {
+ options.SetForceProportional(ForceProportionalAxes);
+
+ vector graphColors;
+ graphColors.reserve(validEqs.size());
+ for (Equation^ eq : validEqs)
+ {
+ auto lineColor = eq->LineColor;
+ graphColors.emplace_back(
+ lineColor.R,
+ lineColor.G,
+ lineColor.B,
+ lineColor.A);
+ }
+ options.SetGraphColors(graphColors);
+ }
+
+ vector Grapher::GetValidEquations()
+ {
+ vector validEqs;
+
+ for (Equation^ eq : Equations)
+ {
+ if (!eq->Expression->IsEmpty())
+ {
+ validEqs.push_back(eq);
+ }
+ }
+
+ return validEqs;
+ }
+
+ void Grapher::OnForceProportionalAxesChanged(DependencyPropertyChangedEventArgs^ args)
+ {
+ UpdateGraph();
+ }
+
+ void Grapher::OnBackgroundColorChanged(const Windows::UI::Color& color)
+ {
+ if (m_renderMain)
+ {
+ m_renderMain->BackgroundColor = color;
+ }
+ }
+
+ void Grapher::OnPointerEntered(PointerRoutedEventArgs^ e)
+ {
+ if (m_renderMain)
+ {
+ OnPointerMoved(e);
+ m_renderMain->DrawNearestPoint = true;
+
+ e->Handled = true;
+ }
+ }
+
+ void Grapher::OnPointerMoved(PointerRoutedEventArgs^ e)
+ {
+ if (m_renderMain)
+ {
+ PointerPoint^ currPoint = e->GetCurrentPoint(/* relativeTo */ this);
+ m_renderMain->PointerLocation = currPoint->Position;
+
+ e->Handled = true;
+ }
+ }
+
+ void Grapher::OnPointerExited(PointerRoutedEventArgs^ e)
+ {
+ if (m_renderMain)
+ {
+ m_renderMain->DrawNearestPoint = false;
+ e->Handled = true;
+ }
+ }
+}
diff --git a/src/GraphControl/Control/Grapher.h b/src/GraphControl/Control/Grapher.h
new file mode 100644
index 00000000..b969aed0
--- /dev/null
+++ b/src/GraphControl/Control/Grapher.h
@@ -0,0 +1,160 @@
+#pragma once
+
+#include "InspectingDataSource.h"
+#include "DirectX/RenderMain.h"
+#include "Equation.h"
+#include "EquationCollection.h"
+#include "IMathSolver.h"
+
+namespace GraphControl
+{
+ [Windows::UI::Xaml::Markup::ContentPropertyAttribute(Name = L"Equations")]
+ public ref class Grapher sealed : public Windows::UI::Xaml::Controls::Control
+ {
+ public:
+ Grapher();
+
+ static void RegisterDependencyProperties();
+
+ #pragma region Windows::UI::Xaml::DataTemplate^ EquationTemplate DependencyProperty
+ static property Windows::UI::Xaml::DependencyProperty^ EquationTemplateProperty
+ {
+ Windows::UI::Xaml::DependencyProperty^ get()
+ {
+ return s_equationTemplateProperty;
+ }
+ }
+
+ property Windows::UI::Xaml::DataTemplate^ EquationTemplate
+ {
+ Windows::UI::Xaml::DataTemplate^ get()
+ {
+ return static_cast(GetValue(s_equationTemplateProperty));
+ }
+ void set(Windows::UI::Xaml::DataTemplate^ value)
+ {
+ SetValue(s_equationTemplateProperty, value);
+ }
+ }
+ #pragma endregion
+
+ #pragma region Platform::Object^ EquationsSource DependencyProperty
+ static property Windows::UI::Xaml::DependencyProperty^ EquationsSourceProperty
+ {
+ Windows::UI::Xaml::DependencyProperty^ get()
+ {
+ return s_equationsSourceProperty;
+ }
+ }
+
+ property Platform::Object^ EquationsSource
+ {
+ Platform::Object^ get()
+ {
+ return GetValue(s_equationsSourceProperty);
+ }
+ void set(Platform::Object^ value)
+ {
+ SetValue(s_equationsSourceProperty, value);
+ }
+ }
+ #pragma endregion
+
+ #pragma region GraphControl::EquationCollection^ Equations DependencyProperty
+ static property Windows::UI::Xaml::DependencyProperty^ EquationsProperty
+ {
+ Windows::UI::Xaml::DependencyProperty^ get()
+ {
+ return s_equationsProperty;
+ }
+ }
+
+ property GraphControl::EquationCollection^ Equations
+ {
+ GraphControl::EquationCollection^ get()
+ {
+ return static_cast< GraphControl::EquationCollection^ >(GetValue(s_equationsProperty));
+ }
+ }
+ #pragma endregion
+
+ #pragma region Windows::UI::Xaml::DataTemplate^ ForceProportionalAxes DependencyProperty
+ static property Windows::UI::Xaml::DependencyProperty^ ForceProportionalAxesTemplateProperty
+ {
+ Windows::UI::Xaml::DependencyProperty^ get()
+ {
+ return s_forceProportionalAxesTemplateProperty;
+ }
+ }
+
+ property bool ForceProportionalAxes
+ {
+ bool get()
+ {
+ return static_cast(GetValue(s_forceProportionalAxesTemplateProperty));
+ }
+ void set(bool value)
+ {
+ SetValue(s_forceProportionalAxesTemplateProperty, value);
+ }
+ }
+ #pragma endregion
+
+ protected:
+ #pragma region Control Overrides
+ void OnApplyTemplate() override;
+
+ void OnPointerEntered(Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) override;
+ void OnPointerMoved(Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) override;
+ void OnPointerExited(Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) override;
+ #pragma endregion
+
+ private:
+ void OnLoaded(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ args);
+ void OnUnloaded(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ args);
+
+ static void OnCustomDependencyPropertyChanged(Windows::UI::Xaml::DependencyObject^ obj, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args);
+ void OnDependencyPropertyChanged(Windows::UI::Xaml::DependencyObject^ obj, Windows::UI::Xaml::DependencyProperty^ p);
+
+ void OnEquationTemplateChanged(Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args);
+
+ void OnEquationsSourceChanged(Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args);
+ void OnDataSourceChanged(GraphControl::InspectingDataSource^ sender, GraphControl::DataSourceChangedEventArgs args);
+
+ void OnEquationsChanged(Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args);
+ void OnEquationChanged();
+
+ void UpdateGraph();
+ void UpdateGraphOptions(Graphing::IGraphingOptions& options, const std::vector& validEqs);
+ std::vector GetValidEquations();
+
+ void OnForceProportionalAxesChanged(Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args);
+
+ void OnBackgroundColorChanged(const Windows::UI::Color& color);
+
+ void SyncEquationsWithItemsSource();
+ void OnItemsAdded(int index, int count);
+ void OnItemsRemoved(int index, int count);
+
+ private:
+ DX::RenderMain^ m_renderMain = nullptr;
+
+ static Windows::UI::Xaml::DependencyProperty^ s_equationTemplateProperty;
+
+ static Windows::UI::Xaml::DependencyProperty^ s_equationsSourceProperty;
+ InspectingDataSource^ m_dataSource;
+ Windows::Foundation::EventRegistrationToken m_tokenDataSourceChanged;
+
+ static Windows::UI::Xaml::DependencyProperty^ s_equationsProperty;
+ Windows::Foundation::EventRegistrationToken m_tokenEquationsChanged;
+ Windows::Foundation::EventRegistrationToken m_tokenEquationChanged;
+
+ static Windows::UI::Xaml::DependencyProperty^ s_forceProportionalAxesTemplateProperty;
+
+ Windows::Foundation::EventRegistrationToken m_tokenBackgroundColorChanged;
+
+ const std::unique_ptr m_solver;
+ const std::shared_ptr m_graph;
+ void OnEquationsVectorChanged(Windows::Foundation::Collections::IObservableVector ^sender, Windows::Foundation::Collections::IVectorChangedEventArgs ^event);
+};
+}
diff --git a/src/GraphControl/Control/InspectingDataSource.cpp b/src/GraphControl/Control/InspectingDataSource.cpp
new file mode 100644
index 00000000..4c96f46e
--- /dev/null
+++ b/src/GraphControl/Control/InspectingDataSource.cpp
@@ -0,0 +1,242 @@
+#include "pch.h"
+#include "InspectingDataSource.h"
+
+using namespace Platform;
+using namespace Platform::Collections;
+using namespace std;
+using namespace Windows::Foundation::Collections;
+using namespace Windows::UI::Xaml::Interop;
+
+namespace winrt
+{
+ using namespace winrt::Windows::Foundation;
+ using namespace winrt::Windows::Foundation::Collections;
+ using namespace winrt::Windows::UI::Xaml::Interop;
+}
+
+namespace GraphControl
+{
+ InspectingDataSource::InspectingDataSource(Object^ source)
+ {
+ if (!source)
+ {
+ throw ref new InvalidArgumentException(L"Argument 'source' is null.");
+ }
+
+ auto inspectable = from_cx(source);
+ if (auto vector = inspectable.try_as>())
+ {
+ m_vector = vector;
+ ListenToCollectionChanges();
+ }
+ else if (auto bindableVector = inspectable.try_as())
+ {
+ // The bindable interop interface are abi compatible with the corresponding
+ // WinRT interfaces.
+
+ m_vector = reinterpret_cast&>(bindableVector);
+ ListenToCollectionChanges();
+ }
+ else if (auto iterable = inspectable.try_as>())
+ {
+ m_vector = WrapIterable(iterable);
+ }
+ else if (auto bindableIterable = inspectable.try_as())
+ {
+ m_vector = WrapIterable(reinterpret_cast &>(bindableIterable));
+ }
+ else
+ {
+ throw ref new InvalidArgumentException(L"Argument 'source' is not a supported vector.");
+ }
+ }
+
+ InspectingDataSource::~InspectingDataSource()
+ {
+ UnlistenToCollectionChanges();
+ }
+
+ unsigned int InspectingDataSource::GetSize()
+ {
+ return m_vector.Size();
+ }
+
+ Object^ InspectingDataSource::GetAt(unsigned int index)
+ {
+ return to_cx