diff --git a/poincare/include/poincare/horizontal_layout_node.h b/poincare/include/poincare/horizontal_layout_node.h index f6dcc9bab..8b60dbaea 100644 --- a/poincare/include/poincare/horizontal_layout_node.h +++ b/poincare/include/poincare/horizontal_layout_node.h @@ -17,9 +17,11 @@ public: m_numberOfChildren(0) {} - bool isHorizontal() const override { return true; } + void addOrMergeChildAtIndex(LayoutNode * l, int index, bool removeEmptyChildren); + void mergeChildrenAtIndex(HorizontalLayoutNode * horizontalLayout, int index, bool removeEmptyChildren); // LayoutNode + bool isHorizontal() const override { return true; } int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; @@ -46,7 +48,10 @@ protected: KDPoint positionOfChild(LayoutNode * l) override; private: + void privateAddSibling(LayoutCursor * cursor, LayoutNode * sibling, bool moveCursor) override; + void privateRemoveChildAtIndex(int index, bool forceRemove); void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override {} + int removeEmptyChildBeforeInsertionAtIndex(int index, bool shouldRemoveOnLeft); int m_numberOfChildren; }; diff --git a/poincare/include/poincare/layout_cursor.h b/poincare/include/poincare/layout_cursor.h index cf24f8533..04e014c21 100644 --- a/poincare/include/poincare/layout_cursor.h +++ b/poincare/include/poincare/layout_cursor.h @@ -50,7 +50,6 @@ public: bool isDefined() const { return m_layoutRef.isDefined(); } - /* Getters and setters */ LayoutRef layoutReference() { return m_layoutRef; } int layoutIdentifier() { return m_layoutRef.identifier(); } @@ -97,7 +96,6 @@ public: return result; } /* Layout modification */ - void clearLayout() {} //TODO void addFractionLayoutAndCollapseSiblings() {} //TODO void addEmptyExponentialLayout() {} //TODO void addEmptyPowerLayout() {} //TODO @@ -109,7 +107,8 @@ public: void performBackspace() {} //TODO bool showEmptyLayoutIfNeeded() { return false; } //TODO bool hideEmptyLayoutIfNeeded() { return false; } //TODO - void addLayoutAndMoveCursor(LayoutRef l) {} //TODO + void addLayoutAndMoveCursor(LayoutRef l); + void clearLayout(); private: constexpr static KDCoordinate k_cursorHeight = 18; diff --git a/poincare/include/poincare/layout_node.h b/poincare/include/poincare/layout_node.h index f93a4338c..004d80337 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -25,7 +25,10 @@ public: virtual bool hasText() const { return false; } //TODO virtual char XNTChar() const { return 'x'; } virtual bool isHorizontal() const { return false; } + virtual bool isEmpty() const { return false; } virtual bool isLeftParenthesis() const { return false; } + virtual bool isVerticalOffset() const { return false; } + virtual bool mustHaveLeftSibling() const { return false; } // Rendering void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); @@ -48,6 +51,7 @@ public: // Hierarchy LayoutNode * parent() const { return static_cast(parentTree()); } LayoutNode * childAtIndex(int i) { return static_cast(childTreeAtIndex(i)); } + LayoutNode * root() { return static_cast(rootTree()); } // Tree navigation virtual void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) {} @@ -57,9 +61,13 @@ public: virtual LayoutCursor equivalentCursor(LayoutCursor * cursor); //TODO // Tree modification + void addSibling(LayoutCursor * cursor, LayoutNode * sibling); + void addSiblingAndMoveCursor(LayoutCursor * cursor, LayoutNode * sibling); + void collapseSiblingsAndMoveCursor(LayoutCursor * cursor) {} //TODO bool removeGreySquaresFromAllMatrixAncestors() { return false; } //TODO bool addGreySquaresToAllMatrixAncestors() { return false; } //TODO virtual LayoutNode * layoutToPointWhenInserting() { return this; } //TODO + LayoutNode * replaceWithJuxtapositionOf(LayoutNode * leftChild, LayoutNode * rightChild) {return nullptr; } //TODO protected: // Iterators class Iterator { @@ -102,6 +110,7 @@ protected: bool m_positioned; bool m_sized; private: + virtual void privateAddSibling(LayoutCursor * cursor, LayoutNode * sibling, bool moveCursor); virtual void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) = 0; }; diff --git a/poincare/include/poincare/layout_reference.h b/poincare/include/poincare/layout_reference.h index 91a1b852f..dc4f9f403 100644 --- a/poincare/include/poincare/layout_reference.h +++ b/poincare/include/poincare/layout_reference.h @@ -14,11 +14,10 @@ class LayoutReference : public TreeReference { public: using TreeReference::TreeReference; - /* Allow every LayoutReference to be transformed into a - * LayoutReference, i.e. Layout */ - operator LayoutReference() const { - return LayoutReference(this->node()); - } + // Operators + + // Allow every LayoutReference to be transformed into a LayoutRef + operator LayoutReference() const { return LayoutReference(this->node()); } LayoutReference& operator=(LayoutReference& lr) { this->setTo(lr); return *this; @@ -30,36 +29,49 @@ public: inline bool operator==(LayoutReference l) { return this->identifier() == l.identifier(); } inline bool operator!=(LayoutReference l) { return this->identifier() != l.identifier(); } - static TreeNode * FailedAllocationStaticNode(); + // Rendering + void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite) { + return this->typedNode()->draw(ctx, p, expressionColor, backgroundColor); + } + KDSize layoutSize() { return this->typedNode()->layoutSize(); } + KDPoint layoutOrigin() { return this->typedNode()->layoutOrigin(); } + KDPoint absoluteOrigin() { return this->typedNode()->absoluteOrigin(); } + KDCoordinate baseline() { return this->typedNode()->baseline(); } + void invalidAllSizesPositionsAndBaselines() { return this->typedNode()->invalidAllSizesPositionsAndBaselines(); } + // Layout properties + bool isHorizontal() const { return this->typedNode()->isHorizontal(); } + bool isLeftParenthesis() const { return this->typedNode()->isLeftParenthesis(); } + bool hasText() { return this->typedNode()->hasText(); } + char XNTChar() const { return this->typedNode()->XNTChar(); } + + // Layout modification + bool removeGreySquaresFromAllMatrixAncestors() { return this->typedNode()->removeGreySquaresFromAllMatrixAncestors(); } + bool addGreySquaresToAllMatrixAncestors() { return this->typedNode()->addGreySquaresToAllMatrixAncestors(); } + LayoutReference layoutToPointWhenInserting() { return LayoutReference(this->typedNode()->layoutToPointWhenInserting()); } + + // Cursor LayoutCursor cursor() const; + LayoutCursor equivalentCursor(LayoutCursor * cursor); + + // Hierarchy LayoutReference childAtIndex(int i) { TreeReference treeRefChild = TreeReference::treeChildAtIndex(i); return LayoutReference(treeRefChild.node()); } + LayoutReference root() { return LayoutReference(this->typedNode()->root()); } + // Hierarchy modification void replaceChildAtIndex(int oldChildIndex, LayoutReference newChild) { TreeReference::replaceChildAtIndex(oldChildIndex, newChild); } + void addSiblingAndMoveCursor(LayoutCursor * cursor, LayoutReference sibling) { return this->typedNode()->addSiblingAndMoveCursor(cursor, sibling.typedNode()); } //TODO + void collapseSiblingsAndMoveCursor(LayoutCursor * cursor) {} //TODO + LayoutReference replaceWithJuxtapositionOf(LayoutReference leftChild, LayoutReference rightChild); //TODO - void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite) { - return this->typedNode()->draw(ctx, p, expressionColor, backgroundColor); - } - - bool isHorizontal() const { return this->typedNode()->isHorizontal(); } - bool isLeftParenthesis() const { return this->typedNode()->isLeftParenthesis(); } - bool hasText() { return this->typedNode()->hasText(); } - char XNTChar() const { return this->typedNode()->XNTChar(); } - KDSize layoutSize() { return this->typedNode()->layoutSize(); } - KDPoint layoutOrigin() { return this->typedNode()->layoutOrigin(); } - KDPoint absoluteOrigin() { return this->typedNode()->absoluteOrigin(); } - KDCoordinate baseline() { return this->typedNode()->baseline(); } - LayoutCursor equivalentCursor(LayoutCursor * cursor); - void invalidAllSizesPositionsAndBaselines() { return this->typedNode()->invalidAllSizesPositionsAndBaselines(); } - bool removeGreySquaresFromAllMatrixAncestors() { return this->typedNode()->removeGreySquaresFromAllMatrixAncestors(); } - bool addGreySquaresToAllMatrixAncestors() { return this->typedNode()->addGreySquaresToAllMatrixAncestors(); } - LayoutReference layoutToPointWhenInserting() { return LayoutReference(this->typedNode()->layoutToPointWhenInserting()); } + // Allocation failure + static TreeNode * FailedAllocationStaticNode(); }; typedef LayoutReference LayoutRef; diff --git a/poincare/include/poincare/tree_node.h b/poincare/include/poincare/tree_node.h index 5ffec60c8..5df3cf9db 100644 --- a/poincare/include/poincare/tree_node.h +++ b/poincare/include/poincare/tree_node.h @@ -35,9 +35,7 @@ public: } } - virtual const char * description() const { - return "UNKNOWN"; - } + virtual const char * description() const { return "UNKNOWN";} // Serialization virtual bool needsParenthesisWithParent(TreeNode * parentNode) { return false; } //TODO virtual pure and override on expresionNode/layoutNode @@ -66,7 +64,7 @@ public: // Hierarchy TreeNode * parentTree() const; - TreeNode * editableRootTree(); + TreeNode * rootTree(); virtual int numberOfChildren() const = 0; virtual void incrementNumberOfChildren(int increment = 1) {} //TODO Put an assert false virtual void decrementNumberOfChildren(int decrement = 1) {} //TODO Put an assert false //TODO what if somebody i stealing a unary tree's only child ? diff --git a/poincare/include/poincare/tree_reference.h b/poincare/include/poincare/tree_reference.h index 2048d5d27..19aeeb527 100644 --- a/poincare/include/poincare/tree_reference.h +++ b/poincare/include/poincare/tree_reference.h @@ -234,10 +234,11 @@ public: TreePool::sharedPool()->move(secondChild.node(), firstChildNode); } - void mergeChildren(TreeReference t) { + void mergeChildrenAtIndex(TreeReference t, int i) { + assert(i >= 0 && i <= numberOfChildren()); // Steal operands int numberOfNewChildren = t.numberOfChildren(); - TreePool::sharedPool()->moveChildren(t.node(), node()->lastDescendant()->next()); + TreePool::sharedPool()->moveChildren(t.node(), node()->childTreeAtIndex(i)->nextSibling()); t.node()->eraseNumberOfChildren(); // If t is a child, remove it if (node()->hasChild(t.node())) { diff --git a/poincare/src/horizontal_layout_node.cpp b/poincare/src/horizontal_layout_node.cpp index 4268bc074..6e7c23384 100644 --- a/poincare/src/horizontal_layout_node.cpp +++ b/poincare/src/horizontal_layout_node.cpp @@ -5,6 +5,26 @@ namespace Poincare { static inline KDCoordinate maxCoordinate(KDCoordinate c1, KDCoordinate c2) { return c1 > c2 ? c1 : c2; } +void HorizontalLayoutNode::addOrMergeChildAtIndex(LayoutNode * l, int index, bool removeEmptyChildren) { + if (l->isHorizontal()) { + mergeChildrenAtIndex(static_cast(l), index, removeEmptyChildren); + } else { + addChildAtIndex(l, index); + } +} + +void HorizontalLayoutNode::mergeChildrenAtIndex(HorizontalLayoutNode * h, int index, bool removeEmptyChildren) { + /* Remove any empty child that would be next to the inserted layout. + * If the layout to insert starts with a vertical offset layout, any empty + * layout child directly on the left of the inserted layout (if there is one) + * should not be removed: it will be the base for the VerticalOffsetLayout. */ + bool shouldRemoveOnLeft = h->numberOfChildren() == 0 ? true : !(h->childAtIndex(0)->mustHaveLeftSibling()); + int newIndex = removeEmptyChildBeforeInsertionAtIndex(index, shouldRemoveOnLeft); + + // Merge the horizontal layout + LayoutRef(this).mergeChildrenAtIndex(LayoutRef(h), newIndex); +} + int HorizontalLayoutNode::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { if (numberOfChildren() == 0) { if (bufferSize == 0) { @@ -101,4 +121,87 @@ KDPoint HorizontalLayoutNode::positionOfChild(LayoutNode * l) { return KDPoint(x, y); } +// Private + +void HorizontalLayoutNode::privateAddSibling(LayoutCursor * cursor, LayoutNode * sibling, bool moveCursor) { + int childrenCount = numberOfChildren(); + // Add the "sibling" as a child. + if (cursor->position() == LayoutCursor::Position::Left) { + int indexForInsertion = 0; + /* If the first child is empty, remove it before adding the layout, unless + * the new sibling needs the empty layout as a base. */ + if (childrenCount > 0 && childAtIndex(0)->isEmpty()) { + if (sibling->mustHaveLeftSibling()) { + indexForInsertion = 1; + } else { + /* We force the removal of the child even followed by a neighbourg + * requiring a left sibling as we are about to add a sibling in first + * position anyway. */ + privateRemoveChildAtIndex(0, true); + } + } + if (moveCursor) { + if (childrenCount > indexForInsertion) { + cursor->setLayoutNode(childAtIndex(indexForInsertion)); + } else { + cursor->setLayoutNode(this); + cursor->setPosition(LayoutCursor::Position::Right); + } + } + addOrMergeChildAtIndex(sibling, indexForInsertion, false); + return; + } + assert(cursor->position() == LayoutCursor::Position::Right); + // If the last child is empty, remove it before adding the layout. + if (childrenCount > 0 && childAtIndex(childrenCount - 1)->isEmpty() && !sibling->mustHaveLeftSibling()) { + /* Force remove the last child. */ + privateRemoveChildAtIndex(childrenCount - 1, true); + childrenCount--; + } + addOrMergeChildAtIndex(sibling, childrenCount, false); + if (moveCursor) { + cursor->setLayoutNode(this); + } +} + +void HorizontalLayoutNode::privateRemoveChildAtIndex(int index, bool forceRemove) { + /* Remove the child before potentially adding an EmptyLayout. Indeed, adding + * a new child would remove any EmptyLayout surrounding the new child and in + * the case the child to be removed was an Empty layout, it would result in + * removing it twice if we remove it afterwards. */ + removeChild(childAtIndex(index)); + /* If the child to remove is at index 0 and its right sibling must have a left + * sibling (e.g. it is a VerticalOffsetLayout), replace the child with an + * EmptyLayout instead of removing it. */ + + /*if (!forceRemove && index == 0 && numberOfChildren() > 0 && child(0)->mustHaveLeftSibling()) { + addChildAtIndex(new EmptyLayout(), 0); + }*/ //TODO +} + +int HorizontalLayoutNode::removeEmptyChildBeforeInsertionAtIndex(int index, bool shouldRemoveOnLeft) { + int currentNumberOfChildren = numberOfChildren(); + assert(index >= 0 && index <= currentNumberOfChildren); + int newIndex = index; + /* If empty, remove the child that would be on the right of the inserted + * layout. */ + if (newIndex < currentNumberOfChildren) { + LayoutNode * c = childAtIndex(newIndex); + if (c->isEmpty()) { + removeChild(c); + currentNumberOfChildren--; + } + } + /* If empty, remove the child that would be on the left of the inserted + * layout. */ + if (shouldRemoveOnLeft && newIndex - 1 >= 0 && newIndex - 1 <= currentNumberOfChildren -1) { + LayoutNode * c = childAtIndex(newIndex - 1); + if (c->isEmpty()) { + removeChild(c); + newIndex = index - 1; + } + } + return newIndex; +} + } diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index 3386f5883..7a0d39673 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -58,6 +58,22 @@ void LayoutCursor::moveUnder(bool * shouldRecomputeLayout) { layoutReference().typedNode()->moveCursorDown(this, shouldRecomputeLayout); } +/* Layout modification */ +void LayoutCursor::addLayoutAndMoveCursor(LayoutRef l) { + bool layoutWillBeMerged = l.isHorizontal(); + m_layoutRef.addSiblingAndMoveCursor(this, l); //TODO + if (!layoutWillBeMerged) { + l.collapseSiblingsAndMoveCursor(this); + } +} + +void LayoutCursor::clearLayout() { + LayoutRef rootLayoutR = m_layoutRef.root(); + assert(rootLayoutR.isHorizontal()); + rootLayoutR.removeChildren(); + m_layoutRef = rootLayoutR; +} + /* Private */ KDCoordinate LayoutCursor::layoutHeight() { diff --git a/poincare/src/layout_node.cpp b/poincare/src/layout_node.cpp index 2e607c4c4..bf5177fdb 100644 --- a/poincare/src/layout_node.cpp +++ b/poincare/src/layout_node.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -71,4 +72,72 @@ LayoutCursor LayoutNode::equivalentCursor(LayoutCursor * cursor) { return LayoutCursor(cursor->layoutReference()); } +// Tree modification +void LayoutNode::addSibling(LayoutCursor * cursor, LayoutNode * sibling) { + privateAddSibling(cursor, sibling, false); +} + +void LayoutNode::addSiblingAndMoveCursor(LayoutCursor * cursor, LayoutNode * sibling) { + privateAddSibling(cursor, sibling, true); +} + +// Private + +void LayoutNode::privateAddSibling(LayoutCursor * cursor, LayoutNode * sibling, bool moveCursor) { + /* The layout must have a parent, because HorizontalLayout overrides + * privateAddSibling and only an HorizontalLayout can be the root layout. */ + LayoutNode * p = parent(); + assert(p != nullptr); + if (p->isHorizontal()) { + int indexInParent = p->indexOfChild(this); + int siblingIndex = cursor->position() == LayoutCursor::Position::Left ? indexInParent : indexInParent + 1; + + /* Special case: If the neighbour sibling is a VerticalOffsetLayout, let it + * handle the insertion of the new sibling. Do not enter the special case if + * "this" is a VerticalOffsetLayout, to avoid an infinite loop. */ + if (!isVerticalOffset()) { + LayoutNode * neighbour = nullptr; + if (cursor->position() == LayoutCursor::Position::Left && indexInParent > 0) { + neighbour = p->childAtIndex(indexInParent - 1); + } else if (cursor->position() ==LayoutCursor::Position::Right && indexInParent < p->numberOfChildren() - 1) { + neighbour = p->childAtIndex(indexInParent + 1); + } + if (neighbour != nullptr && neighbour->isVerticalOffset()) { + cursor->setLayoutNode(neighbour); + cursor->setPosition(cursor->position() == LayoutCursor::Position::Left ? LayoutCursor::Position::Right : LayoutCursor::Position::Left); + if (moveCursor) { + neighbour->addSiblingAndMoveCursor(cursor, sibling); + } else { + neighbour->addSibling(cursor, sibling); + } + return; + } + } + + // Else, let the parent add the sibling. + if (moveCursor) { + if (siblingIndex < p->numberOfChildren()) { + cursor->setLayoutNode(p->childAtIndex(siblingIndex)); + cursor->setPosition(LayoutCursor::Position::Left); + } else { + cursor->setLayoutNode(p); + cursor->setPosition(LayoutCursor::Position::Right); + } + } + static_cast(p)->addOrMergeChildAtIndex(sibling, siblingIndex, true); //TODO + return; + } + LayoutNode * juxtapositionLayout = nullptr; + if (cursor->position() == LayoutCursor::Position::Left) { + juxtapositionLayout = replaceWithJuxtapositionOf(sibling, this); //TODO + } else { + assert(cursor->position() == LayoutCursor::Position::Right); + juxtapositionLayout = replaceWithJuxtapositionOf(this, sibling); + } + if (moveCursor) { + cursor->setLayoutNode(juxtapositionLayout); + cursor->setPosition(LayoutCursor::Position::Right); + } +} + } diff --git a/poincare/src/tree_node.cpp b/poincare/src/tree_node.cpp index 56c863bb3..ce92e7e09 100644 --- a/poincare/src/tree_node.cpp +++ b/poincare/src/tree_node.cpp @@ -81,7 +81,7 @@ TreeNode * TreeNode::parentTree() const { #endif } -TreeNode * TreeNode::editableRootTree() { +TreeNode * TreeNode::rootTree() { for (TreeNode * root : TreePool::sharedPool()->roots()) { if (hasAncestor(root, true)) { return root;