Files
Upsilon/apps/shared/function_graph_controller.cpp
Gabriel Ozouf 20cbefad41 [function_graph_controller] Fix y for first move
Method yForCursorFirstMove is supposed to expand the window so that the
first move of the cursor won't cause it to pan. It now filters extreme
values to prevent the original window to be totally squashed, such as
with the drawing of e^x and (x-100)(x+10) at the same time.

Change-Id: Icd6c26b01475a112c4231f293e03ab31d80d5e51
2020-11-04 15:58:20 +01:00

206 lines
7.9 KiB
C++

#include "function_graph_controller.h"
#include "function_app.h"
#include "../apps_container.h"
#include <poincare/coordinate_2D.h>
#include <assert.h>
#include <cmath>
#include <float.h>
#include <algorithm>
using namespace Poincare;
namespace Shared {
FunctionGraphController::FunctionGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, Preferences::AngleUnit * angleUnitVersion) :
InteractiveCurveViewController(parentResponder, inputEventHandlerDelegate, header, interactiveRange, curveView, cursor, rangeVersion),
m_angleUnitVersion(angleUnitVersion),
m_indexFunctionSelectedByCursor(indexFunctionSelectedByCursor)
{
}
bool FunctionGraphController::isEmpty() const {
if (functionStore()->numberOfActiveFunctions() == 0) {
return true;
}
return false;
}
void FunctionGraphController::didBecomeFirstResponder() {
if (curveView()->isMainViewSelected()) {
bannerView()->abscissaValue()->setParentResponder(this);
bannerView()->abscissaValue()->setDelegates(textFieldDelegateApp(), this);
Container::activeApp()->setFirstResponder(bannerView()->abscissaValue());
} else {
InteractiveCurveViewController::didBecomeFirstResponder();
}
}
void FunctionGraphController::viewWillAppear() {
functionGraphView()->setBannerView(bannerView());
functionGraphView()->setAreaHighlight(NAN,NAN);
if (functionGraphView()->context() == nullptr) {
functionGraphView()->setContext(textFieldDelegateApp()->localContext());
}
Preferences::AngleUnit newAngleUnitVersion = Preferences::sharedPreferences()->angleUnit();
if (*m_angleUnitVersion != newAngleUnitVersion) {
*m_angleUnitVersion = newAngleUnitVersion;
initCursorParameters();
}
InteractiveCurveViewController::viewWillAppear();
}
bool FunctionGraphController::handleEnter() {
Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor());
curveParameterController()->setRecord(record);
StackViewController * stack = stackController();
stack->push(curveParameterController());
return true;
}
void FunctionGraphController::selectFunctionWithCursor(int functionIndex) {
*m_indexFunctionSelectedByCursor = functionIndex;
}
void FunctionGraphController::reloadBannerView() {
assert(functionStore()->numberOfActiveFunctions() > 0);
Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor());
reloadBannerViewForCursorOnFunction(m_cursor, record, functionStore(), AppsContainer::sharedAppsContainer()->globalContext());
}
double FunctionGraphController::defaultCursorT(Ion::Storage::Record record) {
return (interactiveCurveViewRange()->xMin()+interactiveCurveViewRange()->xMax())/2.0f;
}
FunctionStore * FunctionGraphController::functionStore() const {
return FunctionApp::app()->functionStore();
}
void FunctionGraphController::initCursorParameters() {
Poincare::Context * context = textFieldDelegateApp()->localContext();
const int activeFunctionsCount = functionStore()->numberOfActiveFunctions();
int functionIndex = 0;
Coordinate2D<double> xy;
double t;
do {
Ion::Storage::Record record = functionStore()->activeRecordAtIndex(functionIndex);
ExpiringPointer<Function> firstFunction = functionStore()->modelForRecord(record);
t = defaultCursorT(record);
xy = firstFunction->evaluateXYAtParameter(t, context);
} while ((std::isnan(xy.x2()) || std::isinf(xy.x2())) && ++functionIndex < activeFunctionsCount);
if (functionIndex == activeFunctionsCount) {
functionIndex = 0;
}
m_cursor->moveTo(t, xy.x1(), xy.x2());
selectFunctionWithCursor(functionIndex);
}
bool FunctionGraphController::moveCursorVertically(int direction) {
int currentActiveFunctionIndex = indexFunctionSelectedByCursor();
Poincare::Context * context = textFieldDelegateApp()->localContext();
int nextActiveFunctionIndex = nextCurveIndexVertically(direction > 0, currentActiveFunctionIndex, context);
if (nextActiveFunctionIndex < 0) {
return false;
}
// Clip the current t to the domain of the next function
ExpiringPointer<Function> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(nextActiveFunctionIndex));
double clippedT = m_cursor->t();
if (!std::isnan(f->tMin())) {
assert(!std::isnan(f->tMax()));
clippedT = std::min<double>(f->tMax(), std::max<double>(f->tMin(), clippedT));
}
Poincare::Coordinate2D<double> cursorPosition = f->evaluateXYAtParameter(clippedT, context);
m_cursor->moveTo(clippedT, cursorPosition.x1(), cursorPosition.x2());
selectFunctionWithCursor(nextActiveFunctionIndex);
return true;
}
bool FunctionGraphController::isCursorHanging() {
Poincare::Context * context = textFieldDelegateApp()->localContext();
if (indexFunctionSelectedByCursor() >= functionStore()->numberOfActiveFunctions()) {
return true;
}
ExpiringPointer<Function> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor()));
Coordinate2D<double> xy = f->evaluateXYAtParameter(m_cursor->t(), context);
return xy.x1() != m_cursor->x() || xy.x2() != m_cursor->y();
}
CurveView * FunctionGraphController::curveView() {
return functionGraphView();
}
uint32_t FunctionGraphController::rangeVersion() {
return interactiveCurveViewRange()->rangeChecksum();
}
bool FunctionGraphController::closestCurveIndexIsSuitable(int newIndex, int currentIndex) const {
return newIndex != currentIndex;
}
Coordinate2D<double> FunctionGraphController::xyValues(int curveIndex, double t, Poincare::Context * context) const {
return functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(curveIndex))->evaluateXYAtParameter(t, context);
}
int FunctionGraphController::numberOfCurves() const {
return functionStore()->numberOfActiveFunctions();
}
void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * range) const {
Poincare::Context * context = textFieldDelegateApp()->localContext();
constexpr int maxLength = 10;
float xMins[maxLength], xMaxs[maxLength], yMins[maxLength], yMaxs[maxLength];
int length = functionStore()->numberOfActiveFunctions();
for (int i = 0; i < length; i++) {
ExpiringPointer<Function> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i));
f->rangeForDisplay(xMins + i, xMaxs + i, yMins + i, yMaxs + i, context);
}
float xMin, xMax, yMin, yMax;
Poincare::Zoom::CombineRanges(length, xMins, xMaxs, &xMin, &xMax);
Poincare::Zoom::CombineRanges(length, yMins, yMaxs, &yMin, &yMax);
range->setXMin(xMin);
range->setXMax(xMax);
range->setYMin(yMin);
range->setYMax(yMax);
yRangeForCursorFirstMove(range);
}
void FunctionGraphController::yRangeForCursorFirstMove(InteractiveCurveViewRange * range) const {
Poincare::Context * context = textFieldDelegateApp()->localContext();
assert(functionStore()->numberOfActiveFunctions() > 0);
int functionsCount = functionStore()->numberOfActiveFunctions();
float cursorStep = range->xGridUnit() / k_numberOfCursorStepsInGradUnit;
float y[2]; // Left and Right
bool normalized = range->isOrthonormal();
constexpr float maximalExpansion = 10.f;
float yRange = range->yMax() - range->yMin();
for (int i = 0; i < functionsCount; i++) {
ExpiringPointer<Function> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i));
for (int i = 0; i < 2; i++) {
float step = cursorStep * (i == 0 ? -1 : 1);
y[i] = f->evaluateXYAtParameter(range->xCenter() + step, context).x2();
if (std::isfinite(y[i])) {
if (y[i] < range->yMin() && (range->yMax() - y[i]) < maximalExpansion * yRange) {
range->setYMin(y[i]);
}
if (y[i] > range->yMax() && (y[i] - range->yMin()) < maximalExpansion * yRange) {
range->setYMax(y[i]);
}
}
}
}
if (normalized) {
range->normalize();
}
}
}