[Update] Upstream

This commit is contained in:
Quentin
2020-06-18 19:25:17 +02:00
96 changed files with 1051 additions and 660 deletions

View File

@@ -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

View File

@@ -78,7 +78,13 @@ KDCoordinate IllustratedListController::rowHeight(int j) {
return 0;
}
Shared::ExpiringPointer<Calculation> 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) {

View File

@@ -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, &centerFrame, &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);

View File

@@ -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; }

View File

@@ -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"

View File

@@ -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 <poincare/exception_checkpoint.h>
@@ -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) {

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -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> 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) {

View File

@@ -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<int>(HistoryViewCellDataSource::SubviewType::None) == 0
&& static_cast<int>(HistoryViewCellDataSource::SubviewType::Input) == 1
&& static_cast<int>(HistoryViewCellDataSource::SubviewType::Output) == 2
&& static_cast<int>(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<int>(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;
}
}

View File

@@ -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;
};
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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)"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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'"

View File

@@ -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),

View File

@@ -1,4 +1,4 @@
Apps = "Anwendungen"
AppsCapital = "STAY HOME(GA)"
ForbidenAppInExamMode1 = "Diese Anwendung ist im"
ForbidenAppInExamMode2 = "Prüfungsmodus verboten"
ForbidenAppInExamMode2 = "Prüfungsmodus nicht erlaubt."

View File

@@ -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"

View File

@@ -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 \

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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 "

View File

@@ -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<double>(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<double>(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

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -0,0 +1,40 @@
#include "proportional_model.h"
#include "../store.h"
#include <poincare/layout_helper.h>
#include <assert.h>
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;
}
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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<MessageTableCellWithExpression *>(cell);
castedCell->setMessage(messages[j]);
castedCell->setLayout(m_store->regressionModel((Model::Type) j)->layout());

View File

@@ -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;

View File

@@ -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];
}

View File

@@ -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;

View File

@@ -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};

View File

@@ -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"

View File

@@ -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"

View File

@@ -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]]"

View File

@@ -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;
}

View File

@@ -1,7 +1,6 @@
#include "interactive_curve_view_range.h"
#include <ion.h>
#include <cmath>
#include <float.h>
#include <stddef.h>
#include <assert.h>
#include <poincare/preferences.h>
@@ -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);
}
}
}

View File

@@ -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

View File

@@ -85,6 +85,7 @@ int IntervalParameterController::reusableParameterCellCount(int type) {
}
void IntervalParameterController::buttonAction() {
m_interval->forceRecompute();
StackViewController * stack = stackController();
stack->pop();
if (stack->depth() > 1) {

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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, &centerBaseline, &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, &centerBaseline, &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, &centerFrame, &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);
}

View File

@@ -2,16 +2,23 @@
#define SHARED_SCROLLABLE_MULTIPLE_EXPRESSIONS_VIEW_H
#include <escher.h>
#include <apps/i18n.h>
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; };

View File

@@ -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) {

View File

@@ -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"

View File

@@ -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"

View File

@@ -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; }

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -17,6 +17,7 @@
#include <ion/unicode/utf8_helper.h>
#include <stdint.h>
#include <string.h>
#include <setjmp.h>
/* 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

View File

@@ -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);

View File

@@ -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)

View File

@@ -0,0 +1,19 @@
#include <ion.h>
#include <setjmp.h>
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;
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -0,0 +1,19 @@
#include <ion.h>
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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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
}

View File

@@ -1,6 +1,7 @@
#ifndef LIBA_SETJMP_H
#define LIBA_SETJMP_H
#include <stdint.h>
#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);

View File

@@ -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<typename T> static T StandardNormalCumulativeDistributiveFunctionAtAbscissa(T abscissa);
template<typename T> static T StandardNormalCumulativeDistributiveInverseForProbability(T probability);
};

View File

@@ -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;

View File

@@ -303,6 +303,10 @@ QUIZ_CASE(poincare_approximation_function) {
assert_expression_approximates_to<float>("normcdf(1.2, 3.4, 31.36)", "0.3472125");
assert_expression_approximates_to<double>("normcdf(1.2, 3.4, 31.36)", "3.4721249841587ᴇ-1");
assert_expression_approximates_to<float>("normcdf(-1ᴇ99,3.4,31.36)", "0");
assert_expression_approximates_to<float>("normcdf(1ᴇ99,3.4,31.36)", "1");
assert_expression_approximates_to<float>("normcdf(-6,0,1)", "0");
assert_expression_approximates_to<float>("normcdf(6,0,1)", "1");
assert_expression_approximates_to<float>("normcdf2(0.5, 3.6, 1.3, 11.56)", "0.3436388");
assert_expression_approximates_to<double>("normcdf2(0.5, 3.6, 1.3, 11.56)", "3.4363881299147ᴇ-1");

View File

@@ -487,6 +487,7 @@ Q(st)
Q(hideturtle)
Q(ht)
Q(isvisible)
Q(colormode)
// utime QSTRs
Q(time)

View File

@@ -1,33 +1,17 @@
extern "C" {
#include "modkandinsky.h"
#include <py/objtuple.h>
#include <py/runtime.h>
}
#include <kandinsky.h>
#include <ion.h>
#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_tuple_t *>(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

View File

@@ -1,6 +1,6 @@
#include <py/obj.h>
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);

View File

@@ -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);

View File

@@ -1,6 +1,8 @@
extern "C" {
#include "modturtle.h"
#include <py/gc.h>
#include <py/objtuple.h>
#include <py/runtime.h>
}
#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<double>(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<int>(sTurtle.colorMode()));
} else{
int colorMode = mp_obj_get_int(args[0]);
if (colorMode != static_cast<int>(MicroPython::ColorParser::ColorMode::MaxIntensity1) &&
colorMode != static_cast<int>(MicroPython::ColorParser::ColorMode::MaxIntensity255)) {
mp_raise_ValueError("Colormode can be 1 or 255");
return mp_const_none;
}
sTurtle.setColorMode(static_cast<MicroPython::ColorParser::ColorMode>(colorMode));
return mp_const_none;
}
}
mp_obj_t modturtle_showturtle() {
sTurtle.setVisible(true);
return mp_const_none;

View File

@@ -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();

View File

@@ -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 },

View File

@@ -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;

View File

@@ -8,6 +8,7 @@ extern "C" {
#include <escher/metric.h>
#include <kandinsky.h>
#include <math.h>
#include <python/port/port.h>
/* 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;

View File

@@ -1,12 +1,19 @@
#include "port.h"
#include <ion/keyboard.h>
#include <ion.h>
#include <math.h>
#include <stdint.h>
#include <string.h>
#include <setjmp.h>
/* 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 <escher/palette.h>
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<int>(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<int>(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
* &regs 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**)&regs;
/* 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 <- ...| | | | | |--|--|--|--| | | | | | |
* ^&regs ^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 ^&regs
* ^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);
}

View File

@@ -3,9 +3,11 @@
extern "C" {
#include <stddef.h>
#include <py/obj.h>
}
#include <escher/view_controller.h>
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