Files
Upsilon/poincare/src/layout_node.cpp
2018-08-10 13:23:15 +02:00

265 lines
9.2 KiB
C++

#include <poincare/layout_node.h>
#include <poincare/allocation_failure_layout_node.h>
#include <poincare/horizontal_layout_node.h>
#include <poincare/layout_cursor.h>
#include <poincare/layout_reference.h>
#include <poincare/matrix_layout_node.h>
#include <ion/display.h>
namespace Poincare {
// Rendering
void LayoutNode::draw(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) {
for (LayoutNode * l : children()) {
l->draw(ctx, p, expressionColor, backgroundColor);
}
render(ctx, absoluteOrigin().translatedBy(p), expressionColor, backgroundColor);
}
KDPoint LayoutNode::origin() {
LayoutNode * p = parent();
if (p == nullptr) {
return absoluteOrigin();
} else {
return KDPoint(absoluteOrigin().x() - p->absoluteOrigin().x(),
absoluteOrigin().y() - p->absoluteOrigin().y());
}
}
KDPoint LayoutNode::absoluteOrigin() {
LayoutNode * p = parent();
if (!m_positioned) {
if (p != nullptr) {
m_frame.setOrigin(p->absoluteOrigin().translatedBy(p->positionOfChild(this)));
} else {
m_frame.setOrigin(KDPointZero);
}
m_positioned = true;
}
return m_frame.origin();
}
KDSize LayoutNode::layoutSize() {
if (!m_sized) {
m_frame.setSize(computeSize());
m_sized = true;
}
return m_frame.size();
}
KDCoordinate LayoutNode::baseline() {
if (!m_baselined) {
m_baseline = computeBaseline();
m_baselined = true;
}
return m_baseline;
}
void LayoutNode::invalidAllSizesPositionsAndBaselines() {
m_sized = false;
m_positioned = false;
m_baselined = false;
for (LayoutNode * l : children()) {
l->invalidAllSizesPositionsAndBaselines();
}
}
// Tree navigation
void LayoutNode::moveCursorUp(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) {
moveCursorVertically(VerticalDirection::Up, cursor, shouldRecomputeLayout, equivalentPositionVisited);
}
void LayoutNode::moveCursorDown(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) {
moveCursorVertically(VerticalDirection::Down, cursor, shouldRecomputeLayout, equivalentPositionVisited);
}
void LayoutNode::moveCursorUpInDescendants(LayoutCursor * cursor, bool * shouldRecomputeLayout) {
return moveCursorInDescendantsVertically(VerticalDirection::Up, cursor, shouldRecomputeLayout);
}
void LayoutNode::moveCursorDownInDescendants(LayoutCursor * cursor, bool * shouldRecomputeLayout) {
return moveCursorInDescendantsVertically(VerticalDirection::Down, cursor, shouldRecomputeLayout);
}
LayoutCursor LayoutNode::equivalentCursor(LayoutCursor * cursor) {
/* Only HorizontalLayout or AllocationFailedLayout may have no parent, and
* they overload this method */
assert(parent());
return (cursor->layoutReference().node() == this) ? parent()->equivalentCursor(cursor) : LayoutCursor();
}
// Tree modification
void LayoutNode::deleteBeforeCursor(LayoutCursor * cursor) {
int indexOfPointedLayout = indexOfChild(cursor->layoutNode());
if (indexOfPointedLayout >= 0) {
// Case: The pointed layout is a child. Move Left.
assert(cursor->position() == LayoutCursor::Position::Left);
bool shouldRecomputeLayout = false;
cursor->moveLeft(&shouldRecomputeLayout);
return;
}
assert(cursor->layoutNode() == this);
LayoutNode * p = parent();
// Case: this is the pointed layout.
if (p == nullptr) {
// Case: No parent. Return.
return;
}
if (cursor->position() == LayoutCursor::Position::Left) {
// Case: Left. Ask the parent.
p->deleteBeforeCursor(cursor);
return;
}
assert(cursor->position() == LayoutCursor::Position::Right);
// Case: Right. Delete the layout.
LayoutRef(p).removeChild(LayoutRef(this), cursor);
// WARNING: Do no use "this" afterwards
}
bool LayoutNode::willRemoveChild(LayoutNode * l, LayoutCursor * cursor, bool force) {
if (!force) {
LayoutRef(this).replaceChildWithEmpty(LayoutRef(l), cursor);
return false;
}
return true;
}
// Other
bool LayoutNode::hasText() const {
/* A layout has text if it is not empty or an allocation failure and it is
* not an horizontal layout with no child or with one child with no text. */
if (isEmpty() || isAllocationFailure()){
return false;
}
int numChildren = numberOfChildren();
return !(isHorizontal() && (numChildren == 0 || (numChildren == 1 && !(const_cast<LayoutNode *>(this)->childAtIndex(0)->hasText()))));
}
bool LayoutNode::canBeOmittedMultiplicationLeftFactor() const {
/* WARNING: canBeOmittedMultiplicationLeftFactor is true when and only when
* isCollapsable is true too. If isCollapsable changes, it might not be the
* case anymore so make sure to modify this function if needed. */
int numberOfOpenParentheses = 0;
return isCollapsable(&numberOfOpenParentheses, true);
}
bool LayoutNode::canBeOmittedMultiplicationRightFactor() const {
/* WARNING: canBeOmittedMultiplicationLeftFactor is true when and only when
* isCollapsable is true and isVerticalOffset is false. If one of these
* functions changes, it might not be the case anymore so make sure to modify
* canBeOmittedMultiplicationRightFactor if needed. */
int numberOfOpenParentheses = 0;
return isCollapsable(&numberOfOpenParentheses, false) && !isVerticalOffset();
}
// Private
void LayoutNode::moveCursorVertically(VerticalDirection direction, LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) {
if (!equivalentPositionVisited) {
LayoutCursor cursorEquivalent = equivalentCursor(cursor);
if (cursorEquivalent.isDefined()) {
cursor->setLayoutReference(cursorEquivalent.layoutReference());
cursor->setPosition(cursorEquivalent.position());
if (direction == VerticalDirection::Up) {
cursor->layoutNode()->moveCursorUp(cursor, shouldRecomputeLayout, true);
} else {
cursor->layoutNode()->moveCursorDown(cursor, shouldRecomputeLayout, true);
}
return;
}
}
LayoutNode * p = parent();
if (p == nullptr) {
cursor->setLayoutNode(nullptr);
return;
}
if (direction == VerticalDirection::Up) {
p->moveCursorUp(cursor, shouldRecomputeLayout, true);
} else {
p->moveCursorDown(cursor, shouldRecomputeLayout, true);
}
}
void LayoutNode::moveCursorInDescendantsVertically(VerticalDirection direction, LayoutCursor * cursor, bool * shouldRecomputeLayout) {
LayoutNode * childResult = nullptr;
LayoutNode ** childResultPtr = &childResult;
LayoutCursor::Position resultPosition = LayoutCursor::Position::Left;
/* The distance between the cursor and its next position cannot be greater
* than this initial value of score. */
int resultScore = Ion::Display::Width*Ion::Display::Width + Ion::Display::Height*Ion::Display::Height;
scoreCursorInDescendantsVertically(direction, cursor, shouldRecomputeLayout, childResultPtr, &resultPosition, &resultScore);
// If there is a valid result
LayoutRef resultRef(childResult);
LayoutRef rootRef = LayoutRef(root());
if (*childResultPtr != nullptr) {
*shouldRecomputeLayout = childResult->addGreySquaresToAllMatrixAncestors();
// WARNING: Do not use "this" afterwards
}
if (rootRef.isAllocationFailure()) {
cursor->setLayoutReference(rootRef);
} else {
cursor->setLayoutReference(resultRef);
cursor->setPosition(resultPosition);
}
}
void LayoutNode::scoreCursorInDescendantsVertically (
VerticalDirection direction,
LayoutCursor * cursor,
bool * shouldRecomputeLayout,
LayoutNode ** childResult,
void * resultPosition,
int * resultScore)
{
LayoutCursor::Position * castedResultPosition = static_cast<LayoutCursor::Position *>(resultPosition);
KDPoint cursorMiddleLeft = cursor->middleLeftPoint();
bool layoutIsUnderOrAbove = direction == VerticalDirection::Up ? m_frame.isAbove(cursorMiddleLeft) : m_frame.isUnder(cursorMiddleLeft);
bool layoutContains = m_frame.contains(cursorMiddleLeft);
if (layoutIsUnderOrAbove) {
// Check the distance to a Left cursor.
int currentDistance = LayoutCursor(this, LayoutCursor::Position::Left).middleLeftPoint().squareDistanceTo(cursorMiddleLeft);
if (currentDistance <= *resultScore ){
*childResult = this;
*castedResultPosition = LayoutCursor::Position::Left;
*resultScore = currentDistance;
}
// Check the distance to a Right cursor.
currentDistance = LayoutCursor(this, LayoutCursor::Position::Right).middleLeftPoint().squareDistanceTo(cursorMiddleLeft);
if (currentDistance < *resultScore) {
*childResult = this;
*castedResultPosition = LayoutCursor::Position::Right;
*resultScore = currentDistance;
}
}
if (layoutIsUnderOrAbove || layoutContains) {
for (LayoutNode * c : children()) {
c->scoreCursorInDescendantsVertically(direction, cursor, shouldRecomputeLayout, childResult, castedResultPosition, resultScore);
}
}
}
bool LayoutNode::changeGreySquaresOfAllMatrixAncestors(bool add) {
bool changedSquares = false;
LayoutRef currentAncestor = LayoutRef(parent());
while (currentAncestor.isDefined()) {
if (currentAncestor.isMatrix()) {
if (add) {
MatrixLayoutRef(currentAncestor.node()).addGreySquares();
} else {
MatrixLayoutRef(currentAncestor.node()).removeGreySquares();
}
changedSquares = true;
}
currentAncestor = currentAncestor.parent();
}
return changedSquares;
}
}