Files
Upsilon/poincare/src/horizontal_layout_node.cpp
2018-07-27 16:52:12 +02:00

416 lines
15 KiB
C++

#include <poincare/horizontal_layout_node.h>
#include <poincare/empty_layout_node.h>
#include <poincare/layout_engine.h>
namespace Poincare {
static inline KDCoordinate maxCoordinate(KDCoordinate c1, KDCoordinate c2) { return c1 > c2 ? c1 : c2; }
// LayoutNode
void HorizontalLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) {
if (this == cursor->layoutNode()) {
if (cursor->position() == LayoutCursor::Position::Left) {
// Case: Left. Ask the parent.
LayoutNode * parentNode = parent();
if (parentNode != nullptr) {
parentNode->moveCursorLeft(cursor, shouldRecomputeLayout);
}
return;
}
assert(cursor->position() == LayoutCursor::Position::Right);
/* Case: Right. Go to the last child if there is one, and move Left. Else
* go Left and ask the parent. */
int childrenCount = numberOfChildren();
if (childrenCount >= 1) {
cursor->setLayoutNode(childAtIndex(childrenCount-1));
} else {
cursor->setPosition(LayoutCursor::Position::Left);
}
return cursor->moveLeft(shouldRecomputeLayout);
}
// Case: The cursor is Left of a child.
assert(cursor->position() == LayoutCursor::Position::Left);
int childIndex = indexOfChildByIdentifier(cursor->layoutIdentifier());
assert(childIndex >= 0);
if (childIndex == 0) {
// Case: the child is the leftmost. Ask the parent.
if (parent()) {
cursor->setLayoutNode(this);
return cursor->moveLeft(shouldRecomputeLayout);
}
return;
}
// Case: the child is not the leftmost. Go to its left sibling and move Left.
cursor->setLayoutNode(childAtIndex(childIndex-1));
cursor->setPosition(LayoutCursor::Position::Right);
cursor->moveLeft(shouldRecomputeLayout);
}
void HorizontalLayoutNode::moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) {
if (this == cursor->layoutNode()) {
if (cursor->position() == LayoutCursor::Position::Right) {
// Case: Right. Ask the parent.
LayoutNode * parentNode = parent();
if (parentNode) {
parentNode->moveCursorRight(cursor, shouldRecomputeLayout);
}
return;
}
assert(cursor->position() == LayoutCursor::Position::Left);
// Case: Left
int childrenCount = numberOfChildren();
LayoutNode * parentNode = parent();
if (childrenCount == 0) {
// If there are no children, go Right and ask the parent
cursor->setPosition(LayoutCursor::Position::Right);
if (parentNode != nullptr) {
parentNode->moveCursorRight(cursor, shouldRecomputeLayout);
}
return;
}
/* If there is at least one child, set the cursor to the first child and
* move it Right */
LayoutNode * firstChild = childAtIndex(0);
assert(firstChild != nullptr);
cursor->setLayoutNode(firstChild);
return firstChild->moveCursorRight(cursor, shouldRecomputeLayout);
}
// Case: The cursor is Right of a child.
assert(cursor->position() == LayoutCursor::Position::Right);
int childIndex = indexOfChild(cursor->layoutNode());
assert(childIndex >= 0);
if (childIndex == numberOfChildren() - 1) {
// Case: the child is the rightmost. Ask the parent.
LayoutNode * parentNode = parent();
if (parentNode) {
cursor->setLayoutNode(this);
parentNode->moveCursorRight(cursor, shouldRecomputeLayout);
}
return;
}
/* Case: the child is not the rightmost. Go to its right sibling and move
* Right. */
cursor->setLayoutNode(childAtIndex(childIndex+1));
cursor->setPosition(LayoutCursor::Position::Left);
return childAtIndex(childIndex+1)->moveCursorRight(cursor, shouldRecomputeLayout);
}
LayoutCursor HorizontalLayoutNode::equivalentCursor(LayoutCursor * cursor) {
if (cursor->layoutNode() == this) {
// First or last child, if any
int childrenCount = numberOfChildren();
if (childrenCount == 0) {
return LayoutCursor();
}
int index = cursor->position() == LayoutCursor::Position::Left ? 0 : childrenCount - 1;
return LayoutCursor(childAtIndex(index), cursor->position());
}
// Left or right of a child: return right or left of its sibling, or of this
int indexOfPointedLayout = indexOfChild(cursor->layoutNode());
if (indexOfPointedLayout < 0) {
return LayoutCursor();
}
if (cursor->position() == LayoutCursor::Position::Left) {
if (indexOfPointedLayout == 0) {
return LayoutCursor(this, LayoutCursor::Position::Left);
}
return LayoutCursor(childAtIndex(indexOfPointedLayout - 1), LayoutCursor::Position::Right);
}
assert(cursor->position() == LayoutCursor::Position::Right);
if (indexOfPointedLayout == numberOfChildren() - 1) {
return LayoutCursor(this, LayoutCursor::Position::Right);
}
return LayoutCursor(childAtIndex(indexOfPointedLayout + 1), LayoutCursor::Position::Left);
}
void HorizontalLayoutNode::deleteBeforeCursor(LayoutCursor * cursor) {
LayoutNode * p = parent();
if (p == nullptr
&& cursor->layoutNode() == this
&& (cursor->position() == LayoutCursor::Position::Left
|| numberOfChildren() == 0))
{
/* Case: Left and this is the main layout or Right and this is the main
* layout with no children. Return. */
return;
}
if (cursor->position() == LayoutCursor::Position::Left) {
int indexOfPointedLayout = indexOfChild(cursor->layoutNode());
if (indexOfPointedLayout >= 0) {
/* Case: Left of a child.
* Point Right of the previous child. If there is no previous child, point
* Left of this. Perform another backspace. */
if (indexOfPointedLayout == 0) {
cursor->setLayoutNode(this);
} else {
assert(indexOfPointedLayout > 0);
cursor->setLayoutNode(childAtIndex(indexOfPointedLayout - 1));
cursor->setPosition(LayoutCursor::Position::Right);
}
cursor->performBackspace();
return;
}
}
assert(cursor->layoutNode() == this);
if (cursor->position() == LayoutCursor::Position::Right) {
// Case: Right. Point to the last child and perform backspace.
cursor->setLayoutNode(childAtIndex(numberOfChildren() - 1));
cursor->performBackspace();
return;
}
LayoutNode::deleteBeforeCursor(cursor);
}
int HorizontalLayoutNode::writeTextInBuffer(char * buffer, int bufferSize, PrintFloat::Mode floatDisplayMode, int numberOfSignificantDigits) const {
if (numberOfChildren() == 0) {
if (bufferSize == 0) {
return -1;
}
buffer[0] = 0;
return 0;
}
return LayoutEngine::writeInfixSerializableRefTextInBuffer(SerializableRef(const_cast<HorizontalLayoutNode *>(this)), buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, "");
}
// Protected
void HorizontalLayoutNode::computeSize() {
assert(!m_sized);
KDCoordinate totalWidth = 0;
KDCoordinate maxUnderBaseline = 0;
KDCoordinate maxAboveBaseline = 0;
for (LayoutNode * l : children()) {
KDSize childSize = l->layoutSize();
totalWidth += childSize.width();
maxUnderBaseline = maxCoordinate(maxUnderBaseline, childSize.height() - l->baseline());
maxAboveBaseline = maxCoordinate(maxAboveBaseline, l->baseline());
}
m_frame.setSize(KDSize(totalWidth, maxUnderBaseline + maxAboveBaseline));
m_sized = true;
}
void HorizontalLayoutNode::computeBaseline() {
assert(!m_baselined);
m_baseline = 0;
for (LayoutNode * l : children()) {
m_baseline = maxCoordinate(m_baseline, l->baseline());
}
m_baselined = true;
}
KDPoint HorizontalLayoutNode::positionOfChild(LayoutNode * l) {
assert(hasChild(l));
KDCoordinate x = 0;
int index = indexOfChild(l);
assert(index > -1);
if (index > 0) {
LayoutNode * previousChild = childAtIndex(index-1);
x = previousChild->origin().x() + previousChild->layoutSize().width();
}
KDCoordinate y = baseline() - l->baseline();
return KDPoint(x, y);
}
// Private
bool HorizontalLayoutNode::willAddChildAtIndex(LayoutNode * l, int * index, LayoutCursor * cursor) {
if (m_numberOfChildren > 0) {
*index = HorizontalLayoutRef(this).removeEmptyChildBeforeInsertionAtIndex(*index, !childAtIndex(0)->mustHaveLeftSibling(), cursor);
}
return true;
}
bool HorizontalLayoutNode::willAddSibling(LayoutCursor * cursor, LayoutNode * sibling, bool moveCursor) {
HorizontalLayoutRef thisRef(this);
int newChildIndex = cursor->position() == LayoutCursor::Position::Left ? 0 : numberOfChildren();
thisRef.addOrMergeChildAtIndex(sibling, newChildIndex, true, cursor);
return false;
}
bool HorizontalLayoutNode::willRemoveChild(LayoutNode * l, LayoutCursor * cursor, bool force) {
if (!force && numberOfChildren() == 1) {
assert(childAtIndex(0) == l);
LayoutNode * p = parent();
if (p != nullptr) {
LayoutRef(p).removeChild(this, cursor);
// WARNING: Do not call "this" afterwards
return false;
}
}
return true;
}
void HorizontalLayoutNode::didRemoveChildAtIndex(int index, LayoutCursor * cursor, bool force) {
/* If the child to remove was at index 0 and its right sibling must have a left
* sibling (e.g. a VerticalOffsetLayout), add an empty layout at index 0 */
if (!force && index == 0 && numberOfChildren() > 0 && childAtIndex(0)->mustHaveLeftSibling()) {
HorizontalLayoutRef(this).addChildAtIndex(EmptyLayoutRef(), 0, cursor);
}
}
bool HorizontalLayoutNode::willReplaceChild(LayoutNode * oldChild, LayoutNode * newChild, LayoutCursor * cursor, bool force) {
if (oldChild == newChild) {
return false;
}
if (force) {
return true;
}
HorizontalLayoutRef thisRef(this);
int oldChildIndex = indexOfChild(oldChild);
if (newChild->isEmpty()) {
if (numberOfChildren() > 1) {
/* If the new layout is empty and the horizontal layout has other
* children, just remove the old child. */
thisRef.removeChild(oldChild, nullptr);
// WARNING: do not call "this" afterwards
if (cursor != nullptr) {
if (oldChildIndex == 0) {
cursor->setLayoutReference(thisRef);
cursor->setPosition(LayoutCursor::Position::Left);
} else {
cursor->setLayoutReference(thisRef.childAtIndex(oldChildIndex -1));
cursor->setPosition(LayoutCursor::Position::Right);
}
}
return false;
}
/* The old layout was the only horizontal layout child, so if this has a
* a parent, replace this with the new empty layout. */
LayoutNode * p = parent();
if (p != nullptr) {
thisRef.replaceWith(newChild, cursor);
// WARNING: do not call "this" afterwards
return false;
}
/* This is the main horizontal layout, the old child is its only child and
* the new child is Empty: remove the old child and delete the new child. */
assert(p == nullptr);
thisRef.removeChild(oldChild, nullptr);
// WARNING: do not call "this" afterwards
if (cursor != nullptr) {
cursor->setLayoutReference(thisRef);
cursor->setPosition(LayoutCursor::Position::Left);
}
return false;
}
/* If the new child is also an horizontal layout, steal the children of the
* new layout then destroy it. */
bool oldWasAncestorOfNewLayout = newChild->hasAncestor(oldChild, false);
if (newChild->isHorizontal()) {
int indexForInsertion = indexOfChild(oldChild);
if (cursor != nullptr) {
/* If the old layout is not an ancestor of the new layout, or if the
* cursor was on the right of the new layout, place the cursor on the
* right of the new layout, which is left of the next sibling or right of
* the parent. */
if (!oldWasAncestorOfNewLayout || cursor->position() == LayoutCursor::Position::Right) {
if (oldChildIndex == numberOfChildren() - 1) {
cursor->setLayoutNode(this);
cursor->setPosition(LayoutCursor::Position::Right);
} else {
cursor->setLayoutNode(childAtIndex(oldChildIndex + 1));
cursor->setPosition(LayoutCursor::Position::Left);
}
} else {
/* Else place the cursor on the left of the new layout, which is right
* of the previous sibling or left of the parent. */
if (oldChildIndex == 0) {
cursor->setLayoutNode(this);
cursor->setPosition(LayoutCursor::Position::Left);
} else {
cursor->setLayoutNode(childAtIndex(oldChildIndex - 1));
cursor->setPosition(LayoutCursor::Position::Right);
}
}
}
bool oldChildRemovedAtMerge = oldChild->isEmpty();
thisRef.mergeChildrenAtIndex(HorizontalLayoutRef(newChild), indexForInsertion + 1, true);
// WARNING: do not call "this" afterwards
if (!oldChildRemovedAtMerge) {
thisRef.removeChildAtIndex(indexForInsertion, cursor);
}
return false;
}
// Else, just replace the child.
if (cursor != nullptr && !oldWasAncestorOfNewLayout) {
cursor->setPosition(LayoutCursor::Position::Right);
}
return true;
}
// 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 {
addChildAtIndex(l, index, cursor);
}
}
void HorizontalLayoutRef::mergeChildrenAtIndex(HorizontalLayoutRef h, int index, bool removeEmptyChildren, LayoutCursor * cursor) {
/* 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);
LayoutRef nextPointedLayout(nullptr);
LayoutCursor::Position nextPosition = LayoutCursor::Position::Left;
if (newIndex < numberOfChildren()) {
nextPointedLayout = childAtIndex(newIndex);
nextPosition = LayoutCursor::Position::Left;
} else {
nextPointedLayout = *this;
nextPosition = LayoutCursor::Position::Right;
}
// Merge the horizontal layout
mergeTreeChildrenAtIndex(h, newIndex);
if (cursor != nullptr) {
if (!isAllocationFailure()) {
cursor->setLayoutReference(nextPointedLayout);
cursor->setPosition(nextPosition);
} else {
cursor->setLayoutReference(*this);
}
}
}
int HorizontalLayoutRef::removeEmptyChildBeforeInsertionAtIndex(int index, bool shouldRemoveOnLeft, LayoutCursor * cursor) {
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) {
LayoutRef c = childAtIndex(newIndex);
if (c.isEmpty()) {
removeChild(c, cursor, true);
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) {
LayoutRef c = childAtIndex(newIndex - 1);
if (c.isEmpty()) {
removeChild(c, cursor, true);
newIndex = index - 1;
}
}
return newIndex;
}
}