diff --git a/src/CalcViewModel/Common/Utils.h b/src/CalcViewModel/Common/Utils.h index 9d367e23..25098a29 100644 --- a/src/CalcViewModel/Common/Utils.h +++ b/src/CalcViewModel/Common/Utils.h @@ -10,6 +10,7 @@ // Utility macros to make Models easier to write // generates a member variable called m_ + #define PROPERTY_R(t, n) \ property t n \ { \ @@ -72,6 +73,25 @@ private: \ public: +#define OBSERVABLE_PROPERTY_RW_ALWAYS_NOTIFY(t, n) \ + property t n \ + { \ + t get() \ + { \ + return m_##n; \ + } \ + void set(t value) \ + { \ + m_##n = value; \ + RaisePropertyChanged(L#n); \ + } \ + } \ + \ +private: \ + t m_##n; \ + \ +public: + #define OBSERVABLE_PROPERTY_RW(t, n) \ property t n \ { \ diff --git a/src/CalcViewModel/GraphingCalculator/GraphingCalculatorViewModel.cpp b/src/CalcViewModel/GraphingCalculator/GraphingCalculatorViewModel.cpp index 394c3720..984678a2 100644 --- a/src/CalcViewModel/GraphingCalculator/GraphingCalculatorViewModel.cpp +++ b/src/CalcViewModel/GraphingCalculator/GraphingCalculatorViewModel.cpp @@ -2,17 +2,37 @@ #include "GraphingCalculatorViewModel.h" using namespace CalculatorApp::ViewModel; +using namespace Platform; using namespace Platform::Collections; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::UI::Xaml::Data; namespace CalculatorApp::ViewModel { GraphingCalculatorViewModel::GraphingCalculatorViewModel() : m_IsDecimalEnabled{ true } , m_Equations{ ref new Vector< EquationViewModel^ >() } + , m_Variables{ ref new Vector< VariableViewModel^ >() } { } void GraphingCalculatorViewModel::OnButtonPressed(Object^ parameter) { } + + void GraphingCalculatorViewModel::UpdateVariables(IMap^ variables) + { + Variables->Clear(); + for (auto var : variables) + { + auto variable = ref new VariableViewModel(var->Key, var->Value); + variable->VariableUpdated += ref new EventHandler([this, variable](Object^ sender, VariableChangedEventArgs e) + { + VariableUpdated(variable, VariableChangedEventArgs{ e.variableName, e.newValue }); + }); + Variables->Append(variable); + + } + } } diff --git a/src/CalcViewModel/GraphingCalculator/GraphingCalculatorViewModel.h b/src/CalcViewModel/GraphingCalculator/GraphingCalculatorViewModel.h index 326903a8..2c45f077 100644 --- a/src/CalcViewModel/GraphingCalculator/GraphingCalculatorViewModel.h +++ b/src/CalcViewModel/GraphingCalculator/GraphingCalculatorViewModel.h @@ -5,6 +5,62 @@ namespace CalculatorApp::ViewModel { + public value struct VariableChangedEventArgs sealed + { + Platform::String^ variableName; + double newValue; + }; + + [Windows::UI::Xaml::Data::Bindable] + public ref class VariableViewModel sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged + { + public: + VariableViewModel(Platform::String^ name, double value) : + m_Name(name), + m_Value(value), + m_SliderSettingsVisible(false), + m_Min(0.0), + m_Step(0.1), + m_Max(2.0) + { } + + OBSERVABLE_OBJECT_CALLBACK(OnPropertyChanged); + + OBSERVABLE_PROPERTY_R(Platform::String^, Name); + + // TODO: Consider removing this work around and manually set the textbox text. + OBSERVABLE_PROPERTY_RW_ALWAYS_NOTIFY(double, Value); + OBSERVABLE_PROPERTY_RW_ALWAYS_NOTIFY(double, Min); + OBSERVABLE_PROPERTY_RW_ALWAYS_NOTIFY(double, Step); + OBSERVABLE_PROPERTY_RW_ALWAYS_NOTIFY(double, Max); + OBSERVABLE_PROPERTY_RW(bool, SliderSettingsVisible); + + event Windows::Foundation::EventHandler^ VariableUpdated; + + void SetValue(double value) + { + if (value < Min) + { + value = Min; + } + else if (value > Max) + { + value = Max; + } + + Value = value; + } + + private: + void OnPropertyChanged(Platform::String^ propertyName) + { + if (propertyName == "Value") + { + VariableUpdated(this, VariableChangedEventArgs{ Name, Value }); + } + } + }; + [Windows::UI::Xaml::Data::Bindable] public ref class GraphingCalculatorViewModel sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged { @@ -14,9 +70,13 @@ namespace CalculatorApp::ViewModel OBSERVABLE_OBJECT(); OBSERVABLE_PROPERTY_R(bool, IsDecimalEnabled); OBSERVABLE_PROPERTY_R(Windows::Foundation::Collections::IObservableVector< EquationViewModel^ >^, Equations); + OBSERVABLE_PROPERTY_R(Windows::Foundation::Collections::IObservableVector< VariableViewModel^ >^, Variables); COMMAND_FOR_METHOD(ButtonPressed, GraphingCalculatorViewModel::OnButtonPressed); + event Windows::Foundation::EventHandler^ VariableUpdated; + + void UpdateVariables(Windows::Foundation::Collections::IMap^ variables); private: void OnButtonPressed(Platform::Object^ parameter); }; diff --git a/src/Calculator/App.xaml b/src/Calculator/App.xaml index ec933d64..14fb102f 100644 --- a/src/Calculator/App.xaml +++ b/src/Calculator/App.xaml @@ -1725,6 +1725,16 @@ + + diff --git a/src/Calculator/Resources/en-US/Resources.resw b/src/Calculator/Resources/en-US/Resources.resw index 68d16eb7..e7d703c5 100644 --- a/src/Calculator/Resources/en-US/Resources.resw +++ b/src/Calculator/Resources/en-US/Resources.resw @@ -3439,4 +3439,20 @@ Add Equation Placeholder text for the equation input button + + Variables + Header text for variables area + + + Step + Label text for the step text box + + + Min + Label text for the min text box + + + Max + Label text for the max text box + diff --git a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml index a1ddbb5d..bd0ec8d5 100644 --- a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml +++ b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml @@ -2,6 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="using:CalculatorApp.Controls" + xmlns:converters="using:CalculatorApp.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:graphControl="using:GraphControl" xmlns:local="using:CalculatorApp" @@ -10,6 +11,11 @@ DataContextChanged="GraphingCalculator_DataContextChanged" mc:Ignorable="d"> + + + + + @@ -25,11 +31,12 @@ Grid.Row="1" Grid.Column="0"> - + UseSystemFocusVisuals="True" + VariablesUpdated="GraphVariablesUpdated"> @@ -39,6 +46,175 @@ + + + @@ -50,12 +226,13 @@ + diff --git a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cpp b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cpp index d83fcaf3..b7c37cf5 100644 --- a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cpp +++ b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cpp @@ -11,13 +11,16 @@ using namespace CalculatorApp::ViewModel; using namespace concurrency; using namespace GraphControl; using namespace Platform; +using namespace std; using namespace std::chrono; using namespace Utils; +using namespace Windows::Foundation; 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::Controls; using namespace Windows::UI::Xaml::Input; using namespace Windows::UI::Xaml::Media; using namespace Windows::UI::Xaml::Media::Imaging; @@ -34,6 +37,8 @@ GraphingCalculator::GraphingCalculator() void GraphingCalculator::GraphingCalculator_DataContextChanged(FrameworkElement^ sender, DataContextChangedEventArgs^ args) { ViewModel = dynamic_cast(args->NewValue); + + ViewModel->VariableUpdated += ref new EventHandler(this, &CalculatorApp::GraphingCalculator::OnVariableChanged); } GraphingCalculatorViewModel^ GraphingCalculator::ViewModel::get() @@ -49,3 +54,67 @@ void GraphingCalculator::ViewModel::set(GraphingCalculatorViewModel^ vm) RaisePropertyChanged(StringReference(sc_ViewModelPropertyName)); } } + +void GraphingCalculator::GraphVariablesUpdated(Object^, Object^) +{ + m_viewModel->UpdateVariables(GraphingControl->Variables); +} + +void GraphingCalculator::OnVariableChanged(Platform::Object^ sender, VariableChangedEventArgs args) +{ + GraphingControl->SetVariable(args.variableName, args.newValue); +} + + +void GraphingCalculator::SubmitTextbox(TextBox^ sender) +{ + auto variableViewModel = static_cast(sender->DataContext); + + if (sender->Name == "ValueTextBox") + { + variableViewModel->SetValue(validateDouble(sender->Text, variableViewModel->Value)); + } + else if (sender->Name == "MinTextBox") + { + variableViewModel->Min = validateDouble(sender->Text, variableViewModel->Min); + } + else if (sender->Name == "MaxTextBox") + { + variableViewModel->Step = validateDouble(sender->Text, variableViewModel->Step); + } + else if (sender->Name == "StepTextBox") + { + variableViewModel->Max = validateDouble(sender->Text, variableViewModel->Max); + } +} + +void GraphingCalculator::TextBoxLosingFocus(TextBox^ sender, LosingFocusEventArgs^) +{ + SubmitTextbox(sender); +} + + +void GraphingCalculator::TextBoxKeyDown(TextBox^ sender, KeyRoutedEventArgs^ e) +{ + if (e->Key == ::VirtualKey::Enter) + { + SubmitTextbox(sender); + } +} + +double GraphingCalculator::validateDouble(String^ value, double defaultValue) +{ + try + { + return stod(value->Data()); + } + catch (...) + { + return defaultValue; + } +} + +void GraphingCalculator::TextBoxGotFocus(TextBox^ sender, RoutedEventArgs^ e) +{ + sender->SelectAll(); +} diff --git a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.h b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.h index fc095ae8..8faef860 100644 --- a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.h +++ b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.h @@ -1,7 +1,7 @@ #pragma once #include "Views\GraphingCalculator\GraphingCalculator.g.h" -#include "CalcViewModel/GraphingCalculator/GraphingCalculatorViewModel.h" +#include "CalcViewModel\GraphingCalculator\GraphingCalculatorViewModel.h" #include "Views\NumberPad.xaml.h" namespace CalculatorApp @@ -22,7 +22,17 @@ namespace CalculatorApp private: void GraphingCalculator_DataContextChanged(Windows::UI::Xaml::FrameworkElement^ sender, Windows::UI::Xaml::DataContextChangedEventArgs^ args); - private: + void GraphVariablesUpdated(Platform::Object^ sender, Object^ args); + void OnVariableChanged(Platform::Object^ sender, CalculatorApp::ViewModel::VariableChangedEventArgs args); + + void TextBoxLosingFocus(Windows::UI::Xaml::Controls::TextBox^ textbox, Windows::UI::Xaml::Input::LosingFocusEventArgs^ args); + void TextBoxKeyDown(Windows::UI::Xaml::Controls::TextBox^ textbox, Windows::UI::Xaml::Input::KeyRoutedEventArgs^ e); + void SubmitTextbox(Windows::UI::Xaml::Controls::TextBox^ textbox); + + double validateDouble(Platform::String^ value, double defaultValue); + CalculatorApp::ViewModel::GraphingCalculatorViewModel^ m_viewModel; + + void TextBoxGotFocus(Windows::UI::Xaml::Controls::TextBox^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); }; } diff --git a/src/GraphControl/Control/Grapher.cpp b/src/GraphControl/Control/Grapher.cpp index e7a026fc..643b504d 100644 --- a/src/GraphControl/Control/Grapher.cpp +++ b/src/GraphControl/Control/Grapher.cpp @@ -26,8 +26,12 @@ namespace constexpr auto s_propertyName_EquationTemplate = L"EquationTemplate"; constexpr auto s_propertyName_Equations = L"Equations"; constexpr auto s_propertyName_EquationsSource = L"EquationsSource"; + constexpr auto s_propertyName_Variables = L"Variables"; constexpr auto s_propertyName_ForceProportionalAxes = L"ForceProportionalAxes"; + constexpr auto s_X = L"x"; + constexpr auto s_Y = L"y"; + // Helper function for converting a pointer position to a position that the graphing engine will understand. // posX/posY are the pointer position elements and width,height are the dimensions of the graph surface. // The graphing engine interprets x,y position between the range [-1, 1]. @@ -43,6 +47,7 @@ namespace GraphControl DependencyProperty^ Grapher::s_equationTemplateProperty; DependencyProperty^ Grapher::s_equationsProperty; DependencyProperty^ Grapher::s_equationsSourceProperty; + DependencyProperty^ Grapher::s_variablesProperty; DependencyProperty^ Grapher::s_forceProportionalAxesTemplateProperty; Grapher::Grapher() @@ -54,6 +59,7 @@ namespace GraphControl DefaultStyleKey = StringReference(s_defaultStyleKey); this->SetValue(EquationsProperty, ref new EquationCollection()); + this->SetValue(VariablesProperty, ref new Map()); this->Loaded += ref new RoutedEventHandler(this, &Grapher::OnLoaded); this->Unloaded += ref new RoutedEventHandler(this, &Grapher::OnUnloaded); @@ -145,6 +151,17 @@ namespace GraphControl ref new PropertyChangedCallback(&Grapher::OnCustomDependencyPropertyChanged))); } + if (!s_variablesProperty) + { + s_variablesProperty = DependencyProperty::Register( + StringReference(s_propertyName_Variables), + IObservableMap::typeid, + Grapher::typeid, + ref new PropertyMetadata( + nullptr, + ref new PropertyChangedCallback(&Grapher::OnCustomDependencyPropertyChanged))); + } + if (!s_forceProportionalAxesTemplateProperty) { s_forceProportionalAxesTemplateProperty = DependencyProperty::Register( @@ -350,8 +367,11 @@ namespace GraphControl if (m_graph->TryInitialize(graphExpression.get())) { UpdateGraphOptions(m_graph->GetOptions(), validEqs); + SetGraphArgs(); m_renderMain->Graph = m_graph; + + UpdateVariables(); } } } @@ -360,13 +380,80 @@ namespace GraphControl if (m_graph->TryInitialize()) { UpdateGraphOptions(m_graph->GetOptions(), validEqs); + SetGraphArgs(); m_renderMain->Graph = m_graph; + + UpdateVariables(); } } } } + void Grapher::SetGraphArgs() + { + if (m_graph) + { + for (auto variable : Variables) + { + m_graph->SetArgValue(variable->Key->Data(), variable->Value); + } + } + } + + void Grapher::UpdateVariables() + { + auto updatedVariables = ref new Map(); + if (m_graph) + { + auto graphVariables = m_graph->GetVariables(); + + for (auto graphVar : graphVariables) + { + if (graphVar->GetVariableName() != s_X && graphVar->GetVariableName() != s_Y) + { + auto key = ref new String(graphVar->GetVariableName().data()); + double value = 1.0; + + if (Variables->HasKey(key)) + { + value = Variables->Lookup(key); + } + + updatedVariables->Insert(key, value); + } + } + } + + Variables = updatedVariables; + VariablesUpdated(this, Variables); + } + + void Grapher::SetVariable(Platform::String^ variableName, double newValue) + { + if (Variables->HasKey(variableName)) + { + if (Variables->Lookup(variableName) == newValue) + { + return; + } + + Variables->Remove(variableName); + } + + Variables->Insert(variableName, newValue); + + if (m_graph) + { + m_graph->SetArgValue(variableName->Data(), newValue); + + if (m_renderMain) + { + m_renderMain->RunRenderPass(); + } + } + } + void Grapher::UpdateGraphOptions(IGraphingOptions& options, const vector& validEqs) { options.SetForceProportional(ForceProportionalAxes); diff --git a/src/GraphControl/Control/Grapher.h b/src/GraphControl/Control/Grapher.h index 42901a5a..89979958 100644 --- a/src/GraphControl/Control/Grapher.h +++ b/src/GraphControl/Control/Grapher.h @@ -5,6 +5,7 @@ #include "Equation.h" #include "EquationCollection.h" #include "IMathSolver.h" +#include "Common.h" namespace GraphControl { @@ -78,6 +79,29 @@ namespace GraphControl } #pragma endregion + #pragma region Windows::Foundation::Collections::IObservableMap^ Variables DependencyProperty + static property Windows::UI::Xaml::DependencyProperty^ VariablesProperty + { + Windows::UI::Xaml::DependencyProperty^ get() + { + return s_variablesProperty; + } + } + + property Windows::Foundation::Collections::IObservableMap^ Variables + { + Windows::Foundation::Collections::IObservableMap^ get() + { + return static_cast^>(GetValue(s_variablesProperty)); + } + + void set(Windows::Foundation::Collections::IObservableMap^ value) + { + SetValue(s_variablesProperty, value); + } + } + #pragma endregion + #pragma region Windows::UI::Xaml::DataTemplate^ ForceProportionalAxes DependencyProperty static property Windows::UI::Xaml::DependencyProperty^ ForceProportionalAxesTemplateProperty { @@ -100,6 +124,10 @@ namespace GraphControl } #pragma endregion + event Windows::Foundation::EventHandler^>^ VariablesUpdated; + + void SetVariable(Platform::String^ variableName, double newValue); + protected: #pragma region Control Overrides void OnApplyTemplate() override; @@ -127,12 +155,14 @@ namespace GraphControl void OnDataSourceChanged(GraphControl::InspectingDataSource^ sender, GraphControl::DataSourceChangedEventArgs args); void OnEquationsChanged(Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args); - void OnEquationsVectorChanged(Windows::Foundation::Collections::IObservableVector ^sender, Windows::Foundation::Collections::IVectorChangedEventArgs ^event); + void OnEquationsVectorChanged(Windows::Foundation::Collections::IObservableVector ^sender, Windows::Foundation::Collections::IVectorChangedEventArgs^ event); void OnEquationChanged(); void UpdateGraph(); void UpdateGraphOptions(Graphing::IGraphingOptions& options, const std::vector& validEqs); std::vector GetValidEquations(); + void SetGraphArgs(); + void UpdateVariables(); void OnForceProportionalAxesChanged(Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args); @@ -154,6 +184,7 @@ namespace GraphControl Windows::Foundation::EventRegistrationToken m_tokenDataSourceChanged; static Windows::UI::Xaml::DependencyProperty^ s_equationsProperty; + static Windows::UI::Xaml::DependencyProperty^ s_variablesProperty; Windows::Foundation::EventRegistrationToken m_tokenEquationsChanged; Windows::Foundation::EventRegistrationToken m_tokenEquationChanged; diff --git a/src/GraphingInterfaces/Common.h b/src/GraphingInterfaces/Common.h index 32f2d421..808591dd 100644 --- a/src/GraphingInterfaces/Common.h +++ b/src/GraphingInterfaces/Common.h @@ -1,6 +1,7 @@ #pragma once #include +#include #ifndef GRAPHINGAPI #ifdef GRAPHING_ENGINE_IMPL @@ -38,6 +39,14 @@ namespace Graphing virtual bool IsEmptySet() const = 0; }; + struct IVariable + { + virtual ~IVariable() = default; + + virtual int GetVariableID() const = 0; + virtual const std::wstring& GetVariableName() = 0; + }; + struct IExpressible { virtual ~IExpressible() = default; diff --git a/src/GraphingInterfaces/IGraph.h b/src/GraphingInterfaces/IGraph.h index 6ff29128..a772fa62 100644 --- a/src/GraphingInterfaces/IGraph.h +++ b/src/GraphingInterfaces/IGraph.h @@ -16,6 +16,10 @@ namespace Graphing virtual IGraphingOptions& GetOptions() = 0; + virtual std::vector> GetVariables() = 0; + + virtual void SetArgValue(std::wstring variableName, double value) = 0; + virtual std::shared_ptr< Renderer::IGraphRenderer > GetRenderer() const = 0; virtual bool TryResetSelection() = 0;