diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 2505522de..3bbe81936 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -56,7 +56,7 @@ jobs: steps: - uses: numworks/setup-emscripten@v2 with: - sdk: latest-fastcomp + sdk: 1.39.16-fastcomp - uses: actions/checkout@v1 with: submodules: true diff --git a/apps/calculation/additional_outputs/illustrated_list_controller.cpp b/apps/calculation/additional_outputs/illustrated_list_controller.cpp index 80ea4f52a..3fd481ee6 100644 --- a/apps/calculation/additional_outputs/illustrated_list_controller.cpp +++ b/apps/calculation/additional_outputs/illustrated_list_controller.cpp @@ -78,7 +78,13 @@ KDCoordinate IllustratedListController::rowHeight(int j) { return 0; } Shared::ExpiringPointer calculation = m_calculationStore.calculationAtIndex(calculationIndex); - return calculation->height(App::app()->localContext(), true, true) + 2 * Metric::CommonSmallMargin + Metric::CellSeparatorThickness; + constexpr bool expanded = true; + KDCoordinate result = calculation->memoizedHeight(expanded); + if (result < 0) { + result = ScrollableThreeExpressionsCell::Height(calculation.pointer()); + calculation->setMemoizedHeight(expanded, result); + } + return result + Metric::CellSeparatorThickness; } int IllustratedListController::typeAtLocation(int i, int j) { diff --git a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp index d90fc628d..29fe03a9a 100644 --- a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp +++ b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp @@ -65,6 +65,18 @@ void ScrollableThreeExpressionsView::setCalculation(Calculation * calculation) { layoutSubviews(); } +KDCoordinate ScrollableThreeExpressionsCell::Height(Calculation * calculation) { + ScrollableThreeExpressionsCell cell; + cell.setCalculation(calculation); + KDRect leftFrame = KDRectZero; + KDRect centerFrame = KDRectZero; + KDRect approximateSignFrame = KDRectZero; + KDRect rightFrame = KDRectZero; + cell.subviewFrames(&leftFrame, ¢erFrame, &approximateSignFrame, &rightFrame); + KDRect unionedFrame = leftFrame.unionedWith(centerFrame).unionedWith(rightFrame); + return unionedFrame.height() + 2 * ScrollableThreeExpressionsView::k_margin; +} + void ScrollableThreeExpressionsCell::didBecomeFirstResponder() { reinitSelection(); Container::activeApp()->setFirstResponder(&m_view); diff --git a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h index 42c18becb..e8daa7f26 100644 --- a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h +++ b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h @@ -10,12 +10,16 @@ namespace Calculation { class ScrollableThreeExpressionsView : public Shared::AbstractScrollableMultipleExpressionsView { public: + static constexpr KDCoordinate k_margin = Metric::CommonSmallMargin; ScrollableThreeExpressionsView(Responder * parentResponder) : Shared::AbstractScrollableMultipleExpressionsView(parentResponder, &m_contentCell), m_contentCell() { - setMargins(Metric::CommonSmallMargin, Metric::CommonSmallMargin, Metric::CommonSmallMargin, Metric::CommonSmallMargin); // Left Right margins are already added by TableCell + setMargins(k_margin, k_margin, k_margin, k_margin); // Left Right margins are already added by TableCell setBackgroundColor(KDColorWhite); } void resetMemoization(); void setCalculation(Calculation * calculation); + void subviewFrames(KDRect * leftFrame, KDRect * centerFrame, KDRect * approximateSignFrame, KDRect * rightFrame) { + return m_contentCell.subviewFrames(leftFrame, centerFrame, approximateSignFrame, rightFrame); + } private: class ContentCell : public Shared::AbstractScrollableMultipleExpressionsView::ContentCell { public: @@ -28,12 +32,13 @@ private: }; ContentCell * contentCell() override { return &m_contentCell; }; - const ContentCell * constContentCell() const override { return &m_contentCell; }; + const ContentCell * constContentCell() const override { return &m_contentCell; }; ContentCell m_contentCell; }; class ScrollableThreeExpressionsCell : public TableCell, public Responder { public: + static KDCoordinate Height(Calculation * calculation); ScrollableThreeExpressionsCell() : Responder(nullptr), m_view(this) {} @@ -58,6 +63,9 @@ public: void setSelectedSubviewPosition(ScrollableThreeExpressionsView::SubviewPosition subviewPosition) { m_view.setSelectedSubviewPosition(subviewPosition); } void reinitSelection(); + void subviewFrames(KDRect * leftFrame, KDRect * centerFrame, KDRect * approximateSignFrame, KDRect * rightFrame) { + return m_view.subviewFrames(leftFrame, centerFrame, approximateSignFrame, rightFrame); + } private: // Remove label margin added by TableCell because they're already handled by ScrollableThreeExpressionsView KDCoordinate labelMargin() const override { return 0; } diff --git a/apps/calculation/base.de.i18n b/apps/calculation/base.de.i18n index ebb1bd928..18aab0374 100644 --- a/apps/calculation/base.de.i18n +++ b/apps/calculation/base.de.i18n @@ -4,6 +4,6 @@ AdditionalResults = "Weitere Ergebnisse" DecimalBase = "Dezimal" HexadecimalBase = "Hexadezimal" BinaryBase = "Binär" -PrimeFactors = "Primfaktor" -MixedFraction = "Gemischte Fraktion" -EuclideanDivision = "Euklidische Division" +PrimeFactors = "Primfaktoren" +MixedFraction = "Gemischte Zahl" +EuclideanDivision = "Division mit Rest" diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 3fdf405fc..646e1c05d 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -1,5 +1,6 @@ #include "calculation.h" #include "../shared/poincare_helpers.h" +#include "../shared/scrollable_multiple_expressions_view.h" #include "../global_preferences.h" #include "../exam_mode_configuration.h" #include @@ -124,123 +125,12 @@ Layout Calculation::createApproximateOutputLayout(Context * context, bool * coul } } -KDCoordinate Calculation::height(Context * context, bool expanded, bool allExpressionsInline) { - KDCoordinate result = expanded ? m_expandedHeight : m_height; - if (result >= 0) { - // Height already computed - return result; - } - - // Get input height - Layout inputLayout = createInputLayout(); - KDCoordinate inputHeight = inputLayout.layoutSize().height(); - KDCoordinate inputWidth = inputLayout.layoutSize().width(); - float singleMargin = 2 * Metric::CommonSmallMargin; - float doubleMargin = 4 * Metric::CommonSmallMargin; - bool singleLine = false; - KDCoordinate inputBaseline = inputLayout.baseline(); - - // Get exact output height if needed - Poincare::Layout exactLayout; - bool couldNotCreateExactLayout = false; - if (DisplaysExact(displayOutput(context))) { - // Create the exact output layout - exactLayout = createExactOutputLayout(&couldNotCreateExactLayout); - if (couldNotCreateExactLayout) { - if (displayOutput(context) != DisplayOutput::ExactOnly) { - forceDisplayOutput(DisplayOutput::ApproximateOnly); - } else { - /* We should only display the exact result, but we cannot create it - * -> raise an exception. */ - ExceptionCheckpoint::Raise(); - } - } - } - - if (displayOutput(context) == DisplayOutput::ExactOnly) { - KDCoordinate exactOutputHeight = exactLayout.layoutSize().height(); - KDCoordinate exactOutputWidth = exactLayout.layoutSize().width(); - singleLine = exactOutputWidth + inputWidth < maxWidth - 40; - if (singleLine && Poincare::Preferences::sharedPreferences()->resultDisplay() == Poincare::Preferences::ResultDisplay::Compact && !allExpressionsInline) { - KDCoordinate exactOutputBaseline = exactLayout.baseline(); - result = std::max(inputBaseline, exactOutputBaseline) + std::max(inputHeight - inputBaseline, exactOutputHeight-exactOutputBaseline) + singleMargin; - } else { - if (allExpressionsInline) { - KDCoordinate exactOutputBaseline = exactLayout.baseline(); - result = std::max(inputBaseline, exactOutputBaseline) + std::max(inputHeight - inputBaseline, exactOutputHeight-exactOutputBaseline); - } else { - result = inputHeight + exactOutputHeight + doubleMargin; - } - } +void Calculation::setMemoizedHeight(bool expanded, KDCoordinate height) { + if (expanded) { + m_expandedHeight = height; } else { - bool couldNotCreateApproximateLayout = false; - Layout approximateLayout = createApproximateOutputLayout(context, &couldNotCreateApproximateLayout); - if (couldNotCreateApproximateLayout) { - if (displayOutput(context) == DisplayOutput::ApproximateOnly) { - Poincare::ExceptionCheckpoint::Raise(); - } else { - /* Set the display output to ApproximateOnly, make room in the pool by - * erasing the exact layout, and retry to create the approximate layout */ - forceDisplayOutput(DisplayOutput::ApproximateOnly); - exactLayout = Poincare::Layout(); - couldNotCreateApproximateLayout = false; - approximateLayout = createApproximateOutputLayout(context, &couldNotCreateApproximateLayout); - if (couldNotCreateApproximateLayout) { - Poincare::ExceptionCheckpoint::Raise(); - } - } - } - - KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height(); - KDCoordinate approximateOutputWidth = approximateLayout.layoutSize().width(); - singleLine = approximateOutputWidth + inputWidth < maxWidth - 40; - if (displayOutput(context) == DisplayOutput::ApproximateOnly || (!expanded && displayOutput(context) == DisplayOutput::ExactAndApproximateToggle)) { - if (singleLine && Poincare::Preferences::sharedPreferences()->resultDisplay() == Poincare::Preferences::ResultDisplay::Compact && !allExpressionsInline) { - KDCoordinate approximateOutputBaseline = approximateLayout.baseline(); - result = std::max(inputBaseline, approximateOutputBaseline) + std::max(inputHeight - inputBaseline, approximateOutputHeight-approximateOutputBaseline) + singleMargin; - } else { - if (allExpressionsInline) { - KDCoordinate approximateOutputBaseline = approximateLayout.baseline(); - result = std::max(inputBaseline, approximateOutputBaseline) + std::max(inputHeight - inputBaseline, approximateOutputHeight-approximateOutputBaseline); - } else { - result = inputHeight + approximateOutputHeight + doubleMargin; - } - } - } else { - assert(displayOutput(context) == DisplayOutput::ExactAndApproximate || (displayOutput(context) == DisplayOutput::ExactAndApproximateToggle && expanded)); - KDCoordinate exactOutputHeight = exactLayout.layoutSize().height(); - KDCoordinate exactOutputBaseline = exactLayout.baseline(); - KDCoordinate exactOutputWidth = exactLayout.layoutSize().width(); - KDCoordinate approximateOutputWidth = approximateLayout.layoutSize().width(); - singleLine = exactOutputWidth + approximateOutputWidth + inputWidth < maxWidth - 70; - KDCoordinate approximateOutputBaseline = approximateLayout.baseline(); - if (singleLine && Poincare::Preferences::sharedPreferences()->resultDisplay() == Poincare::Preferences::ResultDisplay::Compact) { - result = std::max(inputBaseline, std::max(exactOutputBaseline, approximateOutputBaseline)) + std::max(inputHeight - inputBaseline, std::max(exactOutputHeight - exactOutputBaseline, approximateOutputHeight-approximateOutputBaseline)) + singleMargin; - } else { - if (allExpressionsInline) { - result = std::max(inputBaseline, std::max(exactOutputBaseline, approximateOutputBaseline)) + std::max(inputHeight - inputBaseline, std::max(exactOutputHeight - exactOutputBaseline, approximateOutputHeight-approximateOutputBaseline)); - } else { - KDCoordinate outputHeight = std::max(exactOutputBaseline, approximateOutputBaseline) + std::max(exactOutputHeight-exactOutputBaseline, approximateOutputHeight-approximateOutputBaseline); - result = inputHeight + outputHeight + doubleMargin; - } - } - } + m_height = height; } - - /* For all display outputs except ExactAndApproximateToggle, the selected - * height and the usual height are identical. We update both heights in - * theses cases. */ - if (displayOutput(context) != DisplayOutput::ExactAndApproximateToggle) { - m_height = result; - m_expandedHeight = result; - } else { - if (expanded) { - m_expandedHeight = result; - } else { - m_height = result; - } - } - return result; } Calculation::DisplayOutput Calculation::displayOutput(Context * context) { diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index b084226df..5c23cadee 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -84,7 +84,8 @@ public: Poincare::Layout createApproximateOutputLayout(Poincare::Context * context, bool * couldNotCreateApproximateLayout); // Memoization of height - KDCoordinate height(Poincare::Context * context, bool expanded = false, bool allExpressionsInline = false); + KDCoordinate memoizedHeight(bool expanded) { return expanded ? m_expandedHeight : m_height; } + void setMemoizedHeight(bool expanded, KDCoordinate height); // Displayed output DisplayOutput displayOutput(Poincare::Context * context); diff --git a/apps/calculation/expression_field.h b/apps/calculation/expression_field.h index a7fe0e20e..284404628 100644 --- a/apps/calculation/expression_field.h +++ b/apps/calculation/expression_field.h @@ -7,7 +7,10 @@ namespace Calculation { class ExpressionField : public ::ExpressionField { public: - using ::ExpressionField::ExpressionField; + ExpressionField(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandler, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate) : + ::ExpressionField(parentResponder, inputEventHandler, textFieldDelegate, layoutFieldDelegate) { + setLayoutInsertionCursorEvent(Ion::Events::Up); + } protected: bool handleEvent(Ion::Events::Event event) override; }; diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 292a6199e..251cc3a1f 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -166,13 +166,17 @@ void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int } if (previousSelectedCellY == -1) { setSelectedSubviewType(SubviewType::Output, false, previousSelectedCellX, previousSelectedCellY); - } else if (selectedRow() < previousSelectedCellY) { - setSelectedSubviewType(SubviewType::Output, false, previousSelectedCellX, previousSelectedCellY); - } else if (selectedRow() > previousSelectedCellY) { - setSelectedSubviewType(SubviewType::Input, false, previousSelectedCellX, previousSelectedCellY); } else if (selectedRow() == -1) { setSelectedSubviewType(SubviewType::Input, false, previousSelectedCellX, previousSelectedCellY); + } else { + HistoryViewCell * selectedCell = (HistoryViewCell *)(t->selectedCell()); + SubviewType nextSelectedSubviewType = selectedSubviewType(); + if (!selectedCell->displaysSingleLine()) { + nextSelectedSubviewType = previousSelectedCellY < selectedRow() ? SubviewType::Input : SubviewType::Output; + } + setSelectedSubviewType(nextSelectedSubviewType, false, previousSelectedCellX, previousSelectedCellY); } + // The selectedCell may change during setSelectedSubviewType HistoryViewCell * selectedCell = (HistoryViewCell *)(t->selectedCell()); if (selectedCell == nullptr) { return; @@ -208,7 +212,13 @@ KDCoordinate HistoryController::rowHeight(int j) { return 0; } Shared::ExpiringPointer calculation = calculationAtIndex(j); - return calculation->height(App::app()->localContext(), j == selectedRow() && selectedSubviewType() == SubviewType::Output); + bool expanded = j == selectedRow() && selectedSubviewType() == SubviewType::Output; + KDCoordinate result = calculation->memoizedHeight(expanded); + if (result < 0) { + result = HistoryViewCell::Height(calculation.pointer(), expanded); + calculation->setMemoizedHeight(expanded, result); + } + return result; } int HistoryController::typeAtLocation(int i, int j) { diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index a4707e847..46a93b987 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -11,9 +11,6 @@ namespace Calculation { /* HistoryViewCellDataSource */ -HistoryViewCellDataSource::HistoryViewCellDataSource() : - m_selectedSubviewType(SubviewType::Output) {} - void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType, bool sameCell, int previousSelectedCellX, int previousSelectedCellY) { HistoryViewCell * selectedCell = nullptr; HistoryViewCell * previouslySelectedCell = nullptr; @@ -37,19 +34,26 @@ void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType, /* HistoryViewCell */ -HistoryViewCell::HistoryViewCell(Responder * parentResponder) : - Responder(parentResponder), - m_calculationDisplayOutput(Calculation::DisplayOutput::Unknown), - m_calculationAdditionInformation(Calculation::AdditionalInformationType::None), - m_calculationExpanded(false), - m_inputView(this, Metric::CommonLargeMargin, Metric::CommonSmallMargin), - m_scrollableOutputView(this) -{ - m_calculationCRC32 = 0; +KDCoordinate HistoryViewCell::Height(Calculation * calculation, bool expanded) { + HistoryViewCell cell(nullptr); + cell.setCalculation(calculation, expanded); + KDRect ellipsisFrame = KDRectZero; + KDRect inputFrame = KDRectZero; + KDRect outputFrame = KDRectZero; + cell.computeSubviewFrames(Ion::Display::Width, KDCOORDINATE_MAX, &ellipsisFrame, &inputFrame, &outputFrame); + return k_margin + inputFrame.unionedWith(outputFrame).height() + k_margin; } -Shared::ScrollableTwoExpressionsView * HistoryViewCell::outputView() { - return &m_scrollableOutputView; +HistoryViewCell::HistoryViewCell(Responder * parentResponder) : + Responder(parentResponder), + m_calculationCRC32(0), + m_calculationDisplayOutput(Calculation::DisplayOutput::Unknown), + m_calculationAdditionInformation(Calculation::AdditionalInformationType::None), + m_inputView(this, k_inputViewHorizontalMargin, k_inputOutputViewsVerticalMargin), + m_scrollableOutputView(this), + m_calculationExpanded(false), + m_calculationSingleLine(false) +{ } void HistoryViewCell::setEven(bool even) { @@ -137,15 +141,6 @@ void HistoryViewCell::cellDidSelectSubview(HistoryViewCellDataSource::SubviewTyp reloadScroll(); } -KDColor HistoryViewCell::backgroundColor() const { - KDColor background = m_even ? Palette::CalculationBackgroundEven : Palette::CalculationBackgroundOdd; - return background; -} - -int HistoryViewCell::numberOfSubviews() const { - return 2 + displayedEllipsis(); -} - View * HistoryViewCell::subviewAtIndex(int index) { /* The order of the subviews should not matter here as they don't overlap. * However, the order determines the order of redrawing as well. For several @@ -169,29 +164,69 @@ View * HistoryViewCell::subviewAtIndex(int index) { return views[index]; } +bool HistoryViewCell::ViewsCanBeSingleLine(KDCoordinate inputViewWidth, KDCoordinate outputViewWidth) { + // k_margin is the separation between the input and output. + return (inputViewWidth + k_margin + outputViewWidth) < Ion::Display::Width - Metric::EllipsisCellWidth; +} + void HistoryViewCell::layoutSubviews(bool force) { - KDCoordinate maxFrameWidth = bounds().width(); - if (displayedEllipsis()) { - m_ellipsis.setFrame(KDRect(maxFrameWidth - Metric::EllipsisCellWidth, 0, Metric::EllipsisCellWidth, bounds().height()), force); - maxFrameWidth -= Metric::EllipsisCellWidth; - } else { - m_ellipsis.setFrame(KDRectZero, force); // Required to mark previous rect as dirty + KDRect frameBounds = bounds(); + if (bounds().width() <= 0 || bounds().height() <= 0) { + // TODO Make this behaviour in a non-virtual layoutSublviews, and all layout subviews should become privateLayoutSubviews + return; } + KDRect ellipsisFrame = KDRectZero; + KDRect inputFrame = KDRectZero; + KDRect outputFrame = KDRectZero; + computeSubviewFrames(frameBounds.width(), frameBounds.height(), &ellipsisFrame, &inputFrame, &outputFrame); + + m_ellipsis.setFrame(ellipsisFrame, force); // Required even if ellipsisFrame is KDRectZero, to mark previous rect as dirty + m_inputView.setFrame(inputFrame,force); + m_scrollableOutputView.setFrame(outputFrame, force); +} + +void HistoryViewCell::computeSubviewFrames(KDCoordinate frameWidth, KDCoordinate frameHeight, KDRect * ellipsisFrame, KDRect * inputFrame, KDRect * outputFrame) { + assert(ellipsisFrame != nullptr && inputFrame != nullptr && outputFrame != nullptr); + + if (displayedEllipsis()) { + *ellipsisFrame = KDRect(frameWidth - Metric::EllipsisCellWidth, 0, Metric::EllipsisCellWidth, frameHeight); + frameWidth -= Metric::EllipsisCellWidth; + } else { + *ellipsisFrame = KDRectZero; + } + KDSize inputSize = m_inputView.minimalSizeForOptimalDisplay(); - m_inputView.setFrame(KDRect( - 0, 0, - std::min(maxFrameWidth, inputSize.width()), - inputSize.height()), - force); KDSize outputSize = m_scrollableOutputView.minimalSizeForOptimalDisplay(); - int singleLine = outputSize.width() + inputSize.width() < bounds().width() - 6; - int outputHeight = (singleLine && Poincare::Preferences::sharedPreferences()->resultDisplay() == Poincare::Preferences::ResultDisplay::Compact) ? (std::max(0, inputSize.height() - outputSize.height()) / 2) + std::max(0, (inputSize.height() - outputSize.height()) / 2) : inputSize.height(); - m_scrollableOutputView.setFrame(KDRect( - std::max(0, maxFrameWidth - outputSize.width()), - outputHeight, - std::min(maxFrameWidth, outputSize.width()), - outputSize.height()), - force); + + /* To compute if the calculation is on a single line, use the expanded width + * if there is both an exact and an approximate layout. */ + m_calculationSingleLine = ViewsCanBeSingleLine(inputSize.width(), m_scrollableOutputView.minimalSizeForOptimalDisplayFullSize().width()); + + KDCoordinate inputY = k_margin; + KDCoordinate outputY = k_margin; + if (m_calculationSingleLine) { + KDCoordinate inputBaseline = m_inputView.layout().baseline(); + KDCoordinate outputBaseline = m_scrollableOutputView.baseline(); + KDCoordinate baselineDifference = outputBaseline - inputBaseline; + if (baselineDifference > 0) { + inputY += baselineDifference; + } else { + outputY += -baselineDifference; + } + } else { + outputY += inputSize.height(); + } + + *inputFrame = KDRect( + 0, + inputY, + std::min(frameWidth, inputSize.width()), + inputSize.height()); + *outputFrame = KDRect( + std::max(0, frameWidth - outputSize.width()), + outputY, + std::min(frameWidth, outputSize.width()), + outputSize.height()); } void HistoryViewCell::resetMemoization() { @@ -285,31 +320,41 @@ void HistoryViewCell::didBecomeFirstResponder() { } bool HistoryViewCell::handleEvent(Ion::Events::Event event) { - assert(m_dataSource); + assert(m_dataSource != nullptr); HistoryViewCellDataSource::SubviewType type = m_dataSource->selectedSubviewType(); - if ((event == Ion::Events::Down && type == HistoryViewCellDataSource::SubviewType::Input) || - (event == Ion::Events::Up && type == HistoryViewCellDataSource::SubviewType::Output) || - (event == Ion::Events::Right && type != HistoryViewCellDataSource::SubviewType::Ellipsis && displayedEllipsis()) || - (event == Ion::Events::Left && type == HistoryViewCellDataSource::SubviewType::Ellipsis)) { - HistoryViewCellDataSource::SubviewType otherSubviewType; - if (event == Ion::Events::Down) { - otherSubviewType = HistoryViewCellDataSource::SubviewType::Output; - } else if (event == Ion::Events::Up) { - otherSubviewType = HistoryViewCellDataSource::SubviewType::Input; - } else if (event == Ion::Events::Right) { - otherSubviewType = HistoryViewCellDataSource::SubviewType::Ellipsis; - } else { - assert(event == Ion::Events::Left); - otherSubviewType = HistoryViewCellDataSource::SubviewType::Output; + assert(type != HistoryViewCellDataSource::SubviewType::None); + HistoryViewCellDataSource::SubviewType otherSubviewType = HistoryViewCellDataSource::SubviewType::None; + if (m_calculationSingleLine) { + static_assert( + static_cast(HistoryViewCellDataSource::SubviewType::None) == 0 + && static_cast(HistoryViewCellDataSource::SubviewType::Input) == 1 + && static_cast(HistoryViewCellDataSource::SubviewType::Output) == 2 + && static_cast(HistoryViewCellDataSource::SubviewType::Ellipsis) == 3, + "The array types is not well-formed anymore"); + HistoryViewCellDataSource::SubviewType types[] = { + HistoryViewCellDataSource::SubviewType::None, + HistoryViewCellDataSource::SubviewType::Input, + HistoryViewCellDataSource::SubviewType::Output, + displayedEllipsis() ? HistoryViewCellDataSource::SubviewType::Ellipsis : HistoryViewCellDataSource::SubviewType::None, + HistoryViewCellDataSource::SubviewType::None, + }; + if (event == Ion::Events::Right || event == Ion::Events::Left) { + otherSubviewType = types[static_cast(type) + (event == Ion::Events::Right ? 1 : -1)]; } - m_dataSource->setSelectedSubviewType(otherSubviewType, true); - return true; + } else if ((event == Ion::Events::Down && type == HistoryViewCellDataSource::SubviewType::Input) + || (event == Ion::Events::Left && type == HistoryViewCellDataSource::SubviewType::Ellipsis)) + { + otherSubviewType = HistoryViewCellDataSource::SubviewType::Output; + } else if (event == Ion::Events::Up && type == HistoryViewCellDataSource::SubviewType::Output) { + otherSubviewType = HistoryViewCellDataSource::SubviewType::Input; + } else if (event == Ion::Events::Right && type != HistoryViewCellDataSource::SubviewType::Ellipsis && displayedEllipsis()) { + otherSubviewType = HistoryViewCellDataSource::SubviewType::Ellipsis; } - return false; -} - -bool HistoryViewCell::displayedEllipsis() const { - return m_highlighted && m_calculationAdditionInformation != Calculation::AdditionalInformationType::None; + if (otherSubviewType == HistoryViewCellDataSource::SubviewType::None) { + return false; + } + m_dataSource->setSelectedSubviewType(otherSubviewType, true); + return true; } } diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index 7d6c12b45..d50256fc8 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -12,14 +12,14 @@ class HistoryViewCell; class HistoryViewCellDataSource { public: enum class SubviewType { - None, - Input, - Output, - Ellipsis + None = 0, + Input = 1, + Output = 2, + Ellipsis = 3 }; - HistoryViewCellDataSource(); + HistoryViewCellDataSource() : m_selectedSubviewType(SubviewType::Output) {} void setSelectedSubviewType(SubviewType subviewType, bool sameCell, int previousSelectedX = -1, int previousSelectedY = -1); - SubviewType selectedSubviewType() { return m_selectedSubviewType; } + SubviewType selectedSubviewType() const { return m_selectedSubviewType; } private: /* This method should belong to a delegate instead of a data source but as * both the data source and the delegate will be the same controller, we @@ -31,39 +31,51 @@ private: class HistoryViewCell : public ::EvenOddCell, public Responder { public: + constexpr static KDCoordinate k_margin = Metric::CommonSmallMargin; + constexpr static KDCoordinate k_inputOutputViewsVerticalMargin = k_margin; + constexpr static KDCoordinate k_inputViewHorizontalMargin = Shared::AbstractScrollableMultipleExpressionsView::k_horizontalMargin; + static KDCoordinate Height(Calculation * calculation, bool expanded); HistoryViewCell(Responder * parentResponder = nullptr); + static bool ViewsCanBeSingleLine(KDCoordinate inputViewWidth, KDCoordinate outputViewWidth); void cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type, HistoryViewCellDataSource::SubviewType previousType = HistoryViewCellDataSource::SubviewType::None); void setEven(bool even) override; void setHighlighted(bool highlight) override; void reloadSubviewHighlight(); void setDataSource(HistoryViewCellDataSource * dataSource) { m_dataSource = dataSource; } + bool displaysSingleLine() const { + return m_calculationSingleLine; + } Responder * responder() override { return this; } Poincare::Layout layout() const override; - KDColor backgroundColor() const override; + KDColor backgroundColor() const override { return m_even ? KDColorWhite : Palette::WallScreen; } void resetMemoization(); void setCalculation(Calculation * calculation, bool expanded); - int numberOfSubviews() const override; + int numberOfSubviews() const override { return 2 + displayedEllipsis(); } View * subviewAtIndex(int index) override; void layoutSubviews(bool force = false) override; void didBecomeFirstResponder() override; bool handleEvent(Ion::Events::Event event) override; - Shared::ScrollableTwoExpressionsView * outputView(); + Shared::ScrollableTwoExpressionsView * outputView() { return &m_scrollableOutputView; } Calculation::AdditionalInformationType additionalInformationType() const { return m_calculationAdditionInformation; } private: constexpr static KDCoordinate k_resultWidth = 80; + void computeSubviewFrames(KDCoordinate frameWidth, KDCoordinate frameHeight, KDRect * ellipsisFrame, KDRect * inputFrame, KDRect * outputFrame); void reloadScroll(); void reloadOutputSelection(HistoryViewCellDataSource::SubviewType previousType); - bool displayedEllipsis() const; + bool displayedEllipsis() const { + return m_highlighted && m_calculationAdditionInformation != Calculation::AdditionalInformationType::None; + } uint32_t m_calculationCRC32; Calculation::DisplayOutput m_calculationDisplayOutput; Calculation::AdditionalInformationType m_calculationAdditionInformation; - bool m_calculationExpanded; ScrollableExpressionView m_inputView; Shared::ScrollableTwoExpressionsView m_scrollableOutputView; EvenOddCellWithEllipsis m_ellipsis; HistoryViewCellDataSource * m_dataSource; + bool m_calculationExpanded; + bool m_calculationSingleLine; }; } diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index dd8ed3bdd..ba5d4676d 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -167,6 +167,7 @@ PythonTurtleBlue = "Blaue Farbe" PythonTurtleBrown = "Braune Farbe" PythonTurtleCircle = "Circle of radius r pixels" PythonTurtleColor = "Stiftfarbe setzen" +PythonTurtleColorMode = "Set the color mode to 1.0 or 255" PythonTurtleForward = "Move forward by x pixels" PythonTurtleFunction = "turtle module function prefix" PythonTurtleGoto = "Move to (x,y) coordinates" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index d6b4bdd60..f833a5464 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -167,6 +167,7 @@ PythonTurtleBlue = "Blue color" PythonTurtleBrown = "Brown color" PythonTurtleCircle = "Circle of radius r pixels" PythonTurtleColor = "Set the pen color" +PythonTurtleColorMode = "Set the color mode to 1.0 or 255" PythonTurtleForward = "Move forward by x pixels" PythonTurtleFunction = "turtle module function prefix" PythonTurtleGoto = "Move to (x,y) coordinates" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index 3828c32d4..0eefedac4 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -167,6 +167,7 @@ PythonTurtleBlue = "Blue color" PythonTurtleBrown = "Brown color" PythonTurtleCircle = "Circle of radius r pixels" PythonTurtleColor = "Set the pen color" +PythonTurtleColorMode = "Set the color mode to 1.0 or 255" PythonTurtleForward = "Move forward by x pixels" PythonTurtleFunction = "turtle module function prefix" PythonTurtleGoto = "Move to (x,y) coordinates" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index e5fb40b32..2a98a9105 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -167,6 +167,7 @@ PythonTurtleBlue = "Couleur bleue" PythonTurtleBrown = "Couleur marron" PythonTurtleCircle = "Cercle de rayon r pixels" PythonTurtleColor = "Modifie la couleur du tracé" +PythonTurtleColorMode = "Met le mode de couleur à 1.0 ou 255" PythonTurtleForward = "Avance de x pixels" PythonTurtleFunction = "Préfixe fonction du module turtle" PythonTurtleGoto = "Va au point de coordonnées (x,y)" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index e81f1a746..862d85bd4 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -167,6 +167,7 @@ PythonTurtleBlue = "Kék szín" PythonTurtleBrown = "Barna szín" PythonTurtleCircle = "r pixel sugarú kör" PythonTurtleColor = "Állítsa be az toll színét" +PythonTurtleColorMode = "Set the color mode to 1.0 or 255" PythonTurtleForward = "Ugrás x pixelrel" PythonTurtleFunction = "teknös modul funkció elötag" PythonTurtleGoto = "Mozgatás (x, y) koordinátákra" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index f308d790f..7294e19e0 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -167,6 +167,7 @@ PythonTurtleBlue = "Blue color" PythonTurtleBrown = "Brown color" PythonTurtleCircle = "Circle of radius r pixels" PythonTurtleColor = "Set the pen color" +PythonTurtleColorMode = "Set the color mode to 1.0 or 255" PythonTurtleForward = "Move forward by x pixels" PythonTurtleFunction = "turtle module function prefix" PythonTurtleGoto = "Move to (x,y) coordinates" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index a12692ffc..c0fd92a28 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -208,6 +208,7 @@ PythonTurtleCommandBrown = "'brown'" PythonTurtleCommandCircle = "circle(r)" PythonTurtleCommandColor = "color('c')/color(r,g,b)" PythonTurtleCommandColorWithoutArg = "color(\x11)" +PythonTurtleCommandColorMode = "colormode(x)" PythonTurtleCommandForward = "forward(x)" PythonTurtleCommandGoto = "goto(x,y)" PythonTurtleCommandGreen = "'green'" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 5043c7f7f..bdb79c4db 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -145,6 +145,7 @@ const ToolboxMessageTree TurtleModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandShowturtle, I18n::Message::PythonTurtleShowturtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandHideturtle, I18n::Message::PythonTurtleHideturtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandColor, I18n::Message::PythonTurtleColor, false, I18n::Message::PythonTurtleCommandColorWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandColorMode, I18n::Message::PythonTurtleColorMode), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandBlue, I18n::Message::PythonTurtleBlue, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandRed, I18n::Message::PythonTurtleRed, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGreen, I18n::Message::PythonTurtleGreen, false), @@ -285,6 +286,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandCircle, I18n::Message::PythonTurtleCircle), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCmathFunction, I18n::Message::PythonCmathFunction, false, I18n::Message::PythonCommandCmathFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColor, I18n::Message::PythonColor), + ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandColorMode, I18n::Message::PythonTurtleColorMode), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandComplex, I18n::Message::PythonComplex), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCopySign, I18n::Message::PythonCopySign), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCos, I18n::Message::PythonCos), diff --git a/apps/home/base.de.i18n b/apps/home/base.de.i18n index a28432cae..5243a554b 100644 --- a/apps/home/base.de.i18n +++ b/apps/home/base.de.i18n @@ -1,4 +1,4 @@ Apps = "Anwendungen" AppsCapital = "STAY HOME(GA)" ForbidenAppInExamMode1 = "Diese Anwendung ist im" -ForbidenAppInExamMode2 = "Prüfungsmodus verboten" +ForbidenAppInExamMode2 = "Prüfungsmodus nicht erlaubt." diff --git a/apps/probability/base.de.i18n b/apps/probability/base.de.i18n index 45d52dd65..5c7c68fba 100644 --- a/apps/probability/base.de.i18n +++ b/apps/probability/base.de.i18n @@ -3,12 +3,12 @@ ProbaAppCapital = "WAHRSCHEINLICHKEIT" ChooseDistribution = "Wählen Sie eine Verteilung" Binomial = "Binomial" Geometric = "Geometrische" -Uniforme = "Uniform" +Uniforme = "Gleichverteilung" Normal = "Normal" ChiSquared = "Chi-Quadrat" UniformDistribution = "Uniformverteilung" ExponentialDistribution = "Exponentialverteilung" -GeometricDistribution = "Geometrischeverteilung" +GeometricDistribution = "Geometrische Verteilung" PoissonDistribution = "Poisson-Verteilung" ChiSquaredDistribution = "Chi-Quadrat-Verteilung" StudentDistribution = "Student-Verteilung" diff --git a/apps/regression/Makefile b/apps/regression/Makefile index e49c97c9a..d5726035f 100644 --- a/apps/regression/Makefile +++ b/apps/regression/Makefile @@ -15,6 +15,7 @@ app_regression_test_src += $(addprefix apps/regression/model/,\ logistic_model.cpp \ model.cpp \ power_model.cpp \ + proportional_model.cpp \ quadratic_model.cpp \ quartic_model.cpp \ trigonometric_model.cpp \ diff --git a/apps/regression/base.de.i18n b/apps/regression/base.de.i18n index 2e06f3bba..b49e91e7c 100644 --- a/apps/regression/base.de.i18n +++ b/apps/regression/base.de.i18n @@ -10,6 +10,7 @@ ValueNotReachedByRegression = "Wert in diesem Fenster nicht erreicht" NumberOfDots = "Punktanzahl" Covariance = "Kovarianz" Linear = "Lineare" +Proportional = "Proportional" Quadratic = "Quadratische" Cubic = "Kubische" Quartic = "Biquadratische" diff --git a/apps/regression/base.en.i18n b/apps/regression/base.en.i18n index 891c30eb3..7b257c653 100644 --- a/apps/regression/base.en.i18n +++ b/apps/regression/base.en.i18n @@ -10,6 +10,7 @@ ValueNotReachedByRegression = "Value not reached in this window" NumberOfDots = "Number of points" Covariance = "Covariance" Linear = "Linear" +Proportional = "Proportional" Quadratic = "Quadratic" Cubic = "Cubic" Quartic = "Quartic" diff --git a/apps/regression/base.es.i18n b/apps/regression/base.es.i18n index 15b0fff73..147304a40 100644 --- a/apps/regression/base.es.i18n +++ b/apps/regression/base.es.i18n @@ -10,6 +10,7 @@ ValueNotReachedByRegression = "Valor no alcanzado en esta ventana" NumberOfDots = "Número de puntos" Covariance = "Covarianza" Linear = "Lineal" +Proportional = "Proporcional" Quadratic = "Cuadrática" Cubic = "Cúbica" Quartic = "Cuártica" diff --git a/apps/regression/base.fr.i18n b/apps/regression/base.fr.i18n index 0d24b1bd8..c8f80d875 100644 --- a/apps/regression/base.fr.i18n +++ b/apps/regression/base.fr.i18n @@ -10,6 +10,7 @@ ValueNotReachedByRegression = "Valeur non atteinte dans cette fenêtre" NumberOfDots = "Nombre de points" Covariance = "Covariance" Linear = "Linéaire" +Proportional = "Proportionnelle" Quadratic = "Quadratique" Cubic = "Cubique" Quartic = "Quartique" diff --git a/apps/regression/base.hu.i18n b/apps/regression/base.hu.i18n index a3f937e5d..495bd0e17 100644 --- a/apps/regression/base.hu.i18n +++ b/apps/regression/base.hu.i18n @@ -10,6 +10,7 @@ ValueNotReachedByRegression = "Az ablakban az érték még nem volt elérve" NumberOfDots = "Pontok száma" Covariance = "Kovariancia" Linear = "Lineáris" +Proportional = "Proportional" Quadratic = "Másodfokú" Cubic = "Kocka" Quartic = "Kvartikus" diff --git a/apps/regression/base.pt.i18n b/apps/regression/base.pt.i18n index 7ec8ab7b5..4ff5a3eaf 100644 --- a/apps/regression/base.pt.i18n +++ b/apps/regression/base.pt.i18n @@ -10,6 +10,7 @@ ValueNotReachedByRegression = "Valor não alcançado nesta janela" NumberOfDots = "Número de pontos" Covariance = "Covariancia" Linear = "Linear" +Proportional = "Proporcional" Quadratic = "Quadrática" Cubic = "Cúbica" Quartic = "Quarto grau" diff --git a/apps/regression/base.universal.i18n b/apps/regression/base.universal.i18n index bb896dc36..a26602af1 100644 --- a/apps/regression/base.universal.i18n +++ b/apps/regression/base.universal.i18n @@ -1,3 +1,4 @@ +ProportionalRegressionFormula = " y=a·x " QuadraticRegressionFormula = " y=a·x^2+b·x+c " CubicRegressionFormula = " y=a·x^3+b·x^2+c·x+d " QuarticRegressionFormula = " y=a·x^4+b·x^3+c·x^2+d·x+e " diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index f582d315e..b207c3ed9 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -188,13 +188,14 @@ void GraphController::reloadBannerView() { } if (m_store->seriesRegressionType(*m_selectedSeriesIndex) == Model::Type::Linear) { + int index = model->numberOfCoefficients(); // Set "r=..." numberOfChar = 0; legend = " r="; double r = m_store->correlationCoefficient(*m_selectedSeriesIndex); numberOfChar += strlcpy(buffer, legend, bufferSize); numberOfChar += PoincareHelpers::ConvertFloatToText(r, buffer + numberOfChar, bufferSize - numberOfChar, Preferences::LargeNumberOfSignificantDigits); - m_bannerView.subTextAtIndex(2)->setText(buffer); + m_bannerView.subTextAtIndex(0+index)->setText(buffer); // Set "r2=..." numberOfChar = 0; @@ -202,11 +203,11 @@ void GraphController::reloadBannerView() { double r2 = m_store->squaredCorrelationCoefficient(*m_selectedSeriesIndex); numberOfChar += strlcpy(buffer, legend, bufferSize); numberOfChar += PoincareHelpers::ConvertFloatToText(r2, buffer + numberOfChar, bufferSize - numberOfChar, Preferences::LargeNumberOfSignificantDigits); - m_bannerView.subTextAtIndex(3)->setText(buffer); + m_bannerView.subTextAtIndex(1+index)->setText(buffer); // Clean the last subview buffer[0] = 0; - m_bannerView.subTextAtIndex(4)->setText(buffer); + m_bannerView.subTextAtIndex(2+index)->setText(buffer); } else { // Empty all non used subviews diff --git a/apps/regression/model/cubic_model.cpp b/apps/regression/model/cubic_model.cpp index 42fdb5c7d..393e2120e 100644 --- a/apps/regression/model/cubic_model.cpp +++ b/apps/regression/model/cubic_model.cpp @@ -55,24 +55,21 @@ double CubicModel::evaluate(double * modelCoefficients, double x) const { } double CubicModel::partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const { - if (derivateCoefficientIndex == 0) { - // Derivate: x^3 - return x*x*x; - } - if (derivateCoefficientIndex == 1) { - // Derivate: x^2 - return x*x; - } - if (derivateCoefficientIndex == 2) { - // Derivate: x - return x; - } - if (derivateCoefficientIndex == 3) { - // Derivate: 1 - return 1; - } - assert(false); - return 0.0; + switch (derivateCoefficientIndex) { + case 0: + // Derivate with respect to a: x^3 + return x*x*x; + case 1: + // Derivate with respect to b: x^2 + return x*x; + case 2: + // Derivate with respect to c: x + return x; + default: + // Derivate with respect to d: 1 + assert(derivateCoefficientIndex == 3); + return 1.0; + }; } Expression CubicModel::expression(double * modelCoefficients) { diff --git a/apps/regression/model/exponential_model.cpp b/apps/regression/model/exponential_model.cpp index bffb66c53..aecdc3cae 100644 --- a/apps/regression/model/exponential_model.cpp +++ b/apps/regression/model/exponential_model.cpp @@ -86,18 +86,15 @@ void ExponentialModel::fit(Store * store, int series, double * modelCoefficients } double ExponentialModel::partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const { - double a = modelCoefficients[0]; - double b = modelCoefficients[1]; + const double b = modelCoefficients[1]; if (derivateCoefficientIndex == 0) { - // Derivate: exp(b*x) + // Derivate with respect to a: exp(b*x) return exp(b*x); } - if (derivateCoefficientIndex == 1) { - // Derivate: a*x*exp(b*x) - return a*x*exp(b*x); - } - assert(false); - return 0.0; + assert(derivateCoefficientIndex == 1); + // Derivate with respect to b: a*x*exp(b*x) + double a = modelCoefficients[0]; + return a*x*exp(b*x); } } diff --git a/apps/regression/model/linear_model.cpp b/apps/regression/model/linear_model.cpp index 3de85628f..afe159ea2 100644 --- a/apps/regression/model/linear_model.cpp +++ b/apps/regression/model/linear_model.cpp @@ -38,15 +38,12 @@ void LinearModel::fit(Store * store, int series, double * modelCoefficients, Poi double LinearModel::partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const { if (derivateCoefficientIndex == 0) { - // Derivate: x + // Derivate with respect to a: x return x; } - if (derivateCoefficientIndex == 1) { - // Derivate: 1; - return 1; - } - assert(false); - return 0.0; + assert(derivateCoefficientIndex == 1); + // Derivate with respect to b: 1 + return 1.0; } } diff --git a/apps/regression/model/logarithmic_model.cpp b/apps/regression/model/logarithmic_model.cpp index f5c23a105..7beeb119c 100644 --- a/apps/regression/model/logarithmic_model.cpp +++ b/apps/regression/model/logarithmic_model.cpp @@ -33,16 +33,13 @@ double LogarithmicModel::levelSet(double * modelCoefficients, double xMin, doubl double LogarithmicModel::partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const { if (derivateCoefficientIndex == 0) { - // Derivate: ln(x) - assert(x >0); + // Derivate with respect to a: ln(x) + assert(x > 0); return log(x); } - if (derivateCoefficientIndex == 1) { - // Derivate: 1 - return 1; - } - assert(false); - return 0.0; + assert(derivateCoefficientIndex == 1); + // Derivate with respect to b: 1 + return 1.0; } bool LogarithmicModel::dataSuitableForFit(Store * store, int series) const { diff --git a/apps/regression/model/logistic_model.cpp b/apps/regression/model/logistic_model.cpp index 0876aa70d..ec9c50a48 100644 --- a/apps/regression/model/logistic_model.cpp +++ b/apps/regression/model/logistic_model.cpp @@ -61,21 +61,18 @@ double LogisticModel::partialDerivate(double * modelCoefficients, int derivateCo double a = modelCoefficients[0]; double b = modelCoefficients[1]; double c = modelCoefficients[2]; - double denominator = 1.0+a*exp(-b*x); + double denominator = 1.0 + a * exp(-b * x); if (derivateCoefficientIndex == 0) { - // Derivate: exp(-b*x)*(-1 * c/(1.0+a*exp(-b*x))^2) - return -exp(-b*x) * c/(denominator * denominator); + // Derivate with respect to a: exp(-b*x)*(-1 * c/(1.0+a*exp(-b*x))^2) + return -exp(-b * x) * c / (denominator * denominator); } if (derivateCoefficientIndex == 1) { - // Derivate: (-x)*a*exp(-b*x)*(-1/(1.0+a*exp(-b*x))^2) - return x*a*exp(-b*x)*c/(denominator * denominator); + // Derivate with respect to b: (-x)*a*exp(-b*x)*(-1/(1.0+a*exp(-b*x))^2) + return x * a * exp(-b * x) * c / (denominator * denominator); } - if (derivateCoefficientIndex == 2) { - // Derivate: (-x)*a*exp(-b*x)*(-1/(1.0+a*exp(-b*x))^2) - return 1.0/denominator; - } - assert(false); - return 0.0; + assert(derivateCoefficientIndex == 2); + // Derivate with respect to c: (-x)*a*exp(-b*x)*(-1/(1.0+a*exp(-b*x))^2) + return 1.0 / denominator; } void LogisticModel::specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store, int series) const { @@ -86,7 +83,7 @@ void LogisticModel::specializedInitCoefficientsForFit(double * modelCoefficients * and c. Twice the standard vertical deviation is a rough estimate of c * that is "close enough" to c to seed the coefficient, without being too * dependent on outliers.*/ - modelCoefficients[2] = 2.0*store->standardDeviationOfColumn(series, 1); + modelCoefficients[2] = 2.0 * store->standardDeviationOfColumn(series, 1); } diff --git a/apps/regression/model/model.h b/apps/regression/model/model.h index c018a3528..78f14aab6 100644 --- a/apps/regression/model/model.h +++ b/apps/regression/model/model.h @@ -14,17 +14,18 @@ class Store; class Model { public: enum class Type : uint8_t { - Linear = 0, - Quadratic = 1, - Cubic = 2, - Quartic = 3, - Logarithmic = 4, - Exponential = 5, - Power = 6, - Trigonometric = 7, - Logistic = 8 + Linear = 0, + Proportional = 1, + Quadratic = 2, + Cubic = 3, + Quartic = 4, + Logarithmic = 5, + Exponential = 6, + Power = 7, + Trigonometric = 8, + Logistic = 9 }; - static constexpr int k_numberOfModels = 9; + static constexpr int k_numberOfModels = 10; static constexpr int k_maxNumberOfCoefficients = 5; // This has to verify: k_maxNumberOfCoefficients < Matrix::k_maxNumberOfCoefficients virtual ~Model() = default; virtual Poincare::Layout layout() = 0; diff --git a/apps/regression/model/power_model.cpp b/apps/regression/model/power_model.cpp index b7c0e09e0..a6b768f09 100644 --- a/apps/regression/model/power_model.cpp +++ b/apps/regression/model/power_model.cpp @@ -44,19 +44,16 @@ double PowerModel::partialDerivate(double * modelCoefficients, int derivateCoeff double a = modelCoefficients[0]; double b = modelCoefficients[1]; if (derivateCoefficientIndex == 0) { - // Derivate: pow(x,b) + // Derivate with respect to a: pow(x,b) return pow(x,b); } - if (derivateCoefficientIndex == 1) { - assert(x >= 0); - /* We assume all xi are positive. - * For x = 0, a*pow(x,b) = 0, the partial derivate along b is 0 - * For x > 0, a*pow(x,b) = a*exp(b*ln(x)), the partial derivate along b is - * ln(x)*a*pow(x,b) */ - return x == 0 ? 0 : log(x)*a*pow(x, b); - } - assert(false); - return 0.0; + assert(derivateCoefficientIndex == 1); + assert(x >= 0); + /* We assume all xi are positive. + * For x = 0, a*pow(x,b) = 0, the partial derivate with respect to b is 0 + * For x > 0, a*pow(x,b) = a*exp(b*ln(x)), the partial derivate with respect + * to b is ln(x)*a*pow(x,b) */ + return x == 0.0 ? 0.0 : log(x) * a * pow(x, b); } void PowerModel::fit(Store * store, int series, double * modelCoefficients, Poincare::Context * context) { diff --git a/apps/regression/model/proportional_model.cpp b/apps/regression/model/proportional_model.cpp new file mode 100644 index 000000000..22f62e5a4 --- /dev/null +++ b/apps/regression/model/proportional_model.cpp @@ -0,0 +1,40 @@ +#include "proportional_model.h" +#include "../store.h" +#include +#include + +using namespace Poincare; + +namespace Regression { + +Layout ProportionalModel::layout() { + if (m_layout.isUninitialized()) { + const char * s = "a·X"; + m_layout = LayoutHelper::String(s, strlen(s), k_layoutFont); + } + return m_layout; +} + +double ProportionalModel::evaluate(double * modelCoefficients, double x) const { + return modelCoefficients[0] * x; +} + +double ProportionalModel::levelSet(double * modelCoefficients, double xMin, double step, double xMax, double y, Poincare::Context * context) { + const double a = modelCoefficients[0]; + if (a == 0.0) { + return NAN; + } + return y/a; +} + +void ProportionalModel::fit(Store * store, int series, double * modelCoefficients, Poincare::Context * context) { + modelCoefficients[0] = store->slope(series); +} + +double ProportionalModel::partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const { + assert(derivateCoefficientIndex == 0); + // Derivate: x + return x; +} + +} diff --git a/apps/regression/model/proportional_model.h b/apps/regression/model/proportional_model.h new file mode 100644 index 000000000..b2eca094f --- /dev/null +++ b/apps/regression/model/proportional_model.h @@ -0,0 +1,24 @@ +#ifndef REGRESSION_PROPORTIONAL_MODEL_H +#define REGRESSION_PROPORTIONAL_MODEL_H + +#include "model.h" + +namespace Regression { + +class ProportionalModel : public Model { +public: + using Model::Model; + Poincare::Layout layout() override; + I18n::Message formulaMessage() const override { return I18n::Message::ProportionalRegressionFormula; } + double evaluate(double * modelCoefficients, double x) const override; + double levelSet(double * modelCoefficients, double xMin, double step, double xMax, double y, Poincare::Context * context) override; + void fit(Store * store, int series, double * modelCoefficients, Poincare::Context * context) override; + double partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const override; + int numberOfCoefficients() const override { return 1; } + int bannerLinesCount() const override { return 2; } +}; + +} + + +#endif diff --git a/apps/regression/model/quadratic_model.cpp b/apps/regression/model/quadratic_model.cpp index 66bc7cf0c..d258116fc 100644 --- a/apps/regression/model/quadratic_model.cpp +++ b/apps/regression/model/quadratic_model.cpp @@ -47,19 +47,16 @@ double QuadraticModel::evaluate(double * modelCoefficients, double x) const { double QuadraticModel::partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const { if (derivateCoefficientIndex == 0) { - // Derivate: x^2 + // Derivate with respect to a: x^2 return x*x; } if (derivateCoefficientIndex == 1) { - // Derivate: x + // Derivate with respect to b: x return x; } - if (derivateCoefficientIndex == 2) { - // Derivate: 1 - return 1; - } - assert(false); - return 0.0; + assert(derivateCoefficientIndex == 2); + // Derivate with respect to c: 1 + return 1.0; } Expression QuadraticModel::expression(double * modelCoefficients) { diff --git a/apps/regression/model/quartic_model.cpp b/apps/regression/model/quartic_model.cpp index 2b37e4423..3015b7f52 100644 --- a/apps/regression/model/quartic_model.cpp +++ b/apps/regression/model/quartic_model.cpp @@ -64,28 +64,24 @@ double QuarticModel::evaluate(double * modelCoefficients, double x) const { } double QuarticModel::partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const { - if (derivateCoefficientIndex == 0) { - // Derivate: x^4 - return x*x*x*x; - } - if (derivateCoefficientIndex == 1) { - // Derivate: x^3 - return x*x*x; - } - if (derivateCoefficientIndex == 2) { - // Derivate: x^2 - return x*x; - } - if (derivateCoefficientIndex == 3) { - // Derivate: x - return x; - } - if (derivateCoefficientIndex == 4) { - // Derivate: 1 - return 1; - } - assert(false); - return 0.0; + switch (derivateCoefficientIndex) { + case 0: + // Derivate with respect to a: x^4 + return x*x*x*x; + case 1: + // Derivate with respect to b: x^3 + return x*x*x; + case 2: + // Derivate with respect to c: x^2 + return x*x; + case 3: + // Derivate with respect to d: x + return x; + default: + assert(derivateCoefficientIndex == 4); + // Derivate with respect to e: 1 + return 1.0; + }; } Expression QuarticModel::expression(double * modelCoefficients) { diff --git a/apps/regression/model/trigonometric_model.cpp b/apps/regression/model/trigonometric_model.cpp index f32f2c66b..b6ddd6359 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -46,28 +46,27 @@ double TrigonometricModel::evaluate(double * modelCoefficients, double x) const } double TrigonometricModel::partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const { + if (derivateCoefficientIndex == 3) { + // Derivate with respect to d: 1 + return 1.0; + } + double a = modelCoefficients[0]; double b = modelCoefficients[1]; double c = modelCoefficients[2]; double radianX = x * toRadians(Poincare::Preferences::sharedPreferences()->angleUnit()); + if (derivateCoefficientIndex == 0) { - // Derivate: sin(b*x+c) - return sin(b*radianX+c); + // Derivate with respect to a: sin(b*x+c) + return sin(b * radianX + c); } if (derivateCoefficientIndex == 1) { - // Derivate: x*a*cos(b*x+c); - return radianX*a*cos(b*radianX+c); + // Derivate with respect to b: x*a*cos(b*x+c); + return radianX * a * cos(b * radianX + c); } - if (derivateCoefficientIndex == 2) { - // Derivate: a*cos(b*x+c) - return a*cos(b*radianX+c); - } - if (derivateCoefficientIndex == 3) { - // Derivate: 1 - return 1.0; - } - assert(false); - return 0.0; + assert(derivateCoefficientIndex == 2); + // Derivatewith respect to c: a*cos(b*x+c) + return a * cos(b * radianX + c); } void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store, int series) const { diff --git a/apps/regression/regression_controller.cpp b/apps/regression/regression_controller.cpp index 9cc70181f..188007d6b 100644 --- a/apps/regression/regression_controller.cpp +++ b/apps/regression/regression_controller.cpp @@ -82,7 +82,7 @@ HighlightCell * RegressionController::reusableCell(int index, int type) { void RegressionController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { assert(i == 0); assert(j >= 0 && j < k_numberOfRows); - I18n::Message messages[k_numberOfRows] = {I18n::Message::Linear, I18n::Message::Quadratic, I18n::Message::Cubic, I18n::Message::Quartic, I18n::Message::Logarithmic, I18n::Message::Exponential, I18n::Message::Power, I18n::Message::Trigonometrical, I18n::Message::Logistic}; + I18n::Message messages[k_numberOfRows] = {I18n::Message::Linear, I18n::Message::Proportional, I18n::Message::Quadratic, I18n::Message::Cubic, I18n::Message::Quartic, I18n::Message::Logarithmic, I18n::Message::Exponential, I18n::Message::Power, I18n::Message::Trigonometrical, I18n::Message::Logistic}; MessageTableCellWithExpression * castedCell = static_cast(cell); castedCell->setMessage(messages[j]); castedCell->setLayout(m_store->regressionModel((Model::Type) j)->layout()); diff --git a/apps/regression/regression_controller.h b/apps/regression/regression_controller.h index f0875cc28..bb32f5dca 100644 --- a/apps/regression/regression_controller.h +++ b/apps/regression/regression_controller.h @@ -31,7 +31,7 @@ public: int numberOfRows() const override { return k_numberOfRows; } void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; private: - constexpr static int k_numberOfRows = 9; + constexpr static int k_numberOfRows = 10; constexpr static int k_numberOfCells = 6; // (240 - 70) / 35 MessageTableCellWithExpression m_regressionCells[k_numberOfCells]; SelectableTableView m_selectableTableView; diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index e7d827cb2..cfba25903 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -11,7 +11,7 @@ using namespace Shared; namespace Regression { -static_assert(Model::k_numberOfModels == 9, "Number of models changed, Regression::Store() needs to adapt"); +static_assert(Model::k_numberOfModels == 10, "Number of models changed, Regression::Store() needs to adapt"); static_assert(Store::k_numberOfSeries == 3, "Number of series changed, Regression::Store() needs to adapt (m_seriesChecksum)"); Store::Store() : @@ -285,7 +285,7 @@ double Store::squaredCorrelationCoefficient(int series) const { } Model * Store::regressionModel(int index) { - Model * models[Model::k_numberOfModels] = {&m_linearModel, &m_quadraticModel, &m_cubicModel, &m_quarticModel, &m_logarithmicModel, &m_exponentialModel, &m_powerModel, &m_trigonometricModel, &m_logisticModel}; + Model * models[Model::k_numberOfModels] = {&m_linearModel, &m_proportionalModel, &m_quadraticModel, &m_cubicModel, &m_quarticModel, &m_logarithmicModel, &m_exponentialModel, &m_powerModel, &m_trigonometricModel, &m_logisticModel}; return models[index]; } diff --git a/apps/regression/store.h b/apps/regression/store.h index 2cc6f600f..92acd0f8f 100644 --- a/apps/regression/store.h +++ b/apps/regression/store.h @@ -8,6 +8,7 @@ #include "model/logarithmic_model.h" #include "model/logistic_model.h" #include "model/power_model.h" +#include "model/proportional_model.h" #include "model/quadratic_model.h" #include "model/quartic_model.h" #include "model/trigonometric_model.h" @@ -79,6 +80,7 @@ private: uint32_t m_seriesChecksum[k_numberOfSeries]; Model::Type m_regressionTypes[k_numberOfSeries]; LinearModel m_linearModel; + ProportionalModel m_proportionalModel; QuadraticModel m_quadraticModel; CubicModel m_cubicModel; QuarticModel m_quarticModel; diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index b38d9936d..ff07521e7 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -43,6 +43,13 @@ QUIZ_CASE(linear_regression) { assert_regression_is(x, y, 4, Model::Type::Linear, coefficients); } +QUIZ_CASE(proportional_regression) { + double x[] = {7.0, 5.0, 1.0, 9.0, 3.0}; + double y[] = {-41.4851, -29.62186, -6.454245, -53.4976, -18.03325}; + double coefficients[] = {-5.89}; + assert_regression_is(x, y, 5, Model::Type::Proportional, coefficients); +} + QUIZ_CASE(quadratic_regression) { double x[] = {-34.0, -12.0, 5.0, 86.0, -2.0}; double y[] = {-8241.389, -1194.734, -59.163, - 46245.39, -71.774}; diff --git a/apps/settings/base.de.i18n b/apps/settings/base.de.i18n index 1ee1bf136..1d4dc8519 100644 --- a/apps/settings/base.de.i18n +++ b/apps/settings/base.de.i18n @@ -5,7 +5,7 @@ DisplayMode = "Zahlenformat" EditionMode = "Eingabe" EditionLinear = "Linear " Edition2D = "Natürlich " -ComplexFormat = "Komplex" +ComplexFormat = "Komplexe Zahlen" ExamMode = "Prüfungsmodus" ExamModeActive = "Modus erneut starten" ToDeactivateExamMode1 = "Um den Prüfungsmodus auszuschalten," @@ -29,7 +29,7 @@ Engineering = "Technisch " Scientific = "Wissenschaftlich " SignificantFigures = "Signifikante Stellen " Real = "Reell " -Cartesian = "Algebraische " +Cartesian = "Kartesisch " Polar = "Polar " Brightness = "Helligkeit" SoftwareVersion = "Epsilon version" @@ -39,9 +39,9 @@ MicroPythonVersion = "µPythonversion" ResultDisplay = "Ergebniswiedergabe" DefaultResult = "Erweitert " CompactResult = "Compact " -FontSizes = "Python Schriftgröße" -LargeFont = "Groß " -SmallFont = "Klein " +FontSizes = "Python-Schriftgröße" +LargeFont = "Große " +SmallFont = "Kleine " SerialNumber = "Seriennummer" UpdatePopUp = "Erinnerung: Update" BetaPopUp = "Beta pop-up" diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index 1de5842aa..e284128fd 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -1,13 +1,13 @@ ActivateDeactivate = "Aktivieren/Deaktivieren" ActivateDutchExamMode = "Activate Dutch exam mode" -ActivateExamMode = "Starten Prüfungsmodus" +ActivateExamMode = "Prüfungsmodus starten" ActiveExamModeMessage1 = "Alle Ihre Daten werden " ActiveExamModeMessage2 = "gelöscht, wenn Sie den " ActiveExamModeMessage3 = "Prüfungsmodus einschalten." ActiveDutchExamModeMessage1 = "All your data will be deleted when" ActiveDutchExamModeMessage2 = "you activate the exam mode. Python" ActiveDutchExamModeMessage3 = "application will be unavailable." -Axis = "Achsen" +Axis = "Achse" Cancel = "Abbrechen" ClearColumn = "Spalte löschen" ColumnOptions = "Optionen der Spalte" @@ -63,7 +63,7 @@ StatTab = "Stats" StandardDeviation = "Standardabweichung" Step = "Schrittwert" StorageMemoryFull1 = "Der Speicher ist voll. Löschen Sie" -StorageMemoryFull2 = "von Daten und versuchen Sie es erneut." +StorageMemoryFull2 = "einige Daten und versuchen Sie es erneut." StoreExpressionNotAllowed = "'store' ist verboten" SyntaxError = "Syntaxfehler" Sym = "sym" diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 0f4ce1f8c..9dc2d3725 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -129,7 +129,7 @@ InvSortCommandWithArg = "sort>(L)" K = "k" Lambda = "λ" LcmCommandWithArg = "lcm(p,q)" -LinearRegressionFormula = " y=a·x+b " +LinearRegressionFormula = " y=a·x+b " LogCommandWithArg = "log(x,a)" MatrixCommand = "[[\x11]]" MatrixCommandWithArg = "[[1,2][3,4]]" diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 997149852..6daeacea0 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -57,7 +57,11 @@ float InteractiveCurveViewController::addMargin(float y, float range, bool isVer assert(topMarginRatio + bottomMarginRatio < 1); // Assertion so that the formula is correct float ratioDenominator = 1 - bottomMarginRatio - topMarginRatio; float ratio = isMin ? -bottomMarginRatio : topMarginRatio; - ratio = ratio / ratioDenominator; + /* We want to add slightly more than the required margin, so that + * InteractiveCurveViewRange::panToMakePointVisible does not think a point is + * invisible due to precision problems when checking if it is outside the + * required margin. This is why we add a 1.05f factor. */ + ratio = 1.05f * ratio / ratioDenominator; return y + ratio * range; } diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index dc1c6fd78..9e733b874 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -1,7 +1,6 @@ #include "interactive_curve_view_range.h" #include #include -#include #include #include #include @@ -200,30 +199,38 @@ void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { } } -void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation) { - float xRange = xMax() - xMin(); - float yRange = yMax() - yMin(); - if (x < xMin() + leftMarginRation*xRange - FLT_EPSILON && !std::isinf(x) && !std::isnan(x)) { - m_yAuto = false; - float newXMin = x - leftMarginRation*xRange; - m_xRange.setMax(newXMin + xRange, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat); +void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRatio, float leftMarginRatio) { + if (!std::isinf(x) && !std::isnan(x)) { + const float xRange = xMax() - xMin(); + const float leftMargin = leftMarginRatio * xRange; + if (x < xMin() + leftMargin) { + m_yAuto = false; + const float newXMin = x - leftMargin; + m_xRange.setMax(newXMin + xRange, k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat); + } + const float rightMargin = rightMarginRatio * xRange; + if (x > xMax() - rightMargin) { + m_yAuto = false; + m_xRange.setMax(x + rightMargin, k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetXMin(xMax() - xRange, k_lowerMaxFloat, k_upperMaxFloat); + } } - if (x > xMax() - rightMarginRatio*xRange + FLT_EPSILON && !std::isinf(x) && !std::isnan(x)) { - m_yAuto = false; - m_xRange.setMax(x + rightMarginRatio*xRange, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetXMin(xMax() - xRange, k_lowerMaxFloat, k_upperMaxFloat); - } - if (y < yMin() + bottomMarginRation*yRange - FLT_EPSILON && !std::isinf(y) && !std::isnan(y)) { - m_yAuto = false; - float newYMin = y - bottomMarginRation*yRange; - m_yRange.setMax(newYMin + yRange, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); - } - if (y > yMax() - topMarginRatio*yRange + FLT_EPSILON && !std::isinf(y) && !std::isnan(y)) { - m_yAuto = false; - m_yRange.setMax(y + topMarginRatio*yRange, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetYMin(yMax() - yRange, k_lowerMaxFloat, k_upperMaxFloat); + if (!std::isinf(y) && !std::isnan(y)) { + const float yRange = yMax() - yMin(); + const float bottomMargin = bottomMarginRatio * yRange; + if (y < yMin() + bottomMargin) { + m_yAuto = false; + const float newYMin = y - bottomMargin; + m_yRange.setMax(newYMin + yRange, k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); + } + const float topMargin = topMarginRatio * yRange; + if (y > yMax() - topMargin) { + m_yAuto = false; + m_yRange.setMax(y + topMargin, k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetYMin(yMax() - yRange, k_lowerMaxFloat, k_upperMaxFloat); + } } } diff --git a/apps/shared/interval.h b/apps/shared/interval.h index 4fed90449..3be28dc93 100644 --- a/apps/shared/interval.h +++ b/apps/shared/interval.h @@ -18,6 +18,7 @@ public: void setEnd(double f); void setStep(double f); void setElement(int i, double f); + void forceRecompute(){ m_needCompute = true;} void reset(); void clear(); // TODO: decide the max number of elements after optimization diff --git a/apps/shared/interval_parameter_controller.cpp b/apps/shared/interval_parameter_controller.cpp index 7ab9e275c..cc8a41bca 100644 --- a/apps/shared/interval_parameter_controller.cpp +++ b/apps/shared/interval_parameter_controller.cpp @@ -85,6 +85,7 @@ int IntervalParameterController::reusableParameterCellCount(int type) { } void IntervalParameterController::buttonAction() { + m_interval->forceRecompute(); StackViewController * stack = stackController(); stack->pop(); if (stack->depth() > 1) { diff --git a/apps/shared/parameter_text_field_delegate.cpp b/apps/shared/parameter_text_field_delegate.cpp index 990a0e692..c07a46a18 100644 --- a/apps/shared/parameter_text_field_delegate.cpp +++ b/apps/shared/parameter_text_field_delegate.cpp @@ -5,6 +5,7 @@ namespace Shared { bool ParameterTextFieldDelegate::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) { if (event == Ion::Events::Backspace && !textField->isEditing()) { + textField->reinitDraftTextBuffer(); textField->setEditing(true); return true; } diff --git a/apps/shared/round_cursor_view.cpp b/apps/shared/round_cursor_view.cpp index eff5cffed..6e98fad98 100644 --- a/apps/shared/round_cursor_view.cpp +++ b/apps/shared/round_cursor_view.cpp @@ -66,11 +66,40 @@ bool RoundCursorView::eraseCursorIfPossible() { return false; } // Erase the cursor - KDColor cursorWorkingBuffer[Dots::LargeDotDiameter*Dots::LargeDotDiameter]; + KDColor cursorWorkingBuffer[k_cursorSize * k_cursorSize]; KDContext * ctx = KDIonContext::sharedContext(); ctx->setOrigin(currentFrame.origin()); ctx->setClippingRect(currentFrame); - ctx->fillRectWithPixels(KDRect(0,0,k_cursorSize, k_cursorSize), m_underneathPixelBuffer, cursorWorkingBuffer); + KDSize cursorSize = KDSize(k_cursorSize, k_cursorSize); + + /* We assert that the visible frame is not cropped (indeed a cursor is always + * fully inside the window, thanks to panToMakeCursorVisible). Otherwise, we + * would need to change this algorithm. + * + * +---+ + * | |<- frame m_underneathPixelBuffer: +---+ + * +----+---+--------+ |000| + * | |xxx| |<- parentVisibleFrame |xxx| + * | +---+ | +---+ + * | | + * +-----------------+ + * + * +---+ + * |xxx|: absoluteVisibleFrame + * +---+ + * + * What we would draw with the current algorithm: + * +---+ + * | |<- frame + * +----+---+--------+ + * | |000| |<- parentVisibleFrame + * | +---+ | + * | | + * +-----------------+ + * + * */ + assert(currentFrame.size() == cursorSize); + ctx->fillRectWithPixels(KDRect(0, 0, cursorSize), m_underneathPixelBuffer, cursorWorkingBuffer); // TODO Restore the context to previous values? return true; } diff --git a/apps/shared/scrollable_multiple_expressions_view.cpp b/apps/shared/scrollable_multiple_expressions_view.cpp index 8b1d012e3..eeaeb3a6a 100644 --- a/apps/shared/scrollable_multiple_expressions_view.cpp +++ b/apps/shared/scrollable_multiple_expressions_view.cpp @@ -8,7 +8,7 @@ namespace Shared { AbstractScrollableMultipleExpressionsView::ContentCell::ContentCell() : m_rightExpressionView(), - m_approximateSign(KDFont::LargeFont, I18n::Message::AlmostEqual, 0.5f, 0.5f, Palette::GreyVeryDark), + m_approximateSign(k_font, k_defaultApproximateMessage, 0.5f, 0.5f, Palette::GreyVeryDark), m_centeredExpressionView(), m_selectedSubviewPosition(SubviewPosition::Center), m_displayCenter(true) @@ -55,27 +55,11 @@ void AbstractScrollableMultipleExpressionsView::ContentCell::reloadTextColor() { } KDSize AbstractScrollableMultipleExpressionsView::ContentCell::minimalSizeForOptimalDisplay() const { - KDSize leftSize = KDSizeZero; - KDCoordinate leftViewBaseline = 0; - KDCoordinate width = 0; - if (leftExpressionView() && !leftExpressionView()->layout().isUninitialized()) { - leftSize = leftExpressionView()->minimalSizeForOptimalDisplay(); - leftViewBaseline = leftExpressionView()->layout().baseline(); - width += leftSize.width() + Metric::CommonLargeMargin; - } - KDSize rightExpressionSize = m_rightExpressionView.minimalSizeForOptimalDisplay(); - width += rightExpressionSize.width(); - Layout l = m_rightExpressionView.layout(); - KDCoordinate rightBaseline = l.isUninitialized() ? 0 : l.baseline(); - KDSize centeredExpressionSize = KDSizeZero; - KDCoordinate centeredBaseline = 0; - if (displayCenter()) { - centeredBaseline = m_centeredExpressionView.layout().baseline(); - centeredExpressionSize = m_centeredExpressionView.minimalSizeForOptimalDisplay(); - width += centeredExpressionSize.width() + 2*Metric::CommonLargeMargin + m_approximateSign.minimalSizeForOptimalDisplay().width(); - } - KDCoordinate height = std::max(std::max(centeredBaseline, rightBaseline), leftViewBaseline) + std::max(std::max(centeredExpressionSize.height()-centeredBaseline, rightExpressionSize.height()-rightBaseline), leftSize.height()-leftViewBaseline); - return KDSize(width, height); + return privateMinimalSizeForOptimalDisplay(false); +} + +KDSize AbstractScrollableMultipleExpressionsView::ContentCell::minimalSizeForOptimalDisplayFullSize() const { + return privateMinimalSizeForOptimalDisplay(true); } void AbstractScrollableMultipleExpressionsView::ContentCell::setSelectedSubviewPosition(AbstractScrollableMultipleExpressionsView::SubviewPosition subviewPosition) { @@ -111,6 +95,101 @@ int AbstractScrollableMultipleExpressionsView::ContentCell::numberOfSubviews() c return nbOfSubviews; } +KDCoordinate AbstractScrollableMultipleExpressionsView::ContentCell::baseline(KDCoordinate * leftBaseline, KDCoordinate * centerBaseline, KDCoordinate * rightBaseline) const { + // Left view + KDCoordinate leftViewBaseline = (leftExpressionView() && !leftExpressionView()->layout().isUninitialized()) ? + leftExpressionView()->layout().baseline() : + 0; + if (leftBaseline != nullptr) { + *leftBaseline = leftViewBaseline; + } + + // Center view + KDCoordinate centerViewBaseline = displayCenter() ? m_centeredExpressionView.layout().baseline() : 0; + if (centerBaseline != nullptr) { + *centerBaseline = centerViewBaseline; + } + + // Right view + KDCoordinate rightViewBaseline = m_rightExpressionView.layout().isUninitialized() ? + 0 : + m_rightExpressionView.layout().baseline(); + if (rightBaseline != nullptr) { + *rightBaseline = rightViewBaseline; + } + + return std::max(std::max(leftViewBaseline, centerViewBaseline), rightViewBaseline); +} + +void AbstractScrollableMultipleExpressionsView::ContentCell::subviewFrames(KDRect * leftFrame, KDRect * centerFrame, KDRect * approximateSignFrame, KDRect * rightFrame) { + // Subviews sizes + KDSize leftSize = leftExpressionView() ? leftExpressionView()->minimalSizeForOptimalDisplay() : KDSizeZero; + KDSize centerSize = displayCenter() ? m_centeredExpressionView.minimalSizeForOptimalDisplay() : KDSizeZero; + KDSize rightSize = m_rightExpressionView.minimalSizeForOptimalDisplay(); + + // Compute baselines + KDCoordinate leftBaseline = 0; + KDCoordinate centerBaseline = 0; + KDCoordinate rightBaseline = 0; + KDCoordinate viewBaseline = baseline(&leftBaseline, ¢erBaseline, &rightBaseline); + + // Layout left view + KDCoordinate currentWidth = 0; + if (leftExpressionView()) { + assert(leftFrame != nullptr); + *leftFrame = KDRect(currentWidth, viewBaseline - leftBaseline, leftSize); + currentWidth += leftSize.width() + AbstractScrollableMultipleExpressionsView::k_horizontalMargin; + } + + // Layout center expression + if (displayCenter()) { + assert(centerFrame != nullptr && approximateSignFrame != nullptr); + KDSize approximateSignSize = m_approximateSign.minimalSizeForOptimalDisplay(); + *centerFrame = KDRect(currentWidth, viewBaseline - centerBaseline, centerSize); + currentWidth += AbstractScrollableMultipleExpressionsView::k_horizontalMargin + centerSize.width(); + *approximateSignFrame = KDRect(currentWidth, viewBaseline - approximateSignSize.height()/2, approximateSignSize); + currentWidth += AbstractScrollableMultipleExpressionsView::k_horizontalMargin + approximateSignSize.width(); + } + + // Layout right expression + assert(rightFrame != nullptr); + *rightFrame = KDRect(currentWidth, viewBaseline - rightBaseline, rightSize); +} + +KDSize AbstractScrollableMultipleExpressionsView::ContentCell::privateMinimalSizeForOptimalDisplay(bool forceFullDisplay) const { + KDCoordinate width = 0; + + // Compute baselines + KDCoordinate leftBaseline = 0; + KDCoordinate centerBaseline = 0; + KDCoordinate rightBaseline = 0; + KDCoordinate viewBaseline = baseline(&leftBaseline, ¢erBaseline, &rightBaseline); + + KDSize leftSize = KDSizeZero; + if (leftExpressionView() && !leftExpressionView()->layout().isUninitialized()) { + leftSize = leftExpressionView()->minimalSizeForOptimalDisplay(); + width += leftSize.width() + AbstractScrollableMultipleExpressionsView::k_horizontalMargin; + } + + KDSize centerSize = KDSizeZero; + if (displayCenter() || (forceFullDisplay && !m_centeredExpressionView.layout().isUninitialized())) { + centerSize = m_centeredExpressionView.minimalSizeForOptimalDisplay(); + width += centerSize.width() + 2 * AbstractScrollableMultipleExpressionsView::k_horizontalMargin + m_approximateSign.minimalSizeForOptimalDisplay().width(); + } + + KDSize rightSize = m_rightExpressionView.minimalSizeForOptimalDisplay(); + width += rightSize.width(); + + KDCoordinate height = viewBaseline + + std::max( + std::max( + centerSize.height() - centerBaseline, + rightSize.height() - rightBaseline), + leftSize.height() - leftBaseline); + + return KDSize(width, height); +} + View * AbstractScrollableMultipleExpressionsView::ContentCell::subviewAtIndex(int index) { bool leftIsVisible = leftExpressionView() != nullptr; if (leftIsVisible && index == 0) { @@ -120,36 +199,30 @@ View * AbstractScrollableMultipleExpressionsView::ContentCell::subviewAtIndex(in return views[index - leftIsVisible]; } +KDCoordinate AbstractScrollableMultipleExpressionsView::ContentCell::StandardApproximateViewAndMarginsSize() { + return 2 * AbstractScrollableMultipleExpressionsView::k_horizontalMargin + k_font->stringSize(I18n::translate(k_defaultApproximateMessage)).width(); +} + void AbstractScrollableMultipleExpressionsView::ContentCell::layoutSubviews(bool force) { - // Subviews sizes - KDSize leftSize = leftExpressionView() ? leftExpressionView()->minimalSizeForOptimalDisplay() : KDSizeZero; - KDCoordinate leftViewBaseline = leftExpressionView() && !leftExpressionView()->layout().isUninitialized() ? leftExpressionView()->layout().baseline() : 0; - KDSize centeredExpressionSize = KDSizeZero; - KDCoordinate centeredBaseline = 0; - if (displayCenter()) { - centeredBaseline = m_centeredExpressionView.layout().baseline(); - centeredExpressionSize = m_centeredExpressionView.minimalSizeForOptimalDisplay(); + if (bounds().width() <= 0 || bounds().height() <= 0) { + // TODO Make this behaviour in a non-virtual layoutSublviews, and all layout subviews should become privateLayoutSubviews + return; } - KDSize rightExpressionSize = m_rightExpressionView.minimalSizeForOptimalDisplay(); - KDCoordinate rightBaseline = m_rightExpressionView.layout().isUninitialized() ? 0 : m_rightExpressionView.layout().baseline(); - // Compute baseline - KDCoordinate baseline = std::max(std::max(leftViewBaseline, rightBaseline), centeredBaseline); - // Layout left view - KDCoordinate currentWidth = 0; - if (leftExpressionView()) { - leftExpressionView()->setFrame(KDRect(currentWidth, baseline-leftViewBaseline, leftSize), force); - currentWidth += leftSize.width() + Metric::CommonLargeMargin; + KDRect leftFrame = KDRectZero; + KDRect centerFrame = KDRectZero; + KDRect approximateSignFrame = KDRectZero; + KDRect rightFrame = KDRectZero; + subviewFrames(&leftFrame, ¢erFrame, &approximateSignFrame, &rightFrame); + if (leftExpressionView() != nullptr) { + leftExpressionView()->setFrame(leftFrame, force); } - // Layout centered expression - if (displayCenter()) { - KDSize approximateSignSize = m_approximateSign.minimalSizeForOptimalDisplay(); - m_centeredExpressionView.setFrame(KDRect(currentWidth, baseline-centeredBaseline, centeredExpressionSize), force); - currentWidth += Metric::CommonLargeMargin+centeredExpressionSize.width(); - m_approximateSign.setFrame(KDRect(currentWidth, baseline-approximateSignSize.height()/2, approximateSignSize), force); - currentWidth += Metric::CommonLargeMargin + approximateSignSize.width(); + if (centeredExpressionView() != nullptr) { + centeredExpressionView()->setFrame(centerFrame, force); + } + m_approximateSign.setFrame(approximateSignFrame, force); + if (rightExpressionView() != nullptr) { + rightExpressionView()->setFrame(rightFrame, force); } - // Layout right expression - m_rightExpressionView.setFrame(KDRect(currentWidth, baseline-rightBaseline, rightExpressionSize), force); } AbstractScrollableMultipleExpressionsView::AbstractScrollableMultipleExpressionsView(Responder * parentResponder, View * contentCell) : @@ -194,38 +267,40 @@ void AbstractScrollableMultipleExpressionsView::setDisplayCenter(bool display) { } bool AbstractScrollableMultipleExpressionsView::handleEvent(Ion::Events::Event event) { - bool leftIsVisible = false; - KDCoordinate leftWidth = 0; - if (contentCell()->leftExpressionView()) { - leftWidth = contentCell()->leftExpressionView()->minimalSizeForOptimalDisplay().width(); - leftIsVisible = leftWidth - contentOffset().x() > 0; - } - KDCoordinate rightExpressionWidth = contentCell()->rightExpressionView()->minimalSizeForOptimalDisplay().width(); - bool rightExpressionIsVisible = minimalSizeForOptimalDisplay().width() - rightExpressionWidth - contentOffset().x() < bounds().width(); - bool centeredExpressionIsVisibleOnTheLeft = false; - bool centeredExpressionIsVisibleOnTheRight = false; - if (contentCell()->displayCenter()) { - KDCoordinate centerExpressionWidth = contentCell()->centeredExpressionView()->minimalSizeForOptimalDisplay().width(); - KDCoordinate signWidth = contentCell()->approximateSign()->minimalSizeForOptimalDisplay().width(); - centeredExpressionIsVisibleOnTheLeft = leftWidth + Metric::CommonLargeMargin + centerExpressionWidth - contentOffset().x() > 0; - centeredExpressionIsVisibleOnTheRight = minimalSizeForOptimalDisplay().width() - rightExpressionWidth - signWidth - centerExpressionWidth - 2*Metric::CommonLargeMargin - contentOffset().x() < bounds().width(); - } - // Select center - if ((event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Right && centeredExpressionIsVisibleOnTheLeft) || - (event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Left && centeredExpressionIsVisibleOnTheRight)) { - setSelectedSubviewPosition(SubviewPosition::Center); - return true; - } - // Select left - if ((event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Right && leftIsVisible) || - (event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Center && leftIsVisible)) { - setSelectedSubviewPosition(SubviewPosition::Left); - return true; - } - if ((event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Center && rightExpressionIsVisible) || - (event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Left && rightExpressionIsVisible)) { - setSelectedSubviewPosition(SubviewPosition::Right); - return true; + if (event == Ion::Events::Left || event == Ion::Events::Right ) { + bool leftIsVisible = false; + KDCoordinate leftWidth = 0; + if (contentCell()->leftExpressionView()) { + leftWidth = contentCell()->leftExpressionView()->minimalSizeForOptimalDisplay().width(); + leftIsVisible = leftWidth - contentOffset().x() > 0; + } + KDCoordinate rightExpressionWidth = contentCell()->rightExpressionView()->minimalSizeForOptimalDisplay().width(); + bool rightExpressionIsVisible = minimalSizeForOptimalDisplay().width() - rightExpressionWidth - contentOffset().x() < bounds().width(); + bool centeredExpressionIsVisibleOnTheLeft = false; + bool centeredExpressionIsVisibleOnTheRight = false; + if (contentCell()->displayCenter()) { + KDCoordinate centerExpressionWidth = contentCell()->centeredExpressionView()->minimalSizeForOptimalDisplay().width(); + KDCoordinate signWidth = contentCell()->approximateSign()->minimalSizeForOptimalDisplay().width(); + centeredExpressionIsVisibleOnTheLeft = leftWidth + k_horizontalMargin + centerExpressionWidth - contentOffset().x() > 0; + centeredExpressionIsVisibleOnTheRight = minimalSizeForOptimalDisplay().width() - rightExpressionWidth - signWidth - centerExpressionWidth - 2 * k_horizontalMargin - contentOffset().x() < bounds().width(); + } + // Select center + if ((event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Right && centeredExpressionIsVisibleOnTheLeft) || + (event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Left && centeredExpressionIsVisibleOnTheRight)) { + setSelectedSubviewPosition(SubviewPosition::Center); + return true; + } + // Select left + if ((event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Right && leftIsVisible) || + (event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Center && leftIsVisible)) { + setSelectedSubviewPosition(SubviewPosition::Left); + return true; + } + if ((event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Center && rightExpressionIsVisible) || + (event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Left && rightExpressionIsVisible)) { + setSelectedSubviewPosition(SubviewPosition::Right); + return true; + } } return ScrollableView::handleEvent(event); } diff --git a/apps/shared/scrollable_multiple_expressions_view.h b/apps/shared/scrollable_multiple_expressions_view.h index 2a561ac41..89dc76b16 100644 --- a/apps/shared/scrollable_multiple_expressions_view.h +++ b/apps/shared/scrollable_multiple_expressions_view.h @@ -2,16 +2,23 @@ #define SHARED_SCROLLABLE_MULTIPLE_EXPRESSIONS_VIEW_H #include +#include namespace Shared { class AbstractScrollableMultipleExpressionsView : public ScrollableView, public ScrollViewDataSource { public: + constexpr static KDCoordinate k_horizontalMargin = Metric::CommonLargeMargin; enum class SubviewPosition : uint8_t { Left = 0, Center = 1, Right = 2 }; + + static KDCoordinate StandardApproximateViewAndMarginsSize() { + return ContentCell::StandardApproximateViewAndMarginsSize(); + } + AbstractScrollableMultipleExpressionsView(Responder * parentResponder, View * contentCell); ::EvenOddCell * evenOddCell() { return contentCell(); @@ -28,18 +35,19 @@ public: void setDisplayCenter(bool display); void reloadScroll(); bool handleEvent(Ion::Events::Event event) override; - Poincare::Layout layout() const { - return constContentCell()->layout(); - } + Poincare::Layout layout() const { return constContentCell()->layout(); } + KDCoordinate baseline() const { return constContentCell()->baseline(); } protected: class ContentCell : public ::EvenOddCell { public: + static KDCoordinate StandardApproximateViewAndMarginsSize(); ContentCell(); KDColor backgroundColor() const override; void setHighlighted(bool highlight) override; void setEven(bool even) override; void reloadTextColor(); KDSize minimalSizeForOptimalDisplay() const override; + KDSize minimalSizeForOptimalDisplayFullSize() const; virtual ExpressionView * leftExpressionView() const { return nullptr; } ExpressionView * rightExpressionView() { return &m_rightExpressionView; @@ -59,8 +67,12 @@ protected: void layoutSubviews(bool force = false) override; int numberOfSubviews() const override; virtual Poincare::Layout layout() const override; - + KDCoordinate baseline(KDCoordinate * leftBaseline = nullptr, KDCoordinate * centerBaseline = nullptr, KDCoordinate * rightBaseline = nullptr) const; + void subviewFrames(KDRect * leftFrame, KDRect * centerFrame, KDRect * approximateSignFrame, KDRect * rightFrame); private: + constexpr static const KDFont * k_font = KDFont::LargeFont; + const static I18n::Message k_defaultApproximateMessage = I18n::Message::AlmostEqual; + KDSize privateMinimalSizeForOptimalDisplay(bool forceFullDisplay) const; View * subviewAtIndex(int index) override; ExpressionView m_rightExpressionView; MessageTextView m_approximateSign; @@ -68,8 +80,8 @@ protected: SubviewPosition m_selectedSubviewPosition; bool m_displayCenter; }; - virtual ContentCell * contentCell() = 0; - virtual const ContentCell * constContentCell() const = 0; + virtual ContentCell * contentCell() = 0; + virtual const ContentCell * constContentCell() const = 0; }; class ScrollableTwoExpressionsView : public AbstractScrollableMultipleExpressionsView { @@ -82,7 +94,9 @@ public: Metric::CommonLargeMargin ); } - + KDSize minimalSizeForOptimalDisplayFullSize() const { + return constContentCell()->minimalSizeForOptimalDisplayFullSize(); + } private: ContentCell * contentCell() override { return &m_contentCell; }; const ContentCell * constContentCell() const override { return &m_contentCell; }; diff --git a/apps/shared/zoom_parameter_controller.cpp b/apps/shared/zoom_parameter_controller.cpp index c182147e5..0e3299508 100644 --- a/apps/shared/zoom_parameter_controller.cpp +++ b/apps/shared/zoom_parameter_controller.cpp @@ -58,10 +58,15 @@ int ZoomParameterController::ContentView::numberOfSubviews() const { View * ZoomParameterController::ContentView::subviewAtIndex(int index) { assert(index >= 0 && index < 2); + /* The order of subview is important here : + * If we redraw the curveView before the legendView, that can have some display issue, when exiting sleep mode, which + can be visible, if the redraw of curveView is long (with complicated curve), so we prefer to have legendView + at first subview. + */ if (index == 0) { - return m_curveView; + return &m_legendView; } - return &m_legendView; + return m_curveView; } void ZoomParameterController::ContentView::layoutSubviews(bool force) { diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index e5566257f..5111cd93a 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -3,7 +3,7 @@ UnitTimeMenu = "Zeit" UnitTimeSecondMenu = "Sekunde" UnitTimeSecond = "Sekunde" UnitTimeSecondMilli = "Millisekunde" -UnitTimeSecondMicro = "Microsekunde" +UnitTimeSecondMicro = "Mikrosekunde" UnitTimeSecondNano = "Nanosekunde" UnitTimeMinute = "Minute" UnitTimeHour = "Stunde" @@ -11,14 +11,14 @@ UnitTimeDay = "Tag" UnitTimeWeek = "Woche" UnitTimeMonth = "Monat" UnitTimeYear = "Jahr" -UnitDistanceMenu = "Distanz" +UnitDistanceMenu = "Entfernung" UnitDistanceMeterMenu = "Meter" UnitDistanceMeterKilo = "Kilometer" UnitDistanceMeter = "Meter" UnitDistanceMeterMilli = "Millimeter" UnitDistanceMeterMicro = "Micrometer" UnitDistanceMeterNano = "Nanometer" -UnitDistanceMeterPico = "Picometer" +UnitDistanceMeterPico = "Pikometer" UnitDistanceAstronomicalUnit = "Astronomische Einheit" UnitDistanceLightYear = "Lichtjahr" UnitDistanceParsec = "Parsec" @@ -26,7 +26,7 @@ UnitMassMenu = "Masse" UnitMassGramKilo = "Kilogramm" UnitMassGram = "Gramm" UnitMassGramMilli = "Milligramm" -UnitMassGramMicro = "Microgramm" +UnitMassGramMicro = "Mikrogramm" UnitMassGramNano = "Nanogramm" UnitDistanceImperialMenu = "US Customary" UnitDistanceInch = "Inch" @@ -41,14 +41,14 @@ UnitMassTonne = "Tonne" UnitCurrentMenu = "Elektrischer Strom" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" -UnitCurrentAmpereMicro = "Microampere" -UnitTemperatureMenu = "Temperaturen" +UnitCurrentAmpereMicro = "Mikroampere" +UnitTemperatureMenu = "Temperatur" UnitTemperatureKelvin = "Kelvin" -UnitAmountMenu = "Substanzmenge" +UnitAmountMenu = "Stoffmenge" UnitAmountMole = "Mol" UnitAmountMoleMilli = "Millimol" -UnitAmountMoleMicro = "Micromol" -UnitLuminousIntensityMenu = "Helligkeit" +UnitAmountMoleMicro = "Mikromol" +UnitLuminousIntensityMenu = "Lichtstärke" UnitLuminousIntensityCandela = "Candela" UnitFrequencyMenu = "Frequenz" UnitFrequencyHertzGiga = "Gigahertz" @@ -61,7 +61,7 @@ UnitForceNewton = "Newton" UnitForceNewtonMilli = "Millinewton" UnitPressureMenu = "Druck" UnitPressurePascal = "Pascal" -UnitPressurePascalHecto = "Hectopascal" +UnitPressurePascalHecto = "Hektopascal" UnitPressureBar = "Bar" UnitPressureAtm = "Atmosphere" UnitEnergyMenu = "Energie" @@ -80,33 +80,33 @@ UnitPowerWattMega = "Megawatt" UnitPowerWattKilo = "Kilowatt" UnitPowerWatt = "Watt" UnitPowerWattMilli = "Milliwatt" -UnitPowerWattMicro = "Microwatt" +UnitPowerWattMicro = "Mikrowatt" UnitElectricChargeMenu = "Elektrische Ladung" UnitChargeCoulomb = "Coulomb" -UnitPotentialMenu = "Elektrisches Potenzial" +UnitPotentialMenu = "Elektrische Spannung" UnitPotentialVoltKilo = "Kilovolt" UnitPotentialVolt = "Volt" UnitPotentialVoltMilli = "Millivolt" -UnitPotentialVoltMicro = "Microvolt" +UnitPotentialVoltMicro = "Mikrovolt" UnitCapacitanceMenu = "Elektrische Kapazität" UnitCapacitanceFarad = "Farad" UnitCapacitanceFaradMilli = "Millifarad" -UnitCapacitanceFaradMicro = "Microfarad" +UnitCapacitanceFaradMicro = "Mikrofarad" UnitResistanceMenu = "Elektrischer Widerstand" UnitResistanceOhmKilo = "Kiloohm" UnitResistanceOhm = "Ohm" UnitConductanceMenu = "Elektrische Leitfähigkeit" UnitConductanceSiemens = "Siemens" UnitConductanceSiemensMilli = "Millisiemens" -UnitMagneticFieldMenu = "Magnetisches Feld" +UnitMagneticFieldMenu = "Magnetfeld" UnitMagneticFieldTesla = "Tesla" -InductanceMenu = "Elektrische Induktion" +InductanceMenu = "Elektrische Induktivität" UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Fläche" UnitSurfaceHectar = "Hektar" UnitVolumeMenu = "Volumen" UnitVolumeLiter = "Liter" -UnitVolumeLiterDeci = "Deciliter" +UnitVolumeLiterDeci = "Deziliter" UnitVolumeLiterCenti = "Centiliter" UnitVolumeLiterMilli = "Milliliter" Toolbox = "Werkzeugkasten" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index 4763ca97b..1fbe6667e 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -52,7 +52,7 @@ UnitLuminousIntensityMenu = "Intensité lumineuse" UnitLuminousIntensityCandela = "Candela" UnitFrequencyMenu = "Fréquence" UnitFrequencyHertzGiga = "Gigahertz" -UnitFrequencyHertzMega = "Megahertz" +UnitFrequencyHertzMega = "Mégahertz" UnitFrequencyHertzKilo = "Kilohertz" UnitFrequencyHertz = "Hertz" UnitForceMenu = "Force" @@ -69,14 +69,14 @@ UnitEnergyJouleMenu = "Joule" UnitEnergyJouleKilo = "Kilojoule" UnitEnergyJoule = "Joule" UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Electronvolt" -UnitEnergyElectronVoltMega = "Megaelectronvolt" -UnitEnergyElectronVoltKilo = "Kiloelectronvolt" -UnitEnergyElectronVolt = "Electronvolt" -UnitEnergyElectronVoltMilli = "Millielectronvolt" +UnitEnergyEletronVoltMenu = "Électronvolt" +UnitEnergyElectronVoltMega = "Mégaélectronvolt" +UnitEnergyElectronVoltKilo = "Kiloélectronvolt" +UnitEnergyElectronVolt = "Électronvolt" +UnitEnergyElectronVoltMilli = "Milliélectronvolt" UnitPowerMenu = "Puissance" UnitPowerWattGiga = "Gigawatt" -UnitPowerWattMega = "Megawatt" +UnitPowerWattMega = "Mégawatt" UnitPowerWattKilo = "Kilowatt" UnitPowerWatt = "Watt" UnitPowerWattMilli = "Milliwatt" @@ -106,7 +106,7 @@ UnitSurfaceMenu = "Superficie" UnitSurfaceHectar = "Hectare" UnitVolumeMenu = "Volume" UnitVolumeLiter = "Litre" -UnitVolumeLiterDeci = "Decilitre" +UnitVolumeLiterDeci = "Décilitre" UnitVolumeLiterCenti = "Centilitre" UnitVolumeLiterMilli = "Millilitre" Toolbox = "Boîte à outils" diff --git a/escher/include/escher/expression_field.h b/escher/include/escher/expression_field.h index bdbec1afa..a79da0bdb 100644 --- a/escher/include/escher/expression_field.h +++ b/escher/include/escher/expression_field.h @@ -25,6 +25,7 @@ public: bool isEmpty() const; bool inputViewHeightDidChange(); bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false); + void setLayoutInsertionCursorEvent(Ion::Events::Event event) { m_layoutField.setInsertionCursorEvent(event); } /* View */ int numberOfSubviews() const override { return 1; } diff --git a/escher/include/escher/layout_field.h b/escher/include/escher/layout_field.h index 663458c20..7476727d2 100644 --- a/escher/include/escher/layout_field.h +++ b/escher/include/escher/layout_field.h @@ -19,13 +19,14 @@ public: ScrollableView(parentResponder, &m_contentView, this), EditableField(inputEventHandlerDelegate), m_contentView(), + m_insertionCursorEvent(Ion::Events::None), m_delegate(delegate) {} void setDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, LayoutFieldDelegate * delegate) { m_inputEventHandlerDelegate = inputEventHandlerDelegate; m_delegate = delegate; } Poincare::Context * context() const; bool isEditing() const override { return m_contentView.isEditing(); } void setEditing(bool isEditing) override; - void clearLayout() { m_contentView.clearLayout(); } + void clearLayout(); void scrollToCursor() { scrollToBaselinedRect(m_contentView.cursorRect(), m_contentView.cursor()->baselineWithoutSelection()); } @@ -33,6 +34,7 @@ public: Poincare::Layout layout() const { return m_contentView.expressionView()->layout(); } CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override; void putCursorRightOfLayout(); + void setInsertionCursorEvent(Ion::Events::Event event) { m_insertionCursorEvent = event; } // ScrollableView void setBackgroundColor(KDColor c) override { @@ -60,6 +62,7 @@ private: void scrollRightOfLayout(Poincare::Layout layoutR); void scrollToBaselinedRect(KDRect rect, KDCoordinate baseline); void insertLayoutAtCursor(Poincare::Layout layoutR, Poincare::Expression correspondingExpression, bool forceCursorRightOfLayout = false); + bool eventShouldUpdateInsertionCursor(Ion::Events::Event event) { return event == m_insertionCursorEvent; } class ContentView : public View { public: @@ -83,14 +86,30 @@ private: void copySelection(Poincare::Context * context); bool selectionIsEmpty() const; void deleteSelection(); + void invalidateInsertionCursor() { m_insertionCursor = Poincare::LayoutCursor(); } + void updateInsertionCursor() { + if (!m_insertionCursor.isDefined()) { + m_insertionCursor = m_cursor; + } + } private: int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override; void layoutSubviews(bool force = false) override; void layoutCursorSubview(bool force); + void useInsertionCursor(); KDRect selectionRect() const; Poincare::LayoutCursor m_cursor; + /* The insertion cursor is a secondary cursor that determines where text + * should be inserted. Most of the time this cursor is useless (and is + * therefore disabled), but in an interface where the user can navigate out + * of the field, it's important to keep track of where inserted text should + * go even if the main cursor was moved. + * For instance, this is useful in the Calculation app when the user wants + * to type a division and scroll up the history to insert something at the + * denominator. */ + Poincare::LayoutCursor m_insertionCursor; ExpressionView m_expressionView; TextCursorView m_cursorView; /* The selection starts on the left of m_selectionStart, and ends on the @@ -100,6 +119,7 @@ private: bool m_isEditing; }; ContentView m_contentView; + Ion::Events::Event m_insertionCursorEvent; LayoutFieldDelegate * m_delegate; }; diff --git a/escher/include/escher/view.h b/escher/include/escher/view.h index e0cd46d34..94d84c3f3 100644 --- a/escher/include/escher/view.h +++ b/escher/include/escher/view.h @@ -31,18 +31,11 @@ class View { friend class Shared::RoundCursorView; public: View() : m_frame(KDRectZero), m_superview(nullptr), m_dirtyRect(KDRectZero) {} - virtual ~View() { - for (int i = 0; i < numberOfSubviews(); i++) { - View * subview = subviewAtIndex(i); - if (subview != nullptr) { - subview->m_superview = nullptr; - } - } - } View(View&& other) = default; View(const View& other) = delete; View& operator=(const View& other) = delete; View& operator=(View&& other) = delete; + void resetSuperview() { m_superview = nullptr; } @@ -90,6 +83,12 @@ private: KDPoint absoluteOrigin() const; KDRect absoluteVisibleFrame() const; + /* At destruction, subviews aren't notified that their own pointer + * 'm_superview' is outdated. This is not an issue since all view hierarchy + * is created or destroyed at once: when the app is packed or unpacked. The + * view and its subviews are then destroyed concomitantly. + * Otherwise, we would just have to implement the destructor to notify + * subviews that 'm_superview = nullptr'. */ View * m_superview; KDRect m_dirtyRect; }; diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index 7108bc0e1..60fc8490c 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -11,6 +11,7 @@ using namespace Poincare; LayoutField::ContentView::ContentView() : m_cursor(), + m_insertionCursor(), m_expressionView(0.0f, 0.5f, Palette::PrimaryText, Palette::BackgroundHard, &m_selectionStart, &m_selectionEnd), m_cursorView(), m_selectionStart(), @@ -30,12 +31,21 @@ bool LayoutField::ContentView::setEditing(bool isEditing) { m_expressionView.layout().invalidAllSizesPositionsAndBaselines(); return true; } + } else { + // We're leaving the edition of the current layout + useInsertionCursor(); } layoutSubviews(); markRectAsDirty(bounds()); return false; } +void LayoutField::ContentView::useInsertionCursor() { + if (m_insertionCursor.isDefined()) { + m_cursor = m_insertionCursor; + } +} + void LayoutField::ContentView::clearLayout() { HorizontalLayout h = HorizontalLayout::Builder(); if (m_expressionView.setLayout(h)) { @@ -284,6 +294,11 @@ void LayoutField::setEditing(bool isEditing) { } } +void LayoutField::clearLayout() { + m_contentView.clearLayout(); // Replace the layout with an empty horizontal layout + reloadScroll(); // Put the scroll to offset 0 +} + Context * LayoutField::context() const { return (m_delegate != nullptr) ? m_delegate->context() : nullptr; } @@ -318,6 +333,12 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool * - the text added after a toolbox selection * - the result of a copy-paste. */ + /* This routing can be called even if no actual underlying event has been + * dispatched on the LayoutField. For instance, when someone wants to insert + * text in the field from the outside. In this scenario, let's make sure the + * insertionCursor is invalidated. */ + m_contentView.invalidateInsertionCursor(); + // Delete the selected layouts if needed deleteSelection(); @@ -384,6 +405,9 @@ bool LayoutField::handleEvent(Ion::Events::Event event) { KDSize previousSize = minimalSizeForOptimalDisplay(); bool shouldRecomputeLayout = m_contentView.cursor()->showEmptyLayoutIfNeeded(); bool moveEventChangedLayout = false; + if (!eventShouldUpdateInsertionCursor(event)) { + m_contentView.invalidateInsertionCursor(); + } if (privateHandleMoveEvent(event, &moveEventChangedLayout)) { if (!isEditing()) { setEditing(true); @@ -556,6 +580,9 @@ bool LayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * should LayoutCursor result; result = m_contentView.cursor()->cursorAtDirection(DirectionForMoveEvent(event), shouldRecomputeLayout); if (result.isDefined()) { + if (eventShouldUpdateInsertionCursor(event)) { + m_contentView.updateInsertionCursor(); + } m_contentView.setCursor(result); return true; } diff --git a/escher/src/scroll_view.cpp b/escher/src/scroll_view.cpp index d5617d299..aecbc58c6 100644 --- a/escher/src/scroll_view.cpp +++ b/escher/src/scroll_view.cpp @@ -117,6 +117,9 @@ KDRect ScrollView::visibleContentRect() { } void ScrollView::layoutSubviews(bool force) { + if (bounds().isEmpty()) { + return; + } KDRect r1 = KDRectZero; KDRect r2 = KDRectZero; KDRect innerFrame = decorator()->layoutIndicators(minimalSizeForOptimalDisplay(), contentOffset(), bounds(), &r1, &r2, force); diff --git a/ion/include/ion.h b/ion/include/ion.h index dba438223..9541b6777 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -17,6 +17,7 @@ #include #include #include +#include /* ION is not your regular library. It is a library you link against, but it * will take care of configuring the whole environment for you. In POSIX terms, @@ -52,6 +53,9 @@ void decompress(const uint8_t * src, uint8_t * dst, int srcSize, int dstSize); // Tells whether the stack pointer is within acceptable bounds bool stackSafe(); +// Collect registers in a buffer and returns the stack pointer +uintptr_t collectRegisters(jmp_buf regs); + } #endif diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index 851222b53..e18607612 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -86,8 +86,6 @@ public: uint32_t m_fullNameCRC32; }; - Storage(); - #if ION_STORAGE_LOG void log(); #endif @@ -126,6 +124,8 @@ private: constexpr static uint32_t Magic = 0xEE0BDDBA; constexpr static size_t k_maxRecordSize = (1 << sizeof(record_size_t)*8); + Storage(); + /* Getters/Setters on recordID */ const char * fullNameOfRecord(const Record record); Record::ErrorStatus setFullNameOfRecord(const Record record, const char * fullName); diff --git a/ion/src/device/Makefile b/ion/src/device/Makefile index 4a3a96426..b4f6a6b11 100644 --- a/ion/src/device/Makefile +++ b/ion/src/device/Makefile @@ -10,6 +10,8 @@ ifeq ($(EPSILON_TELEMETRY),1) ion_src += ion/src/shared/telemetry_console.cpp endif +ion_src += ion/src/shared/collect_registers.cpp + ION_DEVICE_SFLAGS = -Iion/src/device/$(MODEL) -Iion/src/device/shared $(call object_for,$(ion_device_src) $(ion_device_flasher_src) $(ion_device_bench_src)): SFLAGS += $(ION_DEVICE_SFLAGS) diff --git a/ion/src/shared/collect_registers.cpp b/ion/src/shared/collect_registers.cpp new file mode 100644 index 000000000..960fd0423 --- /dev/null +++ b/ion/src/shared/collect_registers.cpp @@ -0,0 +1,19 @@ +#include +#include + +namespace Ion { + +/* Forbid inlining to ensure dummy to be at the top of the stack. Otherwise, + * LTO inlining can make regs lower on the stack than some just-allocated + * pointers. */ +__attribute__((noinline))uintptr_t collectRegisters(jmp_buf buf) { + /* TODO: we use setjmp to get the registers values to look for python heap + * root. However, the 'setjmp' does not guarantee that it gets all registers + * values. We should check our setjmp implementation for the device and + * ensure that it also works for other platforms. */ + setjmp(buf); + int dummy; + return (uintptr_t)&dummy; +} + +} diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index 75fd7d747..95a9fd29d 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -28,10 +28,7 @@ constexpr char Storage::seqExtension[]; constexpr char Storage::eqExtension[]; Storage * Storage::sharedStorage() { - static Storage * storage = nullptr; - if (storage == nullptr) { - storage = new (staticStorageArea) Storage(); - } + static Storage * storage = new (staticStorageArea) Storage(); return storage; } @@ -90,20 +87,6 @@ Storage::Record::Record(const char * basename, int basenameLength, const char * // STORAGE -Storage::Storage() : - m_magicHeader(Magic), - m_buffer(), - m_magicFooter(Magic), - m_delegate(nullptr), - m_lastRecordRetrieved(nullptr), - m_lastRecordRetrievedPointer(nullptr) -{ - assert(m_magicHeader == Magic); - assert(m_magicFooter == Magic); - // Set the size of the first record to 0 - overrideSizeAtPosition(m_buffer, 0); -} - #if ION_STORAGE_LOG void Storage::log() { for (char * p : *this) { @@ -305,6 +288,22 @@ void Storage::destroyRecordsWithExtension(const char * extension) { } } +// PRIVATE + +Storage::Storage() : + m_magicHeader(Magic), + m_buffer(), + m_magicFooter(Magic), + m_delegate(nullptr), + m_lastRecordRetrieved(nullptr), + m_lastRecordRetrievedPointer(nullptr) +{ + assert(m_magicHeader == Magic); + assert(m_magicFooter == Magic); + // Set the size of the first record to 0 + overrideSizeAtPosition(m_buffer, 0); +} + const char * Storage::fullNameOfRecord(const Record record) { char * p = pointerOfRecord(record); if (p != nullptr) { diff --git a/ion/src/simulator/android/Makefile b/ion/src/simulator/android/Makefile index e4de06e61..271583307 100644 --- a/ion/src/simulator/android/Makefile +++ b/ion/src/simulator/android/Makefile @@ -7,6 +7,8 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/language.cpp \ ) +ion_src += ion/src/shared/collect_registers.cpp + $(call object_for,ion/src/simulator/shared/main.cpp) : SFLAGS += -DEPSILON_SDL_FULLSCREEN=1 LDFLAGS += -ljnigraphics -llog diff --git a/ion/src/simulator/ios/Makefile b/ion/src/simulator/ios/Makefile index 2d85d1297..334e1d4d1 100644 --- a/ion/src/simulator/ios/Makefile +++ b/ion/src/simulator/ios/Makefile @@ -7,6 +7,8 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ ) +ion_src += ion/src/shared/collect_registers.cpp + $(call object_for,ion/src/simulator/shared/main.cpp) : SFLAGS += -DEPSILON_SDL_FULLSCREEN=1 ifeq ($(EPSILON_TELEMETRY),1) diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index f6cae470e..aa8500471 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -15,6 +15,8 @@ ion_src += $(addprefix ion/src/simulator/linux/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ + collect_registers_x86_64.s \ + collect_registers.cpp \ ) ifeq ($(EPSILON_TELEMETRY),1) diff --git a/ion/src/simulator/macos/Makefile b/ion/src/simulator/macos/Makefile index 81b06f3d4..b00459f75 100644 --- a/ion/src/simulator/macos/Makefile +++ b/ion/src/simulator/macos/Makefile @@ -5,6 +5,8 @@ ion_src += $(addprefix ion/src/simulator/macos/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ apple/language.m \ dummy/callback.cpp \ + collect_registers_x86_64.s \ + collect_registers.cpp \ ) ifeq ($(EPSILON_TELEMETRY),1) diff --git a/ion/src/simulator/shared/collect_registers.cpp b/ion/src/simulator/shared/collect_registers.cpp new file mode 100644 index 000000000..0f73be4b7 --- /dev/null +++ b/ion/src/simulator/shared/collect_registers.cpp @@ -0,0 +1,19 @@ +#include + +extern "C" { + +// define in assembly code +// Force the name as archs (linux/macos) don't mangle C names the same way +extern uintptr_t collect_registers(uintptr_t * regs) asm ("_collect_registers"); + +} +namespace Ion { + +// Wrapper to avoid handling c++ name mangling when writing assembly code + +uintptr_t collectRegisters(jmp_buf buf) { + uintptr_t * regs = (uintptr_t *)buf; + return collect_registers(regs); +} + +} diff --git a/ion/src/simulator/shared/collect_registers_x86_64.s b/ion/src/simulator/shared/collect_registers_x86_64.s new file mode 100644 index 000000000..4740681b4 --- /dev/null +++ b/ion/src/simulator/shared/collect_registers_x86_64.s @@ -0,0 +1,25 @@ +.text + +.global _collect_registers + +_collect_registers: + pushq %r15 + pushq %r14 + pushq %r13 + pushq %r12 + pushq %rbp + pushq %rbx + movq %rbx, (%rdi) + movq %rbp, 8(%rdi) + movq %r12, 16(%rdi) + popq %rbx + popq %rbp + popq %r12 + movq %r13, 24(%rdi) + movq %r14, 32(%rdi) + popq %r13 + movq %r15, 40(%rdi) + popq %r14 + popq %r15 + movq %rsp, %rax + ret diff --git a/ion/src/simulator/web/Makefile b/ion/src/simulator/web/Makefile index 89f41927a..61a7641dc 100644 --- a/ion/src/simulator/web/Makefile +++ b/ion/src/simulator/web/Makefile @@ -22,6 +22,8 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/language.cpp \ ) +ion_src += ion/src/shared/collect_registers.cpp + ifeq ($(EPSILON_TELEMETRY),1) ion_src += ion/src/simulator/shared/dummy/telemetry_init.cpp ion_src += ion/src/shared/telemetry_console.cpp diff --git a/ion/src/simulator/windows/Makefile b/ion/src/simulator/windows/Makefile index 897db8dca..50c6396ac 100644 --- a/ion/src/simulator/windows/Makefile +++ b/ion/src/simulator/windows/Makefile @@ -8,6 +8,8 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ ) +ion_src += ion/src/shared/collect_registers.cpp + ifeq ($(EPSILON_TELEMETRY),1) ion_src += ion/src/simulator/shared/dummy/telemetry_init.cpp ion_src += ion/src/shared/telemetry_console.cpp diff --git a/kandinsky/include/kandinsky/size.h b/kandinsky/include/kandinsky/size.h index 970b4f071..875719de4 100644 --- a/kandinsky/include/kandinsky/size.h +++ b/kandinsky/include/kandinsky/size.h @@ -9,6 +9,9 @@ public: m_width(width), m_height(height) {} constexpr KDCoordinate width() const { return m_width; } constexpr KDCoordinate height() const { return m_height; } + bool operator==(const KDSize &other) const { + return m_width == other.width() && m_height == other.height(); + } private: KDCoordinate m_width; KDCoordinate m_height; diff --git a/kandinsky/src/rect.cpp b/kandinsky/src/rect.cpp index f99a09076..4b4be21c4 100644 --- a/kandinsky/src/rect.cpp +++ b/kandinsky/src/rect.cpp @@ -174,5 +174,5 @@ KDRect KDRect::movedTo(KDPoint p) const { } bool KDRect::isEmpty() const { - return (width() == 0 || height() == 0); + return (width() == 0 || height() == 0); //TODO <= 0 } diff --git a/liba/include/setjmp.h b/liba/include/setjmp.h index 6a7ccf2e4..826b0cf5a 100644 --- a/liba/include/setjmp.h +++ b/liba/include/setjmp.h @@ -1,6 +1,7 @@ #ifndef LIBA_SETJMP_H #define LIBA_SETJMP_H +#include #include "private/macros.h" /* We are preseving registers: @@ -14,7 +15,7 @@ LIBA_BEGIN_DECLS -typedef int jmp_buf[31]; +typedef uintptr_t jmp_buf[31]; void longjmp(jmp_buf env, int val); int setjmp(jmp_buf env); diff --git a/poincare/include/poincare/normal_distribution.h b/poincare/include/poincare/normal_distribution.h index 4806a1467..fe30513f8 100644 --- a/poincare/include/poincare/normal_distribution.h +++ b/poincare/include/poincare/normal_distribution.h @@ -16,11 +16,11 @@ public: * The result of the verification is *result. */ static bool ExpressionMuAndVarAreOK(bool * result, const Expression & mu, const Expression & sigma, Context * context); private: - /* For the standard normal distribution, P(X < y) > 0.9999995 for y >= 4.892 so the + /* For the standard normal distribution, P(X < y) > 0.99999995 for y >= 5.33 so the * value displayed is 1. But this is dependent on the fact that we display * only 7 decimal values! */ static_assert(Preferences::LargeNumberOfSignificantDigits == 7, "k_boundStandardNormalDistribution is ill-defined compared to LargeNumberOfSignificantDigits"); - constexpr static double k_boundStandardNormalDistribution = 4.892; + constexpr static double k_boundStandardNormalDistribution = 5.33; template static T StandardNormalCumulativeDistributiveFunctionAtAbscissa(T abscissa); template static T StandardNormalCumulativeDistributiveInverseForProbability(T probability); }; diff --git a/poincare/src/normal_distribution.cpp b/poincare/src/normal_distribution.cpp index ba62b1dca..710dfacb8 100644 --- a/poincare/src/normal_distribution.cpp +++ b/poincare/src/normal_distribution.cpp @@ -88,8 +88,8 @@ T NormalDistribution::StandardNormalCumulativeDistributiveFunctionAtAbscissa(T a if (std::isnan(abscissa)) { return NAN; } - if (std::isinf(abscissa) || abscissa > k_boundStandardNormalDistribution) { - return (T)1.0; + if (std::isinf(abscissa) || std::fabs(abscissa) > k_boundStandardNormalDistribution) { + return abscissa > (T)0.0 ? (T)1.0 : (T)0.0; } if (abscissa == (T)0.0) { return (T)0.5; diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index d985b1c8e..ee3d66795 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -303,6 +303,10 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximates_to("normcdf(1.2, 3.4, 31.36)", "0.3472125"); assert_expression_approximates_to("normcdf(1.2, 3.4, 31.36)", "3.4721249841587ᴇ-1"); + assert_expression_approximates_to("normcdf(-1ᴇ99,3.4,31.36)", "0"); + assert_expression_approximates_to("normcdf(1ᴇ99,3.4,31.36)", "1"); + assert_expression_approximates_to("normcdf(-6,0,1)", "0"); + assert_expression_approximates_to("normcdf(6,0,1)", "1"); assert_expression_approximates_to("normcdf2(0.5, 3.6, 1.3, 11.56)", "0.3436388"); assert_expression_approximates_to("normcdf2(0.5, 3.6, 1.3, 11.56)", "3.4363881299147ᴇ-1"); diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index a2393218a..6670adc17 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -487,6 +487,7 @@ Q(st) Q(hideturtle) Q(ht) Q(isvisible) +Q(colormode) // utime QSTRs Q(time) diff --git a/python/port/mod/kandinsky/modkandinsky.cpp b/python/port/mod/kandinsky/modkandinsky.cpp index 85f48cf13..8db94f80f 100644 --- a/python/port/mod/kandinsky/modkandinsky.cpp +++ b/python/port/mod/kandinsky/modkandinsky.cpp @@ -1,33 +1,17 @@ extern "C" { #include "modkandinsky.h" -#include #include } #include #include #include "port.h" -static KDColor ColorForTuple(mp_obj_t tuple) { - size_t len; - mp_obj_t * elem; - mp_obj_get_array(tuple, &len, &elem); - if (len != 3) { - mp_raise_TypeError("color needs 3 components"); - } - - return KDColor::RGB888( - mp_obj_get_int(elem[0]), - mp_obj_get_int(elem[1]), - mp_obj_get_int(elem[2]) - ); -} - -static mp_obj_t TupleForRGB(uint8_t r, uint8_t g, uint8_t b) { +static mp_obj_t TupleForKDColor(KDColor c) { mp_obj_tuple_t * t = static_cast(MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL))); - t->items[0] = MP_OBJ_NEW_SMALL_INT(r); - t->items[1] = MP_OBJ_NEW_SMALL_INT(g); - t->items[2] = MP_OBJ_NEW_SMALL_INT(b); + t->items[0] = MP_OBJ_NEW_SMALL_INT(c.red()); + t->items[1] = MP_OBJ_NEW_SMALL_INT(c.green()); + t->items[2] = MP_OBJ_NEW_SMALL_INT(c.blue()); return MP_OBJ_FROM_PTR(t); } @@ -37,12 +21,18 @@ static mp_obj_t TupleForRGB(uint8_t r, uint8_t g, uint8_t b) { * the stackViewController and forces the window to redraw itself. * KDIonContext::sharedContext is set to the frame of the last object drawn. */ -mp_obj_t modkandinsky_color(mp_obj_t red, mp_obj_t green, mp_obj_t blue) { - return TupleForRGB( - mp_obj_get_int(red), - mp_obj_get_int(green), - mp_obj_get_int(blue) - ); +mp_obj_t modkandinsky_color(size_t n_args, const mp_obj_t *args) { + mp_obj_t color; + if (n_args == 1) { + color = args[0]; + } else if (n_args == 2) { + mp_raise_TypeError("color takes 1 or 3 arguments"); + return mp_const_none; + } else { + assert(n_args == 3); + color = mp_obj_new_tuple(n_args, args); + } + return TupleForKDColor(MicroPython::ColorParser::ParseColor(color)); } /* Calling ExecutionEnvironment::displaySandbox() hides the console and switches @@ -54,22 +44,23 @@ mp_obj_t modkandinsky_get_pixel(mp_obj_t x, mp_obj_t y) { KDPoint point(mp_obj_get_int(x), mp_obj_get_int(y)); KDColor c; KDIonContext::sharedContext()->getPixel(point, &c); - return TupleForRGB(c.red(), c.green(), c.blue()); + return TupleForKDColor(c); } -mp_obj_t modkandinsky_set_pixel(mp_obj_t x, mp_obj_t y, mp_obj_t color) { +mp_obj_t modkandinsky_set_pixel(mp_obj_t x, mp_obj_t y, mp_obj_t input) { KDPoint point(mp_obj_get_int(x), mp_obj_get_int(y)); - KDColor kdColor = ColorForTuple(color); + KDColor kdColor = MicroPython::ColorParser::ParseColor(input); MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); KDIonContext::sharedContext()->setPixel(point, kdColor); return mp_const_none; } +//TODO Use good colors mp_obj_t modkandinsky_draw_string(size_t n_args, const mp_obj_t * args) { const char * text = mp_obj_str_get_str(args[0]); KDPoint point(mp_obj_get_int(args[1]), mp_obj_get_int(args[2])); - KDColor textColor = (n_args >= 4) ? ColorForTuple(args[3]) : KDColorBlack; - KDColor backgroundColor = (n_args >= 5) ? ColorForTuple(args[4]) : KDColorWhite; + KDColor textColor = (n_args >= 4) ? MicroPython::ColorParser::ParseColor(args[3]) : KDColorBlack; + KDColor backgroundColor = (n_args >= 5) ? MicroPython::ColorParser::ParseColor(args[4]) : KDColorWhite; MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); KDIonContext::sharedContext()->drawString(text, point, KDFont::LargeFont, textColor, backgroundColor); /* Before and after execution of "modkandinsky_draw_string", @@ -99,8 +90,7 @@ mp_obj_t modkandinsky_fill_rect(size_t n_args, const mp_obj_t * args) { y = y - height; } KDRect rect(x, y, width, height); - KDColor color = ColorForTuple(args[4]); - + KDColor color = MicroPython::ColorParser::ParseColor(args[4]); MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); KDIonContext::sharedContext()->fillRect(rect, color); // Cf comment on modkandinsky_draw_string diff --git a/python/port/mod/kandinsky/modkandinsky.h b/python/port/mod/kandinsky/modkandinsky.h index 4191155bc..12fd2cbc1 100644 --- a/python/port/mod/kandinsky/modkandinsky.h +++ b/python/port/mod/kandinsky/modkandinsky.h @@ -1,6 +1,6 @@ #include -mp_obj_t modkandinsky_color(mp_obj_t red, mp_obj_t green, mp_obj_t blue); +mp_obj_t modkandinsky_color(size_t n_args, const mp_obj_t *args); mp_obj_t modkandinsky_get_pixel(mp_obj_t x, mp_obj_t y); mp_obj_t modkandinsky_set_pixel(mp_obj_t x, mp_obj_t y, mp_obj_t color); mp_obj_t modkandinsky_draw_string(size_t n_args, const mp_obj_t *args); diff --git a/python/port/mod/kandinsky/modkandinsky_table.c b/python/port/mod/kandinsky/modkandinsky_table.c index 253f856a7..565d44e71 100644 --- a/python/port/mod/kandinsky/modkandinsky_table.c +++ b/python/port/mod/kandinsky/modkandinsky_table.c @@ -1,6 +1,6 @@ #include "modkandinsky.h" -STATIC MP_DEFINE_CONST_FUN_OBJ_3(modkandinsky_color_obj, modkandinsky_color); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_color_obj, 1, 3, modkandinsky_color); STATIC MP_DEFINE_CONST_FUN_OBJ_2(modkandinsky_get_pixel_obj, modkandinsky_get_pixel); STATIC MP_DEFINE_CONST_FUN_OBJ_3(modkandinsky_set_pixel_obj, modkandinsky_set_pixel); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_draw_string_obj, 3, 5, modkandinsky_draw_string); diff --git a/python/port/mod/turtle/modturtle.cpp b/python/port/mod/turtle/modturtle.cpp index 913372a3f..e6bafba70 100644 --- a/python/port/mod/turtle/modturtle.cpp +++ b/python/port/mod/turtle/modturtle.cpp @@ -1,6 +1,8 @@ extern "C" { #include "modturtle.h" #include +#include +#include } #include "turtle.h" #include "../../port.h" @@ -130,42 +132,54 @@ mp_obj_t modturtle_isdown() { return sTurtle.isPenDown() ? mp_const_true : mp_const_false; } +mp_float_t uint8tColorToDouble(uint8_t c) { return static_cast(c)/255.0; } + mp_obj_t modturtle_pencolor(size_t n_args, const mp_obj_t *args) { if (n_args == 0) { // pencolor() KDColor c = sTurtle.color(); mp_obj_t mp_col[3]; - mp_col[0] = mp_obj_new_int_from_uint(c.red()); - mp_col[1] = mp_obj_new_int_from_uint(c.green()); - mp_col[2] = mp_obj_new_int_from_uint(c.blue()); + if(sTurtle.colorMode() == MicroPython::ColorParser::ColorMode::MaxIntensity255){ + mp_col[0] = mp_obj_new_int_from_uint(c.red()); + mp_col[1] = mp_obj_new_int_from_uint(c.green()); + mp_col[2] = mp_obj_new_int_from_uint(c.blue()); + } else { + mp_col[0] = mp_obj_new_float(uint8tColorToDouble(c.red())); + mp_col[1] = mp_obj_new_float(uint8tColorToDouble(c.green())); + mp_col[2] = mp_obj_new_float(uint8tColorToDouble(c.blue())); + } return mp_obj_new_tuple(3, mp_col); } - if (n_args == 1) { - if (MP_OBJ_IS_STR(args[0])) { - // pencolor("blue") - size_t l; - sTurtle.setColor(mp_obj_str_get_data(args[0], &l)); - } else { - // pencolor((r, g, b)) - mp_obj_t * rgb; - mp_obj_get_array_fixed_n(args[0], 3, &rgb); - sTurtle.setColor( - KDColor::RGB888( - mp_obj_get_int(rgb[0]), - mp_obj_get_int(rgb[1]), - mp_obj_get_int(rgb[2]))); - } - } else if (n_args == 3) { - // pencolor(r, g, b) - sTurtle.setColor( - KDColor::RGB888( - mp_obj_get_int(args[0]), - mp_obj_get_int(args[1]), - mp_obj_get_int(args[2]))); + if (n_args == 2) { + mp_raise_TypeError("pencolor() takes 0, 1 or 3 arguments"); + return mp_const_none; } + mp_obj_t color; + if (n_args == 1) { + color = args[0]; + } else { + assert(n_args == 3); + color = mp_obj_new_tuple(n_args, args); + } + sTurtle.setColor(MicroPython::ColorParser::ParseColor(color, sTurtle.colorMode())); return mp_const_none; } +mp_obj_t modturtle_colormode(size_t n_args, const mp_obj_t *args) { + if(n_args == 0){ + return mp_obj_new_int_from_uint(static_cast(sTurtle.colorMode())); + } else{ + int colorMode = mp_obj_get_int(args[0]); + if (colorMode != static_cast(MicroPython::ColorParser::ColorMode::MaxIntensity1) && + colorMode != static_cast(MicroPython::ColorParser::ColorMode::MaxIntensity255)) { + mp_raise_ValueError("Colormode can be 1 or 255"); + return mp_const_none; + } + sTurtle.setColorMode(static_cast(colorMode)); + return mp_const_none; + } +} + mp_obj_t modturtle_showturtle() { sTurtle.setVisible(true); return mp_const_none; diff --git a/python/port/mod/turtle/modturtle.h b/python/port/mod/turtle/modturtle.h index 84fda5010..303ea13d4 100644 --- a/python/port/mod/turtle/modturtle.h +++ b/python/port/mod/turtle/modturtle.h @@ -25,6 +25,7 @@ mp_obj_t modturtle_pensize(size_t n_args, const mp_obj_t *args); mp_obj_t modturtle_isvisible(); mp_obj_t modturtle_pencolor(size_t n_args, const mp_obj_t *args); +mp_obj_t modturtle_colormode(size_t n_args, const mp_obj_t *args); mp_obj_t modturtle_showturtle(); mp_obj_t modturtle_hideturtle(); diff --git a/python/port/mod/turtle/modturtle_table.c b/python/port/mod/turtle/modturtle_table.c index c9f2658a9..c190cc540 100644 --- a/python/port/mod/turtle/modturtle_table.c +++ b/python/port/mod/turtle/modturtle_table.c @@ -18,6 +18,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modturtle_pensize_obj, 0, 1, modturtl STATIC MP_DEFINE_CONST_FUN_OBJ_0(modturtle_isdown_obj, modturtle_isdown); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modturtle_pencolor_obj, 0, 3, modturtle_pencolor); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modturtle_colormode_obj, 0, 1, modturtle_colormode); STATIC MP_DEFINE_CONST_FUN_OBJ_0(modturtle_reset_obj, modturtle_reset); @@ -64,6 +65,7 @@ STATIC const mp_rom_map_elem_t modturtle_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_color), (mp_obj_t)&modturtle_pencolor_obj }, { MP_ROM_QSTR(MP_QSTR_pencolor), (mp_obj_t)&modturtle_pencolor_obj }, + { MP_ROM_QSTR(MP_QSTR_colormode), (mp_obj_t)&modturtle_colormode_obj }, { MP_ROM_QSTR(MP_QSTR_reset), (mp_obj_t)&modturtle_reset_obj }, diff --git a/python/port/mod/turtle/turtle.cpp b/python/port/mod/turtle/turtle.cpp index 1a5c3abc2..811650c3b 100644 --- a/python/port/mod/turtle/turtle.cpp +++ b/python/port/mod/turtle/turtle.cpp @@ -175,27 +175,6 @@ void Turtle::setVisible(bool visible) { } } -void Turtle::setColor(const char * color) { - constexpr NameColorPair pairs[] = { - NameColorPair("blue", KDColorBlue), - NameColorPair("red", KDColorRed), - NameColorPair("green", Palette::Green), - NameColorPair("yellow", KDColorYellow), - NameColorPair("brown", Palette::Brown), - NameColorPair("black", KDColorBlack), - NameColorPair("white", KDColorWhite), - NameColorPair("pink", Palette::Pink), - NameColorPair("orange", Palette::Orange), - NameColorPair("purple", Palette::Purple), - NameColorPair("grey", Palette::GreyDark) - }; - for (NameColorPair p : pairs) { - if (strcmp(p.name(), color) == 0) { - m_color = p.color(); - return; - } - } -} void Turtle::viewDidDisappear() { m_drawn = false; diff --git a/python/port/mod/turtle/turtle.h b/python/port/mod/turtle/turtle.h index 87aa58e8f..2c49c6b1d 100644 --- a/python/port/mod/turtle/turtle.h +++ b/python/port/mod/turtle/turtle.h @@ -8,6 +8,7 @@ extern "C" { #include #include #include +#include /* We check for keyboard interruptions using micropython_port_vm_hook_loop and * micropython_port_interruptible_msleep, but even if we catch an interruption, @@ -29,6 +30,7 @@ public: m_y(0), m_heading(0), m_color(k_defaultColor), + m_colorMode(MicroPython::ColorParser::ColorMode::MaxIntensity255), m_penDown(true), m_visible(true), m_speed(k_defaultSpeed), @@ -71,7 +73,10 @@ public: void setColor(uint8_t r, uint8_t g, uint8_t b) { m_color = KDColor::RGB888(r, g, b); } - void setColor(const char * color); + MicroPython::ColorParser::ColorMode colorMode() const {return m_colorMode; } + void setColorMode(MicroPython::ColorParser::ColorMode colorMode){ + m_colorMode = colorMode; + } void viewDidDisappear(); @@ -102,19 +107,6 @@ private: Forward = 2 }; - class NameColorPair { - public: - constexpr NameColorPair(const char * name, KDColor color) : - m_name(name), - m_color(color) - {} - const char * name() const { return m_name; } - KDColor color() const { return m_color; } - private: - const char * m_name; - KDColor m_color; - }; - void setHeadingPrivate(mp_float_t angle); KDPoint position(mp_float_t x, mp_float_t y) const; KDPoint position() const { return position(m_x, m_y); } @@ -149,6 +141,7 @@ private: mp_float_t m_heading; KDColor m_color; + MicroPython::ColorParser::ColorMode m_colorMode; bool m_penDown; bool m_visible; diff --git a/python/port/port.cpp b/python/port/port.cpp index 12f5f5588..bac7595ca 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -1,12 +1,19 @@ #include "port.h" -#include +#include #include #include #include #include +/* py/parsenum.h is a C header which uses C keyword restrict. + * It does not exist in C++ so we define it here in order to be able to include + * py/parsenum.h header. */ +#ifdef __cplusplus +#define restrict // disable +#endif + extern "C" { #include "py/builtin.h" #include "py/compile.h" @@ -15,6 +22,7 @@ extern "C" { #include "py/mperrno.h" #include "py/mphal.h" #include "py/nlr.h" +#include "py/parsenum.h" #include "py/repl.h" #include "py/runtime.h" #include "py/stackctrl.h" @@ -23,6 +31,8 @@ extern "C" { #include "mod/matplotlib/pyplot/modpyplot.h" } +#include + static MicroPython::ScriptProvider * sScriptProvider = nullptr; static MicroPython::ExecutionEnvironment * sCurrentExecutionEnvironment = nullptr; @@ -145,85 +155,130 @@ void MicroPython::registerScriptProvider(ScriptProvider * s) { } void MicroPython::collectRootsAtAddress(char * address, int byteLength) { -#if __EMSCRIPTEN__ - // All objects are aligned, as asserted. + /* All addresses stored on the stack are aligned on sizeof(void *), as + * asserted. This is a consequence of the alignment requirements of compilers + * (Cf http://www.catb.org/esr/structure-packing/). */ assert(((unsigned long)address) % ((unsigned long)sizeof(void *)) == 0); assert(byteLength % sizeof(void *) == 0); gc_collect_root((void **)address, byteLength / sizeof(void *)); -#else - for (size_t i = 0; i < sizeof(void *); i++) { - /* Objects on the stack are not necessarily aligned on sizeof(void *), - * which is also true for pointers refering to the heap. MicroPython - * gc_collect_root expects a table of void * that will be scanned every - * sizeof(void *) step. So we have to scan the stack repetitively with a - * increasing offset to be sure to check every byte for a heap address. - * If some memory can be reinterpreted as a pointer in the heap, gc_collect_root - * will prevent the destruction of the pointed heap memory. At worst (if - * the interpreted pointer was in fact an unaligned object or uninitialized - * memory), we will just keep extra objects in the heap which is not optimal - * but does not cause any crash. */ - char * addressWithOffset = address + i; - // Ensure to round the length to the ceiling - size_t lengthInAddressSize = (byteLength - i + sizeof(void *) - 1)/sizeof(void *); - gc_collect_root((void **)addressWithOffset, lengthInAddressSize); - } -#endif } -void gc_collect(void) { +KDColor MicroPython::ColorParser::ParseColor(mp_obj_t input, ColorMode ColorMode){ + static constexpr int maxColorIntensity = static_cast(ColorMode::MaxIntensity255); + if (mp_obj_is_str(input)) { + size_t l; + const char * color = mp_obj_str_get_data(input, &l); + // TODO add cyan + constexpr NameColorPair pairs[] = { + NameColorPair("blue", KDColorBlue), + NameColorPair("b", KDColorBlue), + NameColorPair("red", KDColorRed), + NameColorPair("r", KDColorRed), + NameColorPair("green", Palette::Green), + NameColorPair("g", Palette::Green), + NameColorPair("yellow", KDColorYellow), + NameColorPair("y", KDColorYellow), + NameColorPair("brown", Palette::Brown), + NameColorPair("black", KDColorBlack), + NameColorPair("k", KDColorBlack), + NameColorPair("white", KDColorWhite), + NameColorPair("w", KDColorWhite), + NameColorPair("pink", Palette::Pink), + NameColorPair("orange", Palette::Orange), + NameColorPair("purple", Palette::Purple), + NameColorPair("grey", Palette::GreyDark) + }; + for (NameColorPair p : pairs) { + if (strcmp(p.name(), color) == 0) { + return p.color(); + } + } + + if (color[0] == '#') { + // TODO handle #abc as #aabbcc (see matplotlib spec) + if (l != 7) { + mp_raise_ValueError("RGB hex values are 6 bytes long"); + } + uint32_t colorInt = mp_obj_get_int(mp_parse_num_integer(color+1, strlen(color+1), 16, NULL)); + return KDColor::RGB24(colorInt); + } + + mp_float_t greyLevel = mp_obj_float_get(mp_parse_num_decimal(color, strlen(color), false, false, NULL)); + if (greyLevel >= 0.0 && greyLevel <= 1.0) { + uint8_t color = maxColorIntensity * (float) greyLevel; + return KDColor::RGB888(color, color, color); + } + mp_raise_ValueError("Grey levels are between 0.0 and 1.0"); + } else if(mp_obj_is_int(input)) { + mp_raise_TypeError("Int are not colors"); + //See https://github.com/numworks/epsilon/issues/1533#issuecomment-618443492 + } else { + size_t len; + mp_obj_t * elem; + + mp_obj_get_array(input, &len, &elem); + + if (len != 3) { + mp_raise_TypeError("Color needs 3 components"); + } + int intensityFactor = maxColorIntensity/static_cast(ColorMode); + return KDColor::RGB888( + intensityFactor * mp_obj_get_float(elem[0]), + intensityFactor * mp_obj_get_float(elem[1]), + intensityFactor * mp_obj_get_float(elem[2]) + ); + } + mp_raise_TypeError("Color couldn't be parsed"); +} + +void gc_collect_regs_and_stack(void) { + // get the registers and the sp + jmp_buf regs; + uintptr_t sp = Ion::collectRegisters(regs); + void * python_stack_top = MP_STATE_THREAD(stack_top); assert(python_stack_top != NULL); - gc_collect_start(); - - modturtle_gc_collect(); - modpyplot_gc_collect(); - - /* get the registers. - * regs is the also the last object on the stack so the stack is bound by - * ®s and python_stack_top. */ - jmp_buf regs; - /* TODO: we use setjmp to get the registers values to look for python heap - * root. However, the 'setjmp' does not guarantee that it gets all registers - * values. We should check our setjmp implementation for the device and - * ensure that it also works for other platforms. */ - setjmp(regs); - - void **regs_ptr = (void**)®s; - /* On the device, the stack is stored in reverse order, but it might not be * the case on a computer. We thus have to take the absolute value of the * addresses difference. */ size_t stackLengthInByte; void ** scanStart; - if ((uintptr_t)python_stack_top > (uintptr_t)regs_ptr) { + if ((uintptr_t)python_stack_top > sp) { /* To compute the stack length: - * regs + * registers * <-----------> * STACK <- ...| | | | | |--|--|--|--| | | | | | | - * ^®s ^python_stack_top + * ^sp ^python_stack_top * */ - stackLengthInByte = (uintptr_t)python_stack_top - (uintptr_t)regs_ptr; - scanStart = regs_ptr; + stackLengthInByte = (uintptr_t)python_stack_top - sp; + scanStart = (void **)sp; } else { /* When computing the stack length, take into account regs' size. - * regs + * registers * <-----------> * STACK -> | | | | | | | | | | | |--|--|--|--| | | |... - * ^python_stack_top ^®s + * ^python_stack_top ^sp * */ - stackLengthInByte = (uintptr_t)regs_ptr - (uintptr_t)python_stack_top + sizeof(regs); + stackLengthInByte = sp - (uintptr_t)python_stack_top + sizeof(regs); scanStart = (void **)python_stack_top; } /* Memory error detectors might find an error here as they might split regs * and stack memory zones. */ MicroPython::collectRootsAtAddress((char *)scanStart, stackLengthInByte); +} + +void gc_collect(void) { + gc_collect_start(); + modturtle_gc_collect(); + modpyplot_gc_collect(); + gc_collect_regs_and_stack(); gc_collect_end(); } @@ -260,3 +315,4 @@ const char * mp_hal_input(const char * prompt) { assert(sCurrentExecutionEnvironment != nullptr); return sCurrentExecutionEnvironment->inputText(prompt); } + diff --git a/python/port/port.h b/python/port/port.h index 9d14ab3a6..3ab3a603f 100644 --- a/python/port/port.h +++ b/python/port/port.h @@ -3,9 +3,11 @@ extern "C" { #include +#include } #include + namespace MicroPython { class ScriptProvider { @@ -39,6 +41,31 @@ void deinit(); void registerScriptProvider(ScriptProvider * s); void collectRootsAtAddress(char * address, int len); +class ColorParser { + private: + class NameColorPair { + public: + constexpr NameColorPair(const char * name, KDColor color) : + m_name(name), + m_color(color) + {} + const char * name() const { return m_name; } + KDColor color() const { return m_color; } + private: + const char * m_name; + KDColor m_color; + }; + + public: + enum class ColorMode { + MaxIntensity1 = 1, + MaxIntensity255 = 255, + }; + + static KDColor ParseColor(mp_obj_t input, ColorMode ColorMode = ColorMode::MaxIntensity255); +}; + + } #endif