diff --git a/apps/graph/Makefile b/apps/graph/Makefile index fab363194..1429ae558 100644 --- a/apps/graph/Makefile +++ b/apps/graph/Makefile @@ -9,6 +9,7 @@ app_objs += $(addprefix apps/graph/,\ graph/banner_view.o\ graph/calculation_parameter_controller.o\ graph/curve_parameter_controller.o\ + graph/extremum_graph_controller.o\ graph/graph_controller.o\ graph/graph_controller_helper.o\ graph/graph_view.o\ diff --git a/apps/graph/graph/calculation_parameter_controller.cpp b/apps/graph/graph/calculation_parameter_controller.cpp index e65c9b4fe..7365ec6cb 100644 --- a/apps/graph/graph/calculation_parameter_controller.cpp +++ b/apps/graph/graph/calculation_parameter_controller.cpp @@ -13,7 +13,8 @@ CalculationParameterController::CalculationParameterController(Responder * paren Metric::CommonBottomMargin, Metric::CommonLeftMargin, this), m_function(nullptr), m_tangentGraphController(nullptr, graphView, bannerView, range, cursor), - m_integralGraphController(nullptr, graphView, range, cursor) + m_integralGraphController(nullptr, graphView, range, cursor), + m_minimumGraphController(nullptr, graphView, bannerView, range, cursor) { } @@ -34,6 +35,10 @@ bool CalculationParameterController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE) { ViewController * controller = nullptr; switch(selectedRow()) { + case 2: + m_minimumGraphController.setFunction(m_function); + controller = &m_minimumGraphController; + break; case 4: m_tangentGraphController.setFunction(m_function); controller = &m_tangentGraphController; diff --git a/apps/graph/graph/calculation_parameter_controller.h b/apps/graph/graph/calculation_parameter_controller.h index 89fd9f0b5..d53780ac8 100644 --- a/apps/graph/graph/calculation_parameter_controller.h +++ b/apps/graph/graph/calculation_parameter_controller.h @@ -4,6 +4,7 @@ #include #include "../cartesian_function.h" #include "tangent_graph_controller.h" +#include "extremum_graph_controller.h" #include "integral_graph_controller.h" #include "graph_view.h" #include "banner_view.h" @@ -31,6 +32,7 @@ private: CartesianFunction * m_function; TangentGraphController m_tangentGraphController; IntegralGraphController m_integralGraphController; + MinimumGraphController m_minimumGraphController; }; } diff --git a/apps/graph/graph/extremum_graph_controller.cpp b/apps/graph/graph/extremum_graph_controller.cpp new file mode 100644 index 000000000..869b005e2 --- /dev/null +++ b/apps/graph/graph/extremum_graph_controller.cpp @@ -0,0 +1,97 @@ +#include "extremum_graph_controller.h" +#include "../app.h" + +using namespace Shared; +using namespace Poincare; + +namespace Graph { + +ExtremumGraphController::ExtremumGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, I18n::Message defaultMessage) : + ViewController(parentResponder), + m_graphView(graphView), + m_bannerView(bannerView), + m_graphRange(curveViewRange), + m_cursor(cursor), + m_function(nullptr), + m_defaultBannerView(KDText::FontSize::Small, defaultMessage, 0.5f, 0.5f, KDColorBlack, Palette::GreyMiddle), + m_isActive(false) +{ +} + +View * ExtremumGraphController::view() { + return m_graphView; +} + +void ExtremumGraphController::viewWillAppear() { + assert(m_function != nullptr); + CartesianFunction::Point p = computeExtremumBetweenBounds(m_graphRange->xMin(), m_graphRange->xMax()); + if (std::isnan(p.abscissa)) { + m_isActive = false; + m_graphView->setCursorView(nullptr); + m_graphView->setBannerView(&m_defaultBannerView); + } else { + m_isActive = true; + m_cursor->moveTo(p.abscissa, p.value); + m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, SimpleInteractiveCurveViewController::k_cursorRightMarginRatio, k_cursorBottomMarginRatio, SimpleInteractiveCurveViewController::k_cursorLeftMarginRatio); + reloadBannerView(); + } + m_graphView->setOkView(nullptr); + m_graphView->reload(); +} + +bool ExtremumGraphController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::EXE || event == Ion::Events::OK) { + StackViewController * stack = static_cast(parentResponder()); + stack->pop(); + return true; + } + if (m_isActive && (event == Ion::Events::Right || event == Ion::Events::Left)) { + int direction = event == Ion::Events::Right ? 1 : -1; + if (moveCursor(direction)) { + reloadBannerView(); + m_graphView->reload(); + return true; + } + } + return false; +} + +void ExtremumGraphController::setFunction(CartesianFunction * function) { + m_graphView->selectFunction(function); + m_function = function; +} + +void ExtremumGraphController::reloadBannerView() { + m_bannerView->setNumberOfSubviews(2); + reloadBannerViewForCursorOnFunction(m_cursor, m_function, 'x'); +} + +bool ExtremumGraphController::moveCursor(int direction) { + double x = m_cursor->x(); + float step = m_graphRange->xGridUnit()/SimpleInteractiveCurveViewController::k_numberOfCursorStepsInGradUnit; + float start = direction > 0 ? x+step : m_graphRange->xMin(); + float end = direction > 0 ? m_graphRange->xMax() : x-step; + CartesianFunction::Point newExtremum = computeExtremumBetweenBounds(start, end); + if (std::isnan(newExtremum.abscissa)) { + return false; + } + m_cursor->moveTo(newExtremum.abscissa, newExtremum.value); + m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, SimpleInteractiveCurveViewController::k_cursorRightMarginRatio, k_cursorBottomMarginRatio, SimpleInteractiveCurveViewController::k_cursorLeftMarginRatio); + return true; +} + +MinimumGraphController::MinimumGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor) : + ExtremumGraphController(parentResponder, graphView, bannerView, curveViewRange, cursor, I18n::Message::NoMinimumFound) +{ +} + +const char * MinimumGraphController::title() { + return I18n::translate(I18n::Message::Minimum); +} + +CartesianFunction::Point MinimumGraphController::computeExtremumBetweenBounds(float start, float end) { + TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app(); + return m_function->mininimumBetweenBounds(start, end, myApp->localContext()); +} + +} diff --git a/apps/graph/graph/extremum_graph_controller.h b/apps/graph/graph/extremum_graph_controller.h new file mode 100644 index 000000000..aa07e8f21 --- /dev/null +++ b/apps/graph/graph/extremum_graph_controller.h @@ -0,0 +1,46 @@ +#ifndef GRAPH_EXTREMUM_GRAPH_CONTROLLER_H +#define GRAPH_EXTREMUM_GRAPH_CONTROLLER_H + +#include "graph_view.h" +#include "banner_view.h" +#include "../../shared/curve_view_cursor.h" +#include "../../shared/interactive_curve_view_range.h" +#include "../../shared/function_banner_delegate.h" +#include "../cartesian_function.h" + +namespace Graph { + +class ExtremumGraphController : public ViewController, public Shared::FunctionBannerDelegate { +public: + ExtremumGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, I18n::Message defaultMessage); + View * view() override; + void viewWillAppear() override; + bool handleEvent(Ion::Events::Event event) override; + void setFunction(CartesianFunction * function); +protected: + constexpr static float k_cursorTopMarginRatio = 0.07f; // (cursorHeight/2)/graphViewHeight + constexpr static float k_cursorBottomMarginRatio = 0.15f; // (cursorHeight/2+bannerHeigh)/graphViewHeight + BannerView * bannerView() override { return m_bannerView; } + void reloadBannerView(); + bool moveCursor(int direction); + virtual CartesianFunction::Point computeExtremumBetweenBounds(float start, float end) = 0; + GraphView * m_graphView; + BannerView * m_bannerView; + Shared::InteractiveCurveViewRange * m_graphRange; + Shared::CurveViewCursor * m_cursor; + CartesianFunction * m_function; + MessageTextView m_defaultBannerView; + bool m_isActive; +}; + +class MinimumGraphController : public ExtremumGraphController { +public: + MinimumGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor); + const char * title() override; +private: + CartesianFunction::Point computeExtremumBetweenBounds(float start, float end) override; +}; + +} + +#endif diff --git a/apps/shared/simple_interactive_curve_view_controller.h b/apps/shared/simple_interactive_curve_view_controller.h index 68d55a787..1fc2b5008 100644 --- a/apps/shared/simple_interactive_curve_view_controller.h +++ b/apps/shared/simple_interactive_curve_view_controller.h @@ -16,10 +16,10 @@ public: SimpleInteractiveCurveViewController(Responder * parentResponder, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor); View * view() override; bool handleEvent(Ion::Events::Event event) override; -protected: constexpr static float k_cursorRightMarginRatio = 0.04f; // (cursorWidth/2)/graphViewWidth constexpr static float k_cursorLeftMarginRatio = 0.04f; // (cursorWidth/2)/graphViewWidth constexpr static float k_numberOfCursorStepsInGradUnit = 5.0f; +protected: virtual bool handleZoom(Ion::Events::Event event); virtual bool handleLeftRightEvent(Ion::Events::Event event); virtual void reloadBannerView() {};