diff --git a/apps/Makefile b/apps/Makefile index 65bcee208..270d8c47d 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -21,6 +21,7 @@ app_objs += $(addprefix apps/,\ interactive_curve_view_controller.o\ interactive_curve_view_range.o\ main.o\ + memoized_curve_view_range.o\ node.o\ node_list_view_controller.o\ node_navigation_controller.o\ diff --git a/apps/banner_view.cpp b/apps/banner_view.cpp index e359c3837..7202e62e1 100644 --- a/apps/banner_view.cpp +++ b/apps/banner_view.cpp @@ -7,8 +7,14 @@ void BannerView::drawRect(KDContext * ctx, KDRect rect) const { } void BannerView::setLegendAtIndex(char * text, int index) { + /* The layout of the banner's subviews depends on their content. + * Indeed, we're using a "centered text" algorithm to layout the subviews. + * So changing a legend implies two things + * - First, we need to update the textView to ensure it has the new content + * - Second, we need to relayout *all* of our subviews. */ TextView * textView = textViewAtIndex(index); textView->setText(text); + layoutSubviews(); } KDSize BannerView::minimalSizeForOptimalDisplay() { @@ -20,7 +26,6 @@ int BannerView::numberOfSubviews() const { } void BannerView::layoutSubviews() { - markRectAsDirty(bounds()); /* We iterate on subviews, adding their width until we exceed the view bound. * The last subview that exceeds the bound is recorded as the first subview of * the next line. For the current line, we scan again the subviews and frame diff --git a/apps/banner_view.h b/apps/banner_view.h index c857e12b4..8f3d89d13 100644 --- a/apps/banner_view.h +++ b/apps/banner_view.h @@ -8,10 +8,10 @@ public: void drawRect(KDContext * ctx, KDRect rect) const override; void setLegendAtIndex(char * text, int index); KDSize minimalSizeForOptimalDisplay() override; - void layoutSubviews() override; private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; + void layoutSubviews() override; int numberOfLines(); virtual TextView * textViewAtIndex(int i) = 0; }; diff --git a/apps/curve_view.cpp b/apps/curve_view.cpp index 45f09d7a0..9fc25662e 100644 --- a/apps/curve_view.cpp +++ b/apps/curve_view.cpp @@ -9,42 +9,30 @@ constexpr KDColor CurveView::k_axisColor; constexpr KDColor CurveView::k_gridColor; CurveView::CurveView(CurveViewRange * curveViewRange, CurveViewCursor * curveViewCursor, BannerView * bannerView, - View * cursorView, float topMarginFactor, float rightMarginFactor, float bottomMarginFactor, - float leftMarginFactor) : + View * cursorView) : View(), m_curveViewRange(curveViewRange), m_curveViewCursor(curveViewCursor), m_bannerView(bannerView), m_cursorView(cursorView), - m_topMarginFactor(topMarginFactor), - m_bottomMarginFactor(bottomMarginFactor), - m_leftMarginFactor(leftMarginFactor), - m_rightMarginFactor(rightMarginFactor) + m_drawnRangeVersion(0) { } void CurveView::reload() { - markRectAsDirty(bounds()); - if (label(Axis::Horizontal, 0) != nullptr) { - computeLabels(Axis::Horizontal); - } - if (label(Axis::Vertical, 0) != nullptr) { - computeLabels(Axis::Vertical); - } - layoutSubviews(); -} - -void CurveView::reloadSelection() { - if (m_curveViewCursor != nullptr) { - float pixelXSelection = roundf(floatToPixel(Axis::Horizontal, m_curveViewCursor->x())); - float pixelYSelection = roundf(floatToPixel(Axis::Vertical, m_curveViewCursor->y())); - KDRect dirtyZone(KDRect(pixelXSelection - k_cursorSize/2, pixelYSelection - k_cursorSize/2, k_cursorSize, k_cursorSize)); - markRectAsDirty(dirtyZone); - layoutCursor(); - if (m_bannerView != nullptr) { - m_bannerView->layoutSubviews(); + uint32_t rangeVersion = m_curveViewRange->rangeChecksum(); + if (m_drawnRangeVersion != rangeVersion) { + // FIXME: This should also be called if the *curve* changed + m_drawnRangeVersion = rangeVersion; + markRectAsDirty(bounds()); + if (label(Axis::Horizontal, 0) != nullptr) { + computeLabels(Axis::Horizontal); + } + if (label(Axis::Vertical, 0) != nullptr) { + computeLabels(Axis::Vertical); } } + layoutSubviews(); } bool CurveView::isMainViewSelected() const { @@ -54,7 +42,7 @@ bool CurveView::isMainViewSelected() const { void CurveView::selectMainView(bool mainViewSelected) { if (m_mainViewSelected != mainViewSelected) { m_mainViewSelected = mainViewSelected; - reloadSelection(); + reload(); layoutSubviews(); } } @@ -65,18 +53,12 @@ void CurveView::setCurveViewRange(CurveViewRange * curveViewRange) { float CurveView::min(Axis axis) const { assert(axis == Axis::Horizontal || axis == Axis::Vertical); - float range = axis == Axis::Horizontal ? m_curveViewRange->xMax() - m_curveViewRange->xMin() : m_curveViewRange->yMax() - m_curveViewRange->yMin(); - float absoluteMin = axis == Axis::Horizontal ? m_curveViewRange->xMin(): m_curveViewRange->yMin(); - float marginFactor = axis == Axis::Horizontal ? m_leftMarginFactor : m_bottomMarginFactor; - return absoluteMin - marginFactor*range; + return (axis == Axis::Horizontal ? m_curveViewRange->xMin(): m_curveViewRange->yMin()); } float CurveView::max(Axis axis) const { assert(axis == Axis::Horizontal || axis == Axis::Vertical); - float range = axis == Axis::Horizontal ? m_curveViewRange->xMax() - m_curveViewRange->xMin() : m_curveViewRange->yMax() - m_curveViewRange->yMin(); - float absoluteMax = (axis == Axis::Horizontal ? m_curveViewRange->xMax() : m_curveViewRange->yMax()); - float marginFactor = axis == Axis::Horizontal ? m_rightMarginFactor : m_topMarginFactor; - return absoluteMax + marginFactor*range; + return (axis == Axis::Horizontal ? m_curveViewRange->xMax() : m_curveViewRange->yMax()); } float CurveView::gridUnit(Axis axis) const { @@ -395,19 +377,6 @@ void CurveView::stampAtLocation(KDContext * ctx, KDRect rect, float pxf, float p } void CurveView::layoutSubviews() { - layoutCursor(); - if (m_bannerView != nullptr) { - m_bannerView->setFrame(bounds()); - KDCoordinate bannerHeight = m_bannerView->minimalSizeForOptimalDisplay().height(); - KDRect bannerFrame(KDRect(0, bounds().height()- bannerHeight, bounds().width(), bannerHeight)); - if (!m_mainViewSelected) { - bannerFrame = KDRectZero; - } - m_bannerView->setFrame(bannerFrame); - } -} - -void CurveView::layoutCursor() { if (m_curveViewCursor != nullptr && m_cursorView != nullptr) { KDCoordinate xCursorPixelPosition = roundf(floatToPixel(Axis::Horizontal, m_curveViewCursor->x())); KDCoordinate yCursorPixelPosition = roundf(floatToPixel(Axis::Vertical, m_curveViewCursor->y())); @@ -417,6 +386,14 @@ void CurveView::layoutCursor() { } m_cursorView->setFrame(cursorFrame); } + if (m_bannerView != nullptr) { + KDCoordinate bannerHeight = m_bannerView->minimalSizeForOptimalDisplay().height(); + KDRect bannerFrame(KDRect(0, bounds().height()- bannerHeight, bounds().width(), bannerHeight)); + if (!m_mainViewSelected) { + bannerFrame = KDRectZero; + } + m_bannerView->setFrame(bannerFrame); + } } int CurveView::numberOfSubviews() const { diff --git a/apps/curve_view.h b/apps/curve_view.h index 2705d6965..c6f499585 100644 --- a/apps/curve_view.h +++ b/apps/curve_view.h @@ -16,11 +16,8 @@ public: Vertical = 1 }; CurveView(CurveViewRange * curveViewRange = nullptr, CurveViewCursor * curveViewCursor = nullptr, - BannerView * bannerView = nullptr, View * cursorView = nullptr, float topMarginFactor = 0.0f, - float rightMarginFactor = 0.0f, float bottomMarginFactor = 0.0f, float leftMarginFactor = 0.0f); - // Reload methods - void reload(); - virtual void reloadSelection(); + BannerView * bannerView = nullptr, View * cursorView = nullptr); + virtual void reload(); // When the main view is selected, the banner view is visible bool isMainViewSelected() const; void selectMainView(bool mainViewSelected); @@ -72,7 +69,6 @@ private: * anti alising. */ void stampAtLocation(KDContext * ctx, KDRect rect, float pxf, float pyf, KDColor color) const; void layoutSubviews() override; - void layoutCursor(); int numberOfSubviews() const override; View * subviewAtIndex(int index) override; /* m_curveViewRange has to be non null but the cursor model, the banner and @@ -82,10 +78,7 @@ private: BannerView * m_bannerView; View * m_cursorView; bool m_mainViewSelected; - float m_topMarginFactor; - float m_bottomMarginFactor; - float m_leftMarginFactor; - float m_rightMarginFactor; + uint32_t m_drawnRangeVersion; }; #endif diff --git a/apps/curve_view_range.cpp b/apps/curve_view_range.cpp index e5d0fffe3..f116230c3 100644 --- a/apps/curve_view_range.cpp +++ b/apps/curve_view_range.cpp @@ -1,7 +1,17 @@ #include "curve_view_range.h" #include +#include +#include +#include #include +uint32_t CurveViewRange::rangeChecksum() { + float data[4] = {xMin(), xMax(), yMin(), yMax()}; + size_t dataLengthInBytes = 4*sizeof(float); + assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4 + return Ion::crc32((uint32_t *)data, dataLengthInBytes>>2); +} + float CurveViewRange::yGridUnit() { return 0.0f; } diff --git a/apps/curve_view_range.h b/apps/curve_view_range.h index 69d13e8b7..83a330491 100644 --- a/apps/curve_view_range.h +++ b/apps/curve_view_range.h @@ -1,12 +1,16 @@ #ifndef APPS_CURVE_VIEW_RANGE_H #define APPS_CURVE_VIEW_RANGE_H +#include + class CurveViewRange { public: enum class Axis { X, Y }; + virtual uint32_t rangeChecksum(); + virtual float xMin() = 0; virtual float xMax() = 0; virtual float yMin() = 0; diff --git a/apps/graph/graph/goto_parameter_controller.cpp b/apps/graph/graph/goto_parameter_controller.cpp index 87732df36..92f6a2f6d 100644 --- a/apps/graph/graph/goto_parameter_controller.cpp +++ b/apps/graph/graph/goto_parameter_controller.cpp @@ -28,7 +28,7 @@ void GoToParameterController::setParameterAtIndex(int parameterIndex, float f) { float y = m_function->evaluateAtAbscissa(f, graphApp->localContext()); m_graphRange->centerAxisAround(CurveViewRange::Axis::X, f); m_graphRange->centerAxisAround(CurveViewRange::Axis::Y, y); - m_graphRange->moveCursorTo(f, y); + m_cursor->moveTo(f, y); } int GoToParameterController::numberOfRows() { diff --git a/apps/graph/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index 0ac43b81d..44d28d469 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -119,7 +119,6 @@ void GraphController::reloadBannerView() { Float(y).convertFloatToText(buffer + legendLength, Float::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits, Float::DisplayMode::Decimal); m_bannerView.setLegendAtIndex(buffer, 2); } - m_bannerView.layoutSubviews(); } void GraphController::initRangeParameters() { @@ -134,20 +133,23 @@ void GraphController::initCursorParameters() { Function * firstFunction = m_functionStore->activeFunctionAtIndex(0); App * graphApp = (Graph::App *)app(); float y = firstFunction->evaluateAtAbscissa(x, graphApp->localContext()); - m_graphRange.moveCursorTo(x, y); + m_cursor.moveTo(x, y); + m_graphRange.panToMakePointVisible(x, y, k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); } -int GraphController::moveCursorHorizontally(int direction) { +bool GraphController::moveCursorHorizontally(int direction) { float xCursorPosition = m_cursor.x(); - float x = direction > 0 ? xCursorPosition + m_graphRange.xGridUnit()/InteractiveCurveViewRange::k_numberOfCursorStepsInGradUnit : - xCursorPosition - m_graphRange.xGridUnit()/InteractiveCurveViewRange::k_numberOfCursorStepsInGradUnit; + float x = direction > 0 ? xCursorPosition + m_graphRange.xGridUnit()/k_numberOfCursorStepsInGradUnit : + xCursorPosition - m_graphRange.xGridUnit()/k_numberOfCursorStepsInGradUnit; Function * f = m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor); App * graphApp = (Graph::App *)app(); float y = f->evaluateAtAbscissa(x, graphApp->localContext()); - return m_graphRange.moveCursorTo(x, y);; + m_cursor.moveTo(x, y); + m_graphRange.panToMakePointVisible(x, y, k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + return true; } -int GraphController::moveCursorVertically(int direction) { +bool GraphController::moveCursorVertically(int direction) { Function * actualFunction = m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor); App * graphApp = (Graph::App *)app(); float y = actualFunction->evaluateAtAbscissa(m_cursor.x(), graphApp->localContext()); @@ -164,9 +166,11 @@ int GraphController::moveCursorVertically(int direction) { } } if (nextFunction == actualFunction) { - return -1; + return false; } - return m_graphRange.moveCursorTo(m_cursor.x(), nextY); + m_cursor.moveTo(m_cursor.x(), nextY); + m_graphRange.panToMakePointVisible(m_cursor.x(), m_cursor.y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + return true; } uint32_t GraphController::modelVersion() { diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index 7efd7d7c7..5613c10b0 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -21,16 +21,20 @@ public: const char * emptyMessage() override; bool didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange) override; - private: + constexpr static float k_cursorTopMarginRatio = 0.025f; + constexpr static float k_cursorRightMarginRatio = 0.015f; + constexpr static float k_cursorBottomMarginRatio = 0.08f; + constexpr static float k_cursorLeftMarginRatio = 0.015f; + constexpr static int k_maxNumberOfCharacters = 8; BannerView * bannerView() override; bool handleEnter() override; void reloadBannerView() override; void initRangeParameters() override; void initCursorParameters() override; - int moveCursorHorizontally(int direction) override; - int moveCursorVertically(int direction) override; + bool moveCursorHorizontally(int direction) override; + bool moveCursorVertically(int direction) override; uint32_t modelVersion() override; uint32_t rangeVersion() override; InteractiveCurveViewRange * interactiveCurveViewRange() override; diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 1d8be6374..8d85f9d14 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -6,7 +6,7 @@ namespace Graph { GraphView::GraphView(FunctionStore * functionStore, InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor, ::BannerView * bannerView, View * cursorView) : - CurveView(graphRange, cursor, bannerView, cursorView, 0.0f, 0.0f, 0.1f, 0.0f), + CurveView(graphRange, cursor, bannerView, cursorView), m_functionStore(functionStore), m_context(nullptr) { diff --git a/apps/graph/graph/initialisation_parameter_controller.cpp b/apps/graph/graph/initialisation_parameter_controller.cpp index 395d8045d..acc689e0d 100644 --- a/apps/graph/graph/initialisation_parameter_controller.cpp +++ b/apps/graph/graph/initialisation_parameter_controller.cpp @@ -28,7 +28,7 @@ void InitialisationParameterController::didBecomeFirstResponder() { bool InitialisationParameterController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK) { RangeMethodPointer rangeMethods[k_totalNumberOfCells] = {&InteractiveCurveViewRange::setTrigonometric, - &InteractiveCurveViewRange::roundAbscissa, &InteractiveCurveViewRange::normalize, &InteractiveCurveViewRange::setDefault}; + &InteractiveCurveViewRange::roundAbscissa, &InteractiveCurveViewRange::normalize, &InteractiveCurveViewRange::setDefault}; (m_graphRange->*rangeMethods[m_selectableTableView.selectedRow()])(); StackViewController * stack = (StackViewController *)parentResponder(); stack->pop(); diff --git a/apps/interactive_curve_view_controller.cpp b/apps/interactive_curve_view_controller.cpp index 8f3cbccbf..55c1a482f 100644 --- a/apps/interactive_curve_view_controller.cpp +++ b/apps/interactive_curve_view_controller.cpp @@ -40,7 +40,7 @@ bool InteractiveCurveViewController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::Down) { headerViewController()->setSelectedButton(-1); curveView()->selectMainView(true); - curveView()->reloadSelection(); + curveView()->reload(); reloadBannerView(); return true; } @@ -65,36 +65,25 @@ bool InteractiveCurveViewController::handleEvent(Ion::Events::Event event) { } if (event == Ion::Events::Left || event == Ion::Events::Right) { int direction = event == Ion::Events::Left ? -1 : 1; - curveView()->reloadSelection(); - int didMoveCursor = moveCursorHorizontally(direction); - if (didMoveCursor == 0) { - curveView()->reloadSelection(); - } - if (didMoveCursor == 1) { + if (moveCursorHorizontally(direction)) { curveView()->reload(); + reloadBannerView(); + return true; } - reloadBannerView(); - return (didMoveCursor >= 0); + return false; } if (event == Ion::Events::Down || event == Ion::Events::Up) { int direction = event == Ion::Events::Down ? -1 : 1; - curveView()->reloadSelection(); - int didMoveCursor = moveCursorVertically(direction); - if (didMoveCursor < 0) { - if (event == Ion::Events::Down) { - return false; - } - curveView()->selectMainView(false); - headerViewController()->setSelectedButton(0); + if (moveCursorVertically(direction)) { + curveView()->reload(); + reloadBannerView(); return true; } - if (didMoveCursor == 0) { - curveView()->reloadSelection(); + if (event == Ion::Events::Down) { + return false; } - if (didMoveCursor == 1) { - curveView()->reload(); - } - reloadBannerView(); + curveView()->selectMainView(false); + headerViewController()->setSelectedButton(0); return true; } if (event == Ion::Events::OK) { diff --git a/apps/interactive_curve_view_controller.h b/apps/interactive_curve_view_controller.h index 676439119..f01db42bd 100644 --- a/apps/interactive_curve_view_controller.h +++ b/apps/interactive_curve_view_controller.h @@ -26,6 +26,7 @@ public: Responder * defaultController() override; protected: + constexpr static float k_numberOfCursorStepsInGradUnit = 5.0f; virtual BannerView * bannerView() = 0; virtual bool handleEnter() = 0; Responder * tabController() const; @@ -34,11 +35,10 @@ protected: virtual void initRangeParameters() = 0; virtual void initCursorParameters() = 0; /* the result of moveCursorVertically/Horizontally means: - * -1 -> the cursor cannot move in this direction - * 0 -> the cursor moved but the window has not changed - * 1-> the cursor moved and the window changed */ - virtual int moveCursorHorizontally(int direction) = 0; - virtual int moveCursorVertically(int direction) = 0; + * false -> the cursor cannot move in this direction + * true -> the cursor moved */ + virtual bool moveCursorHorizontally(int direction) = 0; + virtual bool moveCursorVertically(int direction) = 0; virtual uint32_t modelVersion() = 0; virtual uint32_t rangeVersion() = 0; virtual InteractiveCurveViewRange * interactiveCurveViewRange() = 0; diff --git a/apps/interactive_curve_view_range.cpp b/apps/interactive_curve_view_range.cpp index a11759eb4..8f84eef53 100644 --- a/apps/interactive_curve_view_range.cpp +++ b/apps/interactive_curve_view_range.cpp @@ -5,13 +5,8 @@ #include InteractiveCurveViewRange::InteractiveCurveViewRange(CurveViewCursor * cursor, InteractiveCurveViewRangeDelegate * delegate) : - m_xMin(-10.0f), - m_xMax(10.0f), - m_yMin(-10.0f), - m_yMax(10.0f), + MemoizedCurveViewRange(), m_yAuto(true), - m_xGridUnit(2.0f), - m_yGridUnit(2.0f), m_cursor(cursor), m_delegate(delegate) { @@ -28,70 +23,22 @@ uint32_t InteractiveCurveViewRange::rangeChecksum() { return Ion::crc32((uint32_t *)data, dataLengthInBytes>>2); } -float InteractiveCurveViewRange::xMin() { - return m_xMin; -} - -float InteractiveCurveViewRange::xMax() { - return m_xMax; -} - -float InteractiveCurveViewRange::yMin() { - return m_yMin; -} - -float InteractiveCurveViewRange::yMax() { - return m_yMax; -} - bool InteractiveCurveViewRange::yAuto() { return m_yAuto; } -float InteractiveCurveViewRange::xGridUnit() { - return m_xGridUnit; -} - -float InteractiveCurveViewRange::yGridUnit() { - return m_yGridUnit; -} - void InteractiveCurveViewRange::setXMin(float xMin) { - m_xMin = xMin; - if (m_xMin >= m_xMax) { - m_xMax = xMin + 1.0f; - } + MemoizedCurveViewRange::setXMin(xMin); if (m_delegate) { m_delegate->didChangeRange(this); } - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); } void InteractiveCurveViewRange::setXMax(float xMax) { - m_xMax = xMax; - if (m_xMin >= m_xMax) { - m_xMin = xMax - 1.0f; - } + MemoizedCurveViewRange::setXMax(xMax); if (m_delegate) { m_delegate->didChangeRange(this); } - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); -} - -void InteractiveCurveViewRange::setYMin(float yMin) { - m_yMin = yMin; - if (m_yMin >= m_yMax) { - m_yMax = yMin + 1.0f; - } - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); -} - -void InteractiveCurveViewRange::setYMax(float yMax) { - m_yMax = yMax; - if (m_yMin >= m_yMax) { - m_yMin = yMax - 1.0f; - } - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); } void InteractiveCurveViewRange::setYAuto(bool yAuto) { @@ -183,48 +130,31 @@ void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { } } -bool InteractiveCurveViewRange::moveCursorTo(float x, float y) { - if (m_cursor) { - m_cursor->moveTo(x, y); - float xMargin = k_cursorMarginFactorToBorder * (m_xMax - m_xMin); - float yMargin = k_cursorMarginFactorToBorder * (m_yMax - m_yMin); - bool windowHasMoved = panToMakePointVisible(x, y, xMargin, yMargin); - return windowHasMoved; - } - return false; -} - -bool InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float xMargin, float yMargin) { - bool windowMoved = false; +void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation) { float xRange = m_xMax - m_xMin; float yRange = m_yMax - m_yMin; - if (x < m_xMin + xMargin) { - m_xMin = x - xMargin; + if (x < m_xMin + leftMarginRation*xRange) { + m_xMin = x - leftMarginRation*xRange; m_xMax = m_xMin + xRange; m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); m_yAuto = false; - windowMoved = true; } - if (x > m_xMax - xMargin) { - m_xMax = x + xMargin; + if (x > m_xMax - rightMarginRatio*xRange) { + m_xMax = x + rightMarginRatio*xRange; m_xMin = m_xMax - xRange; m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); m_yAuto = false; - windowMoved = true; } - if (y < m_yMin + yMargin) { - m_yMin = y - yMargin; + if (y < m_yMin + bottomMarginRation*yRange) { + m_yMin = y - bottomMarginRation*yRange; m_yMax = m_yMin + yRange; m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); m_yAuto = false; - windowMoved = true; } - if (y > m_yMax - yMargin) { - m_yMax = y + yMargin; + if (y > m_yMax - topMarginRatio*yRange) { + m_yMax = y + topMarginRatio*yRange; m_yMin = m_yMax - yRange; m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); m_yAuto = false; - windowMoved = true; } - return windowMoved; } diff --git a/apps/interactive_curve_view_range.h b/apps/interactive_curve_view_range.h index 42072b3ad..3fd49b074 100644 --- a/apps/interactive_curve_view_range.h +++ b/apps/interactive_curve_view_range.h @@ -2,28 +2,20 @@ #define APPS_INTERACTIVE_CURVE_VIEW_RANGE_H #include -#include "curve_view_range.h" +#include "memoized_curve_view_range.h" #include "curve_view_cursor.h" #include "interactive_curve_view_range_delegate.h" -class InteractiveCurveViewRange : public CurveViewRange { +class InteractiveCurveViewRange : public MemoizedCurveViewRange { public: InteractiveCurveViewRange(CurveViewCursor * cursor, InteractiveCurveViewRangeDelegate * delegate); void setCursor(CurveViewCursor * cursor); - uint32_t rangeChecksum(); + uint32_t rangeChecksum() override; //CurveViewWindow - float xMin() override; - float xMax() override; - float yMin() override; - float yMax() override; bool yAuto(); - float xGridUnit() override; - float yGridUnit() override; - void setXMin(float f); - void setXMax(float f); - void setYMin(float f); - void setYMax(float f); + void setXMin(float f) override; + void setXMax(float f) override; void setYAuto(bool yAuto); // Window @@ -34,23 +26,10 @@ public: void setTrigonometric(); virtual void setDefault(); void centerAxisAround(Axis axis, float position); - - //Cursor - // moveCursorTo returns true if the window has moved - bool moveCursorTo(float x, float y); - constexpr static float k_numberOfCursorStepsInGradUnit = 5.0f; + void panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation); protected: - constexpr static float k_cursorMarginFactorToBorder = 0.025f; - // Window bounds of the data - float m_xMin; - float m_xMax; - float m_yMin; - float m_yMax; bool m_yAuto; - float m_xGridUnit; - float m_yGridUnit; private: - bool panToMakePointVisible(float x, float y, float xMargin, float yMargin); CurveViewCursor * m_cursor; InteractiveCurveViewRangeDelegate * m_delegate; }; diff --git a/apps/memoized_curve_view_range.cpp b/apps/memoized_curve_view_range.cpp new file mode 100644 index 000000000..e201e59bd --- /dev/null +++ b/apps/memoized_curve_view_range.cpp @@ -0,0 +1,70 @@ +#include "memoized_curve_view_range.h" +#include +#include +#include + +MemoizedCurveViewRange::MemoizedCurveViewRange() : + m_xMin(-10.0f), + m_xMax(10.0f), + m_yMin(-10.0f), + m_yMax(10.0f), + m_xGridUnit(2.0f), + m_yGridUnit(2.0f) +{ +} + +float MemoizedCurveViewRange::xMin() { + return m_xMin; +} + +float MemoizedCurveViewRange::xMax() { + return m_xMax; +} + +float MemoizedCurveViewRange::yMin() { + return m_yMin; +} + +float MemoizedCurveViewRange::yMax() { + return m_yMax; +} + +float MemoizedCurveViewRange::xGridUnit() { + return m_xGridUnit; +} + +float MemoizedCurveViewRange::yGridUnit() { + return m_yGridUnit; +} + +void MemoizedCurveViewRange::setXMin(float xMin) { + m_xMin = xMin; + if (m_xMin >= m_xMax) { + m_xMax = xMin + 1.0f; + } + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); +} + +void MemoizedCurveViewRange::setXMax(float xMax) { + m_xMax = xMax; + if (m_xMin >= m_xMax) { + m_xMin = xMax - 1.0f; + } + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); +} + +void MemoizedCurveViewRange::setYMin(float yMin) { + m_yMin = yMin; + if (m_yMin >= m_yMax) { + m_yMax = yMin + 1.0f; + } + m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); +} + +void MemoizedCurveViewRange::setYMax(float yMax) { + m_yMax = yMax; + if (m_yMin >= m_yMax) { + m_yMin = yMax - 1.0f; + } + m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); +} diff --git a/apps/memoized_curve_view_range.h b/apps/memoized_curve_view_range.h new file mode 100644 index 000000000..fff68b2fd --- /dev/null +++ b/apps/memoized_curve_view_range.h @@ -0,0 +1,32 @@ +#ifndef APPS_MEMOIZED_CURVE_VIEW_RANGE_H +#define APPS_MEMOIZED_CURVE_VIEW_RANGE_H + +#include "curve_view_range.h" + + +class MemoizedCurveViewRange : public CurveViewRange { +public: + MemoizedCurveViewRange(); + //CurveViewWindow + float xMin() override; + float xMax() override; + float yMin() override; + float yMax() override; + float xGridUnit() override; + float yGridUnit() override; + virtual void setXMin(float f); + virtual void setXMax(float f); + void setYMin(float f); + void setYMax(float f); + +protected: + // Window bounds of the data + float m_xMin; + float m_xMax; + float m_yMin; + float m_yMax; + float m_xGridUnit; + float m_yGridUnit; +}; + +#endif diff --git a/apps/probability/law/binomial_law.cpp b/apps/probability/law/binomial_law.cpp index 26388de0a..54aefae5d 100644 --- a/apps/probability/law/binomial_law.cpp +++ b/apps/probability/law/binomial_law.cpp @@ -40,28 +40,31 @@ const char * BinomialLaw::parameterDefinitionAtIndex(int index) { } float BinomialLaw::xMin() { - return floorf(m_parameter1*m_parameter2-5.0f*sqrtf(m_parameter1*m_parameter2*(1-m_parameter2))); + float min = floorf(m_parameter1*m_parameter2-5.0f*sqrtf(m_parameter1*m_parameter2*(1-m_parameter2))); + float max = xMax(); + return min - k_displayLeftMarginRatio * (max - min); } float BinomialLaw::xMax() { - float result = ceilf(m_parameter1*m_parameter2+5.0f*sqrtf(m_parameter1*m_parameter2*(1-m_parameter2))); - if (result <= xMin()) { - result = xMin() + 1.0f; + float min = floorf(m_parameter1*m_parameter2-5.0f*sqrtf(m_parameter1*m_parameter2*(1-m_parameter2))); + float max = ceilf(m_parameter1*m_parameter2+5.0f*sqrtf(m_parameter1*m_parameter2*(1-m_parameter2))); + if (max <= min) { + max = min + 1.0f; } - return result; + return max + k_displayRightMarginRatio*(max - min); } float BinomialLaw::yMin() { - return 0.0f; + return -k_displayBottomMarginRatio*yMax(); } float BinomialLaw::yMax() { int maxAbscissa = m_parameter2 < 1.0f ? (m_parameter1+1)*m_parameter2 : m_parameter1; float result = evaluateAtAbscissa(maxAbscissa); - if (result <= yMin() || isnan(result)) { - result = yMin() + 1.0f; + if (result <= 0.0f || isnan(result)) { + result = 1.0f; } - return result; + return result*(1.0f+ k_displayTopMarginRatio); } float BinomialLaw::evaluateAtAbscissa(float x) const { diff --git a/apps/probability/law/exponential_law.cpp b/apps/probability/law/exponential_law.cpp index f9eb19096..1b79a8e0e 100644 --- a/apps/probability/law/exponential_law.cpp +++ b/apps/probability/law/exponential_law.cpp @@ -33,28 +33,32 @@ const char * ExponentialLaw::parameterDefinitionAtIndex(int index) { } float ExponentialLaw::xMin() { - return 0.0f; + float max = xMax(); + return - k_displayLeftMarginRatio * max; } float ExponentialLaw::xMax() { assert(m_parameter1 != 0.0f); float result = 5.0f/m_parameter1; - if (result <= xMin()) { - result = xMin() + 1.0f; + if (result <= 0.0f) { + result = 1.0f; } - return result; + return result*(1.0f+ k_displayRightMarginRatio); } float ExponentialLaw::yMin() { - return 0.0f; + return -k_displayBottomMarginRatio*yMax(); } float ExponentialLaw::yMax() { float result = m_parameter1; - if (result <= yMin()) { - result = yMin() + 1.0f; + if (result <= 0.0f || isnan(result)) { + result = 1.0f; } - return result; + if (result <= 0.0f) { + result = 1.0f; + } + return result*(1.0f+ k_displayTopMarginRatio); } float ExponentialLaw::evaluateAtAbscissa(float x) const { diff --git a/apps/probability/law/law.h b/apps/probability/law/law.h index 3449537c3..b1efbd4e1 100644 --- a/apps/probability/law/law.h +++ b/apps/probability/law/law.h @@ -34,6 +34,10 @@ public: virtual float rightIntegralInverseForProbability(float * probability); protected: constexpr static int k_maxNumberOfOperations = 1000000; + constexpr static float k_displayTopMarginRatio = 0.05f; + constexpr static float k_displayBottomMarginRatio = 0.2f; + constexpr static float k_displayLeftMarginRatio = 0.05f; + constexpr static float k_displayRightMarginRatio = 0.05f; }; } diff --git a/apps/probability/law/normal_law.cpp b/apps/probability/law/normal_law.cpp index 554ab8cc5..4597ce710 100644 --- a/apps/probability/law/normal_law.cpp +++ b/apps/probability/law/normal_law.cpp @@ -55,16 +55,16 @@ float NormalLaw::xMax() { } float NormalLaw::yMin() { - return 0.0f; + return -k_displayBottomMarginRatio*yMax(); } float NormalLaw::yMax() { float maxAbscissa = m_parameter1; float result = evaluateAtAbscissa(maxAbscissa); - if (isnan(result) || result == yMin()) { - result = yMin() + 1.0f; + if (isnan(result) || result <= 0.0f) { + result = 1.0f; } - return result; + return result*(1.0f+ k_displayTopMarginRatio); } float NormalLaw::evaluateAtAbscissa(float x) const { diff --git a/apps/probability/law/poisson_law.cpp b/apps/probability/law/poisson_law.cpp index fa611a1b9..6bd772702 100644 --- a/apps/probability/law/poisson_law.cpp +++ b/apps/probability/law/poisson_law.cpp @@ -32,26 +32,26 @@ const char * PoissonLaw::parameterDefinitionAtIndex(int index) { } float PoissonLaw::xMin() { - return 0.0f; + return -k_displayLeftMarginRatio*xMax(); } float PoissonLaw::xMax() { assert(m_parameter1 != 0); - return m_parameter1 + 5.0f*sqrtf(m_parameter1); + return (m_parameter1 + 5.0f*sqrtf(m_parameter1))*(1.0f+k_displayRightMarginRatio); } float PoissonLaw::yMin() { - return 0.0f; + return - k_displayBottomMarginRatio * yMax(); } float PoissonLaw::yMax() { int maxAbscissa = (int)m_parameter1; assert(maxAbscissa >= 0.0f); float result = evaluateAtAbscissa(maxAbscissa); - if (result <= yMin()) { - result = yMin() + 1.0f; + if (result <= 0.0f) { + result = 1.0f; } - return result; + return result*(1.0f+ k_displayTopMarginRatio); } float PoissonLaw::evaluateAtAbscissa(float x) const { diff --git a/apps/probability/law/uniform_law.cpp b/apps/probability/law/uniform_law.cpp index 88b6a2e09..e678e581e 100644 --- a/apps/probability/law/uniform_law.cpp +++ b/apps/probability/law/uniform_law.cpp @@ -54,15 +54,15 @@ float UniformLaw::xMax() { } float UniformLaw::yMin() { - return 0.0f; + return -k_displayBottomMarginRatio*yMax(); } float UniformLaw::yMax() { float result = m_parameter1 == m_parameter2 ? k_diracMaximum : 1.0f/(m_parameter2-m_parameter1); - if (result <= yMin()) { - result = yMin() + 1.0f; + if (result <= 0.0f) { + result = 1.0f; } - return result; + return result*(1.0f+ k_displayTopMarginRatio); } float UniformLaw::evaluateAtAbscissa(float t) const { diff --git a/apps/probability/law_curve_view.cpp b/apps/probability/law_curve_view.cpp index 618efe00a..bf199e452 100644 --- a/apps/probability/law_curve_view.cpp +++ b/apps/probability/law_curve_view.cpp @@ -5,7 +5,7 @@ namespace Probability { LawCurveView::LawCurveView() : - CurveView(nullptr, nullptr, nullptr, nullptr, 0.2f, 0.1f, 0.2f, 0.1f), + CurveView(nullptr, nullptr, nullptr, nullptr), m_law(nullptr), m_calculation(nullptr) { diff --git a/apps/regression/go_to_parameter_controller.cpp b/apps/regression/go_to_parameter_controller.cpp index f606fa70c..a4b367d75 100644 --- a/apps/regression/go_to_parameter_controller.cpp +++ b/apps/regression/go_to_parameter_controller.cpp @@ -37,12 +37,12 @@ void GoToParameterController::setParameterAtIndex(int parameterIndex, float f) { float y = m_store->yValueForXValue(f); m_store->centerAxisAround(CurveViewRange::Axis::X, f); m_store->centerAxisAround(CurveViewRange::Axis::Y, y); - m_store->moveCursorTo(f, y); + m_cursor->moveTo(f, y); } else { float x = m_store->xValueForYValue(f); m_store->centerAxisAround(CurveViewRange::Axis::X, x); m_store->centerAxisAround(CurveViewRange::Axis::Y, f); - m_store->moveCursorTo(x, f); + m_cursor->moveTo(x, f); } } diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index d18a3f4d2..646921774 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -91,8 +91,6 @@ void GraphController::reloadBannerView() { strlcpy(buffer, legend, legendLength+1); Float(y).convertFloatToText(buffer+legendLength, Float::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); m_bannerView.setLegendAtIndex(buffer, 4); - - m_bannerView.layoutSubviews(); } void GraphController::initRangeParameters() { @@ -102,50 +100,62 @@ void GraphController::initRangeParameters() { void GraphController::initCursorParameters() { float x = (m_store->xMin() + m_store->xMax())/2.0f; float y = m_store->yValueForXValue(x); - m_store->moveCursorTo(x, y); + m_cursor.moveTo(x, y); + m_store->panToMakePointVisible(x, y, k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); m_selectedDotIndex = -1; } -int GraphController::moveCursorHorizontally(int direction) { +bool GraphController::moveCursorHorizontally(int direction) { if (m_selectedDotIndex >= 0) { int dotSelected = m_store->nextDot(direction, m_selectedDotIndex); if (dotSelected >= 0 && dotSelected < m_store->numberOfPairs()) { m_selectedDotIndex = dotSelected; - return m_store->moveCursorTo(m_store->get(0, m_selectedDotIndex), m_store->get(1, m_selectedDotIndex)); + m_cursor.moveTo(m_store->get(0, m_selectedDotIndex), m_store->get(1, m_selectedDotIndex)); + m_store->panToMakePointVisible(m_cursor.x(), m_cursor.y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + return true; } if (dotSelected == m_store->numberOfPairs()) { m_selectedDotIndex = dotSelected; - return m_store->moveCursorTo(m_store->meanOfColumn(0), m_store->meanOfColumn(1)); + m_cursor.moveTo(m_store->meanOfColumn(0), m_store->meanOfColumn(1)); + m_store->panToMakePointVisible(m_cursor.x(), m_cursor.y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + return true; } - return -1; - } else { - float x = direction > 0 ? m_cursor.x() + m_store->xGridUnit()/InteractiveCurveViewRange::k_numberOfCursorStepsInGradUnit : - m_cursor.x() - m_store->xGridUnit()/InteractiveCurveViewRange::k_numberOfCursorStepsInGradUnit; - float y = m_store->yValueForXValue(x); - return m_store->moveCursorTo(x, y); + return false; } + float x = direction > 0 ? m_cursor.x() + m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit : + m_cursor.x() - m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit; + float y = m_store->yValueForXValue(x); + m_cursor.moveTo(x, y); + m_store->panToMakePointVisible(x, y, k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + return true; } -int GraphController::moveCursorVertically(int direction) { +bool GraphController::moveCursorVertically(int direction) { float yRegressionCurve = m_store->yValueForXValue(m_cursor.x()); if (m_selectedDotIndex >= 0) { if ((yRegressionCurve - m_cursor.y() > 0) == (direction > 0)) { m_selectedDotIndex = -1; - return m_store->moveCursorTo(m_cursor.x(), yRegressionCurve); + m_cursor.moveTo(m_cursor.x(), yRegressionCurve); + m_store->panToMakePointVisible(m_cursor.x(), m_cursor.y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + return true; } else { - return -1; + return false; } } else { int dotSelected = m_store->closestVerticalDot(direction, m_cursor.x()); if (dotSelected >= 0 && dotSelected < m_store->numberOfPairs()) { m_selectedDotIndex = dotSelected; - return m_store->moveCursorTo(m_store->get(0, m_selectedDotIndex), m_store->get(1, m_selectedDotIndex)); + m_cursor.moveTo(m_store->get(0, m_selectedDotIndex), m_store->get(1, m_selectedDotIndex)); + m_store->panToMakePointVisible(m_cursor.x(), m_cursor.y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + return true; } if (dotSelected == m_store->numberOfPairs()) { m_selectedDotIndex = dotSelected; - return m_store->moveCursorTo(m_store->meanOfColumn(0), m_store->meanOfColumn(1)); + m_cursor.moveTo(m_store->meanOfColumn(0), m_store->meanOfColumn(1)); + m_store->panToMakePointVisible(m_cursor.x(), m_cursor.y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + return true; } - return -1; + return false; } } diff --git a/apps/regression/graph_controller.h b/apps/regression/graph_controller.h index 6b6594af0..5d41c62ae 100644 --- a/apps/regression/graph_controller.h +++ b/apps/regression/graph_controller.h @@ -19,6 +19,11 @@ public: bool isEmpty() override; const char * emptyMessage() override; private: + constexpr static float k_cursorTopMarginRatio = 0.025f; + constexpr static float k_cursorRightMarginRatio = 0.015f; + constexpr static float k_cursorBottomMarginRatio = 0.16f; + constexpr static float k_cursorLeftMarginRatio = 0.015f; + constexpr static int k_maxNumberOfCharacters = 8; BannerView * bannerView() override; CurveView * curveView() override; @@ -27,8 +32,8 @@ private: void reloadBannerView() override; void initRangeParameters() override; void initCursorParameters() override; - int moveCursorHorizontally(int direction) override; - int moveCursorVertically(int direction) override; + bool moveCursorHorizontally(int direction) override; + bool moveCursorVertically(int direction) override; uint32_t modelVersion() override; uint32_t rangeVersion() override; BannerView m_bannerView; diff --git a/apps/regression/graph_view.cpp b/apps/regression/graph_view.cpp index 970708ae2..e4c18d764 100644 --- a/apps/regression/graph_view.cpp +++ b/apps/regression/graph_view.cpp @@ -5,7 +5,7 @@ namespace Regression { GraphView::GraphView(Store * store, CurveViewCursor * cursor, ::BannerView * bannerView, View * cursorView) : - CurveView(store, cursor, bannerView, cursorView, 0.05f, 0.05f, 0.25f, 0.05f), + CurveView(store, cursor, bannerView, cursorView), m_store(store) { } diff --git a/apps/regression/initialisation_parameter_controller.cpp b/apps/regression/initialisation_parameter_controller.cpp index dc5a3d9a9..5ca63d714 100644 --- a/apps/regression/initialisation_parameter_controller.cpp +++ b/apps/regression/initialisation_parameter_controller.cpp @@ -27,7 +27,8 @@ void InitialisationParameterController::didBecomeFirstResponder() { bool InitialisationParameterController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK) { - RangeMethodPointer rangeMethods[k_totalNumberOfCells] = {&InteractiveCurveViewRange::roundAbscissa, &InteractiveCurveViewRange::normalize, &InteractiveCurveViewRange::setDefault}; + RangeMethodPointer rangeMethods[k_totalNumberOfCells] = {&InteractiveCurveViewRange::roundAbscissa, + &InteractiveCurveViewRange::normalize, &InteractiveCurveViewRange::setDefault}; (m_store->*rangeMethods[m_selectableTableView.selectedRow()])(); StackViewController * stack = (StackViewController *)parentResponder(); stack->pop(); diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index 39c159f0b..7f0220b99 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -140,10 +140,16 @@ int Store::nextDot(int direction, int dot) { /* Window */ void Store::setDefault() { - m_xMin = minValueOfColumn(0); - m_xMax = maxValueOfColumn(0); - m_yMin = minValueOfColumn(1); - m_yMax = maxValueOfColumn(1); + float min = minValueOfColumn(0); + float max = maxValueOfColumn(0); + float range = max - min; + m_xMin = min - k_displayLeftMarginRatio*range; + m_xMax = max + k_displayRightMarginRatio*range; + min = minValueOfColumn(1); + max = maxValueOfColumn(1); + range = max - min; + m_yMin = min - k_displayBottomMarginRatio*range; + m_yMax = max + k_displayTopMarginRatio*range; if (m_xMin == m_xMax) {; m_xMin = m_xMin - 1.0f; m_xMax = m_xMax + 1.0f; diff --git a/apps/regression/store.h b/apps/regression/store.h index b663e7c0b..699152551 100644 --- a/apps/regression/store.h +++ b/apps/regression/store.h @@ -37,6 +37,10 @@ public: float correlationCoefficient(); float squaredCorrelationCoefficient(); private: + constexpr static float k_displayTopMarginRatio = 0.03f; + constexpr static float k_displayRightMarginRatio = 0.02f; + constexpr static float k_displayBottomMarginRatio = 0.2f; + constexpr static float k_displayLeftMarginRatio = 0.02f; float maxValueOfColumn(int i); float minValueOfColumn(int i); }; diff --git a/apps/statistics/box_controller.cpp b/apps/statistics/box_controller.cpp index 19eabdbf2..789d6797f 100644 --- a/apps/statistics/box_controller.cpp +++ b/apps/statistics/box_controller.cpp @@ -69,7 +69,6 @@ void BoxController::reloadBannerView() { float calculation = (m_store->*calculationMethods[(int)m_view.selectedQuantile()])(); Float(calculation).convertFloatToText(buffer, Float::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); m_boxBannerView.setLegendAtIndex(buffer, 1); - m_boxBannerView.layoutSubviews(); } } diff --git a/apps/statistics/box_range.cpp b/apps/statistics/box_range.cpp index f0f9e33cf..be087e243 100644 --- a/apps/statistics/box_range.cpp +++ b/apps/statistics/box_range.cpp @@ -8,22 +8,25 @@ BoxRange::BoxRange(Store * store) : } float BoxRange::xMin() { - return m_store->minValue(); + float min = m_store->minValue(); + float max = m_store->maxValue(); + max = min >= max ? min + 1 : max; + return min - k_displayLeftMarginRatio*(max-min); } float BoxRange::xMax() { - if (m_store->minValue() >= m_store->maxValue()) { - return m_store->minValue() + 1.0f; - } - return m_store->maxValue(); + float min = m_store->minValue(); + float max = m_store->maxValue(); + max = min >= max ? min + 1 : max; + return max + k_displayRightMarginRatio*(max - min); } float BoxRange::yMin() { - return 0.0f; + return -k_displayBottomMarginRatio; } float BoxRange::yMax() { - return 1.0f; + return 1.0f+k_displayTopMarginRatio; } float BoxRange::xGridUnit() { diff --git a/apps/statistics/box_range.h b/apps/statistics/box_range.h index 118572d73..a85da2b3e 100644 --- a/apps/statistics/box_range.h +++ b/apps/statistics/box_range.h @@ -15,6 +15,10 @@ public: float yMax() override; float xGridUnit() override; private: + constexpr static float k_displayTopMarginRatio = 0.0f; + constexpr static float k_displayRightMarginRatio = 0.2f; + constexpr static float k_displayBottomMarginRatio = 0.4f; + constexpr static float k_displayLeftMarginRatio = 0.2f; Store * m_store; }; diff --git a/apps/statistics/box_view.cpp b/apps/statistics/box_view.cpp index 96ee62007..7b50a8d78 100644 --- a/apps/statistics/box_view.cpp +++ b/apps/statistics/box_view.cpp @@ -5,14 +5,15 @@ namespace Statistics { BoxView::BoxView(Store * store, ::BannerView * bannerView) : - CurveView(&m_boxRange, nullptr, bannerView, nullptr, 0.0f, 0.2f, 0.4f, 0.2f), + CurveView(&m_boxRange, nullptr, bannerView, nullptr), m_store(store), m_boxRange(BoxRange(store)), m_selectedQuantile(Quantile::Min) { } -void BoxView::reloadSelection() { +void BoxView::reload() { + CurveView::reload(); CalculPointer calculationMethods[5] = {&Store::minValue, &Store::firstQuartile, &Store::median, &Store::thirdQuartile, &Store::maxValue}; float calculation = (m_store->*calculationMethods[(int)m_selectedQuantile])(); @@ -32,9 +33,9 @@ bool BoxView::selectQuantile(int selectedQuantile) { return false; } if ((int)m_selectedQuantile != selectedQuantile) { - reloadSelection(); + reload(); m_selectedQuantile = (Quantile)selectedQuantile; - reloadSelection(); + reload(); } return true; } diff --git a/apps/statistics/box_view.h b/apps/statistics/box_view.h index 431aeb82f..42208ebc8 100644 --- a/apps/statistics/box_view.h +++ b/apps/statistics/box_view.h @@ -20,7 +20,7 @@ public: Max = 4 }; BoxView(Store * store, ::BannerView * bannerView); - void reloadSelection() override; + void reload() override; Quantile selectedQuantile(); bool selectQuantile(int selectedQuantile); void drawRect(KDContext * ctx, KDRect rect) const override; diff --git a/apps/statistics/histogram_controller.cpp b/apps/statistics/histogram_controller.cpp index 7b5e26d30..b9bbf60d3 100644 --- a/apps/statistics/histogram_controller.cpp +++ b/apps/statistics/histogram_controller.cpp @@ -43,7 +43,7 @@ bool HistogramController::handleEvent(Ion::Events::Event event) { if (!m_view.isMainViewSelected()) { headerViewController()->setSelectedButton(-1); m_view.selectMainView(true); - m_view.reloadSelection(); + m_view.reload(); reloadBannerView(); return true; } @@ -146,7 +146,6 @@ void HistogramController::reloadBannerView() { float frequency = size/m_store->sumOfColumn(1); Float(frequency).convertFloatToText(buffer+legendLength, Float::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); m_bannerView.setLegendAtIndex(buffer, 2); - m_bannerView.layoutSubviews(); } bool HistogramController::moveSelection(int deltaIndex) { @@ -182,9 +181,8 @@ void HistogramController::initRangeParameters() { if (xMin >= xMax) { xMax = xMin + 10.0f*barWidth; } - m_store->setXMin(xMin); - m_store->setXMax(xMax); - m_store->setYMin(0.0f); + m_store->setXMin(xMin - Store::k_displayLeftMarginRatio*(xMax-xMin)); + m_store->setXMax(xMax + Store::k_displayRightMarginRatio*(xMax-xMin)); float yMax = -FLT_MAX; for (int index = 0; index < m_store->numberOfBars(); index++) { float size = m_store->heightOfBarAtIndex(index); @@ -193,7 +191,8 @@ void HistogramController::initRangeParameters() { } } yMax = yMax/m_store->sumOfColumn(1); - m_store->setYMax(yMax); + m_store->setYMin(-Store::k_displayBottomMarginRatio*yMax); + m_store->setYMax(yMax*(1.0f+Store::k_displayTopMarginRatio)); } void HistogramController::initBarParameters() { diff --git a/apps/statistics/histogram_view.cpp b/apps/statistics/histogram_view.cpp index f5b78cd6e..68d1069b2 100644 --- a/apps/statistics/histogram_view.cpp +++ b/apps/statistics/histogram_view.cpp @@ -5,14 +5,15 @@ namespace Statistics { HistogramView::HistogramView(Store * store, ::BannerView * bannerView) : - CurveView(store, nullptr, bannerView, nullptr, 0.2f, 0.05f, 0.4f, 0.05f), + CurveView(store, nullptr, bannerView, nullptr), m_store(store), m_highlightedBarStart(NAN), m_highlightedBarEnd(NAN) { } -void HistogramView::reloadSelection() { +void HistogramView::reload() { + CurveView::reload(); float pixelLowerBound = floatToPixel(Axis::Horizontal, m_highlightedBarStart)-2; float pixelUpperBound = floatToPixel(Axis::Horizontal, m_highlightedBarEnd)+2; KDRect dirtyZone(KDRect(pixelLowerBound, 0, pixelUpperBound-pixelLowerBound, @@ -34,10 +35,10 @@ void HistogramView::drawRect(KDContext * ctx, KDRect rect) const { void HistogramView::setHighlight(float start, float end) { if (m_highlightedBarStart != start || m_highlightedBarEnd != end) { - reloadSelection(); + reload(); m_highlightedBarStart = start; m_highlightedBarEnd = end; - reloadSelection(); + reload(); } } diff --git a/apps/statistics/histogram_view.h b/apps/statistics/histogram_view.h index e6aedca20..0046e36e9 100644 --- a/apps/statistics/histogram_view.h +++ b/apps/statistics/histogram_view.h @@ -11,7 +11,7 @@ namespace Statistics { class HistogramView : public CurveView { public: HistogramView(Store * store, ::BannerView * bannerView); - void reloadSelection() override; + void reload() override; void drawRect(KDContext * ctx, KDRect rect) const override; void setHighlight(float start, float end); private: diff --git a/apps/statistics/store.cpp b/apps/statistics/store.cpp index 1ffe6d5cf..fab30ffb9 100644 --- a/apps/statistics/store.cpp +++ b/apps/statistics/store.cpp @@ -8,7 +8,7 @@ namespace Statistics { Store::Store() : - InteractiveCurveViewRange(nullptr, nullptr), + MemoizedCurveViewRange(), FloatPairStore(), m_barWidth(1.0f), m_firstDrawnBarAbscissa(0.0f) @@ -72,14 +72,14 @@ int Store::numberOfBars() { bool Store::scrollToSelectedBarIndex(int index) { float startSelectedBar = startOfBarAtIndex(index); float range = m_xMax - m_xMin; - if (m_xMin > startSelectedBar) { - m_xMin = startSelectedBar; + if (m_xMin + k_displayLeftMarginRatio*range > startSelectedBar) { + m_xMin = startSelectedBar - k_displayLeftMarginRatio*range; m_xMax = m_xMin + range; return true; } float endSelectedBar = endOfBarAtIndex(index); - if (endSelectedBar > m_xMax) { - m_xMax = endSelectedBar; + if (endSelectedBar > m_xMax - k_displayRightMarginRatio*range) { + m_xMax = endSelectedBar + k_displayRightMarginRatio*range; m_xMin = m_xMax - range; return true; } diff --git a/apps/statistics/store.h b/apps/statistics/store.h index 28e851f3a..aacb8c3d2 100644 --- a/apps/statistics/store.h +++ b/apps/statistics/store.h @@ -1,12 +1,12 @@ #ifndef STATISTICS_STORE_H #define STATISTICS_STORE_H -#include "../interactive_curve_view_range.h" +#include "../memoized_curve_view_range.h" #include "../float_pair_store.h" namespace Statistics { -class Store : public InteractiveCurveViewRange, public FloatPairStore { +class Store : public MemoizedCurveViewRange, public FloatPairStore { public: Store(); uint32_t barChecksum(); @@ -37,6 +37,10 @@ public: float median(); float sum(); float squaredValueSum(); + constexpr static float k_displayTopMarginRatio = 0.03f; + constexpr static float k_displayRightMarginRatio = 0.02f; + constexpr static float k_displayBottomMarginRatio = 0.3f; + constexpr static float k_displayLeftMarginRatio = 0.02f; private: float defaultValue(int i) override; float sumOfValuesBetween(float x1, float x2); diff --git a/poincare/Makefile b/poincare/Makefile index 5b6fe17ad..759e538ad 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -29,6 +29,7 @@ objs += $(addprefix poincare/src/,\ opposite.o\ parenthesis.o\ power.o\ + product.o\ sine.o\ subtraction.o\ sum.o\ @@ -46,9 +47,11 @@ objs += $(addprefix poincare/src/layout/,\ matrix_layout.o\ nth_root_layout.o\ parenthesis_layout.o\ + product_layout.o\ string_layout.o\ sum_layout.o\ subscript_layout.o\ + symbol_layout.o\ ) tests += $(addprefix poincare/test/,\ diff --git a/poincare/include/poincare.h b/poincare/include/poincare.h index 9c97b3c16..5ee726d56 100644 --- a/poincare/include/poincare.h +++ b/poincare/include/poincare.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 75bff507c..190400e78 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -25,6 +25,7 @@ class Expression { Fraction, Parenthesis, Power, + Product, Sine, Sum, Subtraction, diff --git a/poincare/include/poincare/product.h b/poincare/include/poincare/product.h new file mode 100644 index 000000000..4fc8fb1f2 --- /dev/null +++ b/poincare/include/poincare/product.h @@ -0,0 +1,17 @@ +#ifndef POINCARE_PRODUCT_H +#define POINCARE_PRODUCT_H + +#include +#include + +class Product : public Function { +public: + Product(); + float approximate(Context & context) const override; + Type type() const override; + Expression * cloneWithDifferentOperands(Expression ** newOperands, + int numberOfOperands, bool cloneOperands = true) const override; + ExpressionLayout * createLayout() const override; +}; + +#endif diff --git a/poincare/src/expression_lexer.l b/poincare/src/expression_lexer.l index a31ed323c..2586d2132 100644 --- a/poincare/src/expression_lexer.l +++ b/poincare/src/expression_lexer.l @@ -90,6 +90,7 @@ tan { poincare_expression_yylval.expression = new Tangent(); return FUNCTION; } log { poincare_expression_yylval.expression = new Logarithm(); return FUNCTION; } root { poincare_expression_yylval.expression = new NthRoot(); return FUNCTION; } sum { poincare_expression_yylval.expression = new Sum(); return FUNCTION; } +product { poincare_expression_yylval.expression = new Product(); return FUNCTION; } \+ { return PLUS; } \- { return MINUS; } \* { return MULTIPLY; } diff --git a/poincare/src/layout/product_layout.cpp b/poincare/src/layout/product_layout.cpp new file mode 100644 index 000000000..b5de1234b --- /dev/null +++ b/poincare/src/layout/product_layout.cpp @@ -0,0 +1,78 @@ +#include "product_layout.h" +#include +#include + +ProductLayout::ProductLayout(ExpressionLayout * lowerBoundLayout, ExpressionLayout * upperBoundLayout, ExpressionLayout * argumentLayout) : + ExpressionLayout(), + m_lowerBoundLayout(lowerBoundLayout), + m_upperBoundLayout(upperBoundLayout), + m_argumentLayout(argumentLayout) +{ + m_lowerBoundLayout->setParent(this); + m_upperBoundLayout->setParent(this); + m_argumentLayout->setParent(this); + m_baseline = max(m_upperBoundLayout->size().height()+k_boundHeightMargin+k_symbolHeight, m_argumentLayout->baseline()); +} + +ProductLayout::~ProductLayout() { + delete m_lowerBoundLayout; + delete m_upperBoundLayout; + delete m_argumentLayout; +} + +void ProductLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + KDSize upperBoundSize = m_upperBoundLayout->size(); + KDSize lowerBoundSize = m_lowerBoundLayout->size(); + ctx->fillRect(KDRect(p.x() + max(max(0, (upperBoundSize.width()-k_symbolWidth)/2), (lowerBoundSize.width()-k_symbolWidth)/2), + p.y() + max(upperBoundSize.height()+k_boundHeightMargin, m_argumentLayout->baseline()-k_symbolHeight), + k_lineThickness, k_symbolHeight), expressionColor); + ctx->fillRect(KDRect(p.x() + max(max(0, (upperBoundSize.width()-k_symbolWidth)/2), (lowerBoundSize.width()-k_symbolWidth)/2), + p.y() + max(upperBoundSize.height()+k_boundHeightMargin, m_argumentLayout->baseline()-k_symbolHeight), + k_symbolWidth, k_lineThickness), expressionColor); + ctx->fillRect(KDRect(p.x() + max(max(0, (upperBoundSize.width()-k_symbolWidth)/2), (lowerBoundSize.width()-k_symbolWidth)/2)+k_symbolWidth, + p.y() + max(upperBoundSize.height()+k_boundHeightMargin, m_argumentLayout->baseline()-k_symbolHeight), + k_lineThickness, k_symbolHeight), expressionColor); +} + +KDSize ProductLayout::computeSize() { + KDSize argumentSize = m_argumentLayout->size(); + KDSize lowerBoundSize = m_lowerBoundLayout->size(); + KDSize upperBoundSize = m_upperBoundLayout->size(); + return KDSize( + max(max(k_symbolWidth, lowerBoundSize.width()), upperBoundSize.width())+k_argumentWidthMargin+argumentSize.width(), + m_baseline + max(k_boundHeightMargin+lowerBoundSize.height(), argumentSize.height() - m_argumentLayout->baseline()) + ); +} + +ExpressionLayout * ProductLayout::child(uint16_t index) { + switch (index) { + case 0: + return m_upperBoundLayout; + case 1: + return m_lowerBoundLayout; + case 2: + return m_argumentLayout; + default: + return nullptr; + } +} + +KDPoint ProductLayout::positionOfChild(ExpressionLayout * child) { + KDSize lowerBoundSize = m_lowerBoundLayout->size(); + KDSize upperBoundSize = m_upperBoundLayout->size(); + KDCoordinate x = 0; + KDCoordinate y = 0; + if (child == m_lowerBoundLayout) { + x = max(max(0, (k_symbolWidth-lowerBoundSize.width())/2), (upperBoundSize.width()-lowerBoundSize.width())/2); + y = m_baseline + k_boundHeightMargin; + } else if (child == m_upperBoundLayout) { + x = max(max(0, (k_symbolWidth-upperBoundSize.width())/2), (lowerBoundSize.width()-upperBoundSize.width())/2); + y = m_baseline - k_symbolHeight- k_boundHeightMargin-upperBoundSize.height(); + } else if (child == m_argumentLayout) { + x = max(max(k_symbolWidth, lowerBoundSize.width()), upperBoundSize.width())+k_argumentWidthMargin; + y = m_baseline - m_argumentLayout->baseline(); + } else { + assert(false); + } + return KDPoint(x,y); +} diff --git a/poincare/src/layout/product_layout.h b/poincare/src/layout/product_layout.h new file mode 100644 index 000000000..5011b2a72 --- /dev/null +++ b/poincare/src/layout/product_layout.h @@ -0,0 +1,27 @@ +#ifndef POINCARE_PRODUCT_LAYOUT_H +#define POINCARE_PRODUCT_LAYOUT_H + +#include +#include + +class ProductLayout : public ExpressionLayout { +public: + ProductLayout(ExpressionLayout * lowerBoundLayout, ExpressionLayout * upperBoundLayout, ExpressionLayout * argumentLayout); + ~ProductLayout(); +protected: + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + KDSize computeSize() override; + ExpressionLayout * child(uint16_t index) override; + KDPoint positionOfChild(ExpressionLayout * child) override; +private: + constexpr static KDCoordinate k_symbolHeight = 15; + constexpr static KDCoordinate k_symbolWidth = 9; + constexpr static KDCoordinate k_boundHeightMargin = 2; + constexpr static KDCoordinate k_argumentWidthMargin = 2; + constexpr static KDCoordinate k_lineThickness = 1; + ExpressionLayout * m_lowerBoundLayout; + ExpressionLayout * m_upperBoundLayout; + ExpressionLayout * m_argumentLayout; +}; + +#endif diff --git a/poincare/src/layout/sum_layout.cpp b/poincare/src/layout/sum_layout.cpp index 21668f7ce..509e2225a 100644 --- a/poincare/src/layout/sum_layout.cpp +++ b/poincare/src/layout/sum_layout.cpp @@ -20,24 +20,6 @@ const uint8_t symbolPixel[SumLayout::k_symbolHeight][SumLayout::k_symbolWidth] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, }; -SumLayout::SumLayout(ExpressionLayout * lowerBoundLayout, ExpressionLayout * upperBoundLayout, ExpressionLayout * argumentLayout) : - ExpressionLayout(), - m_lowerBoundLayout(lowerBoundLayout), - m_upperBoundLayout(upperBoundLayout), - m_argumentLayout(argumentLayout) -{ - m_lowerBoundLayout->setParent(this); - m_upperBoundLayout->setParent(this); - m_argumentLayout->setParent(this); - m_baseline = max(m_upperBoundLayout->size().height()+k_boundHeightMargin+k_symbolHeight, m_argumentLayout->baseline()); -} - -SumLayout::~SumLayout() { - delete m_lowerBoundLayout; - delete m_upperBoundLayout; - delete m_argumentLayout; -} - void SumLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { KDSize upperBoundSize = m_upperBoundLayout->size(); KDSize lowerBoundSize = m_lowerBoundLayout->size(); @@ -47,46 +29,3 @@ void SumLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDCo k_symbolWidth, k_symbolHeight); ctx->blendRectWithMask(symbolFrame, expressionColor, (const uint8_t *)symbolPixel, (KDColor *)workingBuffer); } - -KDSize SumLayout::computeSize() { - KDSize argumentSize = m_argumentLayout->size(); - KDSize lowerBoundSize = m_lowerBoundLayout->size(); - KDSize upperBoundSize = m_upperBoundLayout->size(); - return KDSize( - max(max(k_symbolWidth, lowerBoundSize.width()), upperBoundSize.width())+k_argumentWidthMargin+argumentSize.width(), - m_baseline + max(k_boundHeightMargin+lowerBoundSize.height(), argumentSize.height() - m_argumentLayout->baseline()) - ); -} - -ExpressionLayout * SumLayout::child(uint16_t index) { - switch (index) { - case 0: - return m_upperBoundLayout; - case 1: - return m_lowerBoundLayout; - case 2: - return m_argumentLayout; - default: - return nullptr; - } -} - -KDPoint SumLayout::positionOfChild(ExpressionLayout * child) { - KDSize lowerBoundSize = m_lowerBoundLayout->size(); - KDSize upperBoundSize = m_upperBoundLayout->size(); - KDCoordinate x = 0; - KDCoordinate y = 0; - if (child == m_lowerBoundLayout) { - x = max(max(0, (k_symbolWidth-lowerBoundSize.width())/2), (upperBoundSize.width()-lowerBoundSize.width())/2); - y = m_baseline + k_boundHeightMargin; - } else if (child == m_upperBoundLayout) { - x = max(max(0, (k_symbolWidth-upperBoundSize.width())/2), (lowerBoundSize.width()-upperBoundSize.width())/2); - y = m_baseline - k_symbolHeight- k_boundHeightMargin-upperBoundSize.height(); - } else if (child == m_argumentLayout) { - x = max(max(k_symbolWidth, lowerBoundSize.width()), upperBoundSize.width())+k_argumentWidthMargin; - y = m_baseline - m_argumentLayout->baseline(); - } else { - assert(false); - } - return KDPoint(x,y); -} diff --git a/poincare/src/layout/sum_layout.h b/poincare/src/layout/sum_layout.h index d1301402a..e942632ce 100644 --- a/poincare/src/layout/sum_layout.h +++ b/poincare/src/layout/sum_layout.h @@ -1,26 +1,13 @@ #ifndef POINCARE_SUM_LAYOUT_H #define POINCARE_SUM_LAYOUT_H -#include -#include +#include "symbol_layout.h" -class SumLayout : public ExpressionLayout { +class SumLayout : public SymbolLayout { public: - SumLayout(ExpressionLayout * lowerBoundLayout, ExpressionLayout * upperBoundLayout, ExpressionLayout * argumentLayout); - ~SumLayout(); - constexpr static KDCoordinate k_symbolHeight = 15; - constexpr static KDCoordinate k_symbolWidth = 9; -protected: - void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; - KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; - KDPoint positionOfChild(ExpressionLayout * child) override; + using SymbolLayout::SymbolLayout; private: - constexpr static KDCoordinate k_boundHeightMargin = 2; - constexpr static KDCoordinate k_argumentWidthMargin = 2; - ExpressionLayout * m_lowerBoundLayout; - ExpressionLayout * m_upperBoundLayout; - ExpressionLayout * m_argumentLayout; + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; }; #endif diff --git a/poincare/src/layout/symbol_layout.cpp b/poincare/src/layout/symbol_layout.cpp new file mode 100644 index 000000000..95f233709 --- /dev/null +++ b/poincare/src/layout/symbol_layout.cpp @@ -0,0 +1,64 @@ +#include "symbol_layout.h" +#include +#include + +SymbolLayout::SymbolLayout(ExpressionLayout * lowerBoundLayout, ExpressionLayout * upperBoundLayout, ExpressionLayout * argumentLayout) : + ExpressionLayout(), + m_lowerBoundLayout(lowerBoundLayout), + m_upperBoundLayout(upperBoundLayout), + m_argumentLayout(argumentLayout) +{ + m_lowerBoundLayout->setParent(this); + m_upperBoundLayout->setParent(this); + m_argumentLayout->setParent(this); + m_baseline = max(m_upperBoundLayout->size().height()+k_boundHeightMargin+k_symbolHeight, m_argumentLayout->baseline()); +} + +SymbolLayout::~SymbolLayout() { + delete m_lowerBoundLayout; + delete m_upperBoundLayout; + delete m_argumentLayout; +} + +KDSize SymbolLayout::computeSize() { + KDSize argumentSize = m_argumentLayout->size(); + KDSize lowerBoundSize = m_lowerBoundLayout->size(); + KDSize upperBoundSize = m_upperBoundLayout->size(); + return KDSize( + max(max(k_symbolWidth, lowerBoundSize.width()), upperBoundSize.width())+k_argumentWidthMargin+argumentSize.width(), + m_baseline + max(k_boundHeightMargin+lowerBoundSize.height(), argumentSize.height() - m_argumentLayout->baseline()) + ); +} + +ExpressionLayout * SymbolLayout::child(uint16_t index) { + switch (index) { + case 0: + return m_upperBoundLayout; + case 1: + return m_lowerBoundLayout; + case 2: + return m_argumentLayout; + default: + return nullptr; + } +} + +KDPoint SymbolLayout::positionOfChild(ExpressionLayout * child) { + KDSize lowerBoundSize = m_lowerBoundLayout->size(); + KDSize upperBoundSize = m_upperBoundLayout->size(); + KDCoordinate x = 0; + KDCoordinate y = 0; + if (child == m_lowerBoundLayout) { + x = max(max(0, (k_symbolWidth-lowerBoundSize.width())/2), (upperBoundSize.width()-lowerBoundSize.width())/2); + y = m_baseline + k_boundHeightMargin; + } else if (child == m_upperBoundLayout) { + x = max(max(0, (k_symbolWidth-upperBoundSize.width())/2), (lowerBoundSize.width()-upperBoundSize.width())/2); + y = m_baseline - k_symbolHeight- k_boundHeightMargin-upperBoundSize.height(); + } else if (child == m_argumentLayout) { + x = max(max(k_symbolWidth, lowerBoundSize.width()), upperBoundSize.width())+k_argumentWidthMargin; + y = m_baseline - m_argumentLayout->baseline(); + } else { + assert(false); + } + return KDPoint(x,y); +} diff --git a/poincare/src/layout/symbol_layout.h b/poincare/src/layout/symbol_layout.h new file mode 100644 index 000000000..1f473147f --- /dev/null +++ b/poincare/src/layout/symbol_layout.h @@ -0,0 +1,25 @@ +#ifndef POINCARE_SYMBOL_LAYOUT_H +#define POINCARE_SYMBOL_LAYOUT_H + +#include +#include + +class SymbolLayout : public ExpressionLayout { +public: + SymbolLayout(ExpressionLayout * lowerBoundLayout, ExpressionLayout * upperBoundLayout, ExpressionLayout * argumentLayout); + ~SymbolLayout(); + constexpr static KDCoordinate k_symbolHeight = 15; + constexpr static KDCoordinate k_symbolWidth = 9; +protected: + constexpr static KDCoordinate k_boundHeightMargin = 2; + ExpressionLayout * m_lowerBoundLayout; + ExpressionLayout * m_upperBoundLayout; + ExpressionLayout * m_argumentLayout; +private: + KDSize computeSize() override; + ExpressionLayout * child(uint16_t index) override; + KDPoint positionOfChild(ExpressionLayout * child) override; + constexpr static KDCoordinate k_argumentWidthMargin = 2; +}; + +#endif diff --git a/poincare/src/product.cpp b/poincare/src/product.cpp new file mode 100644 index 000000000..d1b6dc77b --- /dev/null +++ b/poincare/src/product.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include "layout/string_layout.h" +#include "layout/horizontal_layout.h" +#include "layout/product_layout.h" +extern "C" { +#include +#include +} + +Product::Product() : + Function("product") +{ +} + +Expression::Type Product::type() const { + return Type::Product; +} + +Expression * Product::cloneWithDifferentOperands(Expression** newOperands, + int numberOfOperands, bool cloneOperands) const { + assert(numberOfOperands == 2); + assert(newOperands != nullptr); + Product * p = new Product(); + p->setArgument(newOperands, numberOfOperands, cloneOperands); + return p; +} + +float Product::approximate(Context& context) const { + NContext nContext = NContext(&context); + Symbol nSymbol = Symbol('n'); + int start = m_args[1]->approximate(context); + int end = m_args[2]->approximate(context); + float result = 1.0f; + for (int i = start; i <= end; i++) { + Float iExpression = Float(i); + nContext.setExpressionForSymbolName(&iExpression, &nSymbol); + result = result*m_args[0]->approximate(nContext); + } + return result; +} + +ExpressionLayout * Product::createLayout() const { + ExpressionLayout ** childrenLayouts = (ExpressionLayout **)malloc(2*sizeof(ExpressionLayout *)); + childrenLayouts[0] = new StringLayout("n=", 2); + childrenLayouts[1] = m_args[1]->createLayout(); + return new ProductLayout(new HorizontalLayout(childrenLayouts, 2), m_args[2]->createLayout(), m_args[0]->createLayout()); +}