Files
Upsilon/poincare/src/sequence_layout.cpp
Léa Saviot 45b8c20059 [poincare/layout] Fix XNT when cursor points outside of integrand
Example:
   3
  sum(|4) and XNT event gives n
  n=1

   3
 |sum(4) and XNT event gives 0
  n=1
2019-09-20 15:11:39 +02:00

279 lines
13 KiB
C++

#include <poincare/sequence_layout.h>
#include <poincare/code_point_layout.h>
#include <poincare/horizontal_layout.h>
#include <poincare/left_parenthesis_layout.h>
#include <poincare/right_parenthesis_layout.h>
#include <assert.h>
namespace Poincare {
static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; }
void SequenceLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) {
if (cursor->layoutNode() == upperBoundLayout())
{
assert(cursor->position() == LayoutCursor::Position::Left);
// Case: Left of the upper bound. Go Left of the sequence.
cursor->setLayoutNode(this);
return;
}
if (cursor->layoutNode() == lowerBoundLayout())
{
assert(cursor->position() == LayoutCursor::Position::Left);
// Case: Left of the lower bound. Go Right of the variable name.
cursor->setLayoutNode(variableLayout());
cursor->setPosition(LayoutCursor::Position::Right);
return;
}
if (cursor->layoutNode() == variableLayout())
{
assert(cursor->position() == LayoutCursor::Position::Left);
// Case: Left of the variable name. Go Left of the sequence.
cursor->setLayoutNode(this);
return;
}
if (cursor->layoutNode() == argumentLayout())
{
assert(cursor->position() == LayoutCursor::Position::Left);
// Case: Left of the argument. Go Right of the lower bound.
cursor->setLayoutNode(lowerBoundLayout());
cursor->setPosition(LayoutCursor::Position::Right);
return;
}
assert(cursor->layoutNode() == this);
if (cursor->position() == LayoutCursor::Position::Right) {
// Case: Right. Go to the argument and move Left.
cursor->setLayoutNode(argumentLayout());
cursor->setPosition(LayoutCursor::Position::Right);
return;
}
assert(cursor->position() == LayoutCursor::Position::Left);
// Case: Left. Ask the parent.
LayoutNode * parentLayout = parent();
if (parentLayout != nullptr) {
parentLayout->moveCursorLeft(cursor, shouldRecomputeLayout);
}
}
void SequenceLayoutNode::moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) {
if (cursor->layoutNode() == lowerBoundLayout()
|| cursor->layoutNode() == upperBoundLayout())
{
assert(cursor->position() == LayoutCursor::Position::Right);
// Case: Right of the bounds. Go Left of the argument.
cursor->setLayoutNode(argumentLayout());
cursor->setPosition(LayoutCursor::Position::Left);
return;
}
if (cursor->layoutNode() == variableLayout())
{
assert(cursor->position() == LayoutCursor::Position::Right);
// Case: Right of the variable name. Go Left of the lower bound.
cursor->setLayoutNode(lowerBoundLayout());
cursor->setPosition(LayoutCursor::Position::Left);
return;
}
if (cursor->layoutNode() == argumentLayout())
{
assert(cursor->position() == LayoutCursor::Position::Right);
// Case: Right of the argument. Go Right.
cursor->setLayoutNode(this);
return;
}
assert(cursor->layoutNode() == this);
if (cursor->position() == LayoutCursor::Position::Left) {
// Case: Left. Go to the upper bound
cursor->setLayoutNode(upperBoundLayout());
return;
}
assert(cursor->position() == LayoutCursor::Position::Right);
// Case: Right. Ask the parent
LayoutNode * parentLayout = parent();
if (parentLayout != nullptr) {
parentLayout->moveCursorRight(cursor, shouldRecomputeLayout);
}
}
void SequenceLayoutNode::moveCursorUp(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) {
if (cursor->layoutNode()->hasAncestor(lowerBoundLayout(), true) || cursor->layoutNode()->hasAncestor(variableLayout(), true)) {
// If the cursor is inside the lower bound or inside the variable name, move it to the upper bound
upperBoundLayout()->moveCursorUpInDescendants(cursor, shouldRecomputeLayout);
return;
}
if (cursor->isEquivalentTo(LayoutCursor(argumentLayout(), LayoutCursor::Position::Left))) {
// If the cursor is Left of the argument, move it to the upper bound
cursor->setLayoutNode(upperBoundLayout());
cursor->setPosition(LayoutCursor::Position::Right);
return;
}
// If the cursor is Left of this, move it to the upper bound
if (cursor->layoutNode() == this && cursor->position() == LayoutCursor::Position::Left) {
cursor->setLayoutNode(upperBoundLayout());
return;
}
LayoutNode::moveCursorUp(cursor, shouldRecomputeLayout, equivalentPositionVisited);
}
void SequenceLayoutNode::moveCursorDown(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) {
if (cursor->layoutNode()->hasAncestor(upperBoundLayout(), true)) {
// If the cursor is inside the upper bound, move it to the lower bound
lowerBoundLayout()->moveCursorDownInDescendants(cursor, shouldRecomputeLayout);
return;
}
// If the cursor is Left of the argument, move it to the lower bound
if (cursor->isEquivalentTo(LayoutCursor(argumentLayout(), LayoutCursor::Position::Left))) {
cursor->setLayoutNode(lowerBoundLayout());
cursor->setPosition(LayoutCursor::Position::Right);
return;
}
// If the cursor is Left of this, move it to the variable bound
if (cursor->layoutNode() == this && cursor->position() == LayoutCursor::Position::Left) {
cursor->setLayoutNode(variableLayout());
return;
}
LayoutNode::moveCursorDown(cursor, shouldRecomputeLayout, equivalentPositionVisited);
}
void SequenceLayoutNode::deleteBeforeCursor(LayoutCursor * cursor) {
if (cursor->isEquivalentTo(LayoutCursor(argumentLayout(), LayoutCursor::Position::Left))) {
// Case: Left of the argument. Delete the layout, keep the argument.
Layout argument = Layout(argumentLayout());
Layout thisRef = Layout(this);
thisRef.replaceChildWithGhostInPlace(argument);
// WARNING: Do not call "this" afterwards
cursor->setLayout(thisRef.childAtIndex(0));
thisRef.replaceWith(argument, cursor);
return;
}
LayoutNode::deleteBeforeCursor(cursor);
}
CodePoint SequenceLayoutNode::XNTCodePoint(int childIndex) const {
return (childIndex == k_argumentLayoutIndex || childIndex == k_variableLayoutIndex) ? CodePoint('n') : UCodePointNull;
}
// Protected
KDSize SequenceLayoutNode::lowerBoundSizeWithVariableEquals() {
KDSize variableSize = variableLayout()->layoutSize();
KDSize lowerBoundSize = lowerBoundLayout()->layoutSize();
KDSize equalSize = k_font->stringSize(k_equal);
return KDSize(
variableSize.width() + equalSize.width() + lowerBoundSize.width(),
subscriptBaseline() + maxCoordinate(maxCoordinate(variableSize.height() - variableLayout()->baseline(), lowerBoundSize.height() - lowerBoundLayout()->baseline()), equalSize.height()/2));
}
KDSize SequenceLayoutNode::computeSize() {
KDSize totalLowerBoundSize = lowerBoundSizeWithVariableEquals();
KDSize upperBoundSize = upperBoundLayout()->layoutSize();
KDSize argumentSize = argumentLayout()->layoutSize();
KDSize argumentSizeWithParentheses = KDSize(
argumentSize.width() + 2*ParenthesisLayoutNode::ParenthesisWidth(),
ParenthesisLayoutNode::HeightGivenChildHeight(argumentSize.height()));
KDSize result = KDSize(
maxCoordinate(maxCoordinate(k_symbolWidth, totalLowerBoundSize.width()), upperBoundSize.width())+k_argumentWidthMargin+argumentSizeWithParentheses.width(),
baseline() + maxCoordinate(k_symbolHeight/2+k_boundHeightMargin+totalLowerBoundSize.height(), argumentSizeWithParentheses.height() - argumentLayout()->baseline()));
return result;
}
KDCoordinate SequenceLayoutNode::computeBaseline() {
return maxCoordinate(upperBoundLayout()->layoutSize().height()+k_boundHeightMargin+(k_symbolHeight+1)/2, argumentLayout()->baseline());
}
KDPoint SequenceLayoutNode::positionOfChild(LayoutNode * l) {
KDSize variableSize = variableLayout()->layoutSize();
KDSize equalSize = k_font->stringSize(k_equal);
KDSize upperBoundSize = upperBoundLayout()->layoutSize();
KDCoordinate x = 0;
KDCoordinate y = 0;
if (l == variableLayout()) {
x = completeLowerBoundX();
y = baseline() + k_symbolHeight/2 + k_boundHeightMargin + subscriptBaseline() - variableLayout()->baseline();
} else if (l == lowerBoundLayout()) {
x = completeLowerBoundX() + equalSize.width() + variableSize.width();
y = baseline() + k_symbolHeight/2 + k_boundHeightMargin + subscriptBaseline() - lowerBoundLayout()->baseline();
} else if (l == upperBoundLayout()) {
x = maxCoordinate(maxCoordinate(0, (k_symbolWidth-upperBoundSize.width())/2), (lowerBoundSizeWithVariableEquals().width()-upperBoundSize.width())/2);
y = baseline() - (k_symbolHeight+1)/2- k_boundHeightMargin-upperBoundSize.height();
} else if (l == argumentLayout()) {
x = maxCoordinate(maxCoordinate(k_symbolWidth, lowerBoundSizeWithVariableEquals().width()), upperBoundSize.width())+k_argumentWidthMargin+ParenthesisLayoutNode::ParenthesisWidth();
y = baseline() - argumentLayout()->baseline();
} else {
assert(false);
}
return KDPoint(x,y);
}
int SequenceLayoutNode::writeDerivedClassInBuffer(const char * operatorName, char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const {
assert(operatorName != nullptr);
if (bufferSize == 0) {
return -1;
}
buffer[bufferSize-1] = 0;
// Write the operator name
int numberOfChar = strlcpy(buffer, operatorName, bufferSize);
if (numberOfChar >= bufferSize-1) { return bufferSize-1; }
/* Add system parentheses to avoid serializing:
* 2)+(1 2),1
* ∑ (5) or π (5)
* n=1 n=1+binomial(3
*/
numberOfChar += SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, UCodePointLeftSystemParenthesis);
if (numberOfChar >= bufferSize-1) { return bufferSize-1; }
LayoutNode * argLayouts[] = {const_cast<SequenceLayoutNode *>(this)->argumentLayout(), const_cast<SequenceLayoutNode *>(this)->variableLayout(), const_cast<SequenceLayoutNode *>(this)->lowerBoundLayout(), const_cast<SequenceLayoutNode *>(this)->upperBoundLayout()};
for (uint8_t i = 0; i < sizeof(argLayouts)/sizeof(argLayouts[0]); i++) {
if (i != 0) {
// Write the comma
numberOfChar += SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, ',');
if (numberOfChar >= bufferSize-1) { return bufferSize-1; }
}
// Write the child with system parentheses
numberOfChar += SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, UCodePointLeftSystemParenthesis);
if (numberOfChar >= bufferSize-1) { return bufferSize-1; }
numberOfChar += argLayouts[i]->serialize(buffer+numberOfChar, bufferSize-numberOfChar, floatDisplayMode, numberOfSignificantDigits);
if (numberOfChar >= bufferSize-1) { return bufferSize-1; }
numberOfChar += SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, UCodePointRightSystemParenthesis);
if (numberOfChar >= bufferSize-1) { return bufferSize-1; }
}
// Write the closing system parenthesis
numberOfChar += SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, UCodePointRightSystemParenthesis);
return numberOfChar;
}
void SequenceLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) {
// Render the "="
KDSize variableSize = variableLayout()->layoutSize();
KDPoint equalPosition = positionOfChild(variableLayout()).translatedBy(KDPoint(variableSize.width(), variableLayout()->baseline()-k_font->stringSize(k_equal).height()/2));
ctx->drawString(k_equal, p.translatedBy(equalPosition), k_font, expressionColor, backgroundColor);
// Render the parentheses
KDCoordinate argumentWithParenthesesHeight = ParenthesisLayoutNode::HeightGivenChildHeight(argumentLayout()->layoutSize().height());
KDPoint argumentPosition = positionOfChild(argumentLayout());
KDPoint leftParenthesisPoint = p.translatedBy(KDPoint(
argumentPosition.x() - ParenthesisLayoutNode::ParenthesisWidth(),
argumentPosition.y() + argumentLayout()->layoutSize().height() - argumentWithParenthesesHeight));
LeftParenthesisLayoutNode::RenderWithChildHeight(argumentWithParenthesesHeight, ctx, leftParenthesisPoint, expressionColor, backgroundColor);
KDPoint rightParenthesisPoint = p.translatedBy(KDPoint(
argumentPosition.x() + argumentLayout()->layoutSize().width(),
argumentPosition.y() + argumentLayout()->layoutSize().height() - argumentWithParenthesesHeight));
RightParenthesisLayoutNode::RenderWithChildHeight(argumentWithParenthesesHeight, ctx, rightParenthesisPoint, expressionColor, backgroundColor);
}
KDCoordinate SequenceLayoutNode::completeLowerBoundX() {
KDSize upperBoundSize = upperBoundLayout()->layoutSize();
return maxCoordinate(maxCoordinate(0, (k_symbolWidth-lowerBoundSizeWithVariableEquals().width())/2),
(upperBoundSize.width()-lowerBoundSizeWithVariableEquals().width())/2);
}
KDCoordinate SequenceLayoutNode::subscriptBaseline() {
return maxCoordinate(maxCoordinate(variableLayout()->baseline(), lowerBoundLayout()->baseline()), k_font->stringSize(k_equal).height()/2);
}
}