diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 6d9af7c61..9c3af4565 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -53,9 +53,17 @@ app_objs += $(addprefix apps/shared/,\ storage_cartesian_function.o\ storage_expression_model.o\ storage_expression_model_store.o\ + storage_expression_model_list_controller.o\ storage_function.o\ + storage_function_banner_delegate.o\ + storage_function_curve_parameter_controller.o\ + storage_function_go_to_parameter_controller.o\ + storage_function_graph_view.o\ + storage_function_graph_controller.o\ + storage_function_list_controller.o\ storage_function_store.o\ storage_list_parameter_controller.o\ + storage_sum_graph_controller.o\ store_cell.o\ store_context.o\ store_controller.o\ diff --git a/apps/shared/storage_function_banner_delegate.cpp b/apps/shared/storage_function_banner_delegate.cpp new file mode 100644 index 000000000..671e650cd --- /dev/null +++ b/apps/shared/storage_function_banner_delegate.cpp @@ -0,0 +1,35 @@ +#include "storage_function_banner_delegate.h" +#include "poincare_helpers.h" +#include "../constant.h" + +using namespace Poincare; + +namespace Shared { + +void StorageFunctionBannerDelegate::reloadBannerViewForCursorOnFunction(CurveViewCursor * cursor, StorageFunction * function, char symbol) { + constexpr int bufferSize = k_maxNumberOfCharacters+PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits); + char buffer[bufferSize]; + const char * space = " "; + int spaceLength = strlen(space); + const char * legend = "0="; + int legendLength = strlen(legend); + int numberOfChar = 0; + strlcpy(buffer, legend, legendLength+1); + numberOfChar += legendLength; + buffer[0] = symbol; + numberOfChar += PoincareHelpers::ConvertFloatToText(cursor->x(), buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits); + strlcpy(buffer+numberOfChar, space, spaceLength+1); + buffer[k_maxDigitLegendLength+2] = 0; + bannerView()->setLegendAtIndex(buffer, 0); + + numberOfChar = 0; + numberOfChar += function->nameWithArgument(buffer, bufferSize, symbol); + legend = "="; + numberOfChar += strlcpy(buffer+numberOfChar, legend, bufferSize-numberOfChar); + numberOfChar += PoincareHelpers::ConvertFloatToText(cursor->y(), buffer+numberOfChar, bufferSize-numberOfChar, Constant::MediumNumberOfSignificantDigits); + strlcpy(buffer+numberOfChar, space, bufferSize-numberOfChar); + buffer[k_maxDigitLegendLength+5] = 0; + bannerView()->setLegendAtIndex(buffer, 1); +} + +} diff --git a/apps/shared/storage_function_banner_delegate.h b/apps/shared/storage_function_banner_delegate.h new file mode 100644 index 000000000..6bc7ecb2b --- /dev/null +++ b/apps/shared/storage_function_banner_delegate.h @@ -0,0 +1,21 @@ +#ifndef SHARED_STORAGE_FUNCTION_BANNER_DELEGATE_H +#define SHARED_STORAGE_FUNCTION_BANNER_DELEGATE_H + +#include "storage_function_store.h" +#include "banner_view.h" +#include "curve_view_cursor.h" + +namespace Shared { + +class StorageFunctionBannerDelegate { +public: + constexpr static int k_maxNumberOfCharacters = 50; + constexpr static int k_maxDigitLegendLength = 11; +protected: + void reloadBannerViewForCursorOnFunction(CurveViewCursor * cursor, StorageFunction * function, char symbol); + virtual BannerView * bannerView() = 0; +}; + +} + +#endif diff --git a/apps/shared/storage_function_curve_parameter_controller.cpp b/apps/shared/storage_function_curve_parameter_controller.cpp new file mode 100644 index 000000000..475cbfbd9 --- /dev/null +++ b/apps/shared/storage_function_curve_parameter_controller.cpp @@ -0,0 +1,43 @@ +#include "storage_function_curve_parameter_controller.h" +#include + +namespace Shared { + +StorageFunctionCurveParameterController::StorageFunctionCurveParameterController(InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor) : + ViewController(nullptr), + m_goToCell(I18n::Message::Goto), + m_selectableTableView(this, this, this), + m_function(nullptr) +{ +} + +View * StorageFunctionCurveParameterController::view() { + return &m_selectableTableView; +} + +void StorageFunctionCurveParameterController::didBecomeFirstResponder() { + if (selectedRow() < 0) { + selectCellAtLocation(0, 0); + } + app()->setFirstResponder(&m_selectableTableView); +} + +bool StorageFunctionCurveParameterController::handleGotoSelection() { + if (m_function == nullptr) { + return false; + } + goToParameterController()->setFunction(m_function); + StackViewController * stack = (StackViewController *)parentResponder(); + stack->push(goToParameterController()); + return true; +} + +KDCoordinate StorageFunctionCurveParameterController::cellHeight() { + return Metric::ParameterCellHeight; +} + +void StorageFunctionCurveParameterController::setFunction(StorageFunction * function) { + m_function = function; +} + +} diff --git a/apps/shared/storage_function_curve_parameter_controller.h b/apps/shared/storage_function_curve_parameter_controller.h new file mode 100644 index 000000000..c856145e1 --- /dev/null +++ b/apps/shared/storage_function_curve_parameter_controller.h @@ -0,0 +1,30 @@ +#ifndef SHARED_STORAGE_FUNCTION_CURVE_PARAMETER_CONTROLLER_H +#define SHARED_STORAGE_FUNCTION_CURVE_PARAMETER_CONTROLLER_H + +#include +#include "storage_function_go_to_parameter_controller.h" +#include "function.h" +#include "curve_view_cursor.h" +#include "interactive_curve_view_range.h" + +namespace Shared { + +class StorageFunctionCurveParameterController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { +public: + StorageFunctionCurveParameterController(InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor); + View * view() override; + void didBecomeFirstResponder() override; + KDCoordinate cellHeight() override; + void setFunction(StorageFunction * function); +protected: + bool handleGotoSelection(); + MessageTableCellWithChevron m_goToCell; + SelectableTableView m_selectableTableView; + StorageFunction * m_function; +private: + virtual StorageFunctionGoToParameterController * goToParameterController() = 0; +}; + +} + +#endif diff --git a/apps/shared/storage_function_go_to_parameter_controller.cpp b/apps/shared/storage_function_go_to_parameter_controller.cpp new file mode 100644 index 000000000..efb6b7596 --- /dev/null +++ b/apps/shared/storage_function_go_to_parameter_controller.cpp @@ -0,0 +1,45 @@ +#include "storage_function_go_to_parameter_controller.h" +#include "text_field_delegate_app.h" +#include +#include + +namespace Shared { + +StorageFunctionGoToParameterController::StorageFunctionGoToParameterController(Responder * parentResponder, InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor, I18n::Message symbol) : + GoToParameterController(parentResponder, graphRange, cursor, symbol), + m_function(nullptr) +{ +} + +const char * StorageFunctionGoToParameterController::title() { + return I18n::translate(I18n::Message::Goto); +} + +double StorageFunctionGoToParameterController::parameterAtIndex(int index) { + assert(index == 0); + return m_cursor->x(); +} + +bool StorageFunctionGoToParameterController::setParameterAtIndex(int parameterIndex, double f) { + assert(parameterIndex == 0); + TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app(); + float y = m_function->evaluateAtAbscissa(f, myApp->localContext()); + if (std::fabs(f) > k_maxDisplayableFloat || std::fabs(y) > k_maxDisplayableFloat) { + app()->displayWarning(I18n::Message::ForbiddenValue); + return false; + } + if (std::isnan(y) || std::isinf(y)) { + app()->displayWarning(I18n::Message::ValueNotReachedByFunction); + return false; + } + m_cursor->moveTo(f, y); + m_graphRange->centerAxisAround(CurveViewRange::Axis::X, m_cursor->x()); + m_graphRange->centerAxisAround(CurveViewRange::Axis::Y, m_cursor->y()); + return true; +} + +void StorageFunctionGoToParameterController::setFunction(StorageFunction * function) { + m_function = function; +} + +} diff --git a/apps/shared/storage_function_go_to_parameter_controller.h b/apps/shared/storage_function_go_to_parameter_controller.h new file mode 100644 index 000000000..070ae4888 --- /dev/null +++ b/apps/shared/storage_function_go_to_parameter_controller.h @@ -0,0 +1,23 @@ +#ifndef SHARED_STORAGE_FUNCTION_GO_TO_PARAMETER_CONTROLLER_H +#define SHARED_STORAGE_FUNCTION_GO_TO_PARAMETER_CONTROLLER_H + +#include "go_to_parameter_controller.h" +#include "storage_function.h" + +namespace Shared { + +class StorageFunctionGoToParameterController : public GoToParameterController { +public: + StorageFunctionGoToParameterController(Responder * parentResponder, InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor, I18n::Message symbol); + const char * title() override; + void setFunction(StorageFunction * function); +protected: + bool setParameterAtIndex(int parameterIndex, double f) override; + StorageFunction * m_function; +private: + double parameterAtIndex(int index) override; +}; + +} + +#endif diff --git a/apps/shared/storage_function_graph_controller.cpp b/apps/shared/storage_function_graph_controller.cpp new file mode 100644 index 000000000..0a934186c --- /dev/null +++ b/apps/shared/storage_function_graph_controller.cpp @@ -0,0 +1,165 @@ +#include "storage_function_graph_controller.h" +#include "text_field_delegate_app.h" +#include +#include +#include + +using namespace Poincare; + +namespace Shared { + +StorageFunctionGraphController::StorageFunctionGraphController(Responder * parentResponder, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Preferences::AngleUnit * angleUnitVersion) : + InteractiveCurveViewController(parentResponder, header, interactiveRange, curveView, cursor, modelVersion, rangeVersion), + m_initialisationParameterController(this, interactiveRange), + m_angleUnitVersion(angleUnitVersion), + m_indexFunctionSelectedByCursor(indexFunctionSelectedByCursor) +{ +} + +bool StorageFunctionGraphController::isEmpty() const { + if (functionStore()->numberOfActiveFunctions() == 0) { + return true; + } + return false; +} + +ViewController * StorageFunctionGraphController::initialisationParameterController() { + return &m_initialisationParameterController; +} + +void StorageFunctionGraphController::viewWillAppear() { + functionGraphView()->setCursorView(cursorView()); + functionGraphView()->setBannerView(bannerView()); + functionGraphView()->setAreaHighlight(NAN,NAN); + + if (functionGraphView()->context() == nullptr) { + TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app(); + functionGraphView()->setContext(myApp->localContext()); + } + Preferences::AngleUnit newAngleUnitVersion = Preferences::sharedPreferences()->angleUnit(); + if (*m_angleUnitVersion != newAngleUnitVersion) { + *m_angleUnitVersion = newAngleUnitVersion; + initCursorParameters(); + } + InteractiveCurveViewController::viewWillAppear(); +} + +bool StorageFunctionGraphController::handleEnter() { + StorageFunction * f = functionStore()->activeFunctionAtIndex(indexFunctionSelectedByCursor()); + curveParameterController()->setFunction(f); + StackViewController * stack = stackController(); + stack->push(curveParameterController()); + return true; +} + +void StorageFunctionGraphController::selectFunctionWithCursor(int functionIndex) { + *m_indexFunctionSelectedByCursor = functionIndex; +} + +void StorageFunctionGraphController::reloadBannerView() { + if (functionStore()->numberOfActiveFunctions() == 0) { + return; + } + StorageFunction * f = functionStore()->activeFunctionAtIndex(indexFunctionSelectedByCursor()); + reloadBannerViewForCursorOnFunction(m_cursor, f, functionStore()->symbol()); +} + +InteractiveCurveViewRangeDelegate::Range StorageFunctionGraphController::computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) { + TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app(); + float min = FLT_MAX; + float max = -FLT_MAX; + float xMin = interactiveCurveViewRange->xMin(); + float xMax = interactiveCurveViewRange->xMax(); + if (functionStore()->numberOfActiveFunctions() <= 0) { + InteractiveCurveViewRangeDelegate::Range range; + range.min = xMin; + range.max = xMax; + return range; + } + for (int i=0; inumberOfActiveFunctions(); i++) { + StorageFunction * f = functionStore()->activeFunctionAtIndex(i); + float y = 0.0f; + float res = curveView()->resolution(); + /* Scan x-range from the middle to the extrema in order to get balanced + * y-range for even functions (y = 1/x). */ + for (int j = -res/2; j <= res/2; j++) { + float x = (xMin+xMax)/2.0+(xMax-xMin)*j/res; + y = f->evaluateAtAbscissa(x, myApp->localContext()); + if (!std::isnan(y) && !std::isinf(y)) { + min = min < y ? min : y; + max = max > y ? max : y; + } + } + } + InteractiveCurveViewRangeDelegate::Range range; + range.min = min; + range.max = max; + return range; +} + +void StorageFunctionGraphController::initRangeParameters() { + interactiveCurveViewRange()->setDefault(); + initCursorParameters(); + selectFunctionWithCursor(0); +} + +double StorageFunctionGraphController::defaultCursorAbscissa() { + return (interactiveCurveViewRange()->xMin()+interactiveCurveViewRange()->xMax())/2.0f; +} + +void StorageFunctionGraphController::initCursorParameters() { + double x = defaultCursorAbscissa(); + TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app(); + int functionIndex = 0; + double y = 0; + do { + StorageFunction * firstFunction = functionStore()->activeFunctionAtIndex(functionIndex++); + y = firstFunction->evaluateAtAbscissa(x, myApp->localContext()); + } while ((std::isnan(y) || std::isinf(y)) && functionIndex < functionStore()->numberOfActiveFunctions()); + m_cursor->moveTo(x, y); + functionIndex = (std::isnan(y) || std::isinf(y)) ? 0 : functionIndex - 1; + selectFunctionWithCursor(functionIndex); + interactiveCurveViewRange()->panToMakePointVisible(x, y, k_displayTopMarginRatio, k_cursorRightMarginRatio, k_displayBottomMarginRatio, k_cursorLeftMarginRatio); +} + +bool StorageFunctionGraphController::moveCursorVertically(int direction) { + StorageFunction * actualFunction = functionStore()->activeFunctionAtIndex(indexFunctionSelectedByCursor()); + TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app(); + double y = actualFunction->evaluateAtAbscissa(m_cursor->x(), myApp->localContext()); + StorageFunction * nextFunction = actualFunction; + double nextY = direction > 0 ? DBL_MAX : -DBL_MAX; + for (int i = 0; i < functionStore()->numberOfActiveFunctions(); i++) { + StorageFunction * f = functionStore()->activeFunctionAtIndex(i); + double newY = f->evaluateAtAbscissa(m_cursor->x(), myApp->localContext()); + bool isNextFunction = direction > 0 ? (newY > y && newY < nextY) : (newY < y && newY > nextY); + if (isNextFunction) { + selectFunctionWithCursor(i); + nextY = newY; + nextFunction = f; + } + } + if (nextFunction == actualFunction) { + return false; + } + m_cursor->moveTo(m_cursor->x(), nextY); + interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + return true; +} + +CurveView * StorageFunctionGraphController::curveView() { + return functionGraphView(); +} + +uint32_t StorageFunctionGraphController::modelVersion() { + return functionStore()->storeChecksum(); +} + +uint32_t StorageFunctionGraphController::rangeVersion() { + return interactiveCurveViewRange()->rangeChecksum(); +} + +bool StorageFunctionGraphController::isCursorVisible() { + return interactiveCurveViewRange()->isCursorVisible(k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); +} + +} diff --git a/apps/shared/storage_function_graph_controller.h b/apps/shared/storage_function_graph_controller.h new file mode 100644 index 000000000..8d0deed71 --- /dev/null +++ b/apps/shared/storage_function_graph_controller.h @@ -0,0 +1,61 @@ +#ifndef SHARED_STORAGE_FUNCTION_GRAPH_CONTROLLER_H +#define SHARED_STORAGE_FUNCTION_GRAPH_CONTROLLER_H + +#include +#include "initialisation_parameter_controller.h" +#include "storage_function_banner_delegate.h" +#include "interactive_curve_view_controller.h" +#include "storage_function_store.h" +#include "storage_function_graph_view.h" +#include "storage_function_curve_parameter_controller.h" + +namespace Shared { + +class StorageFunctionGraphController : public InteractiveCurveViewController, public StorageFunctionBannerDelegate { +public: + StorageFunctionGraphController(Responder * parentResponder, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion); + bool isEmpty() const override; + ViewController * initialisationParameterController() override; + void viewWillAppear() override; + +protected: + constexpr static float k_cursorTopMarginRatio = 0.068f; // (cursorHeight/2)/graphViewHeight + constexpr static float k_cursorBottomMarginRatio = 0.15f; // (cursorHeight/2+bannerHeigh)/graphViewHeight + void reloadBannerView() override; + bool handleEnter() override; + int indexFunctionSelectedByCursor() const { + return *m_indexFunctionSelectedByCursor; + } + virtual void selectFunctionWithCursor(int functionIndex); + virtual double defaultCursorAbscissa(); +private: + /* When y auto is ticked, we use a display margin to be ensure that the user + * can move the cursor along the curve without panning the window */ + constexpr static float k_displayTopMarginRatio = 0.09f; + constexpr static float k_displayBottomMarginRatio = 0.2f; + + // InteractiveCurveViewController + float displayTopMarginRatio() override { return k_displayTopMarginRatio; } + float displayBottomMarginRatio() override { return k_displayBottomMarginRatio; } + // InteractiveCurveViewRangeDelegate + InteractiveCurveViewRangeDelegate::Range computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) override; + + void initRangeParameters() override; + void initCursorParameters() override; + bool moveCursorVertically(int direction) override; + CurveView * curveView() override; + uint32_t modelVersion() override; + uint32_t rangeVersion() override; + bool isCursorVisible() override; + virtual StorageFunctionGraphView * functionGraphView() = 0; + virtual View * cursorView() = 0; + virtual StorageFunctionStore * functionStore() const = 0; + virtual StorageFunctionCurveParameterController * curveParameterController() = 0; + InitialisationParameterController m_initialisationParameterController; + Poincare::Preferences::AngleUnit * m_angleUnitVersion; + int * m_indexFunctionSelectedByCursor; +}; + +} + +#endif diff --git a/apps/shared/storage_function_graph_view.cpp b/apps/shared/storage_function_graph_view.cpp new file mode 100644 index 000000000..8063d891a --- /dev/null +++ b/apps/shared/storage_function_graph_view.cpp @@ -0,0 +1,90 @@ +#include "storage_function_graph_view.h" +#include +#include +#include +using namespace Poincare; + +namespace Shared { + +StorageFunctionGraphView::StorageFunctionGraphView(InteractiveCurveViewRange * graphRange, + CurveViewCursor * cursor, BannerView * bannerView, View * cursorView) : + CurveView(graphRange, cursor, bannerView, cursorView), + m_selectedFunction(nullptr), + m_highlightedStart(NAN), + m_highlightedEnd(NAN), + m_shouldColorHighlighted(false), + m_xLabels{}, + m_yLabels{}, + m_context(nullptr) +{ +} + +void StorageFunctionGraphView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(rect, KDColorWhite); + drawGrid(ctx, rect); + drawAxes(ctx, rect, Axis::Horizontal); + drawAxes(ctx, rect, Axis::Vertical); + drawLabels(ctx, rect, Axis::Horizontal, true); + drawLabels(ctx, rect, Axis::Vertical, true); +} + +void StorageFunctionGraphView::setContext(Context * context) { + m_context = context; +} + +Context * StorageFunctionGraphView::context() const { + return m_context; +} + +void StorageFunctionGraphView::selectFunction(StorageFunction * function) { + if (m_selectedFunction != function) { + m_selectedFunction = function; + reloadBetweenBounds(m_highlightedStart, m_highlightedEnd); + } +} + +void StorageFunctionGraphView::setAreaHighlight(float start, float end) { + if (m_highlightedStart != start || m_highlightedEnd != end) { + float dirtyStart = start > m_highlightedStart ? m_highlightedStart : start; + float dirtyEnd = start > m_highlightedStart ? start : m_highlightedStart; + reloadBetweenBounds(dirtyStart, dirtyEnd); + dirtyStart = end > m_highlightedEnd ? m_highlightedEnd : end; + dirtyEnd = end > m_highlightedEnd ? end : m_highlightedEnd; + reloadBetweenBounds(dirtyStart, dirtyEnd); + if (std::isnan(start) || std::isnan(end)) { + reloadBetweenBounds(m_highlightedStart, m_highlightedEnd); + } + if (std::isnan(m_highlightedStart) || std::isnan(m_highlightedEnd)) { + reloadBetweenBounds(start, end); + } + m_highlightedStart = start; + m_highlightedEnd = end; + } +} + +void StorageFunctionGraphView::setAreaHighlightColor(bool highlightColor) { + if (m_shouldColorHighlighted != highlightColor) { + reloadBetweenBounds(m_highlightedStart, m_highlightedEnd); + m_shouldColorHighlighted = highlightColor; + } +} + +char * StorageFunctionGraphView::label(Axis axis, int index) const { + return (axis == Axis::Horizontal ? (char *)m_xLabels[index] : (char *)m_yLabels[index]); +} + +void StorageFunctionGraphView::reloadBetweenBounds(float start, float end) { + if (start == end) { + return; + } + float pixelLowerBound = floatToPixel(Axis::Horizontal, start)-2.0; + float pixelUpperBound = floatToPixel(Axis::Horizontal, end)+4.0; + /* We exclude the banner frame from the dirty zone to avoid unnecessary + * redrawing */ + KDRect dirtyZone(KDRect(pixelLowerBound, 0, pixelUpperBound-pixelLowerBound, + bounds().height()-m_bannerView->bounds().height())); + markRectAsDirty(dirtyZone); +} + + +} diff --git a/apps/shared/storage_function_graph_view.h b/apps/shared/storage_function_graph_view.h new file mode 100644 index 000000000..6e00241eb --- /dev/null +++ b/apps/shared/storage_function_graph_view.h @@ -0,0 +1,37 @@ +#ifndef SHARED_STORAGE_FUNCTION_GRAPH_VIEW_H +#define SHARED_STORAGE_FUNCTION_GRAPH_VIEW_H + +#include +#include "curve_view.h" +#include "storage_function.h" +#include "../constant.h" +#include "interactive_curve_view_range.h" + +namespace Shared { + +class StorageFunctionGraphView : public CurveView { +public: + StorageFunctionGraphView(InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor, + BannerView * bannerView, View * cursorView); + void drawRect(KDContext * ctx, KDRect rect) const override; + void setContext(Poincare::Context * context); + Poincare::Context * context() const; + void selectFunction(StorageFunction * function); + void setAreaHighlight(float start, float end); + virtual void setAreaHighlightColor(bool highlightColor); +protected: + void reloadBetweenBounds(float start, float end); + StorageFunction * m_selectedFunction; + float m_highlightedStart; + float m_highlightedEnd; + bool m_shouldColorHighlighted; +private: + char * label(Axis axis, int index) const override; + char m_xLabels[k_maxNumberOfXLabels][Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(Constant::ShortNumberOfSignificantDigits)]; + char m_yLabels[k_maxNumberOfYLabels][Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(Constant::ShortNumberOfSignificantDigits)]; + Poincare::Context * m_context; +}; + +} + +#endif diff --git a/apps/shared/storage_sum_graph_controller.cpp b/apps/shared/storage_sum_graph_controller.cpp new file mode 100644 index 000000000..d5c63e3f1 --- /dev/null +++ b/apps/shared/storage_sum_graph_controller.cpp @@ -0,0 +1,317 @@ +#include "storage_sum_graph_controller.h" +#include "../apps_container.h" +#include +#include +#include "poincare_helpers.h" + +#include +#include +#include + +using namespace Poincare; + +namespace Shared { + +StorageSumGraphController::StorageSumGraphController(Responder * parentResponder, StorageFunctionGraphView * graphView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, char sumSymbol) : + SimpleInteractiveCurveViewController(parentResponder, range, graphView, cursor), + m_step(Step::FirstParameter), + m_startSum(NAN), + m_endSum(NAN), + m_function(nullptr), + m_graphRange(range), + m_graphView(graphView), + m_legendView(this, sumSymbol), + m_cursorView() +{ +} + +void StorageSumGraphController::viewWillAppear() { + m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + m_graphView->setBannerView(&m_legendView); + m_graphView->setCursorView(&m_cursorView); + m_graphView->setOkView(nullptr); + m_graphView->selectMainView(true); + m_graphView->setAreaHighlightColor(false); + m_graphView->setAreaHighlight(NAN, NAN); + m_graphView->reload(); + + m_startSum = m_cursor->x(); + m_endSum = NAN; + m_step = Step::FirstParameter; + m_legendView.setLegendMessage(legendMessageAtStep(Step::FirstParameter), Step::FirstParameter); + m_legendView.setEditableZone(m_startSum); + m_legendView.setSumSymbol(m_step); +} + + +void StorageSumGraphController::didEnterResponderChain(Responder * previousFirstResponder) { + app()->setFirstResponder(m_legendView.textField()); +} + +bool StorageSumGraphController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::Plus || event == Ion::Events::Minus) { + return handleZoom(event); + } + if ((int)m_step > 1 && event != Ion::Events::OK && event != Ion::Events::EXE && event != Ion::Events::Back) { + return false; + } + if (event == Ion::Events::Left && !m_legendView.textField()->isEditing()) { + if ((int)m_step > 0 && m_startSum >= m_cursor->x()) { + return false; + } + if (moveCursorHorizontallyToPosition(cursorNextStep(m_cursor->x(), -1))) { + m_graphView->reload(); + return true; + } + return false; + } + if (event == Ion::Events::Right && !m_legendView.textField()->isEditing()) { + if (moveCursorHorizontallyToPosition(cursorNextStep(m_cursor->x(), 1))) { + m_graphView->reload(); + return true; + } + return false; + } + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + return handleEnter(); + } + if (event == Ion::Events::Back && (int)m_step > 0) { + m_step = (Step)((int)m_step-1); + m_legendView.setLegendMessage(legendMessageAtStep(m_step), m_step); + if (m_step == Step::SecondParameter) { + app()->setFirstResponder(m_legendView.textField()); + m_graphView->setAreaHighlightColor(false); + m_graphView->setCursorView(&m_cursorView); + m_endSum = m_cursor->x(); + m_legendView.setEditableZone(m_endSum); + m_legendView.setSumSymbol(m_step, m_startSum); + } + if (m_step == Step::FirstParameter) { + m_graphView->setAreaHighlight(NAN,NAN); + moveCursorHorizontallyToPosition(m_startSum); + m_legendView.setLegendMessage(legendMessageAtStep(m_step), m_step); + m_legendView.setEditableZone(m_startSum); + m_legendView.setSumSymbol(m_step); + m_graphView->reload(); + } + return true; + } + return false; +} + +bool StorageSumGraphController::moveCursorHorizontallyToPosition(double x) { + TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app(); + if (m_function == nullptr) { + return false; + } + double y = m_function->evaluateAtAbscissa(x, myApp->localContext()); + m_cursor->moveTo(x, y); + if (m_step == Step::FirstParameter) { + m_startSum = m_cursor->x(); + m_legendView.setEditableZone(m_startSum); + } + if (m_step == Step::SecondParameter) { + m_graphView->setAreaHighlight(m_startSum, m_cursor->x()); + m_endSum = m_cursor->x(); + m_legendView.setEditableZone(m_endSum); + } + m_graphRange->panToMakePointVisible(x, y, k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + return true; +} + +void StorageSumGraphController::setFunction(StorageFunction * function) { + m_graphView->selectFunction(function); + m_function = function; +} + +bool StorageSumGraphController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { + AppsContainer * appsContainer = ((TextFieldDelegateApp *)app())->container(); + Context * globalContext = appsContainer->globalContext(); + double floatBody = PoincareHelpers::ApproximateToScalar(text, *globalContext); + if (std::isnan(floatBody) || std::isinf(floatBody)) { + app()->displayWarning(I18n::Message::UndefinedValue); + return false; + } + if (m_step == Step::SecondParameter && floatBody < m_startSum) { + app()->displayWarning(I18n::Message::ForbiddenValue); + return false; + } + if (moveCursorHorizontallyToPosition(floatBody)) { + handleEnter(); + m_graphView->reload(); + return true; + } + app()->displayWarning(I18n::Message::ForbiddenValue); + return false; +} + +bool StorageSumGraphController::textFieldDidAbortEditing(TextField * textField) { + char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits)]; + double parameter = NAN; + switch(m_step) { + case Step::FirstParameter: + parameter = m_startSum; + break; + case Step::SecondParameter: + parameter = m_endSum; + break; + default: + assert(false); + } + PrintFloat::convertFloatToText(parameter, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal); + textField->setText(buffer); + return true; +} + +bool StorageSumGraphController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) { + if ((event == Ion::Events::OK || event == Ion::Events::EXE) && !textField->isEditing()) { + return handleEnter(); + } + if (m_step == Step::Result) { + return handleEvent(event); + } + return TextFieldDelegate::textFieldDidReceiveEvent(textField, event); +} + +bool StorageSumGraphController::handleEnter() { + if (m_step == Step::Result) { + StackViewController * stack = (StackViewController *)parentResponder(); + stack->pop(); + return true; + } + if (m_step == Step::FirstParameter) { + m_step = Step::SecondParameter; + m_graphView->setAreaHighlight(m_startSum,m_startSum); + m_endSum = m_cursor->x(); + m_legendView.setEditableZone(m_endSum); + m_legendView.setSumSymbol(m_step, m_startSum); + m_legendView.setLegendMessage(legendMessageAtStep(m_step), m_step); + return true; + } + m_step = (Step)((int)m_step+1); + TextFieldDelegateApp * myApp = static_cast(app()); + double sum = m_function->sumBetweenBounds(m_startSum, m_endSum, myApp->localContext()); + m_legendView.setSumSymbol(m_step, m_startSum, m_endSum, sum, createFunctionLayout(m_function)); + m_legendView.setLegendMessage(I18n::Message::Default, m_step); + m_graphView->setAreaHighlightColor(true); + m_graphView->setCursorView(nullptr); + myApp->setFirstResponder(this); + return true; +} + +/* Legend View */ + +StorageSumGraphController::LegendView::LegendView(StorageSumGraphController * controller, char sumSymbol) : + m_sum(0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle), + m_sumLayout(), + m_legend(KDText::FontSize::Small, I18n::Message::Default, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle), + m_editableZone(controller, m_draftText, m_draftText, TextField::maxBufferSize(), controller, false, KDText::FontSize::Small, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle), + m_sumSymbol(sumSymbol) +{ + m_draftText[0] = 0; +} + +void StorageSumGraphController::LegendView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(bounds(), Palette::GreyMiddle); +} + +KDSize StorageSumGraphController::LegendView::minimalSizeForOptimalDisplay() const { + return KDSize(0, k_legendHeight); +} + +void StorageSumGraphController::LegendView::setLegendMessage(I18n::Message message, Step step) { + m_legend.setMessage(message); + layoutSubviews(step); +} + +void StorageSumGraphController::LegendView::setEditableZone(double d) { + char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits)]; + PrintFloat::convertFloatToText(d, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal); + m_editableZone.setText(buffer); +} + +void StorageSumGraphController::LegendView::setSumSymbol(Step step, double start, double end, double result, Layout functionLayout) { + assert(step == Step::Result || functionLayout.isUninitialized()); + const char sigma[] = {' ', m_sumSymbol}; + if (step == Step::FirstParameter) { + m_sumLayout = LayoutHelper::String(sigma, sizeof(sigma)); + } else if (step == Step::SecondParameter) { + char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits)]; + PrintFloat::convertFloatToText(start, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal); + m_sumLayout = CondensedSumLayout( + LayoutHelper::String(sigma, sizeof(sigma)), + LayoutHelper::String(buffer, strlen(buffer), KDText::FontSize::Small), + EmptyLayout(EmptyLayoutNode::Color::Yellow, false, KDText::FontSize::Small, false)); + } else { + m_sumLayout = LayoutHelper::String(sigma, sizeof(sigma)); + char buffer[2+PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits)]; + PrintFloat::convertFloatToText(start, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal); + Layout start = LayoutHelper::String(buffer, strlen(buffer), KDText::FontSize::Small); + PrintFloat::convertFloatToText(end, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal); + Layout end = LayoutHelper::String(buffer, strlen(buffer), KDText::FontSize::Small); + m_sumLayout = CondensedSumLayout( + LayoutHelper::String(sigma, sizeof(sigma)), + start, + end); + strlcpy(buffer, "= ", 3); + PoincareHelpers::ConvertFloatToText(result, buffer+2, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); + m_sumLayout = HorizontalLayout( + m_sumLayout, + functionLayout, + LayoutHelper::String(buffer, strlen(buffer), KDText::FontSize::Small)); + } + m_sum.setLayout(m_sumLayout); + if (step == Step::Result) { + m_sum.setAlignment(0.5f, 0.5f); + } else { + m_sum.setAlignment(0.0f, 0.5f); + } + layoutSubviews(step); +} + +int StorageSumGraphController::LegendView::numberOfSubviews() const { + return 3; +} + +View * StorageSumGraphController::LegendView::subviewAtIndex(int index) { + assert(index >= 0 && index < 3); + if (index == 0) { + return &m_sum; + } + if (index == 1) { + return &m_editableZone; + } + return &m_legend; +} + +void StorageSumGraphController::LegendView::layoutSubviews() { + layoutSubviews(Step::FirstParameter); +} + +void StorageSumGraphController::LegendView::layoutSubviews(Step step) { + KDCoordinate width = bounds().width(); + KDCoordinate heigth = bounds().height(); + KDSize legendSize = m_legend.minimalSizeForOptimalDisplay(); + + if (legendSize.width() > 0) { + m_sum.setFrame(KDRect(0, k_symbolHeightMargin, width-legendSize.width(), m_sum.minimalSizeForOptimalDisplay().height())); + m_legend.setFrame(KDRect(width-legendSize.width(), 0, legendSize.width(), heigth)); + } else { + m_sum.setFrame(bounds()); + m_legend.setFrame(KDRectZero); + } + + KDSize largeCharSize = KDText::charSize(); + switch(step) { + case Step::FirstParameter: + m_editableZone.setFrame(KDRect(2*largeCharSize.width(), k_symbolHeightMargin+k_sigmaHeight/2, k_editableZoneWidth, k_editableZoneHeight)); + return; + case Step::SecondParameter: + m_editableZone.setFrame(KDRect(2*largeCharSize.width(), k_symbolHeightMargin+k_sigmaHeight/2-k_editableZoneHeight, k_editableZoneWidth, k_editableZoneHeight)); + return; + default: + m_editableZone.setFrame(KDRectZero); + } +} + +} diff --git a/apps/shared/storage_sum_graph_controller.h b/apps/shared/storage_sum_graph_controller.h new file mode 100644 index 000000000..33db729b9 --- /dev/null +++ b/apps/shared/storage_sum_graph_controller.h @@ -0,0 +1,86 @@ +#ifndef SHARED_SUM_GRAPH_CONTROLLER_H +#define SHARED_SUM_GRAPH_CONTROLLER_H + +#include +#include "storage_function_graph_view.h" +#include "interactive_curve_view_range.h" +#include "vertical_cursor_view.h" +#include "curve_view_cursor.h" +#include "simple_interactive_curve_view_controller.h" +#include "storage_function.h" +#include "text_field_delegate.h" + +namespace Shared { + +class StorageSumGraphController : public SimpleInteractiveCurveViewController, public TextFieldDelegate { +public: + StorageSumGraphController(Responder * parentResponder, StorageFunctionGraphView * curveView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, char sumSymbol); + void viewWillAppear() override; + void didEnterResponderChain(Responder * previousFirstResponder) override; + bool handleEvent(Ion::Events::Event event) override; + void setFunction(StorageFunction * function); + bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; + bool textFieldDidAbortEditing(TextField * textField) override; + bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; +protected: + virtual bool moveCursorHorizontallyToPosition(double position); + enum class Step { + FirstParameter = 0, + SecondParameter = 1, + Result = 2 + }; + Step m_step; + double m_startSum; + double m_endSum; + StorageFunction * m_function; + InteractiveCurveViewRange * m_graphRange; +private: + constexpr static float k_cursorTopMarginRatio = 0.06f; // (cursorHeight/2)/graphViewHeight + constexpr static float k_cursorBottomMarginRatio = 0.28f; // (cursorHeight/2+bannerHeigh)/graphViewHeight + virtual I18n::Message legendMessageAtStep(Step step) = 0; + virtual double cursorNextStep(double position, int direction) = 0; + virtual Poincare::Layout createFunctionLayout(StorageFunction * function) = 0; + Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; } + Shared::CurveView * curveView() override { return m_graphView; } + TextFieldDelegateApp * textFieldDelegateApp() override { + return static_cast(app()); + } + bool handleEnter() override; + class LegendView : public View { + public: + LegendView(StorageSumGraphController * controller, char sumSymbol); + LegendView(const LegendView& other) = delete; + LegendView(LegendView&& other) = delete; + LegendView& operator=(const LegendView& other) = delete; + LegendView& operator=(LegendView&& other) = delete; + TextField * textField() { return &m_editableZone; } + KDSize minimalSizeForOptimalDisplay() const override; + void drawRect(KDContext * ctx, KDRect rect) const override; + void setLegendMessage(I18n::Message message, Step step); + void setEditableZone(double d); + void setSumSymbol(Step step, double start = NAN, double end = NAN, double result = NAN, Poincare::Layout sequenceName = Poincare::Layout()); + private: + constexpr static KDCoordinate k_legendHeight = 35; + constexpr static KDCoordinate k_editableZoneWidth = 12*KDText::charSize(KDText::FontSize::Small).width(); + constexpr static KDCoordinate k_editableZoneHeight = KDText::charSize(KDText::FontSize::Small).height(); + constexpr static KDCoordinate k_symbolHeightMargin = 8; + constexpr static KDCoordinate k_sigmaHeight = 18; + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + void layoutSubviews() override; + void layoutSubviews(Step step); + ExpressionView m_sum; + Poincare::Layout m_sumLayout; + MessageTextView m_legend; + TextField m_editableZone; + char m_draftText[TextField::maxBufferSize()]; + char m_sumSymbol; + }; + StorageFunctionGraphView * m_graphView; + LegendView m_legendView; + VerticalCursorView m_cursorView; +}; + +} + +#endif