diff --git a/apps/expression_editor/controller.cpp b/apps/expression_editor/controller.cpp index f19771346..cdce47736 100644 --- a/apps/expression_editor/controller.cpp +++ b/apps/expression_editor/controller.cpp @@ -62,6 +62,8 @@ void Controller::insertLayoutAtCursor(ExpressionLayout * layout, ExpressionLayou bool Controller::privateHandleEvent(Ion::Events::Event event) { if (handleMoveEvent(event)) { + m_expressionLayout->invalidAllSizesPositionsAndBaselines(); + m_view.layoutSubviews(); return true; } ExpressionLayout * newPointedLayout = handleAddEvent(event); diff --git a/poincare/include/poincare/expression_layout.h b/poincare/include/poincare/expression_layout.h index 473827679..a2f4ab83d 100644 --- a/poincare/include/poincare/expression_layout.h +++ b/poincare/include/poincare/expression_layout.h @@ -85,8 +85,8 @@ public: ExpressionLayoutCursor * cursor, ExpressionLayout * previousLayout = nullptr, ExpressionLayout * previousPreviousLayout = nullptr); - bool moveUpInside(ExpressionLayoutCursor * cursor); - bool moveDownInside(ExpressionLayoutCursor * cursor); + virtual bool moveUpInside(ExpressionLayoutCursor * cursor); + virtual bool moveDownInside(ExpressionLayoutCursor * cursor); /* Expression Engine */ virtual int writeTextInBuffer(char * buffer, int bufferSize) const = 0; diff --git a/poincare/src/layout/empty_visible_layout.h b/poincare/src/layout/empty_visible_layout.h index ae5a5e5c2..4d454d97b 100644 --- a/poincare/src/layout/empty_visible_layout.h +++ b/poincare/src/layout/empty_visible_layout.h @@ -20,6 +20,7 @@ public: bool moveRight(ExpressionLayoutCursor * cursor) override; int writeTextInBuffer(char * buffer, int bufferSize) const override; bool isEmpty() const override { return true; } + Color color() const { return m_color; } void setColor(Color color) { m_color = color; } protected: virtual void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; diff --git a/poincare/src/layout/grid_layout.cpp b/poincare/src/layout/grid_layout.cpp index 6fb4874ac..bf3498872 100644 --- a/poincare/src/layout/grid_layout.cpp +++ b/poincare/src/layout/grid_layout.cpp @@ -228,6 +228,7 @@ void GridLayout::addEmptyRow(EmptyVisibleLayout::Color color) { } addChildrenAtIndex(const_cast(const_cast(newChildren)), m_numberOfColumns, numberOfChildren(), false); m_numberOfRows++; + invalidAllSizesPositionsAndBaselines(); } void GridLayout::addEmptyColumn(EmptyVisibleLayout::Color color) { @@ -235,6 +236,7 @@ void GridLayout::addEmptyColumn(EmptyVisibleLayout::Color color) { for (int i = 0; i < m_numberOfRows; i++) { addChildAtIndex(new EmptyVisibleLayout(color), i*m_numberOfColumns + m_numberOfColumns-1); } + invalidAllSizesPositionsAndBaselines(); } void GridLayout::deleteRowAtIndex(int index) { @@ -243,6 +245,7 @@ void GridLayout::deleteRowAtIndex(int index) { DynamicLayoutHierarchy::removeChildAtIndex(index * m_numberOfColumns, true); } m_numberOfRows--; + invalidAllSizesPositionsAndBaselines(); } void GridLayout::deleteColumnAtIndex(int index) { @@ -251,6 +254,7 @@ void GridLayout::deleteColumnAtIndex(int index) { DynamicLayoutHierarchy::removeChildAtIndex(i, true); } m_numberOfColumns--; + invalidAllSizesPositionsAndBaselines(); } bool GridLayout::childIsLeftOfGrid(int index) const { diff --git a/poincare/src/layout/grid_layout.h b/poincare/src/layout/grid_layout.h index dd03e309c..5fac6e1ec 100644 --- a/poincare/src/layout/grid_layout.h +++ b/poincare/src/layout/grid_layout.h @@ -38,6 +38,8 @@ protected: void deleteRowAtIndex(int index); void deleteColumnAtIndex(int index); bool childIsRightOfGrid(int index) const; + bool childIsLeftOfGrid(int index) const; + bool childIsTopOfGrid(int index) const; bool childIsBottomOfGrid(int index) const; int rowAtChildIndex(int index) const; int columnAtChildIndex(int index) const; @@ -51,8 +53,6 @@ private: KDCoordinate height(); KDCoordinate columnWidth(int j); KDCoordinate width(); - bool childIsLeftOfGrid(int index) const; - bool childIsTopOfGrid(int index) const; }; } diff --git a/poincare/src/layout/horizontal_layout.cpp b/poincare/src/layout/horizontal_layout.cpp index 6a7b91e7a..45f1a9747 100644 --- a/poincare/src/layout/horizontal_layout.cpp +++ b/poincare/src/layout/horizontal_layout.cpp @@ -63,13 +63,27 @@ void HorizontalLayout::privateReplaceChild(const ExpressionLayout * oldChild, Ex // replace the horizontal layout with this empty layout (only if this is not // the main layout, so only if the layout has a parent). if (m_parent) { + if (!deleteOldChild) { + removeChildAtIndex(indexOfChild(oldChild), false); + } if (cursor) { - replaceWithAndMoveCursor(newChild, deleteOldChild, cursor); + replaceWithAndMoveCursor(newChild, true, cursor); return; } replaceWith(newChild, deleteOldChild); return; } + // If this is the main horizontal layout, the old child its only child and + // the new child is Empty, remove the old child and delete the new child. + assert(m_parent == nullptr); + removeChildAtIndex(0, deleteOldChild); + delete newChild; + if (cursor == nullptr) { + return; + } + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return; } // If the new child is also an horizontal layout, steal the children of the // new layout then destroy it. diff --git a/poincare/src/layout/matrix_layout.cpp b/poincare/src/layout/matrix_layout.cpp index f88ddb27c..afccc9260 100644 --- a/poincare/src/layout/matrix_layout.cpp +++ b/poincare/src/layout/matrix_layout.cpp @@ -17,6 +17,85 @@ ExpressionLayout * MatrixLayout::clone() const { return layout; } +bool MatrixLayout::moveLeft(ExpressionLayoutCursor * cursor) { + int childIndex = indexOfChild(cursor->pointedExpressionLayout()); + if (childIndex >- 1 + && cursor->position() == ExpressionLayoutCursor::Position::Left + && childIsLeftOfGrid(childIndex)) + { + // Case: Left of a child on the left of the grid. + // Remove the grey squares of the grid, then go left of the grid. + assert(hasGreySquares()); + removeGreySquares(); + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + // Case: Right. + // Add the grey squares to the matrix, then move to the bottom right non empty + // nor grey child. + if (cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + assert(!hasGreySquares()); + addGreySquares(); + ExpressionLayout * lastChild = editableChild((m_numberOfColumns-1)*(m_numberOfRows-1)); + assert(lastChild != nullptr); + cursor->setPointedExpressionLayout(lastChild); + return true; + } + return GridLayout::moveLeft(cursor); +} + +bool MatrixLayout::moveRight(ExpressionLayoutCursor * cursor) { + // Case: Left. + // Add the grey squares to the matrix,, then go to the first entry. + if (cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Left) + { + assert(!hasGreySquares()); + addGreySquares(); + assert(m_numberOfColumns*m_numberOfRows >= 1); + ExpressionLayout * firstChild = editableChild(0); + assert(firstChild != nullptr); + cursor->setPointedExpressionLayout(firstChild); + return true; + } + // Case: The cursor points to a grid's child. + int childIndex = indexOfChild(cursor->pointedExpressionLayout()); + if (childIndex >- 1 + && cursor->position() == ExpressionLayoutCursor::Position::Right + && childIsRightOfGrid(childIndex)) + { + // Case: Right of a child on the right of the grid. + // Remove the grey squares of the grid, then go left of the grid. + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + assert(hasGreySquares()); + removeGreySquares(); + return true; + } + return GridLayout::moveRight(cursor); +} + +bool MatrixLayout::moveUpInside(ExpressionLayoutCursor * cursor) { + bool result = GridLayout::moveUpInside(cursor); + if (result) { + assert(!hasGreySquares()); + addGreySquares(); + } + return result; +} + +bool MatrixLayout::moveDownInside(ExpressionLayoutCursor * cursor) { + bool result = GridLayout::moveDownInside(cursor); + if (result) { + assert(!hasGreySquares()); + addGreySquares(); + } + return result; +} + void MatrixLayout::replaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild) { int oldChildIndex = indexOfChild(oldChild); GridLayout::replaceChild(oldChild, newChild, deleteOldChild); @@ -50,11 +129,13 @@ int MatrixLayout::writeTextInBuffer(char * buffer, int bufferSize) const { buffer[numberOfChar++] = '['; if (numberOfChar >= bufferSize-1) { return bufferSize-1;} - for (int i = 0; i < m_numberOfRows - 1; i++) { + int maxRowIndex = hasGreySquares() ? m_numberOfRows - 1 : m_numberOfRows; + int maxColumnIndex = hasGreySquares() ? m_numberOfColumns - 2 : m_numberOfColumns - 1; + for (int i = 0; i < maxRowIndex; i++) { buffer[numberOfChar++] = '['; if (numberOfChar >= bufferSize-1) { return bufferSize-1;} - numberOfChar += LayoutEngine::writeInfixExpressionLayoutTextInBuffer(this, buffer+numberOfChar, bufferSize-numberOfChar, ",", i*m_numberOfColumns, i* m_numberOfColumns + m_numberOfColumns - 2); + numberOfChar += LayoutEngine::writeInfixExpressionLayoutTextInBuffer(this, buffer+numberOfChar, bufferSize-numberOfChar, ",", i*m_numberOfColumns, i* m_numberOfColumns + maxColumnIndex); buffer[numberOfChar++] = ']'; if (numberOfChar >= bufferSize-1) { return bufferSize-1; } @@ -166,4 +247,32 @@ bool MatrixLayout::isColumnEmpty(int index) const { return true; } +void MatrixLayout::addGreySquares() { + if (!hasGreySquares()) { + addEmptyRow(EmptyVisibleLayout::Color::Grey); + addEmptyColumn(EmptyVisibleLayout::Color::Grey); + } +} + +void MatrixLayout::removeGreySquares() { + if (hasGreySquares()) { + deleteRowAtIndex(m_numberOfRows - 1); + deleteColumnAtIndex(m_numberOfColumns - 1); + } +} + +bool MatrixLayout::hasGreySquares() const { + assert(m_numberOfRows*m_numberOfColumns - 1 >= 0); + const ExpressionLayout * lastChild = child(m_numberOfRows * m_numberOfColumns - 1); + if (lastChild->isEmpty() + && !lastChild->isHorizontal() + && (static_cast(lastChild))->color() == EmptyVisibleLayout::Color::Grey) + { + assert(isRowEmpty(m_numberOfColumns - 1)); + assert(isColumnEmpty(m_numberOfRows - 1)); + return true; + } + return false; +} + } diff --git a/poincare/src/layout/matrix_layout.h b/poincare/src/layout/matrix_layout.h index 047753ca4..6a58bb0cd 100644 --- a/poincare/src/layout/matrix_layout.h +++ b/poincare/src/layout/matrix_layout.h @@ -9,19 +9,36 @@ class MatrixLayout : public GridLayout { public: using GridLayout::GridLayout; ExpressionLayout * clone() const override; + + /* Navigation */ + bool moveLeft(ExpressionLayoutCursor * cursor) override; + bool moveRight(ExpressionLayoutCursor * cursor) override; + bool moveUpInside(ExpressionLayoutCursor * cursor) override; + bool moveDownInside(ExpressionLayoutCursor * cursor) override; + + /* Dynamic layout */ void replaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild = true) override; void replaceChildAndMoveCursor(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) override; + + /* Expression engine */ int writeTextInBuffer(char * buffer, int bufferSize) const override; + + /* Other */ bool isMatrix() const override { return true; } + + /* Special matrix method */ void newRowOrColumnAtIndex(int index); - void childWasReplacedAtIndex(int index); protected: void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; KDSize computeSize() override; KDPoint positionOfChild(ExpressionLayout * child) override; private: + void childWasReplacedAtIndex(int index); bool isRowEmpty(int index) const; bool isColumnEmpty(int index) const; + void addGreySquares(); + void removeGreySquares(); + bool hasGreySquares() const; }; }