Add Pan/Zoom support for the graph surface (#458)
Description of the changes: Add Pan/Zoom support for the graph surface. Currently only supports Mouse/Pen/Touch interactions. Keyboard support will be added separately. How changes were validated: Manual
This commit is contained in:
parent
091732aa94
commit
5ffe1bc858
@ -7,8 +7,10 @@ using namespace GraphControl::DX;
|
|||||||
using namespace Platform;
|
using namespace Platform;
|
||||||
using namespace Platform::Collections;
|
using namespace Platform::Collections;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
using namespace Windows::Devices::Input;
|
||||||
using namespace Windows::Foundation;
|
using namespace Windows::Foundation;
|
||||||
using namespace Windows::Foundation::Collections;
|
using namespace Windows::Foundation::Collections;
|
||||||
|
using namespace Windows::System;
|
||||||
using namespace Windows::UI;
|
using namespace Windows::UI;
|
||||||
using namespace Windows::UI::Input;
|
using namespace Windows::UI::Input;
|
||||||
using namespace Windows::UI::Xaml;
|
using namespace Windows::UI::Xaml;
|
||||||
@ -16,23 +18,33 @@ using namespace Windows::UI::Xaml::Controls;
|
|||||||
using namespace Windows::UI::Xaml::Input;
|
using namespace Windows::UI::Xaml::Input;
|
||||||
using namespace Windows::UI::Xaml::Media;
|
using namespace Windows::UI::Xaml::Media;
|
||||||
|
|
||||||
namespace GraphControl
|
namespace
|
||||||
{
|
{
|
||||||
constexpr auto s_defaultStyleKey = L"GraphControl.Grapher";
|
constexpr auto s_defaultStyleKey = L"GraphControl.Grapher";
|
||||||
constexpr auto s_templateKey_SwapChainPanel = L"GraphSurface";
|
constexpr auto s_templateKey_SwapChainPanel = L"GraphSurface";
|
||||||
|
|
||||||
DependencyProperty^ Grapher::s_equationTemplateProperty;
|
|
||||||
constexpr auto s_propertyName_EquationTemplate = L"EquationTemplate";
|
constexpr auto s_propertyName_EquationTemplate = L"EquationTemplate";
|
||||||
|
|
||||||
DependencyProperty^ Grapher::s_equationsProperty;
|
|
||||||
constexpr auto s_propertyName_Equations = L"Equations";
|
constexpr auto s_propertyName_Equations = L"Equations";
|
||||||
|
|
||||||
DependencyProperty^ Grapher::s_equationsSourceProperty;
|
|
||||||
constexpr auto s_propertyName_EquationsSource = L"EquationsSource";
|
constexpr auto s_propertyName_EquationsSource = L"EquationsSource";
|
||||||
|
|
||||||
DependencyProperty^ Grapher::s_forceProportionalAxesTemplateProperty;
|
|
||||||
constexpr auto s_propertyName_ForceProportionalAxes = L"ForceProportionalAxes";
|
constexpr auto s_propertyName_ForceProportionalAxes = L"ForceProportionalAxes";
|
||||||
|
|
||||||
|
// 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].
|
||||||
|
// Translate the pointer position to the [-1, 1] bounds.
|
||||||
|
__inline pair<double, double> PointerPositionToGraphPosition(double posX, double posY, double width, double height)
|
||||||
|
{
|
||||||
|
return make_pair(( 2 * posX / width - 1 ), ( 1 - 2 * posY / height ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GraphControl
|
||||||
|
{
|
||||||
|
DependencyProperty^ Grapher::s_equationTemplateProperty;
|
||||||
|
DependencyProperty^ Grapher::s_equationsProperty;
|
||||||
|
DependencyProperty^ Grapher::s_equationsSourceProperty;
|
||||||
|
DependencyProperty^ Grapher::s_forceProportionalAxesTemplateProperty;
|
||||||
|
|
||||||
Grapher::Grapher()
|
Grapher::Grapher()
|
||||||
: m_solver{ IMathSolver::CreateMathSolver() }
|
: m_solver{ IMathSolver::CreateMathSolver() }
|
||||||
, m_graph{ m_solver->CreateGrapher() }
|
, m_graph{ m_solver->CreateGrapher() }
|
||||||
@ -45,6 +57,13 @@ namespace GraphControl
|
|||||||
|
|
||||||
this->Loaded += ref new RoutedEventHandler(this, &Grapher::OnLoaded);
|
this->Loaded += ref new RoutedEventHandler(this, &Grapher::OnLoaded);
|
||||||
this->Unloaded += ref new RoutedEventHandler(this, &Grapher::OnUnloaded);
|
this->Unloaded += ref new RoutedEventHandler(this, &Grapher::OnUnloaded);
|
||||||
|
|
||||||
|
this->ManipulationMode =
|
||||||
|
ManipulationModes::TranslateX |
|
||||||
|
ManipulationModes::TranslateY |
|
||||||
|
ManipulationModes::TranslateInertia |
|
||||||
|
ManipulationModes::Scale |
|
||||||
|
ManipulationModes::ScaleInertia;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Grapher::OnLoaded(Object^ sender, RoutedEventArgs^ args)
|
void Grapher::OnLoaded(Object^ sender, RoutedEventArgs^ args)
|
||||||
@ -66,6 +85,20 @@ namespace GraphControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Grapher::ScaleRange(double centerX, double centerY, double scale)
|
||||||
|
{
|
||||||
|
if (m_graph != nullptr && m_renderMain != nullptr)
|
||||||
|
{
|
||||||
|
if (auto renderer = m_graph->GetRenderer())
|
||||||
|
{
|
||||||
|
if (SUCCEEDED(renderer->ScaleRange(centerX, centerY, scale)))
|
||||||
|
{
|
||||||
|
m_renderMain->RunRenderPass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Grapher::OnApplyTemplate()
|
void Grapher::OnApplyTemplate()
|
||||||
{
|
{
|
||||||
auto swapChainPanel = dynamic_cast<SwapChainPanel^>(GetTemplateChild(StringReference(s_templateKey_SwapChainPanel)));
|
auto swapChainPanel = dynamic_cast<SwapChainPanel^>(GetTemplateChild(StringReference(s_templateKey_SwapChainPanel)));
|
||||||
@ -389,4 +422,106 @@ namespace GraphControl
|
|||||||
e->Handled = true;
|
e->Handled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Grapher::OnPointerWheelChanged(PointerRoutedEventArgs^ e)
|
||||||
|
{
|
||||||
|
PointerPoint^ currentPointer = e->GetCurrentPoint(/*relative to*/ this);
|
||||||
|
|
||||||
|
double delta = currentPointer->Properties->MouseWheelDelta;
|
||||||
|
|
||||||
|
// The maximum delta is 120 according to:
|
||||||
|
// https://docs.microsoft.com/en-us/uwp/api/windows.ui.input.pointerpointproperties.mousewheeldelta#Windows_UI_Input_PointerPointProperties_MouseWheelDelta
|
||||||
|
// Apply a dampening effect so that small mouse movements have a smoother zoom.
|
||||||
|
constexpr double scrollDamper = 0.15;
|
||||||
|
double scale = 1.0 + (abs(delta) / WHEEL_DELTA) * scrollDamper;
|
||||||
|
|
||||||
|
// positive delta if wheel scrolled away from the user
|
||||||
|
if (delta >= 0)
|
||||||
|
{
|
||||||
|
scale = 1.0 / scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For scaling, the graphing engine interprets x,y position between the range [-1, 1].
|
||||||
|
// Translate the pointer position to the [-1, 1] bounds.
|
||||||
|
const auto& pos = currentPointer->Position;
|
||||||
|
const auto[centerX, centerY] = PointerPositionToGraphPosition(pos.X, pos.Y, ActualWidth, ActualHeight);
|
||||||
|
|
||||||
|
ScaleRange(centerX, centerY, scale);
|
||||||
|
|
||||||
|
e->Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Grapher::OnPointerPressed(PointerRoutedEventArgs^ e)
|
||||||
|
{
|
||||||
|
// Set the pointer capture to the element being interacted with so that only it
|
||||||
|
// will fire pointer-related events
|
||||||
|
CapturePointer(e->Pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Grapher::OnPointerReleased(PointerRoutedEventArgs^ e)
|
||||||
|
{
|
||||||
|
ReleasePointerCapture(e->Pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Grapher::OnPointerCanceled(PointerRoutedEventArgs^ e)
|
||||||
|
{
|
||||||
|
ReleasePointerCapture(e->Pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Grapher::OnManipulationDelta(ManipulationDeltaRoutedEventArgs^ e)
|
||||||
|
{
|
||||||
|
if (m_renderMain != nullptr && m_graph != nullptr)
|
||||||
|
{
|
||||||
|
if (auto renderer = m_graph->GetRenderer())
|
||||||
|
{
|
||||||
|
// Only call for a render pass if we actually scaled or translated.
|
||||||
|
bool needsRenderPass = false;
|
||||||
|
|
||||||
|
const double width = ActualWidth;
|
||||||
|
const double height = ActualHeight;
|
||||||
|
|
||||||
|
const auto& translation = e->Delta.Translation;
|
||||||
|
double translationX = translation.X;
|
||||||
|
double translationY = translation.Y;
|
||||||
|
if (translationX != 0 || translationY != 0)
|
||||||
|
{
|
||||||
|
// The graphing engine pans the graph according to a ratio for x and y.
|
||||||
|
// A value of +1 means move a half screen in the positive direction for the given axis.
|
||||||
|
// Convert the manipulation's translation values to ratios for the engine.
|
||||||
|
translationX /= -width;
|
||||||
|
translationY /= height;
|
||||||
|
|
||||||
|
if (FAILED(renderer->MoveRangeByRatio(translationX, translationY)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
needsRenderPass = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double scale = e->Delta.Scale; scale != 1.0)
|
||||||
|
{
|
||||||
|
// The graphing engine interprets scale amounts as the inverse of the value retrieved
|
||||||
|
// from the ManipulationUpdatedEventArgs. Invert the scale amount for the engine.
|
||||||
|
scale = 1.0 / scale;
|
||||||
|
|
||||||
|
// Convert from PointerPosition to graph position.
|
||||||
|
const auto& pos = e->Position;
|
||||||
|
const auto[centerX, centerY] = PointerPositionToGraphPosition(pos.X, pos.Y, width, height);
|
||||||
|
|
||||||
|
if (FAILED(renderer->ScaleRange(centerX, centerY, scale)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
needsRenderPass = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsRenderPass)
|
||||||
|
{
|
||||||
|
m_renderMain->RunRenderPass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,11 @@ namespace GraphControl
|
|||||||
void OnPointerEntered(Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) override;
|
void OnPointerEntered(Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) override;
|
||||||
void OnPointerMoved(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;
|
void OnPointerExited(Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) override;
|
||||||
|
void OnPointerWheelChanged(Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) override;
|
||||||
|
void OnPointerPressed(Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) override;
|
||||||
|
void OnPointerReleased(Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) override;
|
||||||
|
void OnPointerCanceled(Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) override;
|
||||||
|
void OnManipulationDelta(Windows::UI::Xaml::Input::ManipulationDeltaRoutedEventArgs^ e) override;
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -122,6 +127,7 @@ namespace GraphControl
|
|||||||
void OnDataSourceChanged(GraphControl::InspectingDataSource^ sender, GraphControl::DataSourceChangedEventArgs args);
|
void OnDataSourceChanged(GraphControl::InspectingDataSource^ sender, GraphControl::DataSourceChangedEventArgs args);
|
||||||
|
|
||||||
void OnEquationsChanged(Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args);
|
void OnEquationsChanged(Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args);
|
||||||
|
void OnEquationsVectorChanged(Windows::Foundation::Collections::IObservableVector<GraphControl::Equation ^> ^sender, Windows::Foundation::Collections::IVectorChangedEventArgs ^event);
|
||||||
void OnEquationChanged();
|
void OnEquationChanged();
|
||||||
|
|
||||||
void UpdateGraph();
|
void UpdateGraph();
|
||||||
@ -136,6 +142,8 @@ namespace GraphControl
|
|||||||
void OnItemsAdded(int index, int count);
|
void OnItemsAdded(int index, int count);
|
||||||
void OnItemsRemoved(int index, int count);
|
void OnItemsRemoved(int index, int count);
|
||||||
|
|
||||||
|
void ScaleRange(double centerX, double centerY, double scale);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DX::RenderMain^ m_renderMain = nullptr;
|
DX::RenderMain^ m_renderMain = nullptr;
|
||||||
|
|
||||||
@ -155,6 +163,5 @@ namespace GraphControl
|
|||||||
|
|
||||||
const std::unique_ptr<Graphing::IMathSolver> m_solver;
|
const std::unique_ptr<Graphing::IMathSolver> m_solver;
|
||||||
const std::shared_ptr<Graphing::IGraph> m_graph;
|
const std::shared_ptr<Graphing::IGraph> m_graph;
|
||||||
void OnEquationsVectorChanged(Windows::Foundation::Collections::IObservableVector<GraphControl::Equation ^> ^sender, Windows::Foundation::Collections::IVectorChangedEventArgs ^event);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -326,24 +326,18 @@ namespace Graphing
|
|||||||
// Zoom out on all axes by the predefined ratio
|
// Zoom out on all axes by the predefined ratio
|
||||||
ZoomOut,
|
ZoomOut,
|
||||||
|
|
||||||
// Zoom out on X axis only, leave the range of Y (and Z in 3D) unchanged
|
// Zoom out on X axis only, leave the range of Y unchanged
|
||||||
WidenX,
|
WidenX,
|
||||||
|
|
||||||
// Zoom in on X axis only, leave the range of Y (and Z in 3D) unchanged
|
// Zoom in on X axis only, leave the range of Y unchanged
|
||||||
ShrinkX,
|
ShrinkX,
|
||||||
|
|
||||||
// Zoom out on Y axis only, leave the range of X (and Z in 3D) unchanged
|
// Zoom out on Y axis only, leave the range of X unchanged
|
||||||
WidenY,
|
WidenY,
|
||||||
|
|
||||||
// Zoom in on Y axis only, leave the range of X (and Z in 3D) unchanged
|
// Zoom in on Y axis only, leave the range of X unchanged
|
||||||
ShrinkY,
|
ShrinkY,
|
||||||
|
|
||||||
// Zoom out on Z axis only, leave the range of X and Y unchanged. Apply to 3D graph only but not 2D graph.
|
|
||||||
WidenZ,
|
|
||||||
|
|
||||||
// Zoom in on Z axis only, leave the range of X and Y unchanged. Apply to 3D graph only but not 2D graph.
|
|
||||||
ShrinkZ,
|
|
||||||
|
|
||||||
// Move the view window of the graph towards the negative X axis.
|
// Move the view window of the graph towards the negative X axis.
|
||||||
MoveNegativeX,
|
MoveNegativeX,
|
||||||
|
|
||||||
@ -356,12 +350,6 @@ namespace Graphing
|
|||||||
// Move the view window of the graph towards the positive Y axis.
|
// Move the view window of the graph towards the positive Y axis.
|
||||||
MovePositiveY,
|
MovePositiveY,
|
||||||
|
|
||||||
// Move the view window of the graph towards the negative Z axis.
|
|
||||||
MoveNegativeZ,
|
|
||||||
|
|
||||||
// Move the view window of the graph towards the positive Z axis.
|
|
||||||
MovePositiveZ,
|
|
||||||
|
|
||||||
// Zoom in on all axes by the predefined ratio. The ratio is smaller than used in ZoomIn result in a smoother motion
|
// Zoom in on all axes by the predefined ratio. The ratio is smaller than used in ZoomIn result in a smoother motion
|
||||||
SmoothZoomIn,
|
SmoothZoomIn,
|
||||||
|
|
||||||
|
@ -16,5 +16,10 @@ namespace Graphing::Renderer
|
|||||||
|
|
||||||
virtual HRESULT DrawD2D1(ID2D1Factory* pDirect2dFactory, ID2D1RenderTarget* pRenderTarget, bool& hasSomeMissingDataOut) = 0;
|
virtual HRESULT DrawD2D1(ID2D1Factory* pDirect2dFactory, ID2D1RenderTarget* pRenderTarget, bool& hasSomeMissingDataOut) = 0;
|
||||||
virtual HRESULT GetClosePointData(float inScreenPointX, float inScreenPointY, int& formulaIdOut, float& xScreenPointOut, float& yScreenPointOut, float& xValueOut, float& yValueOut) = 0;
|
virtual HRESULT GetClosePointData(float inScreenPointX, float inScreenPointY, int& formulaIdOut, float& xScreenPointOut, float& yScreenPointOut, float& xValueOut, float& yValueOut) = 0;
|
||||||
|
|
||||||
|
virtual HRESULT ScaleRange(double centerX, double centerY, double scale) = 0;
|
||||||
|
virtual HRESULT ChangeRange(ChangeRangeAction action) = 0;
|
||||||
|
virtual HRESULT MoveRangeByRatio(double ratioX, double ratioY) = 0;
|
||||||
|
virtual HRESULT ResetRange() = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user