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