From cddbdaa71db9d81e4d4ab5ca2464cc5d44a92cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 20 Jul 2018 10:50:53 +0200 Subject: [PATCH] [poincare] FractionLayoutNode --- poincare/Makefile | 1 + poincare/include/poincare.h | 1 + .../include/poincare/fraction_layout_node.h | 69 ++++++ poincare/include/poincare/layout_cursor.h | 2 +- poincare/include/poincare/layout_reference.h | 2 +- poincare/src/fraction_layout_node.cpp | 219 ++++++++++++++++++ poincare/src/horizontal_layout_node.cpp | 3 + poincare/src/layout_cursor.cpp | 9 + poincare/src/layout_reference.cpp | 43 +++- poincare/src/rational.cpp | 9 +- 10 files changed, 342 insertions(+), 16 deletions(-) create mode 100644 poincare/include/poincare/fraction_layout_node.h create mode 100644 poincare/src/fraction_layout_node.cpp diff --git a/poincare/Makefile b/poincare/Makefile index c93d20646..fa6b54728 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -14,6 +14,7 @@ objs += $(addprefix poincare/src/,\ condensed_sum_layout_node.o\ conjugate_layout_node.o\ empty_layout_node.o\ + fraction_layout_node.o\ horizontal_layout_node.o\ integral_layout_node.o\ layout_cursor.o\ diff --git a/poincare/include/poincare.h b/poincare/include/poincare.h index 5381082c7..4899219bf 100644 --- a/poincare/include/poincare.h +++ b/poincare/include/poincare.h @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include diff --git a/poincare/include/poincare/fraction_layout_node.h b/poincare/include/poincare/fraction_layout_node.h new file mode 100644 index 000000000..cd1be1a5d --- /dev/null +++ b/poincare/include/poincare/fraction_layout_node.h @@ -0,0 +1,69 @@ +#ifndef POINCARE_FRACTION_LAYOUT_NODE_H +#define POINCARE_FRACTION_LAYOUT_NODE_H + +#include +#include +#include + +namespace Poincare { + +class FractionLayoutNode : public LayoutNode { +public: + using LayoutNode::LayoutNode; + + // LayoutNode + void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; + void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; + void moveCursorUp(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + void moveCursorDown(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + void deleteBeforeCursor(LayoutCursor * cursor) override; + int writeTextInBuffer(char * buffer, int bufferSize, PrintFloat::Mode floatDisplayMode, int numberOfSignificantDigits) const override; + bool shouldCollapseSiblingsOnLeft() const override { return true; } + bool shouldCollapseSiblingsOnRight() const override { return true; } + int leftCollapsingAbsorbingChildIndex() const override { return 0; } + int rightCollapsingAbsorbingChildIndex() const override { return 1; } + void didCollapseSiblings(LayoutCursor * cursor) override; + LayoutNode * layoutToPointWhenInserting() override; + bool canBeOmittedMultiplicationRightFactor() const override { return false; } + /* WARNING: We need to override this function, else 1/2 3/4 would be + * serialized as 1/2**3/4, as the two Fraction layouts think their sibling is + * an omitted multiplication layout factor. We have the same problem with + * 2^3 1/2 being serialized as 2^3**1/2, so must override the Right version + * and not canBeOmittedMultiplicationLeftFactor. */ + + // TreeNode + size_t size() const override { return sizeof(FractionLayoutNode); } + int numberOfChildren() const override { return 2; } +#if TREE_LOG + const char * description() const override { + return "FractionLayout"; + } +#endif + +protected: + // LayoutNode + void computeSize() override; + void computeBaseline() override; + KDPoint positionOfChild(LayoutNode * child) override; +private: + constexpr static KDCoordinate k_fractionLineMargin = 2; + constexpr static KDCoordinate k_fractionLineHeight = 1; + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + LayoutNode * numeratorLayout() { return childAtIndex(0); } + LayoutNode * denominatorLayout() { return childAtIndex(1); } +}; + +class FractionLayoutRef : public LayoutReference { +public: + FractionLayoutRef(LayoutRef numerator, LayoutRef denominator) : + LayoutReference() + { + addChildTreeAtIndex(numerator, 0); + addChildTreeAtIndex(denominator, 1); + } + FractionLayoutRef(TreeNode * t) : LayoutReference(t) {} +}; + +} + +#endif diff --git a/poincare/include/poincare/layout_cursor.h b/poincare/include/poincare/layout_cursor.h index f012d640b..dd8eed114 100644 --- a/poincare/include/poincare/layout_cursor.h +++ b/poincare/include/poincare/layout_cursor.h @@ -113,7 +113,7 @@ public: void addEmptySquareRootLayout(); void addEmptySquarePowerLayout(); void addEmptyTenPowerLayout(); - void addFractionLayoutAndCollapseSiblings() {} //TODO + void addFractionLayoutAndCollapseSiblings(); void addXNTCharLayout(); void insertText(const char * text); void addLayoutAndMoveCursor(LayoutRef l); diff --git a/poincare/include/poincare/layout_reference.h b/poincare/include/poincare/layout_reference.h index 57b79cbdd..d2126e919 100644 --- a/poincare/include/poincare/layout_reference.h +++ b/poincare/include/poincare/layout_reference.h @@ -91,7 +91,7 @@ public: assert(p.isDefined()); p.replaceChild(*this, newChild, cursor); } - void replaceWithJuxtapositionOf(LayoutReference leftChild, LayoutReference rightChild, LayoutCursor * cursor); + void replaceWithJuxtapositionOf(LayoutReference leftChild, LayoutReference rightChild, LayoutCursor * cursor, bool putCursorInTheMiddle = false); // Remove void removeChild(LayoutReference l, LayoutCursor * cursor, bool force = false); void removeChildAtIndex(int index, LayoutCursor * cursor, bool force = false) { diff --git a/poincare/src/fraction_layout_node.cpp b/poincare/src/fraction_layout_node.cpp new file mode 100644 index 000000000..8aca523b4 --- /dev/null +++ b/poincare/src/fraction_layout_node.cpp @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include +//#include +#include +#include +#include + +namespace Poincare { + +void FractionLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) { + if (cursor->position() == LayoutCursor::Position::Left + && ((numeratorLayout() && cursor->layoutNode() == numeratorLayout()) + || (denominatorLayout() && cursor->layoutNode() == denominatorLayout()))) + { + // Case: Left of the numerator or the denominator. Go Left of the fraction. + cursor->setLayoutNode(this); + return; + } + assert(cursor->layoutNode() == this); + // Case: Right. Go to the denominator. + if (cursor->position() == LayoutCursor::Position::Right) { + assert(denominatorLayout() != nullptr); + cursor->setLayoutNode(denominatorLayout()); + cursor->setPosition(LayoutCursor::Position::Right); + return; + } + // Case: Left. Ask the parent. + assert(cursor->position() == LayoutCursor::Position::Left); + LayoutNode * parentNode = parent(); + if (parentNode != nullptr) { + parentNode->moveCursorLeft(cursor, shouldRecomputeLayout); + } +} + +void FractionLayoutNode::moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) { + if (cursor->position() == LayoutCursor::Position::Right + && ((numeratorLayout() && cursor->layoutNode() == numeratorLayout()) + || (denominatorLayout() && cursor->layoutNode() == denominatorLayout()))) + { + // Case: Right of the numerator or the denominator. Go Right of the fraction. + cursor->setLayoutNode(this); + return; + } + assert(cursor->layoutNode() == this); + if (cursor->position() == LayoutCursor::Position::Left) { + // Case: Left. Go to the numerator. + assert(numeratorLayout() != nullptr); + cursor->setLayoutNode(numeratorLayout()); + return; + } + // Case: Right. Ask the parent. + assert(cursor->position() == LayoutCursor::Position::Right); + LayoutNode * parentNode = parent(); + if (parentNode) { + parentNode->moveCursorRight(cursor, shouldRecomputeLayout); + } +} + +void FractionLayoutNode::moveCursorUp(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + if (denominatorLayout() && cursor->layoutNode()->hasAncestor(denominatorLayout(), true)) { + // If the cursor is inside denominator, move it to the numerator. + assert(numeratorLayout() != nullptr); + numeratorLayout()->moveCursorUpInDescendants(cursor, shouldRecomputeLayout); + return; + } + if (cursor->layoutNode() == this){ + // If the cursor is Left or Right, move it to the numerator. + assert(numeratorLayout() != nullptr); + cursor->setLayoutNode(numeratorLayout()); + return; + } + LayoutNode::moveCursorUp(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +void FractionLayoutNode::moveCursorDown(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + if (numeratorLayout() && cursor->layoutNode()->hasAncestor(numeratorLayout(), true)) { + // If the cursor is inside numerator, move it to the denominator. + assert(denominatorLayout() != nullptr); + denominatorLayout()->moveCursorDownInDescendants(cursor, shouldRecomputeLayout); + return; + } + if (cursor->layoutNode() == this){ + // If the cursor is Left or Right, move it to the denominator. + assert(denominatorLayout() != nullptr); + cursor->setLayoutNode(denominatorLayout()); + return; + } + LayoutNode::moveCursorDown(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +void FractionLayoutNode::deleteBeforeCursor(LayoutCursor * cursor) { + if (cursor->layoutNode() == denominatorLayout()) { + /* Case: Left of the denominator. Replace the fraction with a horizontal + * juxtaposition of the numerator and the denominator. */ + assert(cursor->position() == LayoutCursor::Position::Left); + if (numeratorLayout()->isEmpty() && denominatorLayout()->isEmpty()) { + /* Case: Numerator and denominator are empty. Move the cursor and replace + * the fraction with an empty layout. */ + FractionLayoutRef(this).replaceWith(EmptyLayoutRef(), cursor); + // WARNING: Do no use "this" afterwards + return; + } + /* Else, replace the fraction with a juxtaposition of the numerator and + * denominator. Place the cursor in the middle of the juxtaposition, which + * is right of the numerator. */ + LayoutRef(this).replaceWithJuxtapositionOf(numeratorLayout(), denominatorLayout(), cursor, true); + // WARNING: Do no use "this" afterwards + return; + } + + if (cursor->layoutNode() == this && cursor->position() == LayoutCursor::Position::Right) { + // Case: Right. Move Right of the denominator. + cursor->setLayoutNode(denominatorLayout()); + return; + } + LayoutNode::deleteBeforeCursor(cursor); +} + +int FractionLayoutNode::writeTextInBuffer(char * buffer, int bufferSize, PrintFloat::Mode floatDisplayMode, int numberOfSignificantDigits) const { + if (bufferSize == 0) { + return -1; + } + buffer[bufferSize-1] = 0; + int numberOfChar = 0; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + + int idxInParent = -1; + LayoutNode * p = parent(); + if (p != nullptr) { + idxInParent = p->indexOfChild(this); + } + + // Add a multiplication if omitted. + if (idxInParent > 0 && p->isHorizontal() && p->childAtIndex(idxInParent - 1)->canBeOmittedMultiplicationLeftFactor()) { + buffer[numberOfChar++] = Ion::Charset::MiddleDot; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + } + + bool addParenthesis = false; + if (idxInParent >= 0 && idxInParent < (p->numberOfChildren() - 1) && p->isHorizontal() && p->childAtIndex(idxInParent + 1)->isVerticalOffset()) { + addParenthesis = true; + // Add parenthesis + buffer[numberOfChar++] = '('; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + } + + // Write the content of the fraction + numberOfChar += LayoutEngine::writeInfixSerializableRefTextInBuffer(SerializableRef(const_cast(this)), buffer+numberOfChar, bufferSize-numberOfChar, floatDisplayMode, numberOfSignificantDigits, "/"); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + if (addParenthesis) { + // Add parenthesis + buffer[numberOfChar++] = ')'; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + } + + // Add a multiplication if omitted. + if (idxInParent >= 0 && idxInParent < (p->numberOfChildren() - 1) && p->isHorizontal() && p->childAtIndex(idxInParent + 1)->canBeOmittedMultiplicationRightFactor()) { + buffer[numberOfChar++] = Ion::Charset::MiddleDot; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + } + + buffer[numberOfChar] = 0; + return numberOfChar; +} + +LayoutNode * FractionLayoutNode::layoutToPointWhenInserting() { + if (numeratorLayout()->isEmpty()){ + return numeratorLayout(); + } + if (denominatorLayout()->isEmpty()){ + return denominatorLayout(); + } + return this; +} + +void FractionLayoutNode::didCollapseSiblings(LayoutCursor * cursor) { + cursor->setLayoutNode(denominatorLayout()); + cursor->setPosition(LayoutCursor::Position::Left); +} + +void FractionLayoutNode::computeSize() { + KDCoordinate width = max(numeratorLayout()->layoutSize().width(), denominatorLayout()->layoutSize().width()) + + 2*Metric::FractionAndConjugateHorizontalOverflow+2*Metric::FractionAndConjugateHorizontalMargin; + KDCoordinate height = numeratorLayout()->layoutSize().height() + + k_fractionLineMargin + k_fractionLineHeight + k_fractionLineMargin + + denominatorLayout()->layoutSize().height(); + m_frame.setSize(KDSize(width, height)); + m_sized = true; +} + +void FractionLayoutNode::computeBaseline() { + m_baseline = numeratorLayout()->layoutSize().height() + k_fractionLineMargin + k_fractionLineHeight; + m_baselined = true; +} + +KDPoint FractionLayoutNode::positionOfChild(LayoutNode * child) { + KDCoordinate x = 0; + KDCoordinate y = 0; + if (child == numeratorLayout()) { + x = (KDCoordinate)((layoutSize().width() - numeratorLayout()->layoutSize().width())/2); + } else if (child == denominatorLayout()) { + x = (KDCoordinate)((layoutSize().width() - denominatorLayout()->layoutSize().width())/2); + y = (KDCoordinate)(numeratorLayout()->layoutSize().height() + 2*k_fractionLineMargin + k_fractionLineHeight); + } else { + assert(false); // Should not happen + } + return KDPoint(x, y); +} + +void FractionLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + KDCoordinate fractionLineY = p.y() + numeratorLayout()->layoutSize().height() + k_fractionLineMargin; + ctx->fillRect(KDRect(p.x()+Metric::FractionAndConjugateHorizontalMargin, fractionLineY, layoutSize().width()-2*Metric::FractionAndConjugateHorizontalMargin, k_fractionLineHeight), expressionColor); +} + +} diff --git a/poincare/src/horizontal_layout_node.cpp b/poincare/src/horizontal_layout_node.cpp index 5f1bea874..08e69912a 100644 --- a/poincare/src/horizontal_layout_node.cpp +++ b/poincare/src/horizontal_layout_node.cpp @@ -346,6 +346,9 @@ bool HorizontalLayoutNode::willReplaceChild(LayoutNode * oldChild, LayoutNode * // HorizontalLayoutRef void HorizontalLayoutRef::addOrMergeChildAtIndex(LayoutRef l, int index, bool removeEmptyChildren, LayoutCursor * cursor) { + if (l.isEmpty() && removeEmptyChildren) { + return; + } if (l.isHorizontal()) { mergeChildrenAtIndex(HorizontalLayoutRef(l.node()), index, removeEmptyChildren, cursor); } else { diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index 600220cb8..229da0fc5 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -115,6 +116,14 @@ void LayoutCursor::addEmptyTenPowerLayout() { } } +void LayoutCursor::addFractionLayoutAndCollapseSiblings() { + HorizontalLayoutRef child1 = HorizontalLayoutRef(EmptyLayoutRef()); + HorizontalLayoutRef child2 = HorizontalLayoutRef(EmptyLayoutRef()); + FractionLayoutRef newChild = FractionLayoutRef(child1, child2); + m_layoutRef.addSibling(this, newChild, true); + LayoutRef(newChild.node()).collapseSiblings(this); +} + void LayoutCursor::addXNTCharLayout() { m_layoutRef.addSibling(this, CharLayoutRef(m_layoutRef.XNTChar()), true); } diff --git a/poincare/src/layout_reference.cpp b/poincare/src/layout_reference.cpp index 351887069..3a1758bbf 100644 --- a/poincare/src/layout_reference.cpp +++ b/poincare/src/layout_reference.cpp @@ -42,16 +42,43 @@ void LayoutRef::replaceChildWithEmpty(LayoutRef oldChild, LayoutCursor * cursor) } template<> -void LayoutRef::replaceWithJuxtapositionOf(LayoutRef leftChild, LayoutRef rightChild, LayoutCursor * cursor) { +void LayoutRef::replaceWithJuxtapositionOf(LayoutRef leftChild, LayoutRef rightChild, LayoutCursor * cursor, bool putCursorInTheMiddle) { LayoutReference p = parent(); assert(p.isDefined()); - assert(!p.isHorizontal()); - /* One of the children to juxtapose might be "this", so we cannot just call - * replaceWith. */ - HorizontalLayoutRef horizontalLayoutR; - p.replaceChild(*this, horizontalLayoutR, cursor); - horizontalLayoutR.addOrMergeChildAtIndex(leftChild, 0, false); - horizontalLayoutR.addOrMergeChildAtIndex(rightChild, 1, false); + if (!p.isHorizontal()) { + /* One of the children to juxtapose might be "this", so we cannot just call + * replaceWith. */ + HorizontalLayoutRef horizontalLayoutR; + p.replaceChild(*this, horizontalLayoutR, cursor); + horizontalLayoutR.addOrMergeChildAtIndex(leftChild, 0, false); + if (putCursorInTheMiddle) { + if (!horizontalLayoutR.isEmpty()) { + cursor->setLayoutReference(horizontalLayoutR.childAtIndex(horizontalLayoutR.numberOfChildren()-1)); + cursor->setPosition(LayoutCursor::Position::Right); + } else { + cursor->setLayoutReference(horizontalLayoutR); + cursor->setPosition(LayoutCursor::Position::Left); + } + } + horizontalLayoutR.addOrMergeChildAtIndex(rightChild, 1, false); + return; + } + /* The parent is an Horizontal layout, so directly add the two juxtaposition + * children to the parent. */ + int idxInParent = p.indexOfChild(*this); + HorizontalLayoutRef castedParent = HorizontalLayoutRef(p.node()); + if (putCursorInTheMiddle) { + if (idxInParent > 0) { + cursor->setLayoutReference(castedParent.childAtIndex(idxInParent-1)); + cursor->setPosition(LayoutCursor::Position::Right); + } else { + cursor->setLayoutReference(castedParent); + cursor->setPosition(LayoutCursor::Position::Left); + } + } + castedParent.addOrMergeChildAtIndex(rightChild, idxInParent, true); + castedParent.addOrMergeChildAtIndex(leftChild, idxInParent, true, putCursorInTheMiddle ? cursor : nullptr); + p.removeChild(*this, cursor->layoutReference() == *this ? cursor : nullptr); } template diff --git a/poincare/src/rational.cpp b/poincare/src/rational.cpp index a453e3c36..fba2b4430 100644 --- a/poincare/src/rational.cpp +++ b/poincare/src/rational.cpp @@ -7,8 +7,7 @@ extern "C" { } #include #include -#include "layout/fraction_layout.h" -#include +#include namespace Poincare { @@ -157,10 +156,8 @@ LayoutRef Rational::createLayout(PrintFloat::Mode floatDisplayMode, int numberOf if (m_denominator.isOne()) { return numeratorLayout; } - return CharLayoutRef('a'); //TODO - /* - ExpressionLayout * denominatorLayout = m_denominator.createLayout(); - return new FractionLayout(numeratorLayout, denominatorLayout, false);*/ + LayoutRef denominatorLayout = m_denominator.createLayout(); + return FractionLayoutRef(numeratorLayout, denominatorLayout); } int Rational::writeTextInBuffer(char * buffer, int bufferSize, PrintFloat::Mode floatDisplayMode, int numberOfSignificantDigits) const {