mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-03-18 21:30:38 +01:00
LayoutField
This commit is contained in:
@@ -29,6 +29,8 @@ objs += $(addprefix escher/src/,\
|
||||
invocation.o\
|
||||
input_view_controller.o\
|
||||
key_view.o\
|
||||
layout_field.o\
|
||||
layout_field_content_view.o\
|
||||
list_view_data_source.o\
|
||||
message_table_cell.o\
|
||||
message_table_cell_with_buffer.o\
|
||||
|
||||
122
escher/include/escher/layout_field.h
Normal file
122
escher/include/escher/layout_field.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#ifndef ESCHER_LAYOUT_FIELD_H
|
||||
#define ESCHER_LAYOUT_FIELD_H
|
||||
|
||||
#include <escher/expression_view.h>
|
||||
#include <escher/layout_field_delegate.h>
|
||||
#include <escher/scrollable_view.h>
|
||||
#include <escher/text_cursor_view.h>
|
||||
#include <escher/text_field.h>
|
||||
#include <kandinsky/point.h>
|
||||
#include <poincare/layout_reference.h>
|
||||
#include <poincare/layout_cursor.h>
|
||||
|
||||
class LayoutField : public ScrollableView, public ScrollViewDataSource {
|
||||
public:
|
||||
LayoutField(Responder * parentResponder, Poincare::LayoutRef layoutR, LayoutFieldDelegate * delegate = nullptr) :
|
||||
ScrollableView(parentResponder, &m_contentView, this),
|
||||
m_contentView(layoutR),
|
||||
m_delegate(delegate)
|
||||
{}
|
||||
void setDelegate(LayoutFieldDelegate * delegate) { m_delegate = delegate; }
|
||||
bool isEditing() const { return m_contentView.isEditing(); }
|
||||
void setEditing(bool isEditing) { m_contentView.setEditing(isEditing); }
|
||||
void clearLayout() { m_contentView.clearLayout(); }
|
||||
void scrollToCursor() {
|
||||
scrollToBaselinedRect(m_contentView.cursorRect(), m_contentView.cursor()->baseline());
|
||||
}
|
||||
void reload();
|
||||
bool hasText() const { return layoutRef().hasText(); }
|
||||
int writeTextInBuffer(char * buffer, int bufferLength) { return layoutRef().writeTextInBuffer(buffer, bufferLength); }
|
||||
Poincare::LayoutRef layoutRef() const { return m_contentView.expressionView()->layoutRef(); }
|
||||
char XNTChar() { return m_contentView.cursor()->layoutReference().XNTChar(); }
|
||||
|
||||
// ScrollableView
|
||||
void setBackgroundColor(KDColor c) override {
|
||||
ScrollableView::setBackgroundColor(c);
|
||||
m_contentView.setBackgroundColor(c);
|
||||
}
|
||||
|
||||
/* Responder */
|
||||
bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override;
|
||||
bool handleEvent(Ion::Events::Event event) override;
|
||||
Toolbox * toolbox() override {
|
||||
return m_delegate != nullptr ? m_delegate->toolboxForLayoutField(this) : nullptr;
|
||||
}
|
||||
bool layoutFieldShouldFinishEditing(Ion::Events::Event event) { // TODO REMOVE ?
|
||||
return m_delegate->layoutFieldShouldFinishEditing(this, event);
|
||||
}
|
||||
|
||||
/* View */
|
||||
KDSize minimalSizeForOptimalDisplay() const override {
|
||||
KDSize contentViewSize = m_contentView.minimalSizeForOptimalDisplay();
|
||||
return KDSize(contentViewSize.width(), contentViewSize.height());
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool privateHandleEvent(Ion::Events::Event event);
|
||||
bool privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout);
|
||||
|
||||
private:
|
||||
constexpr static int k_maxNumberOfLayouts = 152;
|
||||
static_assert(k_maxNumberOfLayouts == TextField::maxBufferSize(), "Maximal number of layouts in a layout field should be equal to max number of char in text field");
|
||||
void scrollRightOfLayout(Poincare::LayoutRef layoutR);
|
||||
void scrollToBaselinedRect(KDRect rect, KDCoordinate baseline);
|
||||
void insertLayoutAtCursor(Poincare::LayoutRef layoutR, Poincare::LayoutRef pointedLayoutRef, bool forceCursorRightOfLayout = false);
|
||||
|
||||
class ContentView : public View {
|
||||
public:
|
||||
ContentView(Poincare::LayoutRef layoutR) :
|
||||
m_cursor(layoutR, Poincare::LayoutCursor::Position::Right),
|
||||
m_expressionView(0.0f, 0.5f, KDColorBlack, KDColorWhite),
|
||||
m_cursorView(),
|
||||
m_isEditing(false)
|
||||
{
|
||||
m_expressionView.setLayoutRef(layoutR);
|
||||
}
|
||||
|
||||
bool isEditing() const { return m_isEditing; }
|
||||
void setEditing(bool isEditing) {
|
||||
m_isEditing = isEditing;
|
||||
markRectAsDirty(bounds());
|
||||
layoutSubviews();
|
||||
}
|
||||
|
||||
void setBackgroundColor(KDColor c) { m_expressionView.setBackgroundColor(c); }
|
||||
void setCursor(Poincare::LayoutCursor cursor) { m_cursor = cursor; }
|
||||
void cursorPositionChanged() { layoutCursorSubview(); }
|
||||
KDRect cursorRect() { return m_cursorView.frame(); }
|
||||
Poincare::LayoutCursor * cursor() { return &m_cursor; }
|
||||
const ExpressionView * expressionView() const { return &m_expressionView; }
|
||||
ExpressionView * editableExpressionView() { return &m_expressionView; }
|
||||
void clearLayout() { m_cursor.clearLayout(); }
|
||||
/* View */
|
||||
KDSize minimalSizeForOptimalDisplay() const override {
|
||||
KDSize evSize = m_expressionView.minimalSizeForOptimalDisplay();
|
||||
return KDSize(evSize.width() + Poincare::LayoutCursor::k_cursorWidth, evSize.height());
|
||||
}
|
||||
private:
|
||||
enum class Position {
|
||||
Top,
|
||||
Bottom
|
||||
};
|
||||
int numberOfSubviews() const override { return 2; }
|
||||
View * subviewAtIndex(int index) override {
|
||||
assert(index >= 0 && index < 2);
|
||||
View * m_views[] = {&m_expressionView, &m_cursorView};
|
||||
return m_views[index];
|
||||
}
|
||||
void layoutSubviews() override {
|
||||
m_expressionView.setFrame(bounds());
|
||||
layoutCursorSubview();
|
||||
}
|
||||
void layoutCursorSubview();
|
||||
Poincare::LayoutCursor m_cursor;
|
||||
ExpressionView m_expressionView;
|
||||
TextCursorView m_cursorView;
|
||||
bool m_isEditing;
|
||||
};
|
||||
ContentView m_contentView;
|
||||
LayoutFieldDelegate * m_delegate;
|
||||
};
|
||||
|
||||
#endif
|
||||
19
escher/include/escher/layout_field_delegate.h
Normal file
19
escher/include/escher/layout_field_delegate.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef ESCHER_LAYOUT_FIELD_DELEGATE_H
|
||||
#define ESCHER_LAYOUT_FIELD_DELEGATE_H
|
||||
|
||||
#include <escher/toolbox.h>
|
||||
#include <ion/events.h>
|
||||
|
||||
class LayoutField;
|
||||
|
||||
class LayoutFieldDelegate {
|
||||
public:
|
||||
virtual bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) = 0;
|
||||
virtual bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) = 0;
|
||||
virtual bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::LayoutRef layoutR, Ion::Events::Event event) { return false; }
|
||||
virtual bool layoutFieldDidAbortEditing(LayoutField * layoutField) { return false; }
|
||||
virtual void layoutFieldDidChangeSize(LayoutField * layoutField) {}
|
||||
virtual Toolbox * toolboxForLayoutField(LayoutField * layoutField) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
266
escher/src/layout_field.cpp
Normal file
266
escher/src/layout_field.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
#include <escher/layout_field.h>
|
||||
#include <apps/i18n.h>
|
||||
#include <escher/clipboard.h>
|
||||
#include <escher/text_field.h>
|
||||
#include <poincare/expression.h>
|
||||
#include <poincare/expression_layout_cursor.h>
|
||||
//#include <poincare/src/layout/matrix_layout.h> //TODO
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace Poincare;
|
||||
|
||||
void LayoutField::ContentView::layoutCursorSubview() {
|
||||
if (!m_isEditing) {
|
||||
m_cursorView.setFrame(KDRectZero);
|
||||
return;
|
||||
}
|
||||
KDPoint expressionViewOrigin = m_expressionView.absoluteDrawingOrigin();
|
||||
LayoutRef pointedLayoutR = m_cursor.layoutReference();
|
||||
LayoutCursor::Position cursorPosition = m_cursor.position();
|
||||
LayoutCursor eqCursor = pointedLayoutR.equivalentCursor(&m_cursor);
|
||||
if (pointedLayoutR.hasChild(eqCursor.layoutReference())) {
|
||||
pointedLayoutR = eqCursor.layoutReference();
|
||||
cursorPosition = eqCursor.position();
|
||||
}
|
||||
KDPoint cursoredExpressionViewOrigin = pointedLayoutR.absoluteOrigin();
|
||||
KDCoordinate cursorX = expressionViewOrigin.x() + cursoredExpressionViewOrigin.x();
|
||||
if (cursorPosition == LayoutCursor::Position::Right) {
|
||||
cursorX += pointedLayoutR.layoutSize().width();
|
||||
}
|
||||
KDPoint cursorTopLeftPosition(cursorX, expressionViewOrigin.y() + cursoredExpressionViewOrigin.y() + pointedLayoutR.baseline() - m_cursor.baseline());
|
||||
m_cursorView.setFrame(KDRect(cursorTopLeftPosition, LayoutCursor::k_cursorWidth, m_cursor.cursorHeight()));
|
||||
}
|
||||
|
||||
void LayoutField::reload() {
|
||||
KDSize previousSize = minimalSizeForOptimalDisplay();
|
||||
layoutRef().invalidAllSizesPositionsAndBaselines();
|
||||
KDSize newSize = minimalSizeForOptimalDisplay();
|
||||
if (m_delegate && previousSize.height() != newSize.height()) {
|
||||
m_delegate->layoutFieldDidChangeSize(this);
|
||||
}
|
||||
m_contentView.cursorPositionChanged();
|
||||
scrollToCursor();
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
bool LayoutField::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) {
|
||||
if (text[0] == 0) {
|
||||
// The text is empty
|
||||
return true;
|
||||
}
|
||||
|
||||
int currentNumberOfLayouts = m_contentView.expressionView()->numberOfLayouts();
|
||||
if (currentNumberOfLayouts >= k_maxNumberOfLayouts - 6) {
|
||||
/* We add -6 because in some cases (Ion::Events::Division,
|
||||
* Ion::Events::Exp...) we let the layout cursor handle the layout insertion
|
||||
* and these events may add at most 6 layouts (e.g *10^•). */
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle special cases
|
||||
if (strcmp(text, Ion::Events::Division.text()) == 0) {
|
||||
m_contentView.cursor()->addFractionLayoutAndCollapseSiblings();
|
||||
} else if (strcmp(text, Ion::Events::Exp.text()) == 0) {
|
||||
m_contentView.cursor()->addEmptyExponentialLayout();
|
||||
} else if (strcmp(text, Ion::Events::Power.text()) == 0) {
|
||||
m_contentView.cursor()->addEmptyPowerLayout();
|
||||
} else if (strcmp(text, Ion::Events::Sqrt.text()) == 0) {
|
||||
m_contentView.cursor()->addEmptySquareRootLayout();
|
||||
} else if (strcmp(text, Ion::Events::Square.text()) == 0) {
|
||||
m_contentView.cursor()->addEmptySquarePowerLayout();
|
||||
} else if (strcmp(text, Ion::Events::EE.text()) == 0) {
|
||||
m_contentView.cursor()->addEmptyTenPowerLayout();
|
||||
} else if ((strcmp(text, "[") == 0) || (strcmp(text, "]") == 0)) {
|
||||
m_contentView.cursor()->addEmptyMatrixLayout();
|
||||
} else {
|
||||
Expression * resultExpression = Expression::parse(text);
|
||||
if (resultExpression == nullptr) {
|
||||
m_contentView.cursor()->insertText(text);
|
||||
return true;
|
||||
}
|
||||
LayoutRef resultLayoutRef = resultExpression->createLayout();
|
||||
delete resultExpression;
|
||||
if (currentNumberOfLayouts + resultLayoutRef.numberOfDescendants(true) >= k_maxNumberOfLayouts) {
|
||||
return true;
|
||||
}
|
||||
// Find the pointed layout.
|
||||
LayoutRef pointedLayoutRef(nullptr);
|
||||
if (strcmp(text, I18n::translate(I18n::Message::RandomCommandWithArg)) == 0) {
|
||||
/* Special case: if the text is "random()", the cursor should not be set
|
||||
* inside the parentheses. */
|
||||
pointedLayoutRef = resultLayoutRef;
|
||||
} else if (resultLayoutRef.isHorizontal()) {
|
||||
/* If the layout is horizontal, pick the first open parenthesis. For now,
|
||||
* all horizontal layouts in MathToolbox have parentheses. */
|
||||
for (LayoutRef l : resultLayoutRef.directChildren()) {
|
||||
if (l.isLeftParenthesis()) {
|
||||
pointedLayoutRef = l;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Insert the layout. If pointedLayout is nullptr, the cursor will be on the
|
||||
* right of the inserted layout. */
|
||||
insertLayoutAtCursor(resultLayoutRef, pointedLayoutRef, forceCursorRightOfText);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayoutField::handleEvent(Ion::Events::Event event) {
|
||||
bool didHandleEvent = false;
|
||||
bool shouldRecomputeLayout = m_contentView.cursor()->showEmptyLayoutIfNeeded();
|
||||
bool moveEventChangedLayout = false;
|
||||
if (privateHandleMoveEvent(event, &moveEventChangedLayout)) {
|
||||
if (!isEditing()) {
|
||||
setEditing(true);
|
||||
}
|
||||
shouldRecomputeLayout = shouldRecomputeLayout || moveEventChangedLayout;
|
||||
didHandleEvent = true;
|
||||
} else if (privateHandleEvent(event)) {
|
||||
shouldRecomputeLayout = true;
|
||||
didHandleEvent = true;
|
||||
}
|
||||
if (didHandleEvent) {
|
||||
shouldRecomputeLayout = m_contentView.cursor()->hideEmptyLayoutIfNeeded() || shouldRecomputeLayout;
|
||||
if (!shouldRecomputeLayout) {
|
||||
m_contentView.cursorPositionChanged();
|
||||
scrollToCursor();
|
||||
} else {
|
||||
reload();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
m_contentView.cursor()->hideEmptyLayoutIfNeeded();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayoutField::privateHandleEvent(Ion::Events::Event event) {
|
||||
if (m_delegate && m_delegate->layoutFieldDidReceiveEvent(this, event)) {
|
||||
return true;
|
||||
}
|
||||
if (Responder::handleEvent(event)) {
|
||||
/* The only event Responder handles is 'Toolbox' displaying. In that case,
|
||||
* the ExpressionLayoutField is forced into editing mode. */
|
||||
if (!isEditing()) {
|
||||
setEditing(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (isEditing() && m_delegate->layoutFieldShouldFinishEditing(this, event)) { //TODO use class method?
|
||||
setEditing(false);
|
||||
if (m_delegate->layoutFieldDidFinishEditing(this, layoutRef(), event)) {
|
||||
clearLayout();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ((event == Ion::Events::OK || event == Ion::Events::EXE) && !isEditing()) {
|
||||
setEditing(true);
|
||||
m_contentView.cursor()->setPLayoutReference(layoutRef());
|
||||
m_contentView.cursor()->setPosition(LayoutCursor::Position::Right);
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Back && isEditing()) {
|
||||
clearLayout();
|
||||
setEditing(false);
|
||||
m_delegate->layoutFieldDidAbortEditing(this);
|
||||
return true;
|
||||
}
|
||||
if (event.hasText() || event == Ion::Events::Paste || event == Ion::Events::Backspace) {
|
||||
if (!isEditing()) {
|
||||
setEditing(true);
|
||||
}
|
||||
if (event.hasText()) {
|
||||
handleEventWithText(event.text());
|
||||
} else if (event == Ion::Events::Paste) {
|
||||
handleEventWithText(Clipboard::sharedClipboard()->storedText(), false, true);
|
||||
} else {
|
||||
assert(event == Ion::Events::Backspace);
|
||||
m_contentView.cursor()->performBackspace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Clear && isEditing()) {
|
||||
clearLayout();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout) {
|
||||
LayoutCursor result;
|
||||
if (event == Ion::Events::Left) {
|
||||
result = m_contentView.cursor()->cursorOnLeft(shouldRecomputeLayout);
|
||||
} else if (event == Ion::Events::Right) {
|
||||
result = m_contentView.cursor()->cursorOnRight(shouldRecomputeLayout);
|
||||
} else if (event == Ion::Events::Up) {
|
||||
result = m_contentView.cursor()->cursorAbove(shouldRecomputeLayout);
|
||||
} else if (event == Ion::Events::Down) {
|
||||
result = m_contentView.cursor()->cursorUnder(shouldRecomputeLayout);
|
||||
} else if (event == Ion::Events::ShiftLeft) {
|
||||
*shouldRecomputeLayout = true;
|
||||
if (m_contentView.cursor()->layoutReference()->removeGreySquaresFromAllMatrixAncestors()) {
|
||||
*shouldRecomputeLayout = true;
|
||||
}
|
||||
result.setPointedLayoutRef(layoutRef());
|
||||
result.setPosition(LayoutCursor::Position::Left);
|
||||
} else if (event == Ion::Events::ShiftRight) {
|
||||
if (m_contentView.cursor()->layoutReference()->removeGreySquaresFromAllMatrixAncestors()) {
|
||||
*shouldRecomputeLayout = true;
|
||||
}
|
||||
result.setPointedLayoutRef(layoutRef());
|
||||
result.setPosition(LayoutCursor::Position::Right);
|
||||
}
|
||||
if (result.isDefined()) {
|
||||
m_contentView.setCursor(result);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LayoutField::scrollRightOfLayout(LayoutRef layoutR) {
|
||||
KDRect layoutRect(layout.absoluteOrigin().translatedBy(m_contentView.expressionView()->drawingOrigin()), layout.size());
|
||||
scrollToBaselinedRect(layoutRect, layout.baseline());
|
||||
}
|
||||
|
||||
void LayoutField::scrollToBaselinedRect(KDRect rect, KDCoordinate baseline) {
|
||||
scrollToContentRect(rect, true);
|
||||
// Show the rect area around its baseline
|
||||
KDCoordinate underBaseline = rect.height() - baseline;
|
||||
KDCoordinate minAroundBaseline = min(baseline, underBaseline);
|
||||
minAroundBaseline = min(minAroundBaseline, bounds().height() / 2);
|
||||
KDRect balancedRect(rect.x(), rect.y() + baseline - minAroundBaseline, rect.width(), 2 * minAroundBaseline);
|
||||
scrollToContentRect(balancedRect, true);
|
||||
}
|
||||
|
||||
void LayoutField::insertLayoutAtCursor(LayoutRef layoutR, LayoutRef pointedLayoutR, bool forceCursorRightOfLayout) {
|
||||
if (!layoutR.isDefined()) {
|
||||
return;
|
||||
}
|
||||
m_contentView.cursor()->showEmptyLayoutIfNeeded();
|
||||
bool layoutWillBeMerged = layoutR->isHorizontal();
|
||||
LayoutRef lastMergedLayoutChild = layoutWillBeMerged ? layout.child(layout.numberOfChildren()-1) : nullptr;
|
||||
m_contentView.cursor()->addLayoutAndMoveCursor(layout);
|
||||
if (!forceCursorRightOfLayout) {
|
||||
if (pointedLayoutR.isDefined() && (pointedLayout != layout || !layoutWillBeMerged)) {
|
||||
m_contentView.cursor()->setPointedLayoutRef(pointedLayoutR);
|
||||
m_contentView.cursor()->setPosition(LayoutCursor::Position::Right);
|
||||
} else if (!layoutWillBeMerged) {
|
||||
m_contentView.cursor()->setPointedLayoutRef(layout.layoutToPointWhenInserting());
|
||||
m_contentView.cursor()->setPosition(LayoutCursor::Position::Right);
|
||||
}
|
||||
} else if (!layoutWillBeMerged) {
|
||||
m_contentView.cursor()->setPointedLayoutRef(layout);
|
||||
m_contentView.cursor()->setPosition(LayoutCursor::Position::Right);
|
||||
}
|
||||
m_contentView.cursor()->layoutReference().addGreySquaresToAllMatrixAncestors();
|
||||
m_contentView.cursor()->hideEmptyLayoutIfNeeded();
|
||||
reload();
|
||||
if (!layoutWillBeMerged) {
|
||||
scrollRightOfLayout(layout);
|
||||
} else {
|
||||
assert(lastMergedLayoutChild != nullptr);
|
||||
scrollRightOfLayout(lastMergedLayoutChild);
|
||||
}
|
||||
scrollToCursor();
|
||||
}
|
||||
@@ -11,11 +11,18 @@ class LayoutCursor {
|
||||
template <typename T>
|
||||
friend class LayoutReference;
|
||||
public:
|
||||
constexpr static KDCoordinate k_cursorWidth = 1;
|
||||
|
||||
enum class Position {
|
||||
Left,
|
||||
Right
|
||||
};
|
||||
|
||||
LayoutCursor(LayoutRef layoutR, Position position = Position::Right) :
|
||||
m_layoutRef(layoutR.node()),
|
||||
m_position(position)
|
||||
{}
|
||||
|
||||
/* Debug */
|
||||
void log() {
|
||||
#if TREE_LOG
|
||||
@@ -56,6 +63,21 @@ public:
|
||||
void moveRight(bool * shouldRecomputeLayout);
|
||||
void moveAbove(bool * shouldRecomputeLayout);
|
||||
void moveUnder(bool * shouldRecomputeLayout);
|
||||
|
||||
/* Layout modification */
|
||||
void clearLayout() {} //TODO
|
||||
void addFractionLayoutAndCollapseSiblings() {} //TODO
|
||||
void addEmptyExponentialLayout() {} //TODO
|
||||
void addEmptyPowerLayout() {} //TODO
|
||||
void addEmptySquareRootLayout() {} //TODO
|
||||
void addEmptySquarePowerLayout() {} //TODO
|
||||
void addEmptyTenPowerLayout() {} //TODO
|
||||
void addEmptyMatrixLayout() {} //TODO
|
||||
void insertText(const char * text) {} //TODO
|
||||
void performBackspace() {} //TODO
|
||||
bool showEmptyLayoutIfNeeded() { return false; } //TODO
|
||||
bool hideEmptyLayoutIfNeeded() { return false; } //TODO
|
||||
|
||||
private:
|
||||
LayoutCursor(LayoutNode * node, Position position = Position::Right) :
|
||||
m_layoutRef(node),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define POINCARE_LAYOUT_ENGINE_H
|
||||
|
||||
#include <poincare/expression.h>
|
||||
#include <poincare/tree_reference.h>
|
||||
|
||||
namespace Poincare {
|
||||
|
||||
@@ -51,6 +52,16 @@ public:
|
||||
const char * operatorName,
|
||||
bool writeFirstChild = true);
|
||||
|
||||
/* LayoutReference to Text */
|
||||
static int writeInfixTreeRefTextInBuffer(
|
||||
const TreeRef treeRef,
|
||||
char * buffer,
|
||||
int bufferSize,
|
||||
int numberOfDigits,
|
||||
const char * operatorName,
|
||||
int firstChildIndex = 0,
|
||||
int lastChildIndex = -1);
|
||||
|
||||
/* Write one char in buffer */
|
||||
static int writeOneCharInBuffer(char * buffer, int bufferSize, char charToWrite);
|
||||
|
||||
@@ -59,6 +70,8 @@ private:
|
||||
// These two functions return the index of the null-terminating char.
|
||||
static int writeInfixExpressionOrExpressionLayoutTextInBuffer(const Expression * expression, const ExpressionLayout * expressionLayout, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName, int firstChildIndex, int lastChildIndex, ChildNeedsParenthesis childNeedsParenthesis);
|
||||
static int writePrefixExpressionOrExpressionLayoutTextInBuffer(const Expression * expression, const ExpressionLayout * expressionLayout, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName, bool writeFirstChild = true);
|
||||
|
||||
static void writeChildTreeInBuffer(TreeRef childRef, TreeRef parentRef, char * buffer, int bufferSize, int numberOfDigits, int * numberOfChar);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifndef POINCARE_LAYOUT_NODE_H
|
||||
#define POINCARE_LAYOUT_NODE_H
|
||||
|
||||
#include <poincare/print_float.h>
|
||||
#include <poincare/tree_node.h>
|
||||
#include <kandinsky.h>
|
||||
|
||||
@@ -23,6 +22,8 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
virtual char XNTChar() const { return 'x'; }
|
||||
|
||||
// Rendering
|
||||
void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite);
|
||||
KDPoint origin();
|
||||
@@ -31,9 +32,6 @@ public:
|
||||
KDCoordinate baseline();
|
||||
virtual void invalidAllSizesPositionsAndBaselines();
|
||||
|
||||
// Serialization
|
||||
virtual int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const = 0;
|
||||
|
||||
// TreeNode
|
||||
static TreeNode * FailedAllocationStaticNode();
|
||||
TreeNode * failedAllocationStaticNode() override { return FailedAllocationStaticNode(); }
|
||||
@@ -46,15 +44,14 @@ public:
|
||||
|
||||
// Hierarchy
|
||||
LayoutNode * parent() const { return static_cast<LayoutNode *>(parentTree()); }
|
||||
LayoutNode * childAtIndex(int i) { return static_cast<LayoutNode *>(childTreeAtIndex(i)); }
|
||||
|
||||
// Tree navigation
|
||||
virtual void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) {}
|
||||
virtual void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) {}
|
||||
virtual void moveCursorUp(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) {}
|
||||
virtual void moveCursorDown(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) {}
|
||||
|
||||
|
||||
LayoutNode * childAtIndex(int i) { return static_cast<LayoutNode *>(childTreeAtIndex(i)); }
|
||||
virtual LayoutCursor equivalentCursor(LayoutCursor * cursor); //TODO
|
||||
|
||||
protected:
|
||||
// Iterators
|
||||
|
||||
@@ -33,13 +33,15 @@ public:
|
||||
TreeReference<T>::replaceChildAtIndex(oldChildIndex, newChild);
|
||||
}
|
||||
|
||||
KDPoint layoutOrigin() {
|
||||
return this->typedNode()->layoutOrigin();
|
||||
}
|
||||
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(); }
|
||||
|
||||
KDPoint absoluteOrigin() {
|
||||
return this->typedNode()->absoluteOrigin();
|
||||
}
|
||||
};
|
||||
|
||||
typedef LayoutReference<LayoutNode> LayoutRef;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef POINCARE_TREE_NODE_H
|
||||
#define POINCARE_TREE_NODE_H
|
||||
|
||||
#include <poincare/print_float.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <strings.h>
|
||||
@@ -36,6 +38,12 @@ public:
|
||||
virtual const char * description() const {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
// Serialization
|
||||
virtual bool needsParenthesisWithParent(TreeNode * parentNode) { return false; } //TODO virtual pure and override on expresionNode/layoutNode
|
||||
virtual int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const { return 0; } //TODO virtual pure
|
||||
|
||||
// Allocation failure
|
||||
virtual bool isAllocationFailure() const { return false; }
|
||||
virtual TreeNode * failedAllocationStaticNode() {
|
||||
assert(false);
|
||||
|
||||
@@ -6,17 +6,11 @@
|
||||
|
||||
namespace Poincare {
|
||||
|
||||
static inline int min(int i, int j) { return i < j ? i : j; }
|
||||
static inline int max(int i, int j) { return i > j ? i : j; }
|
||||
|
||||
class Cursor;
|
||||
|
||||
template <typename T>
|
||||
class TreeReference {
|
||||
friend class TreeNode;
|
||||
friend class AdditionNode;
|
||||
friend class ExpressionNode;
|
||||
friend class Cursor;
|
||||
template <typename U>
|
||||
friend class TreeReference;
|
||||
template <typename U>
|
||||
@@ -37,10 +31,6 @@ public:
|
||||
|
||||
inline bool operator==(TreeReference<TreeNode> t) { return m_identifier == t.identifier(); }
|
||||
|
||||
void setTo(const TreeReference & tr) {
|
||||
setIdentifierAndRetain(tr.identifier());
|
||||
}
|
||||
|
||||
TreeReference<T> clone() const {
|
||||
TreeNode * myNode = node();
|
||||
if (myNode->isAllocationFailure()) {
|
||||
@@ -59,48 +49,40 @@ public:
|
||||
node()->release();
|
||||
}
|
||||
}
|
||||
operator TreeReference<TreeNode>() const { return TreeReference<TreeNode>(this->node()); }
|
||||
|
||||
bool isDefined() const { return m_identifier >= 0 && node() != nullptr; }
|
||||
bool isAllocationFailure() const { return node()->isAllocationFailure(); }
|
||||
|
||||
int nodeRetainCount() const { return node()->retainCount(); }
|
||||
void incrementNumberOfChildren(int increment = 1) { return node()->incrementNumberOfChildren(increment); }
|
||||
void decrementNumberOfChildren(int decrement = 1) { return node()->decrementNumberOfChildren(decrement); }
|
||||
|
||||
operator TreeReference<TreeNode>() const {
|
||||
return TreeReference<TreeNode>(this->node());
|
||||
}
|
||||
|
||||
int identifier() const { return m_identifier; }
|
||||
TreeNode * node() const { return TreePool::sharedPool()->node(m_identifier); }
|
||||
T * typedNode() const {
|
||||
// TODO: Here, assert that the node type is indeed T
|
||||
// ?? Might be allocation failure, not T
|
||||
return static_cast<T*>(node());
|
||||
}
|
||||
|
||||
TreeNode * node() const {
|
||||
return TreePool::sharedPool()->node(m_identifier);
|
||||
}
|
||||
bool isDefined() const { return m_identifier >= 0 && node() != nullptr; } //TODO m_identifier != -1
|
||||
bool isAllocationFailure() const { return node()->isAllocationFailure(); }
|
||||
|
||||
int identifier() const { return m_identifier; }
|
||||
int nodeRetainCount() const { return node()->retainCount(); }
|
||||
void incrementNumberOfChildren(int increment = 1) { return node()->incrementNumberOfChildren(increment); }
|
||||
void decrementNumberOfChildren(int decrement = 1) { return node()->decrementNumberOfChildren(decrement); }
|
||||
|
||||
|
||||
|
||||
// Serialization
|
||||
bool needsParenthesisWithParent(TreeReference<TreeNode> parentRef) { return node()->needsParenthesisWithParent(parentRef.node()); }
|
||||
int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const {
|
||||
return node()->writeTextInBuffer(buffer, bufferSize, numberOfSignificantDigits);
|
||||
}
|
||||
|
||||
// Hierarchy
|
||||
int numberOfChildren() const {
|
||||
return node()->numberOfChildren();
|
||||
}
|
||||
|
||||
TreeReference<T> parent() const {
|
||||
return TreeReference(node()->parentTree());
|
||||
}
|
||||
|
||||
TreeReference<T> treeChildAtIndex(int i) const {
|
||||
return TreeReference(node()->childTreeAtIndex(i));
|
||||
}
|
||||
bool hasChild(TreeReference<TreeNode> t) const { return node()->hasChild(t.node()); };
|
||||
int numberOfChildren() const { return node()->numberOfChildren(); }
|
||||
TreeReference<T> parent() const { return TreeReference(node()->parentTree()); }
|
||||
TreeReference<T> treeChildAtIndex(int i) const { return TreeReference(node()->childTreeAtIndex(i)); }
|
||||
|
||||
// Hierarchy operations
|
||||
|
||||
void addChild(TreeReference<TreeNode> t) {
|
||||
return addChildAtIndex(t, 0);
|
||||
}
|
||||
void addChild(TreeReference<TreeNode> t) { return addChildAtIndex(t, 0); }
|
||||
|
||||
void addChildAtIndex(TreeReference<TreeNode> t, int index) {
|
||||
if (node()->isAllocationFailure()) {
|
||||
@@ -202,8 +184,8 @@ public:
|
||||
if (i == j) {
|
||||
return;
|
||||
}
|
||||
int firstChildIndex = min(i, j);
|
||||
int secondChildIndex = max(i, j);
|
||||
int firstChildIndex = i < j ? i : j;
|
||||
int secondChildIndex = i > j ? i : j;
|
||||
TreeReference<T> firstChild = treeChildAtIndex(firstChildIndex);
|
||||
TreeReference<T> secondChild = treeChildAtIndex(secondChildIndex);
|
||||
TreeNode * firstChildNode = firstChild.node();
|
||||
@@ -242,6 +224,9 @@ protected:
|
||||
node()->retain();
|
||||
}
|
||||
private:
|
||||
void setTo(const TreeReference & tr) {
|
||||
setIdentifierAndRetain(tr.identifier());
|
||||
}
|
||||
int m_identifier;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace Poincare {
|
||||
|
||||
static inline KDCoordinate max(KDCoordinate c1, KDCoordinate c2) { return c1 > c2 ? c1 : c2; }
|
||||
static inline KDCoordinate maxCoordinate(KDCoordinate c1, KDCoordinate c2) { return c1 > c2 ? c1 : c2; }
|
||||
|
||||
int HorizontalLayoutNode::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const {
|
||||
if (numberOfChildren() == 0) {
|
||||
@@ -13,7 +13,7 @@ int HorizontalLayoutNode::writeTextInBuffer(char * buffer, int bufferSize, int n
|
||||
buffer[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
return LayoutEngine::writeInfixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, "");
|
||||
return LayoutEngine::writeInfixTreeRefTextInBuffer(TreeRef(const_cast<HorizontalLayoutNode *>(this)), buffer, bufferSize, numberOfSignificantDigits, "");
|
||||
}
|
||||
|
||||
void HorizontalLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) {
|
||||
@@ -64,16 +64,16 @@ void HorizontalLayoutNode::moveCursorRight(LayoutCursor * cursor, bool * shouldR
|
||||
}
|
||||
}
|
||||
|
||||
KDSize HorizontalLayoutNode::computeSize() {
|
||||
void HorizontalLayoutNode::computeSize() {
|
||||
assert(!m_sized);
|
||||
KDCoordinate totalWidth = 0;
|
||||
KDCoordinate maxUnderBaseline = 0;
|
||||
KDCoordinate maxAboveBaseline = 0;
|
||||
for (LayoutNode * l : directChildren()) {
|
||||
KDSize childSize = l->size();
|
||||
for (LayoutNode * l : children()) {
|
||||
KDSize childSize = l->layoutSize();
|
||||
totalWidth += childSize.width();
|
||||
maxUnderBaseline = max(maxUnderBaseline, childSize.height() - l->baseline());
|
||||
maxAboveBaseline = max(maxAboveBaseline, l->baseline());
|
||||
maxUnderBaseline = maxCoordinate(maxUnderBaseline, childSize.height() - l->baseline());
|
||||
maxAboveBaseline = maxCoordinate(maxAboveBaseline, l->baseline());
|
||||
}
|
||||
m_frame.setSize(KDSize(totalWidth, maxUnderBaseline + maxAboveBaseline));
|
||||
m_sized = true;
|
||||
@@ -82,8 +82,8 @@ KDSize HorizontalLayoutNode::computeSize() {
|
||||
void HorizontalLayoutNode::computeBaseline() {
|
||||
assert(!m_baselined);
|
||||
m_baseline = 0;
|
||||
for (LayoutNode * l : directChildren()) {
|
||||
m_baseline = max(m_baseline, l->baseline());
|
||||
for (LayoutNode * l : children()) {
|
||||
m_baseline = maxCoordinate(m_baseline, l->baseline());
|
||||
}
|
||||
m_baselined = true;
|
||||
}
|
||||
@@ -94,8 +94,8 @@ KDPoint HorizontalLayoutNode::positionOfChild(LayoutNode * l) {
|
||||
int index = indexOfChild(l);
|
||||
assert(index > -1);
|
||||
if (index > 0) {
|
||||
LayoutNode * previousChild = child(index-1);
|
||||
x = previousChild->origin().x() + previousChild->size().width();
|
||||
LayoutNode * previousChild = childAtIndex(index-1);
|
||||
x = previousChild->origin().x() + previousChild->layoutSize().width();
|
||||
}
|
||||
KDCoordinate y = baseline() - l->baseline();
|
||||
return KDPoint(x, y);
|
||||
|
||||
@@ -186,6 +186,76 @@ int LayoutEngine::writePrefixExpressionOrExpressionLayoutTextInBuffer(const Expr
|
||||
return numberOfChar;
|
||||
}
|
||||
|
||||
/* LayoutReference to Text */
|
||||
int LayoutEngine::writeInfixTreeRefTextInBuffer(
|
||||
const TreeRef treeRef,
|
||||
char * buffer,
|
||||
int bufferSize,
|
||||
int numberOfDigits,
|
||||
const char * operatorName,
|
||||
int firstChildIndex,
|
||||
int lastChildIndex)
|
||||
{
|
||||
// If buffer has size 0 or 1, put a zero if it fits and return
|
||||
assert(treeRef.isDefined());
|
||||
if (bufferSize == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
buffer[bufferSize-1] = 0; // Null-terminate the buffer
|
||||
if (bufferSize == 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get some information on the TreeRef
|
||||
int numberOfChar = 0;
|
||||
int numberOfOperands = treeRef.numberOfChildren();
|
||||
assert(numberOfOperands > 0);
|
||||
|
||||
// Write the first child, with parentheses if needed
|
||||
writeChildTreeInBuffer(treeRef.treeChildAtIndex(firstChildIndex), treeRef, buffer, bufferSize, numberOfDigits, &numberOfChar);
|
||||
if (numberOfChar >= bufferSize-1) {
|
||||
return bufferSize-1;
|
||||
}
|
||||
// For all remaining children:
|
||||
int lastIndex = lastChildIndex < 0 ? numberOfOperands - 1 : lastChildIndex;
|
||||
for (int i = firstChildIndex + 1; i < lastIndex+1; i++) {
|
||||
// Write the operator
|
||||
numberOfChar += strlcpy(buffer+numberOfChar, operatorName, bufferSize-numberOfChar);
|
||||
if (numberOfChar >= bufferSize-1) {
|
||||
return bufferSize-1;
|
||||
}
|
||||
// Write the child, with parentheses if needed
|
||||
writeChildTreeInBuffer(treeRef.treeChildAtIndex(i), treeRef, buffer, bufferSize, numberOfDigits, &numberOfChar);
|
||||
if (numberOfChar >= bufferSize-1) {
|
||||
return bufferSize-1;
|
||||
}
|
||||
}
|
||||
|
||||
// Null-terminate the buffer
|
||||
buffer[numberOfChar] = 0;
|
||||
return numberOfChar;
|
||||
}
|
||||
|
||||
void LayoutEngine::writeChildTreeInBuffer(TreeRef childRef, TreeRef parentRef, char * buffer, int bufferSize, int numberOfDigits, int * numberOfChar) {
|
||||
// Write the child with parentheses if needed
|
||||
bool addParentheses = childRef.needsParenthesisWithParent(parentRef);
|
||||
if (addParentheses) {
|
||||
buffer[*numberOfChar++] = '('; //TODO ok ?
|
||||
if (*numberOfChar >= bufferSize-1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
*numberOfChar += childRef.writeTextInBuffer(buffer + *numberOfChar, bufferSize - *numberOfChar, numberOfDigits);
|
||||
if (*numberOfChar >= bufferSize-1) {
|
||||
return;
|
||||
}
|
||||
if (addParentheses) {
|
||||
buffer[*numberOfChar++] = ')';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int LayoutEngine::writeOneCharInBuffer(char * buffer, int bufferSize, char charToWrite) {
|
||||
if (bufferSize == 0) {
|
||||
return -1;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <poincare/layout_node.h>
|
||||
#include <poincare/allocation_failed_layout_node.h>
|
||||
#include <poincare/layout_cursor.h>
|
||||
#include <poincare/layout_reference.h>
|
||||
|
||||
namespace Poincare {
|
||||
@@ -65,4 +66,9 @@ TreeNode * LayoutNode::FailedAllocationStaticNode() {
|
||||
return LayoutRef::FailedAllocationStaticNode();
|
||||
}
|
||||
|
||||
// Tree navigation
|
||||
LayoutCursor LayoutNode::equivalentCursor(LayoutCursor * cursor) {
|
||||
return LayoutCursor(cursor->layoutReference());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <poincare/layout_cursor.h>
|
||||
#include <poincare/allocation_failed_layout_node.h>
|
||||
#include <poincare/layout_node.h>
|
||||
#include <poincare/layout_cursor.h>
|
||||
#include <poincare/char_layout_node.h>
|
||||
|
||||
namespace Poincare {
|
||||
@@ -21,6 +22,11 @@ LayoutCursor LayoutReference<T>::cursor() const {
|
||||
return LayoutCursor(this->typedNode());
|
||||
}
|
||||
|
||||
template<>
|
||||
LayoutCursor LayoutRef::equivalentCursor(LayoutCursor * cursor) {
|
||||
return this->typedNode()->equivalentCursor(cursor);
|
||||
}
|
||||
|
||||
template LayoutCursor LayoutReference<LayoutNode>::cursor() const;
|
||||
template LayoutCursor LayoutReference<CharLayoutNode>::cursor() const;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user