diff --git a/apps/calculation/Makefile b/apps/calculation/Makefile index 77906d671..b5362a517 100644 --- a/apps/calculation/Makefile +++ b/apps/calculation/Makefile @@ -6,13 +6,13 @@ app_objs += $(addprefix apps/calculation/,\ calculation.o\ calculation_store.o\ edit_expression_controller.o\ + expression_field.o\ history_view_cell.o\ history_controller.o\ output_expressions_view.o\ scrollable_expression_view.o\ scrollable_output_expressions_view.o\ selectable_table_view.o\ - text_field.o\ ) i18n_files += $(addprefix apps/calculation/,\ diff --git a/apps/calculation/app.cpp b/apps/calculation/app.cpp index ef09162ed..b08026b81 100644 --- a/apps/calculation/app.cpp +++ b/apps/calculation/app.cpp @@ -43,7 +43,7 @@ void App::Snapshot::tidy() { } App::App(Container * container, Snapshot * snapshot) : - TextFieldDelegateApp(container, snapshot, &m_editExpressionController), + ExpressionFieldDelegateApp(container, snapshot, &m_editExpressionController), m_historyController(&m_editExpressionController, snapshot->calculationStore()), m_editExpressionController(&m_modalViewController, &m_historyController, snapshot->calculationStore()) { @@ -65,6 +65,27 @@ bool App::textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event e return false; } +bool App::expressionLayoutFieldDidReceiveEvent(::ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) { + if ((event == Ion::Events::Var || event == Ion::Events::XNT) && ExpressionFieldDelegateApp::expressionLayoutFieldDidReceiveEvent(expressionLayoutField, event)) { + return true; + } + if (expressionLayoutField->isEditing() && expressionLayoutField->expressionLayoutFieldShouldFinishEditing(event)) { + if (!expressionLayoutField->hasText()) { + return true; + } + + int bufferLength = TextField::maxBufferSize(); + char bufferForParsing[bufferLength]; + expressionLayoutField->writeTextInBuffer(bufferForParsing, bufferLength); + + if (!textInputIsCorrect(bufferForParsing)) { + displayWarning(I18n::Message::SyntaxError); + return true; + } + } + return false; +} + bool App::textInputIsCorrect(const char * text) { /* Here, we check that the expression entered by the user can be printed with * less than k_printedExpressionLength characters. Otherwise, we prevent the diff --git a/apps/calculation/app.h b/apps/calculation/app.h index e8dd96f7b..dea3fdca7 100644 --- a/apps/calculation/app.h +++ b/apps/calculation/app.h @@ -9,7 +9,7 @@ namespace Calculation { -class App : public Shared::TextFieldDelegateApp { +class App : public Shared::ExpressionFieldDelegateApp { public: class Descriptor : public ::App::Descriptor { public: @@ -29,6 +29,7 @@ public: }; bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override; bool textInputIsCorrect(const char * text); + bool expressionLayoutFieldDidReceiveEvent(::ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) override; const char * XNT() override; private: App(Container * container, Snapshot * snapshot); diff --git a/apps/calculation/edit_expression_controller.cpp b/apps/calculation/edit_expression_controller.cpp index 43e37501d..561f6bb48 100644 --- a/apps/calculation/edit_expression_controller.cpp +++ b/apps/calculation/edit_expression_controller.cpp @@ -1,74 +1,71 @@ #include "edit_expression_controller.h" -#include "../apps_container.h" #include "app.h" +#include "../apps_container.h" +#include +#include #include using namespace Shared; namespace Calculation { -EditExpressionController::ContentView::ContentView(Responder * parentResponder, TableView * subview, TextFieldDelegate * textFieldDelegate) : +EditExpressionController::ContentView::ContentView(Responder * parentResponder, TableView * subview, TextFieldDelegate * textFieldDelegate, ExpressionLayoutFieldDelegate * expressionLayoutFieldDelegate) : View(), m_mainView(subview), - m_textField(parentResponder, m_textBody, TextField::maxBufferSize(), textFieldDelegate) + m_layout(new Poincare::HorizontalLayout()), + m_expressionField(parentResponder, m_textBody, k_bufferLength, m_layout, textFieldDelegate, expressionLayoutFieldDelegate) { m_textBody[0] = 0; } -int EditExpressionController::ContentView::numberOfSubviews() const { - return 2; +EditExpressionController::ContentView::~ContentView() { + delete m_layout; } View * EditExpressionController::ContentView::subviewAtIndex(int index) { - View * views[2] = {m_mainView, &m_textField}; - return views[index]; + assert(index >= 0 && index < numberOfSubviews()); + if (index == 0) { + return m_mainView; + } + assert(index == 1); + return &m_expressionField; } void EditExpressionController::ContentView::layoutSubviews() { - KDRect mainViewFrame(0, 0, bounds().width(), bounds().height() - k_textFieldHeight-k_separatorThickness); + KDCoordinate inputViewFrameHeight = m_expressionField.minimalSizeForOptimalDisplay().height(); + KDRect mainViewFrame(0, 0, bounds().width(), bounds().height() - inputViewFrameHeight); m_mainView->setFrame(mainViewFrame); - KDRect inputViewFrame(k_textMargin, bounds().height() - k_textFieldHeight, bounds().width()-k_textMargin, k_textFieldHeight); - m_textField.setFrame(inputViewFrame); + KDRect inputViewFrame(0, bounds().height() - inputViewFrameHeight, bounds().width(), inputViewFrameHeight); + m_expressionField.setFrame(inputViewFrame); } -void EditExpressionController::ContentView::drawRect(KDContext * ctx, KDRect rect) const { - // Draw the separator - ctx->fillRect(KDRect(0, bounds().height() -k_textFieldHeight-k_separatorThickness, bounds().width(), k_separatorThickness), Palette::GreyMiddle); - // Color the margin - ctx->fillRect(KDRect(0, bounds().height() -k_textFieldHeight, k_textMargin, k_textFieldHeight), m_textField.backgroundColor()); -} - -TextField * EditExpressionController::ContentView::textField() { - return &m_textField; -} - -TableView * EditExpressionController::ContentView::mainView() { - return m_mainView; +void EditExpressionController::ContentView::reload() { + layoutSubviews(); + markRectAsDirty(bounds()); } EditExpressionController::EditExpressionController(Responder * parentResponder, HistoryController * historyController, CalculationStore * calculationStore) : DynamicViewController(parentResponder), m_historyController(historyController), - m_calculationStore(calculationStore) + m_calculationStore(calculationStore), + m_inputViewHeightIsMaximal(false) { m_cacheBuffer[0] = 0; } const char * EditExpressionController::textBody() { - return ((ContentView *)view())->textField()->text(); + return ((ContentView *)view())->expressionField()->text(); } void EditExpressionController::insertTextBody(const char * text) { - TextField * tf = ((ContentView *)view())->textField(); - tf->setEditing(true, false); - tf->handleEventWithText(text); + ((ContentView *)view())->expressionField()->insertText(text); } bool EditExpressionController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::Up) { if (m_calculationStore->numberOfCalculations() > 0) { m_cacheBuffer[0] = 0; - ((ContentView *)view())->textField()->setEditing(false, false); + ((ContentView *)view())->expressionField()->setEditing(false, false); app()->setFirstResponder(m_historyController); } return true; @@ -79,55 +76,108 @@ bool EditExpressionController::handleEvent(Ion::Events::Event event) { void EditExpressionController::didBecomeFirstResponder() { int lastRow = m_calculationStore->numberOfCalculations() > 0 ? m_calculationStore->numberOfCalculations()-1 : 0; m_historyController->scrollToCell(0, lastRow); - ((ContentView *)view())->textField()->setEditing(true, false); - app()->setFirstResponder(((ContentView *)view())->textField()); + ((ContentView *)view())->expressionField()->setEditing(true, false); + app()->setFirstResponder(((ContentView *)view())->expressionField()); } bool EditExpressionController::textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) { if (textField->isEditing() && textField->textFieldShouldFinishEditing(event) && textField->draftTextLength() == 0 && m_cacheBuffer[0] != 0) { - App * calculationApp = (App *)app(); - /* The input text store in m_cacheBuffer might have beed correct the first - * time but then be too long when replacing ans in another context */ - if (!calculationApp->textInputIsCorrect(m_cacheBuffer)) { - return true; - } - m_calculationStore->push(m_cacheBuffer, calculationApp->localContext()); - m_historyController->reload(); - ((ContentView *)view())->mainView()->scrollToCell(0, m_historyController->numberOfRows()-1); - return true; + return inputViewDidReceiveEvent(event); } return textFieldDelegateApp()->textFieldDidReceiveEvent(textField, event); } bool EditExpressionController::textFieldDidFinishEditing(::TextField * textField, const char * text, Ion::Events::Event event) { - App * calculationApp = (App *)app(); - strlcpy(m_cacheBuffer, textBody(), TextField::maxBufferSize()); - m_calculationStore->push(textBody(), calculationApp->localContext()); - m_historyController->reload(); - ((ContentView *)view())->mainView()->scrollToCell(0, m_historyController->numberOfRows()-1); - ((ContentView *)view())->textField()->setEditing(true); - ((ContentView *)view())->textField()->setText(""); - return true; + return inputViewDidFinishEditing(text, event); } -bool EditExpressionController::textFieldDidAbortEditing(::TextField * textField, const char * text) { - ((ContentView *)view())->textField()->setEditing(true); - ((ContentView *)view())->textField()->setText(text); - return false; +bool EditExpressionController::textFieldDidAbortEditing(::TextField * textField) { + return inputViewDidAbortEditing(textField->text()); +} + +bool EditExpressionController::expressionLayoutFieldDidReceiveEvent(::ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) { + if (expressionLayoutField->isEditing() && expressionLayoutField->expressionLayoutFieldShouldFinishEditing(event) && !expressionLayoutField->hasText() && m_calculationStore->numberOfCalculations() > 0) { + return inputViewDidReceiveEvent(event); + } + return expressionFieldDelegateApp()->expressionLayoutFieldDidReceiveEvent(expressionLayoutField, event); +} + +bool EditExpressionController::expressionLayoutFieldDidFinishEditing(::ExpressionLayoutField * expressionLayoutField, const char * text, Ion::Events::Event event) { + return inputViewDidFinishEditing(text, event); +} + +bool EditExpressionController::expressionLayoutFieldDidAbortEditing(::ExpressionLayoutField * expressionLayoutField) { + return inputViewDidAbortEditing(nullptr); +} + +void EditExpressionController::expressionLayoutFieldDidChangeSize(::ExpressionLayoutField * expressionLayoutField) { + /* Reload the view only if the ExpressionField height actually changes, i.e. + * not if the height is already maximal and stays maximal. */ + if (view()) { + bool newInputViewHeightIsMaximal = static_cast(view())->expressionField()->heightIsMaximal(); + if (!m_inputViewHeightIsMaximal || !newInputViewHeightIsMaximal) { + m_inputViewHeightIsMaximal = newInputViewHeightIsMaximal; + reloadView(); + } + } } TextFieldDelegateApp * EditExpressionController::textFieldDelegateApp() { return (App *)app(); } +ExpressionFieldDelegateApp * EditExpressionController::expressionFieldDelegateApp() { + return (App *)app(); +} + View * EditExpressionController::loadView() { - return new ContentView(this, (TableView *)m_historyController->view(), this); + return new ContentView(this, (TableView *)m_historyController->view(), this, this); } void EditExpressionController::unloadView(View * view) { delete view; } +void EditExpressionController::reloadView() { + ((ContentView *)view())->reload(); + m_historyController->reload(); + if (m_historyController->numberOfRows() > 0) { + ((ContentView *)view())->mainView()->scrollToCell(0, m_historyController->numberOfRows()-1); + } +} + +bool EditExpressionController::inputViewDidReceiveEvent(Ion::Events::Event event) { + App * calculationApp = (App *)app(); + /* The input text store in m_cacheBuffer might have beed correct the first + * time but then be too long when replacing ans in another context */ + if (!calculationApp->textInputIsCorrect(m_cacheBuffer)) { + return true; + } + m_calculationStore->push(m_cacheBuffer, calculationApp->localContext()); + m_historyController->reload(); + ((ContentView *)view())->mainView()->scrollToCell(0, m_historyController->numberOfRows()-1); + return true; +} + +bool EditExpressionController::inputViewDidFinishEditing(const char * text, Ion::Events::Event event) { + App * calculationApp = (App *)app(); + strlcpy(m_cacheBuffer, textBody(), TextField::maxBufferSize()); + m_calculationStore->push(textBody(), calculationApp->localContext()); + m_historyController->reload(); + ((ContentView *)view())->mainView()->scrollToCell(0, m_historyController->numberOfRows()-1); + ((ContentView *)view())->expressionField()->setEditing(true); + ((ContentView *)view())->expressionField()->setText(""); + return true; +} + +bool EditExpressionController::inputViewDidAbortEditing(const char * text) { + if (text != nullptr) { + ((ContentView *)view())->expressionField()->setEditing(true, true); + ((ContentView *)view())->expressionField()->setText(text); + } + return false; +} + void EditExpressionController::viewDidDisappear() { DynamicViewController::viewDidDisappear(); m_historyController->viewDidDisappear(); diff --git a/apps/calculation/edit_expression_controller.h b/apps/calculation/edit_expression_controller.h index 50f4e27ff..1ad0b1c52 100644 --- a/apps/calculation/edit_expression_controller.h +++ b/apps/calculation/edit_expression_controller.h @@ -2,16 +2,18 @@ #define CALCULATION_EDIT_EXPRESSION_CONTROLLER_H #include +#include +#include "expression_field.h" #include "../shared/text_field_delegate.h" +#include "../shared/expression_layout_field_delegate.h" #include "history_controller.h" #include "calculation_store.h" -#include "text_field.h" namespace Calculation { class HistoryController; /* TODO: implement a split view */ -class EditExpressionController : public DynamicViewController, public Shared::TextFieldDelegate { +class EditExpressionController : public DynamicViewController, public Shared::TextFieldDelegate, public Shared::ExpressionLayoutFieldDelegate { public: EditExpressionController(Responder * parentResponder, HistoryController * historyController, CalculationStore * calculationStore); void didBecomeFirstResponder() override; @@ -19,33 +21,53 @@ public: bool handleEvent(Ion::Events::Event event) override; const char * textBody(); void insertTextBody(const char * text); -bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override; + + /* TextFieldDelegate */ + bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override; bool textFieldDidFinishEditing(::TextField * textField, const char * text, Ion::Events::Event event) override; - bool textFieldDidAbortEditing(::TextField * textField, const char * text) override; + bool textFieldDidAbortEditing(::TextField * textField) override; + + /* ExpressionLayoutFieldDelegate */ + bool expressionLayoutFieldDidReceiveEvent(::ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) override; + bool expressionLayoutFieldDidFinishEditing(::ExpressionLayoutField * expressionLayoutField, const char * text, Ion::Events::Event event) override; + bool expressionLayoutFieldDidAbortEditing(::ExpressionLayoutField * expressionLayoutField) override; + void expressionLayoutFieldDidChangeSize(::ExpressionLayoutField * expressionLayoutField) override; + private: class ContentView : public View { public: - ContentView(Responder * parentResponder, TableView * subview, TextFieldDelegate * textFieldDelegate); - int numberOfSubviews() const override; + ContentView(Responder * parentResponder, TableView * subview, TextFieldDelegate * textFieldDelegate, ExpressionLayoutFieldDelegate * expressionLayoutFieldDelegate); + ~ContentView(); + ContentView(const ContentView& other) = delete; + ContentView(ContentView&& other) = delete; + ContentView& operator=(const ContentView& other) = delete; + ContentView& operator=(ContentView&& other) = delete; + void reload(); + TableView * mainView() { return m_mainView; } + ExpressionField * expressionField() { return &m_expressionField; } + /* View */ + int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override; void layoutSubviews() override; - TextField * textField(); - TableView * mainView(); - void drawRect(KDContext * ctx, KDRect rect) const override; private: - static constexpr KDCoordinate k_textFieldHeight = 37; - static constexpr KDCoordinate k_textMargin= 5; - constexpr static int k_separatorThickness = 1; + static constexpr int k_bufferLength = TextField::maxBufferSize(); TableView * m_mainView; - TextField m_textField; - char m_textBody[TextField::maxBufferSize()]; + char m_textBody[k_bufferLength]; + Poincare::ExpressionLayout * m_layout; + ExpressionField m_expressionField; }; View * loadView() override; void unloadView(View * view) override; + void reloadView(); + bool inputViewDidReceiveEvent(Ion::Events::Event event); + bool inputViewDidFinishEditing(const char * text, Ion::Events::Event event); + bool inputViewDidAbortEditing(const char * text); Shared::TextFieldDelegateApp * textFieldDelegateApp() override; + Shared::ExpressionFieldDelegateApp * expressionFieldDelegateApp() override; char m_cacheBuffer[TextField::maxBufferSize()]; HistoryController * m_historyController; CalculationStore * m_calculationStore; + bool m_inputViewHeightIsMaximal; }; } diff --git a/apps/calculation/expression_field.cpp b/apps/calculation/expression_field.cpp new file mode 100644 index 000000000..37c2c4151 --- /dev/null +++ b/apps/calculation/expression_field.cpp @@ -0,0 +1,25 @@ +#include "expression_field.h" + +namespace Calculation { + +bool ExpressionField::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::Back) { + return false; + } + if (event == Ion::Events::Ans) { + insertText("ans"); + return true; + } + if (isEditing() && isEmpty() && + (event == Ion::Events::Multiplication || + event == Ion::Events::Plus || + event == Ion::Events::Power || + event == Ion::Events::Square || + event == Ion::Events::Division || + event == Ion::Events::Sto)) { + insertText("ans"); + } + return(::ExpressionField::handleEvent(event)); +} + +} diff --git a/apps/calculation/expression_field.h b/apps/calculation/expression_field.h new file mode 100644 index 000000000..a7fe0e20e --- /dev/null +++ b/apps/calculation/expression_field.h @@ -0,0 +1,17 @@ +#ifndef CALCULATION_EXPRESSION_FIELD_H +#define CALCULATION_EXPRESSION_FIELD_H + +#include + +namespace Calculation { + +class ExpressionField : public ::ExpressionField { +public: + using ::ExpressionField::ExpressionField; +protected: + bool handleEvent(Ion::Events::Event event) override; +}; + +} + +#endif diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 119c3ec54..3b901f486 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -83,7 +83,7 @@ void HistoryViewCell::layoutSubviews() { } void HistoryViewCell::setCalculation(Calculation * calculation) { - m_inputView.setExpression(calculation->inputLayout()); + m_inputView.setExpressionLayout(calculation->inputLayout()); App * calculationApp = (App *)app(); /* Both output expressions have to be updated at the same time. The * outputView points to deleted layouts and a call to diff --git a/apps/calculation/output_expressions_view.cpp b/apps/calculation/output_expressions_view.cpp index f7221d957..beefae084 100644 --- a/apps/calculation/output_expressions_view.cpp +++ b/apps/calculation/output_expressions_view.cpp @@ -16,8 +16,8 @@ OutputExpressionsView::OutputExpressionsView(Responder * parentResponder) : } void OutputExpressionsView::setExpressions(ExpressionLayout ** expressionsLayout) { - m_approximateExpressionView.setExpression(expressionsLayout[0]); - m_exactExpressionView.setExpression(expressionsLayout[1]); + m_approximateExpressionView.setExpressionLayout(expressionsLayout[0]); + m_exactExpressionView.setExpressionLayout(expressionsLayout[1]); layoutSubviews(); } diff --git a/apps/calculation/scrollable_expression_view.cpp b/apps/calculation/scrollable_expression_view.cpp index def347a0f..70e10b526 100644 --- a/apps/calculation/scrollable_expression_view.cpp +++ b/apps/calculation/scrollable_expression_view.cpp @@ -10,13 +10,14 @@ ScrollableExpressionView::ScrollableExpressionView(Responder * parentResponder) { } -void ScrollableExpressionView::setExpression(ExpressionLayout * expressionLayout) { - m_expressionView.setExpression(expressionLayout); +void ScrollableExpressionView::setExpressionLayout(ExpressionLayout * expressionLayout) { + m_expressionView.setExpressionLayout(expressionLayout); layoutSubviews(); } void ScrollableExpressionView::setBackgroundColor(KDColor backgroundColor) { m_expressionView.setBackgroundColor(backgroundColor); + ScrollableView::setBackgroundColor(backgroundColor); } KDSize ScrollableExpressionView::minimalSizeForOptimalDisplay() const { diff --git a/apps/calculation/scrollable_expression_view.h b/apps/calculation/scrollable_expression_view.h index a613600f1..38ae2d8d4 100644 --- a/apps/calculation/scrollable_expression_view.h +++ b/apps/calculation/scrollable_expression_view.h @@ -8,8 +8,8 @@ namespace Calculation { class ScrollableExpressionView : public ScrollableView, public ScrollViewDataSource { public: ScrollableExpressionView(Responder * parentResponder); - void setExpression(Poincare::ExpressionLayout * expressionLayout); - void setBackgroundColor(KDColor backgroundColor); + void setExpressionLayout(Poincare::ExpressionLayout * expressionLayout); + void setBackgroundColor(KDColor backgroundColor) override; KDSize minimalSizeForOptimalDisplay() const override; private: ExpressionView m_expressionView; diff --git a/apps/calculation/text_field.cpp b/apps/calculation/text_field.cpp deleted file mode 100644 index 4e7d9c032..000000000 --- a/apps/calculation/text_field.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "text_field.h" - -namespace Calculation { - -TextField::TextField(Responder * parentResponder, char * textBuffer, size_t textBufferSize, TextFieldDelegate * delegate) : - ::TextField(parentResponder, textBuffer, textBuffer, textBufferSize, delegate, false) -{ - setEditing(true); -} - -bool TextField::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::Back) { - return false; - } - if (event == Ion::Events::Ans) { - return handleEventWithText("ans"); - } - if (isEditing() && draftTextLength() == 0 && - (event == Ion::Events::Multiplication || - event == Ion::Events::Plus || - event == Ion::Events::Power || - event == Ion::Events::Square || - event == Ion::Events::Division || - event == Ion::Events::Sto)) { - handleEventWithText("ans"); - } - return(::TextField::handleEvent(event)); -} - -} diff --git a/apps/calculation/text_field.h b/apps/calculation/text_field.h deleted file mode 100644 index 6d2f12392..000000000 --- a/apps/calculation/text_field.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef CALCULATION_TEXT_FIELD_H -#define CALCULATION_TEXT_FIELD_H - -#include - -namespace Calculation { - -class TextField : public ::TextField { -public: - TextField(Responder * parentResponder, char * textBuffer, size_t textBufferSize, TextFieldDelegate * delegate); - bool handleEvent(Ion::Events::Event event) override; -}; - -} - -#endif diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index bcb3233f1..afeeb6310 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -19,7 +19,7 @@ PythonCommandBin = "bin(x)" PythonCommandCeil = "ceil(x)" PythonCommandChoice = "choice(list)" PythonCommandCmathFunction = "cmath.function" -PythonCommandCmathFunctionWithoutArg = "cmath." +PythonCommandCmathFunctionWithoutArg = "cmath.•" PythonCommandColor = "color(r,g,b)" PythonCommandComplex = "complex(a,b)" PythonCommandCopySign = "copysign(x,y)" @@ -57,7 +57,7 @@ PythonCommandIsFinite = "isfinite(x)" PythonCommandIsInfinite = "isinf(x)" PythonCommandIsNaN = "isnan(x)" PythonCommandKandinskyFunction = "kandinsky.function" -PythonCommandKandinskyFunctionWithoutArg = "kandinsky." +PythonCommandKandinskyFunctionWithoutArg = "kandinsky.•" PythonCommandLdexp = "ldexp(x,i)" PythonCommandLgamma = "lgamma(x)" PythonCommandLength = "len(object)" @@ -66,7 +66,7 @@ PythonCommandLog10 = "log10(x)" PythonCommandLog2 = "log2(x)" PythonCommandLogComplex = "log(z,a)" PythonCommandMathFunction = "math.function" -PythonCommandMathFunctionWithoutArg = "math." +PythonCommandMathFunctionWithoutArg = "math.•" PythonCommandMax = "max(list)" PythonCommandMin = "min(list)" PythonCommandModf = "modf(x)" @@ -80,7 +80,7 @@ PythonCommandRadians = "radians(x)" PythonCommandRandom = "random()" PythonCommandRandint = "randint(a,b)" PythonCommandRandomFunction = "random.function" -PythonCommandRandomFunctionWithoutArg = "random." +PythonCommandRandomFunctionWithoutArg = "random.•" PythonCommandRandrange = "randrange(start, stop)" PythonCommandRangeStartStop = "range(start, stop)" PythonCommandRangeStop = "range(stop)" @@ -100,6 +100,6 @@ PythonCommandTanh = "tanh(x)" PythonCommandTrunc = "trunc(x)" PythonCommandImag = "z.imag" PythonCommandReal = "z.real" -PythonCommandImagWithoutArg = "().imag" -PythonCommandRealWithoutArg = "().real" +PythonCommandImagWithoutArg = "•.imag" +PythonCommandRealWithoutArg = "•.real" PythonCommandUniform = "uniform(a,b)" diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index ec5ffcde3..9f0e4ee96 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -279,7 +279,7 @@ bool ConsoleController::textFieldDidFinishEditing(TextField * textField, const c return true; } -bool ConsoleController::textFieldDidAbortEditing(TextField * textField, const char * text) { +bool ConsoleController::textFieldDidAbortEditing(TextField * textField) { if (inputRunLoopActive()) { askInputRunLoopTermination(); } else { diff --git a/apps/code/console_controller.h b/apps/code/console_controller.h index 1dbb68956..1bdcc00d7 100644 --- a/apps/code/console_controller.h +++ b/apps/code/console_controller.h @@ -59,7 +59,7 @@ public: bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; - bool textFieldDidAbortEditing(TextField * textField, const char * text) override; + bool textFieldDidAbortEditing(TextField * textField) override; Toolbox * toolboxForTextInput(TextInput * textInput) override; // MicroPython::ExecutionEnvironment diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index 6824979e3..f0cd2ef00 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -57,7 +57,7 @@ void MenuController::willExitResponderChain(Responder * nextFirstResponder) { TextField * tf = static_cast(m_selectableTableView.selectedCell())->editableTextCell()->textField(); if (tf->isEditing()) { tf->setEditing(false); - textFieldDidAbortEditing(tf, tf->text()); + textFieldDidAbortEditing(tf); } } } @@ -345,8 +345,8 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char return false; } -bool MenuController::textFieldDidAbortEditing(TextField * textField, const char * text) { - if (strlen(text) <= strlen(ScriptStore::k_scriptExtension)) { +bool MenuController::textFieldDidAbortEditing(TextField * textField) { + if (strlen(textField->text()) <= strlen(ScriptStore::k_scriptExtension)) { // The previous text was an empty name. Use a numbered default script name. char numberedDefaultName[k_defaultScriptNameMaxSize]; numberedDefaultScriptName(numberedDefaultName); diff --git a/apps/code/menu_controller.h b/apps/code/menu_controller.h index 7d0dcf2f6..b536e05d7 100644 --- a/apps/code/menu_controller.h +++ b/apps/code/menu_controller.h @@ -57,7 +57,7 @@ public: bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; - bool textFieldDidAbortEditing(TextField * textField, const char * text) override; + bool textFieldDidAbortEditing(TextField * textField) override; bool textFieldDidHandleEvent(TextField * textField, bool returnValue, bool textHasChanged) override; Toolbox * toolboxForTextInput(TextInput * textInput) override { return nullptr; } diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index a0ff335d2..85b6d0a93 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -291,10 +291,10 @@ bool PythonToolbox::selectLeaf(ToolboxMessageTree * selectedMessageTree) { m_selectableTableView.deselectTable(); ToolboxMessageTree * node = selectedMessageTree; const char * editedText = I18n::translate(node->insertedText()); - char strippedEditedText[strlen(editedText)+1]; - Shared::ToolboxHelpers::TextToInsertForCommandMessage(node->insertedText(), strippedEditedText); - TextInput * textInput = static_cast(sender()); - textInput->handleEventWithText(strippedEditedText, true); + int strippedEditedTextMaxLength = strlen(editedText)+1; + char strippedEditedText[strippedEditedTextMaxLength]; + Shared::ToolboxHelpers::TextToInsertForCommandMessage(node->insertedText(), strippedEditedText, strippedEditedTextMaxLength, true); + sender()->handleEventWithText(strippedEditedText, true); app()->dismissModalViewController(); return true; } diff --git a/apps/code/python_toolbox.h b/apps/code/python_toolbox.h index 5d9dab80f..03c48ebf0 100644 --- a/apps/code/python_toolbox.h +++ b/apps/code/python_toolbox.h @@ -10,10 +10,7 @@ namespace Code { class PythonToolbox : public Toolbox { public: - typedef void (*Action)(void * sender, const char * text); PythonToolbox(); - - // StackViewController bool handleEvent(Ion::Events::Event event) override; protected: KDCoordinate rowHeight(int j) override; diff --git a/apps/code/toolbox.universal.i18n b/apps/code/toolbox.universal.i18n index 0521ae967..680a88d50 100644 --- a/apps/code/toolbox.universal.i18n +++ b/apps/code/toolbox.universal.i18n @@ -20,27 +20,27 @@ NonEqualityCondition = "!=" NonEqualityConditionWithArg = "x!=y" EqualityCondition = "==" EqualityConditionWithArg = "x==y" -WhileLoop = "while ():\n " +WhileLoop = "while •:\n " WhileLoopWithArg = "while condition:\n instruction" -IfOrIfElseStatement = "if () or ():\n \nelse:\n " +IfOrIfElseStatement = "if • or :\n \nelse:\n " IfOrIfElseStatementWithArg = "if condition1 or condition2:\n instruction1\nelse:\n instruction2" -IfAndIfElseStatement = "if () and ():\n \nelse:\n " +IfAndIfElseStatement = "if • and :\n \nelse:\n " IfAndIfElseStatementWithArg = "if condition1 and condition2:\n instruction1\nelse:\n instruction2" -IfElifElseStatement = "if ():\n \nelif ():\n \nelse:\n " +IfElifElseStatement = "if •:\n \nelif :\n \nelse:\n " IfElifElseStatementWithArg = "if condition1:\n instruction1\nelif condition2:\n instruction2\nelse:\n instruction3" -IfThenStatement= "if ():\n " +IfThenStatement= "if •:\n " IfThenStatementWithArg = "if condition:\n instruction" -IfElseStatement = "if ():\n \nelse:\n " +IfElseStatement = "if •:\n \nelse:\n " IfElseStatementWithArg = "if condition:\n instruction1\nelse:\n instruction2" -ForInListLoop = "for i in ():\n " +ForInListLoop = "for i in •:\n " ForInListLoopWithArg = "for i in list:\n instruction" -ForInRange3ArgsLoop = "for i in range(,,):\n " +ForInRange3ArgsLoop = "for i in range(•,,):\n " ForInRange3ArgsLoopWithArg = "for i in range(start, stop, step):\n instruction" -ForInRange2ArgsLoop = "for i in range(,):\n " +ForInRange2ArgsLoop = "for i in range(•,):\n " ForInRange2ArgsLoopWithArg = "for i in range(start, stop):\n instruction" -ForInRange1ArgLoop = "for i in range():\n " +ForInRange1ArgLoop = "for i in range(•):\n " ForInRange1ArgLoopWithArg = "for i in range(size):\n instruction" -PythonCommandDef = "def ():\n " +PythonCommandDef = "def •():\n " PythonCommandDefWithArg = "def function(x):" PythonCommandReturn = "return " RandomModule = "random" diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index 1dbb42739..b97b69d07 100644 --- a/apps/code/variable_box_controller.cpp +++ b/apps/code/variable_box_controller.cpp @@ -115,8 +115,9 @@ void VariableBoxController::ContentViewController::willDisplayCellForIndex(Highl } void VariableBoxController::ContentViewController::insertTextInCaller(const char * text) { - char commandBuffer[strlen(text)+1]; - Shared::ToolboxHelpers::TextToInsertForCommandText(text, commandBuffer); + int commandBufferMaxSize = strlen(text)+1; + char commandBuffer[commandBufferMaxSize]; + Shared::ToolboxHelpers::TextToInsertForCommandText(text, commandBuffer, commandBufferMaxSize); m_textInputCaller->handleEventWithText(commandBuffer); } diff --git a/apps/graph/app.cpp b/apps/graph/app.cpp index 1bd39a231..257165fe5 100644 --- a/apps/graph/app.cpp +++ b/apps/graph/app.cpp @@ -70,7 +70,7 @@ App::App(Container * container, Snapshot * snapshot) : m_valuesHeader(&m_valuesStackViewController, &m_valuesAlternateEmptyViewController, &m_valuesController), m_valuesStackViewController(&m_tabViewController, &m_valuesHeader), m_tabViewController(&m_inputViewController, snapshot, &m_listStackViewController, &m_graphStackViewController, &m_valuesStackViewController), - m_inputViewController(&m_modalViewController, &m_tabViewController, this) + m_inputViewController(&m_modalViewController, &m_tabViewController, this, this) { } diff --git a/apps/graph/graph/integral_graph_controller.cpp b/apps/graph/graph/integral_graph_controller.cpp index 49fd032fc..c83d43113 100644 --- a/apps/graph/graph/integral_graph_controller.cpp +++ b/apps/graph/graph/integral_graph_controller.cpp @@ -1,6 +1,6 @@ #include "integral_graph_controller.h" #include "../../shared/text_field_delegate.h" -#include "../../../poincare/src/layout/string_layout.h" +#include #include "../app.h" #include @@ -39,7 +39,7 @@ double IntegralGraphController::cursorNextStep(double x, int direction) { ExpressionLayout * IntegralGraphController::createFunctionLayout(const char * functionName) { char buffer[7] = "0(x)dx"; buffer[0] = functionName[0]; - return new StringLayout(buffer, strlen(buffer), KDText::FontSize::Small); + return LayoutEngine::createStringLayout(buffer, strlen(buffer), KDText::FontSize::Small); } } diff --git a/apps/graph/list/list_controller.cpp b/apps/graph/list/list_controller.cpp index 1eb2f4a0d..9c84175d6 100644 --- a/apps/graph/list/list_controller.cpp +++ b/apps/graph/list/list_controller.cpp @@ -90,7 +90,7 @@ void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) { void ListController::willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) { FunctionExpressionCell * myCell = (FunctionExpressionCell *)cell; Function * f = m_functionStore->functionAtIndex(j); - myCell->setExpression(f->layout()); + myCell->setExpressionLayout(f->layout()); bool active = f->isActive(); KDColor textColor = active ? KDColorBlack : Palette::GreyDark; myCell->setTextColor(textColor); diff --git a/apps/i18n.py b/apps/i18n.py index 808df06bc..7a6a71ede 100644 --- a/apps/i18n.py +++ b/apps/i18n.py @@ -12,7 +12,8 @@ ion_special_characters = { u'μ': "Ion::Charset::SmallMu", u'σ': "Ion::Charset::SmallSigma", u'≤': "Ion::Charset::LessEqual", - u'≈': "Ion::Charset::AlmostEqual" + u'≈': "Ion::Charset::AlmostEqual", + u'•': "Ion::Charset::Empty" } def ion_char(i18n_letter): diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index 6ec75cb8a..62337c781 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -1,13 +1,17 @@ #include "math_toolbox.h" #include "./shared/toolbox_helpers.h" +#include #include #include +using namespace Poincare; + /* TODO: find a shorter way to initialize tree models * We create one model tree: each node keeps the label of the row it refers to * and the text which would be edited by clicking on the row. When the node is a * subtree, the edited text is set at I18n::Message::Default. */ + const ToolboxMessageTree calculChildren[4] = { ToolboxMessageTree(I18n::Message::DiffCommandWithArg, I18n::Message::DerivateNumber, I18n::Message::DiffCommandWithArg), ToolboxMessageTree(I18n::Message::IntCommandWithArg, I18n::Message::Integral, I18n::Message::IntCommandWithArg), @@ -33,7 +37,8 @@ const ToolboxMessageTree arithmeticChildren[5] = { ToolboxMessageTree(I18n::Message::QuoCommandWithArg, I18n::Message::Quotient, I18n::Message::QuoCommandWithArg)}; #if MATRICES_ARE_DEFINED -const ToolboxMessageTree matricesChildren[5] = { +const ToolboxMessageTree matricesChildren[6] = { + ToolboxMessageTree(I18n::Message::MatrixCommandWithArg, I18n::Message::NewMatrix, I18n::Message::MatrixCommand), ToolboxMessageTree(I18n::Message::InverseCommandWithArg, I18n::Message::Inverse, I18n::Message::InverseCommandWithArg), ToolboxMessageTree(I18n::Message::DeterminantCommandWithArg, I18n::Message::Determinant, I18n::Message::DeterminantCommandWithArg), ToolboxMessageTree(I18n::Message::TransposeCommandWithArg, I18n::Message::Transpose, I18n::Message::TransposeCommandWithArg), @@ -72,12 +77,13 @@ const ToolboxMessageTree predictionChildren[3] = { ToolboxMessageTree(I18n::Message::ConfidenceCommandWithArg, I18n::Message::Confidence, I18n::Message::ConfidenceCommandWithArg)}; #if LIST_ARE_DEFINED -const ToolboxMessageTree menu[12] = {ToolboxMessageTree(I18n::Message::AbsCommandWithArg, I18n::Message::AbsoluteValue, I18n::Message::AbsCommandWithArg), +const ToolboxMessageTree menu[12] = { #elif MATRICES_ARE_DEFINED -const ToolboxMessageTree menu[11] = {ToolboxMessageTree(I18n::Message::AbsCommandWithArg, I18n::Message::AbsoluteValue, I18n::Message::AbsCommandWithArg), +const ToolboxMessageTree menu[11] = { #else -const ToolboxMessageTree menu[10] = {ToolboxMessageTree(I18n::Message::AbsCommandWithArg, I18n::Message::AbsoluteValue, I18n::Message::AbsCommandWithArg), +const ToolboxMessageTree menu[10] = { #endif + ToolboxMessageTree(I18n::Message::AbsCommandWithArg, I18n::Message::AbsoluteValue, I18n::Message::AbsCommandWithArg), ToolboxMessageTree(I18n::Message::RootCommandWithArg, I18n::Message::NthRoot, I18n::Message::RootCommandWithArg), ToolboxMessageTree(I18n::Message::LogCommandWithArg, I18n::Message::BasedLogarithm, I18n::Message::LogCommandWithArg), ToolboxMessageTree(I18n::Message::Calculation, I18n::Message::Default, I18n::Message::Default, calculChildren, 4), @@ -85,7 +91,7 @@ const ToolboxMessageTree menu[10] = {ToolboxMessageTree(I18n::Message::AbsComman ToolboxMessageTree(I18n::Message::Probability, I18n::Message::Default, I18n::Message::Default, probabilityChildren, 2), ToolboxMessageTree(I18n::Message::Arithmetic, I18n::Message::Default, I18n::Message::Default, arithmeticChildren, 5), #if MATRICES_ARE_DEFINED - ToolboxMessageTree(I18n::Message::Matrices, I18n::Message::Default, I18n::Message::Default, matricesChildren, 5), + ToolboxMessageTree(I18n::Message::Matrices, I18n::Message::Default, I18n::Message::Default, matricesChildren, 6), #endif #if LIST_ARE_DEFINED ToolboxMessageTree(I18n::Message::Lists, I18n::Message::Default, I18n::Message::Default, listesChildren, 5), @@ -101,21 +107,22 @@ const ToolboxMessageTree toolboxModel = ToolboxMessageTree(I18n::Message::Toolbo const ToolboxMessageTree toolboxModel = ToolboxMessageTree(I18n::Message::Toolbox, I18n::Message::Default, I18n::Message::Default, menu, 10); #endif -MathToolbox::MathToolbox() : Toolbox(nullptr, I18n::translate(rootModel()->label())) +MathToolbox::MathToolbox() : + Toolbox(nullptr, I18n::translate(rootModel()->label())) { } -TextField * MathToolbox::sender() { - return (TextField *)Toolbox::sender(); -} - bool MathToolbox::selectLeaf(ToolboxMessageTree * selectedMessageTree) { - m_selectableTableView.deselectTable(); ToolboxMessageTree * messageTree = selectedMessageTree; - const char * editedText = I18n::translate(messageTree->insertedText()); - char strippedEditedText[strlen(editedText)]; - Shared::ToolboxHelpers::TextToInsertForCommandMessage(messageTree->insertedText(), strippedEditedText); - sender()->handleEventWithText(strippedEditedText); + m_selectableTableView.deselectTable(); + + // Translate the message and remove the arguments + const char * text = I18n::translate(messageTree->insertedText()); + int maxTextToInsertLength = strlen(text) + 1; + char textToInsert[maxTextToInsertLength]; + Shared::ToolboxHelpers::TextToInsertForCommandText(text, textToInsert, maxTextToInsertLength, true); + + sender()->handleEventWithText(textToInsert); app()->dismissModalViewController(); return true; } diff --git a/apps/math_toolbox.h b/apps/math_toolbox.h index 0579969de..fd7f1ad7a 100644 --- a/apps/math_toolbox.h +++ b/apps/math_toolbox.h @@ -9,7 +9,6 @@ class MathToolbox : public Toolbox { public: MathToolbox(); protected: - TextField * sender() override; bool selectLeaf(ToolboxMessageTree * selectedMessageTree) override; const ToolboxMessageTree * rootModel() override; MessageTableCellWithMessage * leafCellAtIndex(int index) override; diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index 42a8962ad..00a7efa23 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -1,8 +1,9 @@ #include "calculation_controller.h" #include "../constant.h" #include "../apps_container.h" -#include "../../poincare/src/layout/baseline_relative_layout.h" -#include "../../poincare/src/layout/string_layout.h" +#include "../../poincare/src/layout/char_layout.h" +#include "../../poincare/src/layout/horizontal_layout.h" +#include "../../poincare/src/layout/vertical_offset_layout.h" #include #include @@ -21,7 +22,7 @@ CalculationController::CalculationController(Responder * parentResponder, Button m_calculationCells{}, m_store(store) { - m_r2Layout = new BaselineRelativeLayout(new StringLayout("r", 1, KDText::FontSize::Small), new StringLayout("2", 1, KDText::FontSize::Small), BaselineRelativeLayout::Type::Superscript); + m_r2Layout = new HorizontalLayout(new CharLayout('r', KDText::FontSize::Small), new VerticalOffsetLayout(new CharLayout('2', KDText::FontSize::Small), VerticalOffsetLayout::Type::Superscript, false), false); } CalculationController::~CalculationController() { @@ -129,7 +130,7 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int if (i == 0) { if (j == numberOfRows()-1) { EvenOddExpressionCell * myCell = (EvenOddExpressionCell *)cell; - myCell->setExpression(m_r2Layout); + myCell->setExpressionLayout(m_r2Layout); return; } EvenOddMessageTextCell * myCell = (EvenOddMessageTextCell *)cell; diff --git a/apps/regression/store_controller.cpp b/apps/regression/store_controller.cpp index 62e2e4400..e08bb358d 100644 --- a/apps/regression/store_controller.cpp +++ b/apps/regression/store_controller.cpp @@ -2,8 +2,9 @@ #include "app.h" #include "../apps_container.h" #include "../constant.h" -#include "../../poincare/src/layout/baseline_relative_layout.h" -#include "../../poincare/src/layout/string_layout.h" +#include "../../poincare/src/layout/char_layout.h" +#include "../../poincare/src/layout/horizontal_layout.h" +#include "../../poincare/src/layout/vertical_offset_layout.h" #include using namespace Poincare; @@ -15,8 +16,8 @@ StoreController::StoreController(Responder * parentResponder, Store * store, But Shared::StoreController(parentResponder, store, header), m_titleCells{} { - m_titleLayout[0] = new BaselineRelativeLayout(new StringLayout("X", 1, KDText::FontSize::Small), new StringLayout("i", 1, KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); - m_titleLayout[1] = new BaselineRelativeLayout(new StringLayout("Y", 1, KDText::FontSize::Small), new StringLayout("i", 1, KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + m_titleLayout[0] = new HorizontalLayout(new CharLayout('X', KDText::FontSize::Small), new VerticalOffsetLayout(new CharLayout('i', KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), false); + m_titleLayout[1] = new HorizontalLayout(new CharLayout('Y', KDText::FontSize::Small), new VerticalOffsetLayout(new CharLayout('i', KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), false); } StoreController::~StoreController() { @@ -34,7 +35,7 @@ void StoreController::willDisplayCellAtLocation(HighlightCell * cell, int i, int return; } EvenOddExpressionCell * mytitleCell = (EvenOddExpressionCell *)cell; - mytitleCell->setExpression(m_titleLayout[i]); + mytitleCell->setExpressionLayout(m_titleLayout[i]); } HighlightCell * StoreController::titleCells(int index) { diff --git a/apps/sequence/app.cpp b/apps/sequence/app.cpp index a875a05d0..0ccd7c74e 100644 --- a/apps/sequence/app.cpp +++ b/apps/sequence/app.cpp @@ -72,7 +72,7 @@ App::App(Container * container, Snapshot * snapshot) : m_valuesHeader(nullptr, &m_valuesAlternateEmptyViewController, &m_valuesController), m_valuesStackViewController(&m_tabViewController, &m_valuesHeader), m_tabViewController(&m_inputViewController, snapshot, &m_listStackViewController, &m_graphStackViewController, &m_valuesStackViewController), - m_inputViewController(&m_modalViewController, &m_tabViewController, &m_listController) + m_inputViewController(&m_modalViewController, &m_tabViewController, &m_listController, &m_listController) { } diff --git a/apps/sequence/graph/term_sum_controller.cpp b/apps/sequence/graph/term_sum_controller.cpp index 7aca21bfd..4d0574fae 100644 --- a/apps/sequence/graph/term_sum_controller.cpp +++ b/apps/sequence/graph/term_sum_controller.cpp @@ -1,12 +1,17 @@ #include "term_sum_controller.h" #include "../../shared/text_field_delegate.h" -#include "../../../poincare/src/layout/baseline_relative_layout.h" -#include "../../../poincare/src/layout/string_layout.h" #include "../app.h" -#include +#include "../../../poincare/src/layout/char_layout.h" +#include "../../../poincare/src/layout/horizontal_layout.h" +#include "../../../poincare/src/layout/vertical_offset_layout.h" + #include + +extern "C" { +#include #include +} using namespace Shared; using namespace Poincare; @@ -46,7 +51,13 @@ double TermSumController::cursorNextStep(double x, int direction) { } ExpressionLayout * TermSumController::createFunctionLayout(const char * functionName) { - return new BaselineRelativeLayout(new StringLayout(functionName, 1, KDText::FontSize::Small), new StringLayout("n", 1, KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + return new HorizontalLayout( + new CharLayout(functionName[0], KDText::FontSize::Small), + new VerticalOffsetLayout( + new CharLayout('n', KDText::FontSize::Small), + VerticalOffsetLayout::Type::Subscript, + false), + false); } } diff --git a/apps/sequence/list/list_controller.cpp b/apps/sequence/list/list_controller.cpp index 072fb1688..23e0ecf2c 100644 --- a/apps/sequence/list/list_controller.cpp +++ b/apps/sequence/list/list_controller.cpp @@ -24,20 +24,21 @@ const char * ListController::title() { } Toolbox * ListController::toolboxForTextInput(TextInput * textInput) { - int recurrenceDepth = -1; - int sequenceDefinition = sequenceDefinitionForRow(selectedRow()); - Sequence * sequence = m_sequenceStore->functionAtIndex(functionIndexForRow(selectedRow())); - if (sequenceDefinition == 0) { - recurrenceDepth = sequence->numberOfElements()-1; - } - m_sequenceToolbox.setExtraCells(sequence->name(), recurrenceDepth); - return &m_sequenceToolbox; + return toolboxForSender(textInput); +} + +Toolbox * ListController::toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) { + return toolboxForSender(expressionLayoutField); } TextFieldDelegateApp * ListController::textFieldDelegateApp() { return (App *)app(); } +ExpressionFieldDelegateApp * ListController::expressionFieldDelegateApp() { + return (App *)app(); +} + int ListController::numberOfRows() { int numberOfRows = 0; for (int i = 0; i < m_sequenceStore->numberOfFunctions(); i++) { @@ -82,6 +83,20 @@ void ListController::selectPreviousNewSequenceCell() { } } +Toolbox * ListController::toolboxForSender(Responder * sender) { + // Set extra cells + int recurrenceDepth = -1; + int sequenceDefinition = sequenceDefinitionForRow(selectedRow()); + Sequence * sequence = m_sequenceStore->functionAtIndex(functionIndexForRow(selectedRow())); + if (sequenceDefinition == 0) { + recurrenceDepth = sequence->numberOfElements()-1; + } + m_sequenceToolbox.setExtraCells(sequence->name(), recurrenceDepth); + // Set sender + m_sequenceToolbox.setSender(sender); + return &m_sequenceToolbox; +} + void ListController::editExpression(Sequence * sequence, int sequenceDefinition, Ion::Events::Event event) { char * initialText = nullptr; char initialTextContent[TextField::maxBufferSize()]; @@ -169,13 +184,13 @@ void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) { SequenceTitleCell * myCell = (SequenceTitleCell *)cell; Sequence * sequence = m_sequenceStore->functionAtIndex(functionIndexForRow(j)); if (sequenceDefinitionForRow(j) == 0) { - myCell->setExpression(sequence->definitionName()); + myCell->setExpressionLayout(sequence->definitionName()); } if (sequenceDefinitionForRow(j) == 1) { - myCell->setExpression(sequence->firstInitialConditionName()); + myCell->setExpressionLayout(sequence->firstInitialConditionName()); } if (sequenceDefinitionForRow(j) == 2) { - myCell->setExpression(sequence->secondInitialConditionName()); + myCell->setExpressionLayout(sequence->secondInitialConditionName()); } KDColor nameColor = sequence->isActive() ? sequence->color() : Palette::GreyDark; myCell->setColor(nameColor); @@ -185,13 +200,13 @@ void ListController::willDisplayExpressionCellAtIndex(HighlightCell * cell, int FunctionExpressionCell * myCell = (FunctionExpressionCell *)cell; Sequence * sequence = m_sequenceStore->functionAtIndex(functionIndexForRow(j)); if (sequenceDefinitionForRow(j) == 0) { - myCell->setExpression(sequence->layout()); + myCell->setExpressionLayout(sequence->layout()); } if (sequenceDefinitionForRow(j) == 1) { - myCell->setExpression(sequence->firstInitialConditionLayout()); + myCell->setExpressionLayout(sequence->firstInitialConditionLayout()); } if (sequenceDefinitionForRow(j) == 2) { - myCell->setExpression(sequence->secondInitialConditionLayout()); + myCell->setExpressionLayout(sequence->secondInitialConditionLayout()); } bool active = sequence->isActive(); KDColor textColor = active ? KDColorBlack : Palette::GreyDark; diff --git a/apps/sequence/list/list_controller.h b/apps/sequence/list/list_controller.h index 3e8e9075d..a6a95bc06 100644 --- a/apps/sequence/list/list_controller.h +++ b/apps/sequence/list/list_controller.h @@ -5,16 +5,17 @@ #include "../sequence_title_cell.h" #include "../sequence_store.h" #include "../../shared/function_expression_cell.h" -#include "type_parameter_controller.h" -#include "../../shared/new_function_cell.h" #include "../../shared/list_controller.h" +#include "../../shared/new_function_cell.h" +#include "../../shared/expression_layout_field_delegate.h" #include "../../shared/text_field_delegate.h" #include "list_parameter_controller.h" #include "sequence_toolbox.h" +#include "type_parameter_controller.h" namespace Sequence { -class ListController : public Shared::ListController, public Shared::TextFieldDelegate { +class ListController : public Shared::ListController, public Shared::TextFieldDelegate, public Shared::ExpressionLayoutFieldDelegate { public: ListController(Responder * parentResponder, SequenceStore * sequenceStore, ButtonRowController * header, ButtonRowController * footer); const char * title() override; @@ -22,9 +23,12 @@ public: virtual KDCoordinate rowHeight(int j) override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; Toolbox * toolboxForTextInput(TextInput * textInput) override; + Toolbox * toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) override; void selectPreviousNewSequenceCell(); private: + Toolbox * toolboxForSender(Responder * sender); Shared::TextFieldDelegateApp * textFieldDelegateApp() override; + Shared::ExpressionFieldDelegateApp * expressionFieldDelegateApp() override; void editExpression(Sequence * sequence, int sequenceDefinitionIndex, Ion::Events::Event event); ListParameterController * parameterController() override; int maxNumberOfRows() override; diff --git a/apps/sequence/list/list_parameter_controller.cpp b/apps/sequence/list/list_parameter_controller.cpp index bf66421aa..77bae98de 100644 --- a/apps/sequence/list/list_parameter_controller.cpp +++ b/apps/sequence/list/list_parameter_controller.cpp @@ -103,7 +103,7 @@ void ListParameterController::willDisplayCellForIndex(HighlightCell * cell, int cell->setHighlighted(index == selectedRow()); // See FIXME in SelectableTableView::reloadData() Shared::ListParameterController::willDisplayCellForIndex(cell, index); if (cell == &m_typeCell && m_sequence != nullptr) { - m_typeCell.setExpression(m_sequence->definitionName()); + m_typeCell.setExpressionLayout(m_sequence->definitionName()); } if (cell == &m_initialRankCell && m_sequence != nullptr) { MessageTableCellWithEditableText * myCell = (MessageTableCellWithEditableText *) cell; diff --git a/apps/sequence/list/sequence_toolbox.cpp b/apps/sequence/list/sequence_toolbox.cpp index f76cfb452..b27604498 100644 --- a/apps/sequence/list/sequence_toolbox.cpp +++ b/apps/sequence/list/sequence_toolbox.cpp @@ -1,7 +1,9 @@ #include "sequence_toolbox.h" #include "../sequence_store.h" -#include "../../../poincare/src/layout/baseline_relative_layout.h" -#include "../../../poincare/src/layout/string_layout.h" +#include "../../../poincare/src/layout/char_layout.h" +#include "../../../poincare/src/layout/horizontal_layout.h" +#include "../../../poincare/src/layout/vertical_offset_layout.h" +#include #include using namespace Poincare; @@ -87,31 +89,31 @@ void SequenceToolbox::setExtraCells(const char * sequenceName, int recurrenceDep const char * otherSequenceName = SequenceStore::k_sequenceNames[1-sequenceIndex]; for (int j = 0; j < recurrenceDepth; j++) { const char * indice = j == 0 ? "n" : "n+1"; - m_addedCellLayout[j] = new BaselineRelativeLayout(new StringLayout(sequenceName, 1, KDText::FontSize::Large), new StringLayout(indice, strlen(indice), KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); - m_addedCellLayout[j+recurrenceDepth] = new BaselineRelativeLayout(new StringLayout(otherSequenceName, 1, KDText::FontSize::Large), new StringLayout(indice, strlen(indice), KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + m_addedCellLayout[j] = new HorizontalLayout( + new CharLayout(sequenceName[0], KDText::FontSize::Large), + new VerticalOffsetLayout(LayoutEngine::createStringLayout(indice, strlen(indice), KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + false); + m_addedCellLayout[j+recurrenceDepth] = new HorizontalLayout( + new CharLayout(otherSequenceName[0], KDText::FontSize::Large), + new VerticalOffsetLayout(LayoutEngine::createStringLayout(indice, strlen(indice), KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + false); } if (recurrenceDepth < 2) { const char * indice = recurrenceDepth == 0 ? "n" : (recurrenceDepth == 1 ? "n+1" : "n+2"); - m_addedCellLayout[2*recurrenceDepth] = new BaselineRelativeLayout(new StringLayout(otherSequenceName, 1, KDText::FontSize::Large), new StringLayout(indice, strlen(indice), KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + m_addedCellLayout[2*recurrenceDepth] = new HorizontalLayout( + new CharLayout(otherSequenceName[0], KDText::FontSize::Large), + new VerticalOffsetLayout(LayoutEngine::createStringLayout(indice, strlen(indice), KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + false); } for (int index = 0; index < k_maxNumberOfDisplayedRows; index++) { - m_addedCells[index].setExpression(m_addedCellLayout[index]); + m_addedCells[index].setExpressionLayout(m_addedCellLayout[index]); } } bool SequenceToolbox::selectAddedCell(int selectedRow){ - char buffer[10]; - BaselineRelativeLayout * layout = (BaselineRelativeLayout *)m_addedCellLayout[selectedRow]; - StringLayout * nameLayout = (StringLayout *)layout->baseLayout(); - StringLayout * subscriptLayout = (StringLayout *)layout->indiceLayout(); - int currentChar = 0; - strlcpy(buffer, nameLayout->text(), strlen(nameLayout->text())+1); - currentChar += strlen(nameLayout->text()); - buffer[currentChar++] = '('; - strlcpy(buffer+currentChar, subscriptLayout->text(), strlen(subscriptLayout->text())+1); - currentChar += strlen(subscriptLayout->text()); - buffer[currentChar++] = ')'; - buffer[currentChar] = 0; + int bufferSize = 10; + char buffer[bufferSize]; + m_addedCellLayout[selectedRow]->writeTextInBuffer(buffer, bufferSize); sender()->handleEventWithText(buffer); app()->dismissModalViewController(); return true; diff --git a/apps/sequence/list/type_parameter_controller.cpp b/apps/sequence/list/type_parameter_controller.cpp index f2e8dc88a..95f9c8918 100644 --- a/apps/sequence/list/type_parameter_controller.cpp +++ b/apps/sequence/list/type_parameter_controller.cpp @@ -2,8 +2,10 @@ #include "list_controller.h" #include "../app.h" #include -#include "../../../poincare/src/layout/baseline_relative_layout.h" -#include "../../../poincare/src/layout/string_layout.h" +#include +#include "../../../poincare/src/layout/char_layout.h" +#include "../../../poincare/src/layout/horizontal_layout.h" +#include "../../../poincare/src/layout/vertical_offset_layout.h" using namespace Poincare; @@ -126,9 +128,12 @@ void TypeParameterController::willDisplayCellAtLocation(HighlightCell * cell, in delete m_expressionLayouts[j]; m_expressionLayouts[j] = nullptr; } - m_expressionLayouts[j] = new BaselineRelativeLayout(new StringLayout(nextName, 1, size), new StringLayout(subscripts[j], strlen(subscripts[j]), KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + m_expressionLayouts[j] = new HorizontalLayout( + new CharLayout(nextName[0], size), + new VerticalOffsetLayout(LayoutEngine::createStringLayout(subscripts[j], strlen(subscripts[j]), KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + false); ExpressionTableCellWithPointer * myCell = (ExpressionTableCellWithPointer *)cell; - myCell->setExpression(m_expressionLayouts[j]); + myCell->setExpressionLayout(m_expressionLayouts[j]); } void TypeParameterController::setSequence(Sequence * sequence) { diff --git a/apps/sequence/sequence.cpp b/apps/sequence/sequence.cpp index 44d42ba32..d42e29a87 100644 --- a/apps/sequence/sequence.cpp +++ b/apps/sequence/sequence.cpp @@ -1,8 +1,10 @@ #include "sequence.h" #include "sequence_store.h" #include "cache_context.h" -#include "../../poincare/src/layout/string_layout.h" -#include "../../poincare/src/layout/baseline_relative_layout.h" +#include +#include "../../poincare/src/layout/char_layout.h" +#include "../../poincare/src/layout/horizontal_layout.h" +#include "../../poincare/src/layout/vertical_offset_layout.h" #include #include @@ -221,7 +223,10 @@ int Sequence::numberOfElements() { Poincare::ExpressionLayout * Sequence::nameLayout() { if (m_nameLayout == nullptr) { - m_nameLayout = new BaselineRelativeLayout(new StringLayout(name(), 1), new StringLayout("n", 1, KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + m_nameLayout = new HorizontalLayout( + new CharLayout(name()[0], KDText::FontSize::Small), + new VerticalOffsetLayout(new CharLayout('n', KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + false); } return m_nameLayout; } @@ -229,13 +234,22 @@ Poincare::ExpressionLayout * Sequence::nameLayout() { Poincare::ExpressionLayout * Sequence::definitionName() { if (m_definitionName == nullptr) { if (m_type == Type::Explicit) { - m_definitionName = new BaselineRelativeLayout(new StringLayout(name(), 1), new StringLayout("n ", 2, KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + m_definitionName = new HorizontalLayout( + new CharLayout(name()[0], KDText::FontSize::Large), + new VerticalOffsetLayout(LayoutEngine::createStringLayout("n ", 2, KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + false); } if (m_type == Type::SingleRecurrence) { - m_definitionName = new BaselineRelativeLayout(new StringLayout(name(), 1), new StringLayout("n+1 ", 4, KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + m_definitionName = new HorizontalLayout( + new CharLayout(name()[0], KDText::FontSize::Large), + new VerticalOffsetLayout(LayoutEngine::createStringLayout("n+1 ", 4, KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + false); } if (m_type == Type::DoubleRecurrence) { - m_definitionName = new BaselineRelativeLayout(new StringLayout(name(), 1), new StringLayout("n+2 ", 4, KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + m_definitionName = new HorizontalLayout( + new CharLayout(name()[0], KDText::FontSize::Large), + new VerticalOffsetLayout(LayoutEngine::createStringLayout("n+2 ", 4, KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + false); } } return m_definitionName; @@ -244,13 +258,14 @@ Poincare::ExpressionLayout * Sequence::definitionName() { Poincare::ExpressionLayout * Sequence::firstInitialConditionName() { char buffer[k_initialRankNumberOfDigits+1]; Integer(m_initialRank).writeTextInBuffer(buffer, k_initialRankNumberOfDigits+1); - if (m_firstInitialConditionName == nullptr) { - if (m_type == Type::SingleRecurrence) { - m_firstInitialConditionName = new BaselineRelativeLayout(new StringLayout(name(), 1), new StringLayout(buffer, strlen(buffer), KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); - } - if (m_type == Type::DoubleRecurrence) { - m_firstInitialConditionName = new BaselineRelativeLayout(new StringLayout(name(), 1), new StringLayout(buffer, strlen(buffer), KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); - } + if (m_firstInitialConditionName == nullptr + && (m_type == Type::SingleRecurrence + || m_type == Type::DoubleRecurrence)) + { + m_firstInitialConditionName = new HorizontalLayout( + new CharLayout(name()[0], KDText::FontSize::Small), + new VerticalOffsetLayout(new CharLayout('0', KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + false); } return m_firstInitialConditionName; } @@ -260,8 +275,10 @@ Poincare::ExpressionLayout * Sequence::secondInitialConditionName() { Integer(m_initialRank+1).writeTextInBuffer(buffer, k_initialRankNumberOfDigits+1); if (m_secondInitialConditionName == nullptr) { if (m_type == Type::DoubleRecurrence) { - m_secondInitialConditionName = new BaselineRelativeLayout(new StringLayout(name(), 1), new StringLayout(buffer, strlen(buffer), KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); - + m_secondInitialConditionName = new HorizontalLayout( + new CharLayout(name()[0], KDText::FontSize::Small), + new VerticalOffsetLayout(new CharLayout('1', KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + false); } } return m_secondInitialConditionName; diff --git a/apps/sequence/sequence_title_cell.cpp b/apps/sequence/sequence_title_cell.cpp index 3f86911d0..7138d103f 100644 --- a/apps/sequence/sequence_title_cell.cpp +++ b/apps/sequence/sequence_title_cell.cpp @@ -12,8 +12,8 @@ SequenceTitleCell::SequenceTitleCell(Orientation orientation) : { } -void SequenceTitleCell::setExpression(Poincare::ExpressionLayout * expressionLayout) { - m_titleTextView.setExpression(expressionLayout); +void SequenceTitleCell::setExpressionLayout(Poincare::ExpressionLayout * expressionLayout) { + m_titleTextView.setExpressionLayout(expressionLayout); } void SequenceTitleCell::setHighlighted(bool highlight) { diff --git a/apps/sequence/sequence_title_cell.h b/apps/sequence/sequence_title_cell.h index 253c43902..1fe659c07 100644 --- a/apps/sequence/sequence_title_cell.h +++ b/apps/sequence/sequence_title_cell.h @@ -8,7 +8,7 @@ namespace Sequence { class SequenceTitleCell : public Shared::FunctionTitleCell { public: SequenceTitleCell(Orientation orientation); - void setExpression(Poincare::ExpressionLayout * expressionLayout); + void setExpressionLayout(Poincare::ExpressionLayout * expressionLayout); void setEven(bool even) override; void setHighlighted(bool highlight) override; void setColor(KDColor color) override; diff --git a/apps/sequence/values/values_controller.cpp b/apps/sequence/values/values_controller.cpp index 184685a91..0a1bc8f94 100644 --- a/apps/sequence/values/values_controller.cpp +++ b/apps/sequence/values/values_controller.cpp @@ -30,7 +30,7 @@ void ValuesController::willDisplayCellAtLocation(HighlightCell * cell, int i, in if (j == 0 && i > 0) { SequenceTitleCell * myCell = (SequenceTitleCell *)cell; Sequence * sequence = m_sequenceStore->activeFunctionAtIndex(i-1); - myCell->setExpression(sequence->nameLayout()); + myCell->setExpressionLayout(sequence->nameLayout()); myCell->setColor(sequence->color()); } } diff --git a/apps/settings/Makefile b/apps/settings/Makefile index ac039b8e7..9078f0463 100644 --- a/apps/settings/Makefile +++ b/apps/settings/Makefile @@ -3,6 +3,7 @@ snapshot_headers += apps/settings/app.h app_objs += $(addprefix apps/settings/,\ app.o\ + helpers.o\ language_controller.o\ main_controller.o\ settings_message_tree.o\ diff --git a/apps/settings/base.de.i18n b/apps/settings/base.de.i18n index 37faed397..8a166a47d 100644 --- a/apps/settings/base.de.i18n +++ b/apps/settings/base.de.i18n @@ -2,6 +2,9 @@ SettingsApp = "Einstellungen" SettingsAppCapital = "EINSTELLUNGEN" AngleUnit = "Winkeleinheit" DisplayMode = "Zahlenformat" +EditionMode = "Eintrag" +EditionLinear = "In Zeilen " +Edition2D = "Natürliche " ComplexFormat = "Komplex" ExamMode = "Testmodus" ActivateExamMode = "Start Testmodus" diff --git a/apps/settings/base.en.i18n b/apps/settings/base.en.i18n index 6ed3a0c64..ee51a6f23 100644 --- a/apps/settings/base.en.i18n +++ b/apps/settings/base.en.i18n @@ -2,6 +2,9 @@ SettingsApp = "Settings" SettingsAppCapital = "SETTINGS" AngleUnit = "Angle measure" DisplayMode = "Result format" +EditionMode = "Writing format" +EditionLinear = "Linear " +Edition2D = "Natural " ComplexFormat = "Complex format" ExamMode = "Exam mode" ActivateExamMode = "Activate exam mode" diff --git a/apps/settings/base.es.i18n b/apps/settings/base.es.i18n index 169e8c727..d4b6a3907 100644 --- a/apps/settings/base.es.i18n +++ b/apps/settings/base.es.i18n @@ -2,6 +2,9 @@ SettingsApp = "Configuracion" SettingsAppCapital = "CONFIGURACION" AngleUnit = "Medida del angulo" DisplayMode = "Formato resultado" +EditionMode = "Formato escritura" +EditionLinear = "En línea " +Edition2D = "Natural " ComplexFormat = "Formato complejo" ExamMode = "Modo examen" ActivateExamMode = "Activar el modo examen" diff --git a/apps/settings/base.fr.i18n b/apps/settings/base.fr.i18n index 7f08a1732..fc2922fa3 100644 --- a/apps/settings/base.fr.i18n +++ b/apps/settings/base.fr.i18n @@ -2,6 +2,9 @@ SettingsApp = "Parametres" SettingsAppCapital = "PARAMETRES" AngleUnit = "Unite d'angle" DisplayMode = "Format resultat" +EditionMode = "Format écriture" +EditionLinear = "En ligne " +Edition2D = "Naturelle " ComplexFormat = "Forme complexe" ExamMode = "Mode examen" ActivateExamMode = "Activer le mode examen" diff --git a/apps/settings/base.pt.i18n b/apps/settings/base.pt.i18n index fcceaadcd..a7443e5f6 100644 --- a/apps/settings/base.pt.i18n +++ b/apps/settings/base.pt.i18n @@ -2,6 +2,9 @@ SettingsApp = "Configuracao" SettingsAppCapital = "CONFIGURACAO" AngleUnit = "Valor do angulo" DisplayMode = "Formato numerico" +EditionMode = "Formato escrita " +EditionLinear = "Em linha " +Edition2D = "Natural " ComplexFormat = "Complexos" ExamMode = "Modo de Exame" ActivateExamMode = "Inicio modo de exame" diff --git a/apps/settings/helpers.cpp b/apps/settings/helpers.cpp new file mode 100644 index 000000000..2581a0a4c --- /dev/null +++ b/apps/settings/helpers.cpp @@ -0,0 +1,27 @@ +#include "helpers.h" +#include +#include +#include "../../poincare/src/layout/horizontal_layout.h" +#include "../../poincare/src/layout/vertical_offset_layout.h" + +using namespace Poincare; + +namespace Settings { +namespace Helpers { + +ExpressionLayout * CartesianComplexFormat(KDText::FontSize fontSize) { + const char text[] = {'a','+', Ion::Charset::IComplex, 'b', ' '}; + return LayoutEngine::createStringLayout(text, sizeof(text), fontSize); +} + +ExpressionLayout * PolarComplexFormat(KDText::FontSize fontSize) { + const char base[] = {'r', Ion::Charset::Exponential}; + const char superscript[] = {Ion::Charset::IComplex, Ion::Charset::SmallTheta, ' '}; + return new HorizontalLayout( + LayoutEngine::createStringLayout(base, sizeof(base), fontSize), + new VerticalOffsetLayout(LayoutEngine::createStringLayout(superscript, sizeof(superscript), fontSize), VerticalOffsetLayout::Type::Superscript, false), + false); +} + +} +} diff --git a/apps/settings/helpers.h b/apps/settings/helpers.h new file mode 100644 index 000000000..01dacd0ba --- /dev/null +++ b/apps/settings/helpers.h @@ -0,0 +1,15 @@ +#ifndef SETTINGS_HELPERS_H +#define SETTINGS_HELPERS_H + +#include + +namespace Settings { +namespace Helpers { + +Poincare::ExpressionLayout * CartesianComplexFormat(KDText::FontSize fontSize); +Poincare::ExpressionLayout * PolarComplexFormat(KDText::FontSize fontSize); + +} +} + +#endif diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 0795a53b3..d036c8787 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -1,36 +1,40 @@ #include "main_controller.h" +#include "helpers.h" #include "../global_preferences.h" #include "../i18n.h" -#include "../../poincare/src/layout/baseline_relative_layout.h" -#include "../../poincare/src/layout/string_layout.h" #include -#include using namespace Poincare; namespace Settings { const SettingsMessageTree angleChildren[2] = {SettingsMessageTree(I18n::Message::Degres), SettingsMessageTree(I18n::Message::Radian)}; -const SettingsMessageTree FloatDisplayModeChildren[3] = {SettingsMessageTree(I18n::Message::Auto), SettingsMessageTree(I18n::Message::Scientific), SettingsMessageTree(I18n::Message::SignificantFigures)}; +const SettingsMessageTree editionModeChildren[2] = {SettingsMessageTree(I18n::Message::EditionLinear), SettingsMessageTree(I18n::Message::Edition2D)}; +const SettingsMessageTree floatDisplayModeChildren[3] = {SettingsMessageTree(I18n::Message::Auto), SettingsMessageTree(I18n::Message::Scientific), SettingsMessageTree(I18n::Message::SignificantFigures)}; const SettingsMessageTree complexFormatChildren[2] = {SettingsMessageTree(I18n::Message::Default), SettingsMessageTree(I18n::Message::Default)}; const SettingsMessageTree examChildren[1] = {SettingsMessageTree(I18n::Message::ActivateExamMode)}; const SettingsMessageTree aboutChildren[3] = {SettingsMessageTree(I18n::Message::SoftwareVersion), SettingsMessageTree(I18n::Message::SerialNumber), SettingsMessageTree(I18n::Message::FccId)}; #if EPSILON_SOFTWARE_UPDATE_PROMPT -const SettingsMessageTree menu[8] = +const SettingsMessageTree menu[9] = #else -const SettingsMessageTree menu[7] = +const SettingsMessageTree menu[8] = #endif - {SettingsMessageTree(I18n::Message::AngleUnit, angleChildren, 2), SettingsMessageTree(I18n::Message::DisplayMode, FloatDisplayModeChildren, 3), SettingsMessageTree(I18n::Message::ComplexFormat, complexFormatChildren, 2), - SettingsMessageTree(I18n::Message::Brightness), SettingsMessageTree(I18n::Message::Language), SettingsMessageTree(I18n::Message::ExamMode, examChildren, 1), + {SettingsMessageTree(I18n::Message::AngleUnit, angleChildren, 2), + SettingsMessageTree(I18n::Message::DisplayMode, floatDisplayModeChildren, 3), + SettingsMessageTree(I18n::Message::EditionMode, editionModeChildren, 2), + SettingsMessageTree(I18n::Message::ComplexFormat, complexFormatChildren, 2), + SettingsMessageTree(I18n::Message::Brightness), + SettingsMessageTree(I18n::Message::Language), + SettingsMessageTree(I18n::Message::ExamMode, examChildren, 1), #if EPSILON_SOFTWARE_UPDATE_PROMPT SettingsMessageTree(I18n::Message::UpdatePopUp), #endif SettingsMessageTree(I18n::Message::About, aboutChildren, 3)}; #if EPSILON_SOFTWARE_UPDATE_PROMPT -const SettingsMessageTree model = SettingsMessageTree(I18n::Message::SettingsApp, menu, 8); +const SettingsMessageTree model = SettingsMessageTree(I18n::Message::SettingsApp, menu, 9); #else -const SettingsMessageTree model = SettingsMessageTree(I18n::Message::SettingsApp, menu, 7); +const SettingsMessageTree model = SettingsMessageTree(I18n::Message::SettingsApp, menu, 8); #endif MainController::MainController(Responder * parentResponder) : @@ -150,14 +154,14 @@ int MainController::reusableCellCount(int type) { } int MainController::typeAtLocation(int i, int j) { - if (j == 2) { + if (j == 3) { return 1; } - if (j == 3) { + if (j == 4) { return 2; } #if EPSILON_SOFTWARE_UPDATE_PROMPT - if (j == 6) { + if (j == 7) { return 3; } #endif @@ -168,36 +172,33 @@ void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { MessageTableCell * myCell = (MessageTableCell *)cell; myCell->setMessage(m_messageTreeModel->children(index)->label()); - if (index == 2) { + if (index == 3) { if (m_complexFormatLayout) { delete m_complexFormatLayout; m_complexFormatLayout = nullptr; } if (Preferences::sharedPreferences()->complexFormat() == Expression::ComplexFormat::Cartesian) { - const char text[] = {'a','+', Ion::Charset::IComplex, 'b', ' '}; - m_complexFormatLayout = new StringLayout(text, sizeof(text), KDText::FontSize::Small); + m_complexFormatLayout = Helpers::CartesianComplexFormat(KDText::FontSize::Small); } else { - const char base[] = {'r', Ion::Charset::Exponential}; - const char superscript[] = {Ion::Charset::IComplex, Ion::Charset::SmallTheta, ' '}; - m_complexFormatLayout = new BaselineRelativeLayout(new StringLayout(base, sizeof(base), KDText::FontSize::Small), new StringLayout(superscript, sizeof(superscript), KDText::FontSize::Small), BaselineRelativeLayout::Type::Superscript); + m_complexFormatLayout = Helpers::PolarComplexFormat(KDText::FontSize::Small); } MessageTableCellWithChevronAndExpression * myExpCell = (MessageTableCellWithChevronAndExpression *)cell; - myExpCell->setExpression(m_complexFormatLayout); + myExpCell->setExpressionLayout(m_complexFormatLayout); return; } - if (index == 3) { + if (index == 4) { MessageTableCellWithGauge * myGaugeCell = (MessageTableCellWithGauge *)cell; GaugeView * myGauge = (GaugeView *)myGaugeCell->accessoryView(); myGauge->setLevel((float)GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()/(float)Ion::Backlight::MaxBrightness); return; } - if (index == 4) { + if (index == 5) { int index = (int)GlobalPreferences::sharedGlobalPreferences()->language()-1; static_cast(cell)->setSubtitle(I18n::LanguageNames[index]); return; } #if EPSILON_SOFTWARE_UPDATE_PROMPT - if (index == 6) { + if (index == 7) { MessageTableCellWithSwitch * mySwitchCell = (MessageTableCellWithSwitch *)cell; SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->showUpdatePopUp()); @@ -212,6 +213,9 @@ void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { case 1: myTextCell->setSubtitle(m_messageTreeModel->children(index)->children((int)Preferences::sharedPreferences()->displayMode())->label()); break; + case 2: + myTextCell->setSubtitle(m_messageTreeModel->children(index)->children((int)Preferences::sharedPreferences()->editionMode())->label()); + break; default: myTextCell->setSubtitle(I18n::Message::Default); break; diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index d880f9256..88e19c92b 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -31,12 +31,12 @@ public: private: StackViewController * stackController() const; #if EPSILON_SOFTWARE_UPDATE_PROMPT - constexpr static int k_totalNumberOfCell = 8; + constexpr static int k_totalNumberOfCell = 9; MessageTableCellWithSwitch m_updateCell; #else - constexpr static int k_totalNumberOfCell = 7; + constexpr static int k_totalNumberOfCell = 8; #endif - constexpr static int k_numberOfSimpleChevronCells = 5; + constexpr static int k_numberOfSimpleChevronCells = 6; MessageTableCellWithChevronAndMessage m_cells[k_numberOfSimpleChevronCells]; MessageTableCellWithChevronAndExpression m_complexFormatCell; MessageTableCellWithGauge m_brightnessCell; diff --git a/apps/settings/sub_controller.cpp b/apps/settings/sub_controller.cpp index ae64fc26c..fad10800c 100644 --- a/apps/settings/sub_controller.cpp +++ b/apps/settings/sub_controller.cpp @@ -1,8 +1,7 @@ #include "sub_controller.h" +#include "helpers.h" #include "../global_preferences.h" #include "../apps_container.h" -#include "../../poincare/src/layout/baseline_relative_layout.h" -#include "../../poincare/src/layout/string_layout.h" #include #include @@ -23,13 +22,10 @@ SubController::SubController(Responder * parentResponder) : m_cells[i].setAccessoryFontSize(KDText::FontSize::Small); m_cells[i].setAccessoryTextColor(Palette::GreyDark); } - const char text[] = {'a','+', Ion::Charset::IComplex, 'b', ' '}; - m_complexFormatLayout[0] = new StringLayout(text, sizeof(text)); - const char base[] = {'r', Ion::Charset::Exponential}; - const char superscript[] = {Ion::Charset::IComplex, Ion::Charset::SmallTheta, ' '}; - m_complexFormatLayout[1] = new BaselineRelativeLayout(new StringLayout(base, sizeof(base)), new StringLayout(superscript, sizeof(superscript)), BaselineRelativeLayout::Type::Superscript); + m_complexFormatLayout[0] = Helpers::CartesianComplexFormat(KDText::FontSize::Large); + m_complexFormatLayout[1] = Helpers::PolarComplexFormat(KDText::FontSize::Large); for (int i = 0; i < 2; i++) { - m_complexFormatCells[i].setExpression(m_complexFormatLayout[i]); + m_complexFormatCells[i].setExpressionLayout(m_complexFormatLayout[i]); } m_editableCell.setMessage(I18n::Message::SignificantFigures); m_editableCell.setMessageFontSize(KDText::FontSize::Large); @@ -74,7 +70,7 @@ bool SubController::handleEvent(Ion::Events::Event event) { return true; } if (event == Ion::Events::OK || event == Ion::Events::EXE) { - /* Behavious of "Exam mode" menu*/ + /* Behaviour of "Exam mode" menu*/ if (m_messageTreeModel->label() == I18n::Message::ExamMode) { if (GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::Activate) { return false; @@ -250,6 +246,9 @@ void SubController::setPreferenceWithValueIndex(I18n::Message message, int value if (message == I18n::Message::DisplayMode) { Preferences::sharedPreferences()->setDisplayMode((PrintFloat::Mode)valueIndex); } + if (message == I18n::Message::EditionMode) { + Preferences::sharedPreferences()->setEditionMode((Preferences::EditionMode)valueIndex); + } if (message == I18n::Message::ComplexFormat) { Preferences::sharedPreferences()->setComplexFormat((Expression::ComplexFormat)valueIndex); } @@ -262,6 +261,9 @@ int SubController::valueIndexForPreference(I18n::Message message) { if (message == I18n::Message::DisplayMode) { return (int)Preferences::sharedPreferences()->displayMode(); } + if (message == I18n::Message::EditionMode) { + return (int)Preferences::sharedPreferences()->editionMode(); + } if (message == I18n::Message::ComplexFormat) { return (int)Preferences::sharedPreferences()->complexFormat(); } diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index affc2009f..6ee024d51 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -35,6 +35,8 @@ LcmCommandWithArg = "lcm(p,q)" LeftIntegralFirstLegend = "P(X≤" LeftIntegralSecondLegend = ")=" LogCommandWithArg = "log(x,a)" +MatrixCommand = "[[•]]" +MatrixCommandWithArg = "[[1,2][3,4]]" MaxCommandWithArg = "max(L)" MinCommandWithArg = "min(L)" Mu = "μ" diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 55f2e7c7e..01c64faa4 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -6,6 +6,7 @@ app_objs += $(addprefix apps/shared/,\ curve_view_cursor.o\ curve_view_range.o\ editable_cell_table_view_controller.o\ + expression_field_delegate_app.o\ float_pair_store.o\ float_parameter_controller.o\ function.o\ @@ -37,6 +38,7 @@ app_objs += $(addprefix apps/shared/,\ regular_table_view_data_source.o\ round_cursor_view.o\ simple_interactive_curve_view_controller.o\ + expression_layout_field_delegate.o\ store_controller.o\ store_parameter_controller.o\ sum_graph_controller.o\ diff --git a/apps/shared/expression_field_delegate_app.cpp b/apps/shared/expression_field_delegate_app.cpp new file mode 100644 index 000000000..c61514728 --- /dev/null +++ b/apps/shared/expression_field_delegate_app.cpp @@ -0,0 +1,71 @@ +#include "expression_field_delegate_app.h" +#include "../i18n.h" +#include "../apps_container.h" + +using namespace Poincare; + +namespace Shared { + +ExpressionFieldDelegateApp::ExpressionFieldDelegateApp(Container * container, Snapshot * snapshot, ViewController * rootViewController) : + TextFieldDelegateApp(container, snapshot, rootViewController), + ExpressionLayoutFieldDelegate() +{ +} + +char ExpressionFieldDelegateApp::privateXNT(ExpressionLayoutField * expressionLayoutField) { + char xntCharFromLayout = expressionLayoutField->XNTChar(); + if (xntCharFromLayout != Ion::Charset::Empty) { + return xntCharFromLayout; + } + return XNT()[0]; +} + +bool ExpressionFieldDelegateApp::expressionLayoutFieldShouldFinishEditing(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) { + return event == Ion::Events::OK || event == Ion::Events::EXE; +} + +bool ExpressionFieldDelegateApp::expressionLayoutFieldDidReceiveEvent(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) { + if (expressionLayoutField->isEditing() && expressionLayoutField->expressionLayoutFieldShouldFinishEditing(event)) { + if (!expressionLayoutField->hasText()) { + expressionLayoutField->app()->displayWarning(I18n::Message::SyntaxError); + return true; + } + int bufferSize = 256; + char buffer[bufferSize]; + expressionLayoutField->writeTextInBuffer(buffer, bufferSize); + Expression * exp = Expression::parse(buffer); + if (exp != nullptr) { + delete exp; + } + if (exp == nullptr) { + expressionLayoutField->app()->displayWarning(I18n::Message::SyntaxError); + return true; + } + } + if (event == Ion::Events::Var) { + if (!expressionLayoutField->isEditing()) { + expressionLayoutField->setEditing(true); + } + AppsContainer * appsContainer = (AppsContainer *)expressionLayoutField->app()->container(); + VariableBoxController * variableBoxController = appsContainer->variableBoxController(); + variableBoxController->setSender(expressionLayoutField); + expressionLayoutField->app()->displayModalViewController(variableBoxController, 0.f, 0.f, Metric::PopUpTopMargin, Metric::PopUpLeftMargin, 0, Metric::PopUpRightMargin); + return true; + } + if (event == Ion::Events::XNT) { + if (!expressionLayoutField->isEditing()) { + expressionLayoutField->setEditing(true); + } + const char xnt[2] = {privateXNT(expressionLayoutField), 0}; + return expressionLayoutField->handleEventWithText(xnt); + } + return false; +} + +Toolbox * ExpressionFieldDelegateApp::toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) { + Toolbox * toolbox = container()->mathToolbox(); + toolbox->setSender(expressionLayoutField); + return toolbox; +} + +} diff --git a/apps/shared/expression_field_delegate_app.h b/apps/shared/expression_field_delegate_app.h new file mode 100644 index 000000000..264492a87 --- /dev/null +++ b/apps/shared/expression_field_delegate_app.h @@ -0,0 +1,22 @@ +#ifndef SHARED_EXPRESSION_FIELD_DELEGATE_APP_H +#define SHARED_EXPRESSION_FIELD_DELEGATE_APP_H + +#include "text_field_delegate_app.h" +#include + +namespace Shared { + +class ExpressionFieldDelegateApp : public TextFieldDelegateApp, public ExpressionLayoutFieldDelegate { +public: + virtual ~ExpressionFieldDelegateApp() = default; + bool expressionLayoutFieldShouldFinishEditing(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) override; + virtual bool expressionLayoutFieldDidReceiveEvent(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) override; + Toolbox * toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) override; +protected: + char privateXNT(ExpressionLayoutField * expressionLayoutField); + ExpressionFieldDelegateApp(Container * container, Snapshot * snapshot, ViewController * rootViewController); +}; + +} + +#endif diff --git a/apps/shared/expression_layout_field_delegate.cpp b/apps/shared/expression_layout_field_delegate.cpp new file mode 100644 index 000000000..a6cc06de8 --- /dev/null +++ b/apps/shared/expression_layout_field_delegate.cpp @@ -0,0 +1,31 @@ +#include "expression_layout_field_delegate.h" + +using namespace Poincare; + +namespace Shared { + +bool ExpressionLayoutFieldDelegate::expressionLayoutFieldShouldFinishEditing(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) { + return expressionFieldDelegateApp()->expressionLayoutFieldShouldFinishEditing(expressionLayoutField, event); +} + +bool ExpressionLayoutFieldDelegate::expressionLayoutFieldDidReceiveEvent(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) { + return expressionFieldDelegateApp()->expressionLayoutFieldDidReceiveEvent(expressionLayoutField, event); +} + +bool ExpressionLayoutFieldDelegate::expressionLayoutFieldDidFinishEditing(ExpressionLayoutField * expressionLayoutField, const char * text, Ion::Events::Event event) { + return expressionFieldDelegateApp()->expressionLayoutFieldDidFinishEditing(expressionLayoutField, text, event); +} + +bool ExpressionLayoutFieldDelegate::expressionLayoutFieldDidAbortEditing(ExpressionLayoutField * expressionLayoutField) { + return expressionFieldDelegateApp()->expressionLayoutFieldDidAbortEditing(expressionLayoutField); +} + +void ExpressionLayoutFieldDelegate::expressionLayoutFieldDidChangeSize(ExpressionLayoutField * expressionLayoutField) { + return expressionFieldDelegateApp()->expressionLayoutFieldDidChangeSize(expressionLayoutField); +} + +Toolbox * ExpressionLayoutFieldDelegate::toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) { + return expressionFieldDelegateApp()->toolboxForExpressionLayoutField(expressionLayoutField); +} + +} diff --git a/apps/shared/expression_layout_field_delegate.h b/apps/shared/expression_layout_field_delegate.h new file mode 100644 index 000000000..5d9ff3c04 --- /dev/null +++ b/apps/shared/expression_layout_field_delegate.h @@ -0,0 +1,23 @@ +#ifndef SHARED_EXPRESSION_LAYOUT_FIELD_DELEGATE_H +#define SHARED_EXPRESSION_LAYOUT_FIELD_DELEGATE_H + +#include +#include "expression_field_delegate_app.h" + +namespace Shared { + +class ExpressionLayoutFieldDelegate : public ::ExpressionLayoutFieldDelegate { +public: + bool expressionLayoutFieldShouldFinishEditing(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) override; + bool expressionLayoutFieldDidReceiveEvent(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) override; + bool expressionLayoutFieldDidFinishEditing(ExpressionLayoutField * expressionLayoutField, const char * text, Ion::Events::Event event) override; + bool expressionLayoutFieldDidAbortEditing(ExpressionLayoutField * expressionLayoutField) override; + void expressionLayoutFieldDidChangeSize(ExpressionLayoutField * expressionLayoutField) override; + Toolbox * toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) override; +private: + virtual ExpressionFieldDelegateApp * expressionFieldDelegateApp() = 0; +}; + +} + +#endif diff --git a/apps/shared/function_app.cpp b/apps/shared/function_app.cpp index 982e8e854..0d82d3dcb 100644 --- a/apps/shared/function_app.cpp +++ b/apps/shared/function_app.cpp @@ -53,7 +53,7 @@ void FunctionApp::Snapshot::reset() { } FunctionApp::FunctionApp(Container * container, Snapshot * snapshot, ViewController * rootViewController) : - TextFieldDelegateApp(container, snapshot, rootViewController) + ExpressionFieldDelegateApp(container, snapshot, rootViewController) { } @@ -62,7 +62,7 @@ void FunctionApp::willBecomeInactive() { m_modalViewController.dismissModalViewController(); } if (inputViewController()->isDisplayingModal()) { - inputViewController()->abortTextFieldEditionAndDismiss(); + inputViewController()->abortEditionAndDismiss(); } ::App::willBecomeInactive(); } diff --git a/apps/shared/function_app.h b/apps/shared/function_app.h index 1f2cc52b3..8fae050f1 100644 --- a/apps/shared/function_app.h +++ b/apps/shared/function_app.h @@ -2,7 +2,7 @@ #define SHARED_FUNCTION_APP_H #include -#include "text_field_delegate_app.h" +#include "expression_field_delegate_app.h" #include "curve_view_cursor.h" #include "interval.h" @@ -10,7 +10,7 @@ class AppsContainer; namespace Shared { -class FunctionApp : public TextFieldDelegateApp { +class FunctionApp : public ExpressionFieldDelegateApp { public: class Snapshot : public ::App::Snapshot, public TabViewDataSource { public: diff --git a/apps/shared/function_expression_cell.cpp b/apps/shared/function_expression_cell.cpp index fee970761..c9345ee71 100644 --- a/apps/shared/function_expression_cell.cpp +++ b/apps/shared/function_expression_cell.cpp @@ -11,8 +11,8 @@ FunctionExpressionCell::FunctionExpressionCell() : { } -void FunctionExpressionCell::setExpression(ExpressionLayout * expressionLayout) { - m_expressionView.setExpression(expressionLayout); +void FunctionExpressionCell::setExpressionLayout(ExpressionLayout * expressionLayout) { + m_expressionView.setExpressionLayout(expressionLayout); } void FunctionExpressionCell::setTextColor(KDColor textColor) { diff --git a/apps/shared/function_expression_cell.h b/apps/shared/function_expression_cell.h index 0b2288f56..351bbe360 100644 --- a/apps/shared/function_expression_cell.h +++ b/apps/shared/function_expression_cell.h @@ -9,7 +9,7 @@ namespace Shared { class FunctionExpressionCell : public EvenOddCell { public: FunctionExpressionCell(); - void setExpression(Poincare::ExpressionLayout * expressionLayout); + void setExpressionLayout(Poincare::ExpressionLayout * expressionLayout); void setTextColor(KDColor color); void setEven(bool even) override; void setHighlighted(bool highlight) override; diff --git a/apps/shared/sum_graph_controller.cpp b/apps/shared/sum_graph_controller.cpp index 0f162a333..49a31a458 100644 --- a/apps/shared/sum_graph_controller.cpp +++ b/apps/shared/sum_graph_controller.cpp @@ -1,8 +1,7 @@ #include "sum_graph_controller.h" #include "../apps_container.h" +#include #include "../../poincare/src/layout/condensed_sum_layout.h" -#include "../../poincare/src/layout/string_layout.h" -#include "../../poincare/src/layout/horizontal_layout.h" #include #include @@ -145,7 +144,7 @@ bool SumGraphController::textFieldDidFinishEditing(TextField * textField, const return false; } -bool SumGraphController::textFieldDidAbortEditing(TextField * textField, const char * text) { +bool SumGraphController::textFieldDidAbortEditing(TextField * textField) { char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits)]; double parameter = NAN; switch(m_step) { @@ -245,28 +244,35 @@ void SumGraphController::LegendView::setSumSymbol(Step step, double start, doubl } const char sigma[] = {' ', m_sumSymbol}; if (step == Step::FirstParameter) { - m_sumLayout = new StringLayout(sigma, sizeof(sigma)); + m_sumLayout = LayoutEngine::createStringLayout(sigma, sizeof(sigma)); } else if (step == Step::SecondParameter) { char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits)]; PrintFloat::convertFloatToText(start, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits, PrintFloat::Mode::Decimal); - m_sumLayout = new CondensedSumLayout(new StringLayout(sigma, sizeof(sigma)), new StringLayout(buffer, strlen(buffer), KDText::FontSize::Small), nullptr); + m_sumLayout = new CondensedSumLayout( + LayoutEngine::createStringLayout(sigma, sizeof(sigma)), + LayoutEngine::createStringLayout(buffer, strlen(buffer), KDText::FontSize::Small), + new EmptyLayout(EmptyLayout::Color::Yellow, false, KDText::FontSize::Small, false), + false); } else { char buffer[2+PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits)]; PrintFloat::convertFloatToText(start, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits, PrintFloat::Mode::Decimal); - ExpressionLayout * start = new StringLayout(buffer, strlen(buffer), KDText::FontSize::Small); + ExpressionLayout * start = LayoutEngine::createStringLayout(buffer, strlen(buffer), KDText::FontSize::Small); PrintFloat::convertFloatToText(end, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits, PrintFloat::Mode::Decimal); - ExpressionLayout * end = new StringLayout(buffer, strlen(buffer), KDText::FontSize::Small); - m_sumLayout = new CondensedSumLayout(new StringLayout(sigma, sizeof(sigma)), start, end); - + ExpressionLayout * end = LayoutEngine::createStringLayout(buffer, strlen(buffer), KDText::FontSize::Small); + m_sumLayout = new CondensedSumLayout( + LayoutEngine::createStringLayout(sigma, sizeof(sigma)), + start, + end, + false); ExpressionLayout * childrenLayouts[3]; strlcpy(buffer, "= ", 3); PrintFloat::convertFloatToText(result, buffer+2, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); - childrenLayouts[2] = new StringLayout(buffer, strlen(buffer), KDText::FontSize::Small); + childrenLayouts[2] = LayoutEngine::createStringLayout(buffer, strlen(buffer), KDText::FontSize::Small); childrenLayouts[1] = functionLayout; childrenLayouts[0] = m_sumLayout; - m_sumLayout = new HorizontalLayout(childrenLayouts, 3); + m_sumLayout = new HorizontalLayout(childrenLayouts, 3, false); } - m_sum.setExpression(m_sumLayout); + m_sum.setExpressionLayout(m_sumLayout); if (step == Step::Result) { m_sum.setAlignment(0.5f, 0.5f); } else { diff --git a/apps/shared/sum_graph_controller.h b/apps/shared/sum_graph_controller.h index 4915de927..98a110957 100644 --- a/apps/shared/sum_graph_controller.h +++ b/apps/shared/sum_graph_controller.h @@ -20,7 +20,7 @@ public: bool handleEvent(Ion::Events::Event event) override; void setFunction(Function * function); bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; - bool textFieldDidAbortEditing(TextField * textField, const char * text) override; + bool textFieldDidAbortEditing(TextField * textField) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; protected: virtual bool moveCursorHorizontallyToPosition(double position); diff --git a/apps/shared/text_field_delegate_app.cpp b/apps/shared/text_field_delegate_app.cpp index 0d49ddd99..d8a26093a 100644 --- a/apps/shared/text_field_delegate_app.cpp +++ b/apps/shared/text_field_delegate_app.cpp @@ -93,7 +93,7 @@ bool TextFieldDelegateApp::textFieldDidReceiveEvent(TextField * textField, Ion:: } AppsContainer * appsContainer = (AppsContainer *)textField->app()->container(); VariableBoxController * variableBoxController = appsContainer->variableBoxController(); - variableBoxController->setTextFieldCaller(textField); + variableBoxController->setSender(textField); textField->app()->displayModalViewController(variableBoxController, 0.f, 0.f, Metric::PopUpTopMargin, Metric::PopUpLeftMargin, 0, Metric::PopUpRightMargin); return true; } @@ -108,7 +108,9 @@ bool TextFieldDelegateApp::textFieldDidReceiveEvent(TextField * textField, Ion:: } Toolbox * TextFieldDelegateApp::toolboxForTextInput(TextInput * textInput) { - return container()->mathToolbox(); + Toolbox * toolbox = container()->mathToolbox(); + toolbox->setSender(textInput); + return toolbox; } } diff --git a/apps/shared/toolbox_helpers.cpp b/apps/shared/toolbox_helpers.cpp index 333a38171..0a0e50035 100644 --- a/apps/shared/toolbox_helpers.cpp +++ b/apps/shared/toolbox_helpers.cpp @@ -1,36 +1,62 @@ #include "toolbox_helpers.h" +#include +#include #include +#include namespace Shared { namespace ToolboxHelpers { -int CursorIndexInCommand(const char * text) { - for (size_t i = 0; i < strlen(text); i++) { +int CursorIndexInCommandText(const char * text) { + size_t textLength = strlen(text); + for (size_t i = 0; i < textLength; i++) { if (text[i] == '(' || text[i] == '\'') { return i + 1; } + if (text[i] == ']') { + return i; + } } - return strlen(text); + return textLength; } -void TextToInsertForCommandMessage(I18n::Message message, char * buffer) { - const char * messageText = I18n::translate(message); - TextToInsertForCommandText(messageText, buffer); +void TextToInsertForCommandMessage(I18n::Message message, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar) { + TextToInsertForCommandText(I18n::translate(message), buffer, bufferSize, replaceArgsWithEmptyChar); } -void TextToInsertForCommandText(const char * command, char * buffer) { +void TextToInsertForCommandText(const char * command, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar) { int currentNewTextIndex = 0; + int numberOfOpenParentheses = 0; int numberOfOpenBrackets = 0; bool insideQuote = false; + bool argumentAlreadyReplaced = false; size_t commandLength = strlen(command); for (size_t i = 0; i < commandLength; i++) { if (command[i] == ')') { + numberOfOpenParentheses--; + } + if (command[i] == ']') { numberOfOpenBrackets--; } - if ((numberOfOpenBrackets == 0 || command[i] == ',') && (!insideQuote || command[i] == '\'')) { + if (((numberOfOpenParentheses == 0 && numberOfOpenBrackets == 0) + || command[i] == ',' + || (numberOfOpenBrackets > 0 && (command[i] == ',' || command[i] == '[' || command[i] == ']'))) + && (!insideQuote || command[i] == '\'')) { + assert(currentNewTextIndex < bufferSize); + if (argumentAlreadyReplaced) { + argumentAlreadyReplaced = false; + } buffer[currentNewTextIndex++] = command[i]; + } else { + if (replaceArgsWithEmptyChar && !argumentAlreadyReplaced) { + buffer[currentNewTextIndex++] = Ion::Charset::Empty; + argumentAlreadyReplaced = true; + } } if (command[i] == '(') { + numberOfOpenParentheses++; + } + if (command[i] == '[') { numberOfOpenBrackets++; } if (command[i] == '\'') { diff --git a/apps/shared/toolbox_helpers.h b/apps/shared/toolbox_helpers.h index e28e8e4cc..16233ffe6 100644 --- a/apps/shared/toolbox_helpers.h +++ b/apps/shared/toolbox_helpers.h @@ -6,17 +6,17 @@ namespace Shared { namespace ToolboxHelpers { -int CursorIndexInCommand(const char * text); -/* Returns the index of the cursor position in a Command, which is the smallest +int CursorIndexInCommandText(const char * text); +/* Returns the index of the cursor position in a command, which is the smallest * index between : * - After the first open parenthesis * - The end of the text */ -void TextToInsertForCommandMessage(I18n::Message message, char * buffer); -void TextToInsertForCommandText(const char * command, char * buffer); +void TextToInsertForCommandMessage(I18n::Message message, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar = false); +void TextToInsertForCommandText(const char * command, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar = false); /* Removes the arguments from a command: - * - Removes text between parentheses, except commas */ + * - Removes text between parentheses or brackets, except commas */ } } diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index 51ab735dc..1fa986b1d 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -7,6 +7,7 @@ ComplexNumber = "Komplexen Zahlen" Probability = "Kombinatorik" Arithmetic = "Arithmetisch" Matrices = "Matrizen" +NewMatrix = "Neue Matrix" Lists = "Listen" HyperbolicTrigonometry = "Hyperbelfunktionen" Fluctuation = "Konfidenzintervall" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index 316edfd3b..bd4e8208f 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -7,6 +7,7 @@ ComplexNumber = "Complex numbers" Probability = "Combinatorics" Arithmetic = "Arithmetic" Matrices = "Matrix" +NewMatrix = "New matrix" Lists = "List" HyperbolicTrigonometry = "Hyperbolic trigonometry" Fluctuation = "Prediction Interval" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index ce94af30c..8a50cc81a 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -7,6 +7,7 @@ ComplexNumber = "Numeros complejos" Probability = "Combinatoria" Arithmetic = "Aritmetica" Matrices = "Matriz" +NewMatrix = "Nueva matriz" Lists = "Listas" HyperbolicTrigonometry = "Trigonometria hiperbolica" Fluctuation = "Interval de prediccion" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index 0cc5f94eb..bcbdec13a 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -7,6 +7,7 @@ ComplexNumber = "Nombres complexes" Probability = "Denombrement" Arithmetic = "Arithmetique" Matrices = "Matrices" +NewMatrix = "Nouvelle matrice" Lists = "Listes" HyperbolicTrigonometry = "Trigonometrie hyperbolique" Fluctuation = "Intervalle fluctuation" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index e76c19602..ba00c693f 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -7,6 +7,7 @@ ComplexNumber = "Numeros complexos" Probability = "Combinatoria" Arithmetic = "Aritmetica" Matrices = "Matrizes" +NewMatrix = "Nova matriz" Lists = "Listas" HyperbolicTrigonometry = "Funcoes hiperbolicas" Fluctuation = "Intervalo de confianca" diff --git a/apps/variable_box_controller.cpp b/apps/variable_box_controller.cpp index f2793e561..05d0cd985 100644 --- a/apps/variable_box_controller.cpp +++ b/apps/variable_box_controller.cpp @@ -10,7 +10,7 @@ using namespace Poincare; VariableBoxController::ContentViewController::ContentViewController(Responder * parentResponder, GlobalContext * context) : ViewController(parentResponder), m_context(context), - m_textFieldCaller(nullptr), + m_sender(nullptr), m_firstSelectedRow(0), m_previousSelectedRow(0), m_currentPage(Page::RootMenu), @@ -20,14 +20,13 @@ VariableBoxController::ContentViewController::ContentViewController(Responder * m_selectableTableView.setShowsIndicators(false); } -const char * VariableBoxController::ContentViewController::title() { - return I18n::translate(I18n::Message::Variables); -} - View * VariableBoxController::ContentViewController::view() { return &m_selectableTableView; } +const char * VariableBoxController::ContentViewController::title() { + return I18n::translate(I18n::Message::Variables); +} void VariableBoxController::ContentViewController::didBecomeFirstResponder() { m_selectableTableView.reloadData(); m_selectableTableView.scrollToCell(0,0); @@ -67,7 +66,7 @@ bool VariableBoxController::ContentViewController::handleEvent(Ion::Events::Even char label[3]; putLabelAtIndexInBuffer(selectedRow(), label); const char * editedText = label; - m_textFieldCaller->handleEventWithText(editedText); + m_sender->handleEventWithText(editedText); #if MATRIX_VARIABLES m_selectableTableView.deselectTable(); m_currentPage = Page::RootMenu; @@ -198,34 +197,21 @@ int VariableBoxController::ContentViewController::typeAtLocation(int i, int j) { return 0; } -const Expression * VariableBoxController::ContentViewController::expressionForIndex(int index) { - if (m_currentPage == Page::Scalar) { - const Symbol symbol = Symbol('A'+index); - return m_context->expressionForSymbol(&symbol); - } - if (m_currentPage == Page::Matrix) { - const Symbol symbol = Symbol::matrixSymbol('0'+(char)index); - return m_context->expressionForSymbol(&symbol); - } -#if LIST_VARIABLES - if (m_currentPage == Page::List) { - return nullptr; - } -#endif - return nullptr; +void VariableBoxController::ContentViewController::reloadData() { + m_selectableTableView.reloadData(); } -ExpressionLayout * VariableBoxController::ContentViewController::expressionLayoutForIndex(int index) { - if (m_currentPage == Page::Matrix) { - const Symbol symbol = Symbol::matrixSymbol('0'+(char)index); - return m_context->expressionLayoutForSymbol(&symbol); - } -#if LIST_VARIABLES - if (m_currentPage == Page::List) { - return nullptr; - } +void VariableBoxController::ContentViewController::resetPage() { +#if MATRIX_VARIABLES + m_currentPage = Page::RootMenu; +#else + m_currentPage = Page::Scalar; #endif - return nullptr; +} + +void VariableBoxController::ContentViewController::viewDidDisappear() { + m_selectableTableView.deselectTable(); + ViewController::viewDidDisappear(); } VariableBoxController::ContentViewController::Page VariableBoxController::ContentViewController::pageAtIndex(int index) { @@ -266,25 +252,34 @@ I18n::Message VariableBoxController::ContentViewController::nodeLabelAtIndex(int return labels[index]; } -void VariableBoxController::ContentViewController::setTextFieldCaller(TextField * textField) { - m_textFieldCaller = textField; -} - -void VariableBoxController::ContentViewController::reloadData() { - m_selectableTableView.reloadData(); -} - -void VariableBoxController::ContentViewController::resetPage() { -#if MATRIX_VARIABLES - m_currentPage = Page::RootMenu; -#else - m_currentPage = Page::Scalar; +const Expression * VariableBoxController::ContentViewController::expressionForIndex(int index) { + if (m_currentPage == Page::Scalar) { + const Symbol symbol = Symbol('A'+index); + return m_context->expressionForSymbol(&symbol); + } + if (m_currentPage == Page::Matrix) { + const Symbol symbol = Symbol::matrixSymbol('0'+(char)index); + return m_context->expressionForSymbol(&symbol); + } +#if LIST_VARIABLES + if (m_currentPage == Page::List) { + return nullptr; + } #endif + return nullptr; } -void VariableBoxController::ContentViewController::viewDidDisappear() { - m_selectableTableView.deselectTable(); - ViewController::viewDidDisappear(); +ExpressionLayout * VariableBoxController::ContentViewController::expressionLayoutForIndex(int index) { + if (m_currentPage == Page::Matrix) { + const Symbol symbol = Symbol::matrixSymbol('0'+(char)index); + return m_context->expressionLayoutForSymbol(&symbol); + } +#if LIST_VARIABLES + if (m_currentPage == Page::List) { + return nullptr; + } +#endif + return nullptr; } VariableBoxController::VariableBoxController(GlobalContext * context) : @@ -297,8 +292,8 @@ void VariableBoxController::didBecomeFirstResponder() { app()->setFirstResponder(&m_contentViewController); } -void VariableBoxController::setTextFieldCaller(TextField * textField) { - m_contentViewController.setTextFieldCaller(textField); +void VariableBoxController::setSender(Responder * sender) { + m_contentViewController.setSender(sender); } void VariableBoxController::viewWillAppear() { diff --git a/apps/variable_box_controller.h b/apps/variable_box_controller.h index 17d40bd71..bd536d80c 100644 --- a/apps/variable_box_controller.h +++ b/apps/variable_box_controller.h @@ -12,7 +12,7 @@ class VariableBoxController : public StackViewController { public: VariableBoxController(Poincare::GlobalContext * context); void didBecomeFirstResponder() override; - void setTextFieldCaller(TextField * textField); + void setSender(Responder * sender); void viewWillAppear() override; void viewDidDisappear() override; private: @@ -31,7 +31,7 @@ private: KDCoordinate cumulatedHeightFromIndex(int j) override; int indexFromCumulatedHeight(KDCoordinate offsetY) override; int typeAtLocation(int i, int j) override; - void setTextFieldCaller(TextField * textField); + void setSender(Responder * responder) { m_sender = responder; } void reloadData(); void resetPage(); void viewDidDisappear() override; @@ -56,9 +56,8 @@ private: I18n::Message nodeLabelAtIndex(int index); const Poincare::Expression * expressionForIndex(int index); Poincare::ExpressionLayout * expressionLayoutForIndex(int index); - Poincare::GlobalContext * m_context; - TextField * m_textFieldCaller; + Responder * m_sender; int m_firstSelectedRow; int m_previousSelectedRow; Page m_currentPage; diff --git a/apps/variable_box_leaf_cell.cpp b/apps/variable_box_leaf_cell.cpp index 5310a4296..2fe428367 100644 --- a/apps/variable_box_leaf_cell.cpp +++ b/apps/variable_box_leaf_cell.cpp @@ -77,7 +77,7 @@ void VariableBoxLeafCell::setSubtitle(const char * text) { } void VariableBoxLeafCell::setExpressionLayout(ExpressionLayout * expressionLayout) { - m_expressionView.setExpression(expressionLayout); + m_expressionView.setExpressionLayout(expressionLayout); } void VariableBoxLeafCell::drawRect(KDContext * ctx, KDRect rect) const { diff --git a/escher/Makefile b/escher/Makefile index c09c89a02..aa6865b1c 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -13,6 +13,7 @@ objs += $(addprefix escher/src/,\ dynamic_view_controller.o\ editable_text_cell.o\ ellipsis_view.o\ + expression_field.o\ even_odd_cell.o\ even_odd_cell_with_ellipsis.o\ even_odd_buffer_text_cell.o\ @@ -49,6 +50,8 @@ objs += $(addprefix escher/src/,\ scroll_view_data_source.o\ scroll_view_indicator.o\ scrollable_view.o\ + expression_layout_field.o\ + expression_layout_field_content_view.o\ selectable_table_view.o\ selectable_table_view_data_source.o\ selectable_table_view_delegate.o\ @@ -74,7 +77,6 @@ objs += $(addprefix escher/src/,\ tiled_view.o\ timer.o\ toolbox.o\ - toolbox_message_tree.o\ transparent_view.o\ view.o\ view_controller.o\ diff --git a/escher/include/escher.h b/escher/include/escher.h index 9d136ab4f..578eb9585 100644 --- a/escher/include/escher.h +++ b/escher/include/escher.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/escher/include/escher/even_odd_expression_cell.h b/escher/include/escher/even_odd_expression_cell.h index 05daf3818..157981777 100644 --- a/escher/include/escher/even_odd_expression_cell.h +++ b/escher/include/escher/even_odd_expression_cell.h @@ -10,7 +10,7 @@ public: KDColor textColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); void setEven(bool even) override; void setHighlighted(bool highlight) override; - void setExpression(Poincare::ExpressionLayout * expressionLayout); + void setExpressionLayout(Poincare::ExpressionLayout * expressionLayout); void setBackgroundColor(KDColor backgroundColor); void setTextColor(KDColor textColor); KDSize minimalSizeForOptimalDisplay() const override; diff --git a/escher/include/escher/expression_field.h b/escher/include/escher/expression_field.h new file mode 100644 index 000000000..3146cecc9 --- /dev/null +++ b/escher/include/escher/expression_field.h @@ -0,0 +1,48 @@ +#ifndef ESCHER_EXPRESSION_FIELD_H +#define ESCHER_EXPRESSION_FIELD_H + +#include +#include +#include +#include +#include + +class ExpressionField : public Responder, public View { +public: + ExpressionField(Responder * parentResponder, char * textBuffer, int textBufferLength, Poincare::ExpressionLayout * layout, TextFieldDelegate * textFieldDelegate, ExpressionLayoutFieldDelegate * expressionLayoutFieldDelegate); + + void setEditing(bool isEditing, bool reinitDraftBuffer = true); + bool isEditing() const; + const char * text(); + void setText(const char * text); + void insertText(const char * text); + void reload(); + bool editionIsInTextField() const; + bool isEmpty() const; + bool heightIsMaximal() const; + bool handleEventWithText(const char * text, bool indentation = false) override; + + /* View */ + int numberOfSubviews() const override { return 1; } + View * subviewAtIndex(int index) override; + void layoutSubviews() override; + void drawRect(KDContext * ctx, KDRect rect) const override; + KDSize minimalSizeForOptimalDisplay() const override; + + /* Responder */ + bool handleEvent(Ion::Events::Event event) override; + +private: + static constexpr KDCoordinate k_textFieldHeight = 37; + static constexpr KDCoordinate k_horizontalMargin = 5; + static constexpr KDCoordinate k_verticalMargin = 5; + constexpr static int k_separatorThickness = 1; + KDCoordinate inputViewHeight() const; + KDCoordinate maximalHeight() const; + TextField m_textField; + ExpressionLayoutField m_expressionLayoutField; + char * m_textBuffer; + int m_textBufferLength; +}; + +#endif diff --git a/escher/include/escher/expression_layout_field.h b/escher/include/escher/expression_layout_field.h new file mode 100644 index 000000000..f87e53af6 --- /dev/null +++ b/escher/include/escher/expression_layout_field.h @@ -0,0 +1,80 @@ +#ifndef ESCHER_EXPRESSION_LAYOUT_FIELD_H +#define ESCHER_EXPRESSION_LAYOUT_FIELD_H + +#include +#include +#include +#include +#include +#include +#include + +class ExpressionLayoutField : public ScrollableView, public ScrollViewDataSource { +public: + ExpressionLayoutField(Responder * parentResponder, Poincare::ExpressionLayout * expressionLayout, ExpressionLayoutFieldDelegate * delegate = nullptr); + void setDelegate(ExpressionLayoutFieldDelegate * delegate) { m_delegate = delegate; } + bool isEditing() const; + void setEditing(bool isEditing); + void clearLayout(); + void scrollToCursor(); + void reload(); + bool hasText() const; + void writeTextInBuffer(char * buffer, int bufferLength); + bool handleEventWithText(const char * text, bool indentation = false) override; + Poincare::ExpressionLayout * expressionLayout(); + char XNTChar(); + void setBackgroundColor(KDColor c) override; + + /* Responder */ + Toolbox * toolbox() override; + bool handleEvent(Ion::Events::Event event) override; + + bool expressionLayoutFieldShouldFinishEditing(Ion::Events::Event event); + + void insertLayoutAtCursor(Poincare::ExpressionLayout * layout, Poincare::ExpressionLayout * pointedLayout); + void insertLayoutFromTextAtCursor(const char * text); + + /* View */ + KDSize minimalSizeForOptimalDisplay() const override; + +protected: + virtual bool privateHandleEvent(Ion::Events::Event event); + bool privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout); +private: + void scrollRightOfLayout(Poincare::ExpressionLayout * layout); + void scrollToBaselinedRect(KDRect rect, KDCoordinate baseline); + + class ContentView : public View { + public: + ContentView(Poincare::ExpressionLayout * expressionLayout); + bool isEditing() const { return m_isEditing; } + void setEditing(bool isEditing); + void setBackgroundColor(KDColor c); + void setCursor(Poincare::ExpressionLayoutCursor cursor) { m_cursor = cursor; } + void cursorPositionChanged(); + KDRect cursorRect(); + Poincare::ExpressionLayoutCursor * cursor() { return &m_cursor; } + const ExpressionView * expressionView() const { return &m_expressionView; } + ExpressionView * editableExpressionView() { return &m_expressionView; } + void clearLayout(); + /* View */ + KDSize minimalSizeForOptimalDisplay() const override; + private: + enum class Position { + Top, + Bottom + }; + int numberOfSubviews() const override { return 2; } + View * subviewAtIndex(int index) override; + void layoutSubviews() override; + void layoutCursorSubview(); + Poincare::ExpressionLayoutCursor m_cursor; + ExpressionView m_expressionView; + TextCursorView m_cursorView; + bool m_isEditing; + }; + ContentView m_contentView; + ExpressionLayoutFieldDelegate * m_delegate; +}; + +#endif diff --git a/escher/include/escher/expression_layout_field_delegate.h b/escher/include/escher/expression_layout_field_delegate.h new file mode 100644 index 000000000..80d284f85 --- /dev/null +++ b/escher/include/escher/expression_layout_field_delegate.h @@ -0,0 +1,19 @@ +#ifndef ESCHER_EXPRESSION_LAYOUT_FIELD_DELEGATE_H +#define ESCHER_EXPRESSION_LAYOUT_FIELD_DELEGATE_H + +#include +#include + +class ExpressionLayoutField; + +class ExpressionLayoutFieldDelegate { +public: + virtual bool expressionLayoutFieldShouldFinishEditing(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) = 0; + virtual bool expressionLayoutFieldDidReceiveEvent(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) = 0; + virtual bool expressionLayoutFieldDidFinishEditing(ExpressionLayoutField * expressionLayoutField, const char * text, Ion::Events::Event event) { return false; } + virtual bool expressionLayoutFieldDidAbortEditing(ExpressionLayoutField * expressionLayoutField) { return false; } + virtual void expressionLayoutFieldDidChangeSize(ExpressionLayoutField * expressionLayoutField) {} + virtual Toolbox * toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) = 0; +}; + +#endif diff --git a/escher/include/escher/expression_table_cell.h b/escher/include/escher/expression_table_cell.h index 0bd233a33..4318dc603 100644 --- a/escher/include/escher/expression_table_cell.h +++ b/escher/include/escher/expression_table_cell.h @@ -9,7 +9,7 @@ public: ExpressionTableCell(Layout layout = Layout::Horizontal); View * labelView() const override; void setHighlighted(bool highlight) override; - void setExpression(Poincare::ExpressionLayout * expressionLayout); + void setExpressionLayout(Poincare::ExpressionLayout * expressionLayout); private: ExpressionView m_labelExpressionView; }; diff --git a/escher/include/escher/expression_view.h b/escher/include/escher/expression_view.h index f5632a453..cd3577f19 100644 --- a/escher/include/escher/expression_view.h +++ b/escher/include/escher/expression_view.h @@ -16,12 +16,14 @@ public: ExpressionView(float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); Poincare::ExpressionLayout * expressionLayout() const; - void setExpression(Poincare::ExpressionLayout * expressionLayout); + void setExpressionLayout(Poincare::ExpressionLayout * expressionLayout); void drawRect(KDContext * ctx, KDRect rect) const override; void setBackgroundColor(KDColor backgroundColor); void setTextColor(KDColor textColor); void setAlignment(float horizontalAlignment, float verticalAlignment); KDSize minimalSizeForOptimalDisplay() const override; + KDPoint drawingOrigin() const; + KDPoint absoluteDrawingOrigin() const; private: /* Warning: we do not need to delete the previous expression layout when * deleting object or setting a new expression layout. Indeed, the expression diff --git a/escher/include/escher/input_view_controller.h b/escher/include/escher/input_view_controller.h index c4f2a2aa9..59c9a7366 100644 --- a/escher/include/escher/input_view_controller.h +++ b/escher/include/escher/input_view_controller.h @@ -1,53 +1,64 @@ #ifndef ESCHER_INPUT_VIEW_CONTROLLER_H #define ESCHER_INPUT_VIEW_CONTROLLER_H +#include +#include #include #include #include #include -class InputViewController : public ModalViewController, TextFieldDelegate { +/* TODO Implement a split view. Because we use a modal view, the main view is + * redrawn underneath the modal view, which is visible and ugly. */ + +class InputViewController : public ModalViewController, TextFieldDelegate, ExpressionLayoutFieldDelegate { public: - InputViewController(Responder * parentResponder, ViewController * child, TextFieldDelegate * textFieldDelegate); + InputViewController(Responder * parentResponder, ViewController * child, TextFieldDelegate * textFieldDelegate, ExpressionLayoutFieldDelegate * expressionLayoutFieldDelegate); void edit(Responder * caller, Ion::Events::Event event, void * context, const char * initialText, Invocation::Action successAction, Invocation::Action failureAction); const char * textBody(); + void abortEditionAndDismiss(); + + /* TextFieldDelegate */ bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; - void abortTextFieldEditionAndDismiss(); bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; - bool textFieldDidAbortEditing(TextField * textField, const char * text) override; + bool textFieldDidAbortEditing(TextField * textField) override; Toolbox * toolboxForTextInput(TextInput * textInput) override; + + /* ExpressionLayoutFieldDelegate */ + bool expressionLayoutFieldShouldFinishEditing(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) override; + bool expressionLayoutFieldDidReceiveEvent(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) override; + bool expressionLayoutFieldDidFinishEditing(ExpressionLayoutField * expressionLayoutField, const char * text, Ion::Events::Event event) override; + bool expressionLayoutFieldDidAbortEditing(ExpressionLayoutField * expressionLayoutField) override; + void expressionLayoutFieldDidChangeSize(ExpressionLayoutField * expressionLayoutField) override; + Toolbox * toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) override; + private: - class TextFieldController : public ViewController { + class ExpressionFieldController : public ViewController { public: - TextFieldController(Responder * parentResponder, TextFieldDelegate * textFieldDelegate); + ExpressionFieldController(Responder * parentResponder, TextFieldDelegate * textFieldDelegate, ExpressionLayoutFieldDelegate * expressionLayoutFieldDelegate); + ~ExpressionFieldController(); + ExpressionFieldController(const ExpressionFieldController& other) = delete; + ExpressionFieldController(ExpressionFieldController&& other) = delete; + ExpressionFieldController& operator=(const ExpressionFieldController& other) = delete; + ExpressionFieldController& operator=(ExpressionFieldController&& other) = delete; void didBecomeFirstResponder() override; - View * view() override; - TextField * textField(); + View * view() override { return &m_expressionField; } + ExpressionField * expressionField() { return &m_expressionField; } private: - class ContentView : public Responder, public View { - public: - ContentView(Responder * parentResponder, TextFieldDelegate * textFieldDelegate); - void didBecomeFirstResponder() override; - TextField * textField(); - void drawRect(KDContext * ctx, KDRect rect) const override; - KDSize minimalSizeForOptimalDisplay() const override; - private: - View * subviewAtIndex(int index) override; - int numberOfSubviews() const override; - void layoutSubviews() override; - constexpr static KDCoordinate k_inputHeight = 37; - constexpr static KDCoordinate k_separatorThickness = 1; - constexpr static KDCoordinate k_textMargin = 5; - TextField m_textField; - char m_textBody[TextField::maxBufferSize()]; - }; - ContentView m_view; + static constexpr int k_bufferLength = TextField::maxBufferSize(); + Poincare::ExpressionLayout * m_layout; + char m_textBuffer[k_bufferLength]; + ExpressionField m_expressionField; }; - TextFieldController m_textFieldController; + bool inputViewDidFinishEditing(); + bool inputViewDidAbortEditing(); + ExpressionFieldController m_expressionFieldController; Invocation m_successAction; Invocation m_failureAction; TextFieldDelegate * m_textFieldDelegate; + ExpressionLayoutFieldDelegate * m_expressionLayoutFieldDelegate; + bool m_inputViewHeightIsMaximal; }; #endif diff --git a/escher/include/escher/message_table_cell_with_chevron_and_expression.h b/escher/include/escher/message_table_cell_with_chevron_and_expression.h index e1423629a..ae8b45c7d 100644 --- a/escher/include/escher/message_table_cell_with_chevron_and_expression.h +++ b/escher/include/escher/message_table_cell_with_chevron_and_expression.h @@ -9,7 +9,7 @@ public: MessageTableCellWithChevronAndExpression(I18n::Message message = (I18n::Message)0, KDText::FontSize size = KDText::FontSize::Small); View * subAccessoryView() const override; void setHighlighted(bool highlight) override; - void setExpression(Poincare::ExpressionLayout * expressionLayout); + void setExpressionLayout(Poincare::ExpressionLayout * expressionLayout); private: ExpressionView m_subtitleView; }; diff --git a/escher/include/escher/metric.h b/escher/include/escher/metric.h index 55d2ba3dc..a2a7a1021 100644 --- a/escher/include/escher/metric.h +++ b/escher/include/escher/metric.h @@ -26,6 +26,9 @@ public: constexpr static KDCoordinate StoreRowHeight = 50; constexpr static KDCoordinate ToolboxRowHeight = 40; constexpr static KDCoordinate StackTitleHeight = 20; + constexpr static KDCoordinate FractionAndConjugateHorizontalOverflow = 2; + constexpr static KDCoordinate FractionAndConjugateHorizontalMargin = 2; + constexpr static KDCoordinate MinimalBracketAndParenthesisHeight = 18; }; #endif diff --git a/escher/include/escher/modal_view_controller.h b/escher/include/escher/modal_view_controller.h index ae466b820..198e473b3 100644 --- a/escher/include/escher/modal_view_controller.h +++ b/escher/include/escher/modal_view_controller.h @@ -13,10 +13,13 @@ public: void didBecomeFirstResponder() override; void displayModalViewController(ViewController * vc, float verticalAlignment, float horizontalAlignment, KDCoordinate topMargin = 0, KDCoordinate leftMargin = 0, KDCoordinate bottomMargin = 0, KDCoordinate rightMargin = 0); + void reloadModalViewController(); void dismissModalViewController(); bool isDisplayingModal(); void viewWillAppear() override; void viewDidDisappear() override; +protected: + void reloadView(); private: class ContentView : public View { public: @@ -29,8 +32,9 @@ private: KDCoordinate topMargin, KDCoordinate leftMargin, KDCoordinate bottomMargin, KDCoordinate rightMargin); void dismissModalView(); bool isDisplayingModal() const; + void reload(); private: - KDRect frame() const; + KDRect modalViewFrame() const; View * m_regularView; View * m_currentModalView; bool m_isDisplayingModal; diff --git a/escher/include/escher/responder.h b/escher/include/escher/responder.h index 691c4c208..45deaa8b9 100644 --- a/escher/include/escher/responder.h +++ b/escher/include/escher/responder.h @@ -10,6 +10,7 @@ class Responder { public: Responder(Responder * parentResponder); virtual bool handleEvent(Ion::Events::Event event); // Default implementation does nothing + virtual bool handleEventWithText(const char * text, bool indentation = false) { return false; } virtual void didBecomeFirstResponder(); virtual void willResignFirstResponder(); virtual void didEnterResponderChain(Responder * previousFirstResponder); diff --git a/escher/include/escher/scroll_view.h b/escher/include/escher/scroll_view.h index d8b27e61d..097913aa1 100644 --- a/escher/include/escher/scroll_view.h +++ b/escher/include/escher/scroll_view.h @@ -29,7 +29,7 @@ public: bool showsIndicators() const { return m_showsIndicators; } void setColorsBackground(bool c) { m_colorsBackground = c; } bool colorsBackground() const { return m_colorsBackground; } - void setBackgroundColor(KDColor c) { m_backgroundColor = c; } + virtual void setBackgroundColor(KDColor c) { m_backgroundColor = c; } KDColor backgroundColor() const { return m_backgroundColor; } ScrollViewIndicator * verticalScrollIndicator() { return &m_verticalScrollIndicator; } diff --git a/escher/include/escher/scrollable_view.h b/escher/include/escher/scrollable_view.h index 73d6b7067..4ceeca709 100644 --- a/escher/include/escher/scrollable_view.h +++ b/escher/include/escher/scrollable_view.h @@ -1,8 +1,9 @@ #ifndef ESCHER_SCROLLABLE_VIEW_H #define ESCHER_SCROLLABLE_VIEW_H -#include +#include #include +#include class ScrollableView : public Responder, public ScrollView { public: diff --git a/escher/include/escher/text_cursor_view.h b/escher/include/escher/text_cursor_view.h index a950c0ec4..6b9c02206 100644 --- a/escher/include/escher/text_cursor_view.h +++ b/escher/include/escher/text_cursor_view.h @@ -6,6 +6,7 @@ class TextCursorView : public View { public: using View::View; + KDRect frame() const { return m_frame; } void drawRect(KDContext * ctx, KDRect rect) const override; KDSize minimalSizeForOptimalDisplay() const override; constexpr static KDCoordinate k_width = 1; diff --git a/escher/include/escher/text_field.h b/escher/include/escher/text_field.h index 99c0b6178..a856d4d02 100644 --- a/escher/include/escher/text_field.h +++ b/escher/include/escher/text_field.h @@ -8,8 +8,8 @@ class TextField : public TextInput { public: TextField(Responder * parentResponder, char * textBuffer, char * draftTextBuffer, size_t textBufferSize, - TextFieldDelegate * delegate = nullptr, bool hasTwoBuffers = true, KDText::FontSize size = KDText::FontSize::Large, float horizontalAlignment = 0.0f, - float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor = KDColorWhite); + TextFieldDelegate * delegate = nullptr, bool hasTwoBuffers = true, KDText::FontSize size = KDText::FontSize::Large, + float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); void setDelegate(TextFieldDelegate * delegate) { m_delegate = delegate; } void setDraftTextBuffer(char * draftTextBuffer); bool isEditing() const; diff --git a/escher/include/escher/text_field_delegate.h b/escher/include/escher/text_field_delegate.h index e7ea98b1f..d3355d7b3 100644 --- a/escher/include/escher/text_field_delegate.h +++ b/escher/include/escher/text_field_delegate.h @@ -10,7 +10,7 @@ public: virtual bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) = 0; virtual bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) = 0; virtual bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { return false; }; - virtual bool textFieldDidAbortEditing(TextField * textField, const char * text) {return false;}; + virtual bool textFieldDidAbortEditing(TextField * textField) {return false;}; virtual bool textFieldDidHandleEvent(TextField * textField, bool returnValue, bool textHasChanged) { return returnValue; }; }; diff --git a/escher/include/escher/text_input.h b/escher/include/escher/text_input.h index 441bc33d0..513386659 100644 --- a/escher/include/escher/text_input.h +++ b/escher/include/escher/text_input.h @@ -12,14 +12,12 @@ public: TextInput(Responder * parentResponder, View * contentView); Toolbox * toolbox() override; const char * text() const { return nonEditableContentView()->text(); } - void setBackgroundColor(KDColor backgroundColor); - KDColor backgroundColor() const { return nonEditableContentView()->backgroundColor(); } + void setBackgroundColor(KDColor backgroundColor) override; void setTextColor(KDColor textColor); bool removeChar(); size_t cursorLocation() const { return nonEditableContentView()->cursorLocation(); } bool setCursorLocation(int location); virtual void scrollToCursor(); - virtual bool handleEventWithText(const char * text, bool indenting = false) = 0; protected: class ContentView : public View { public: diff --git a/escher/include/escher/text_input_helpers.h b/escher/include/escher/text_input_helpers.h index cb216bdaa..219eaba74 100644 --- a/escher/include/escher/text_input_helpers.h +++ b/escher/include/escher/text_input_helpers.h @@ -8,12 +8,9 @@ namespace TextInputHelpers { int CursorIndexInCommand(const char * text); /* Returns the index of the cursor position in a Command, which is the smallest * index between : - * - After the first open parenthesis/quote if the following element is - * either a quote, a coma or a parenthesi - * - The end of the text - * - Special case: when the text preceding the parenthesis is 'random', the - * cursor position is the end of the text. */ -constexpr static const char * k_random = "random"; + * - The first EmptyChar index (which is the position of the first argument) + * - The first empty quote + * - The end of the text */ } #endif diff --git a/escher/include/escher/toolbox.h b/escher/include/escher/toolbox.h index 30a87bd22..7c4f22bb5 100644 --- a/escher/include/escher/toolbox.h +++ b/escher/include/escher/toolbox.h @@ -12,7 +12,7 @@ class Toolbox : public StackViewController, public ListViewDataSource, public SelectableTableViewDataSource { public: Toolbox(Responder * parentResponder, const char * title = 0); - void setSender(Responder * sender); + void setSender(Responder * sender) { m_sender = sender; } // StackViewController bool handleEvent(Ion::Events::Event event) override; @@ -72,7 +72,7 @@ protected: bool handleEventForRow(Ion::Events::Event event, int selectedRow); bool selectSubMenu(ToolboxMessageTree * selectedMessageTree); bool returnToPreviousMenu(); - virtual Responder * sender(); + Responder * sender() { return m_sender; } virtual bool selectLeaf(ToolboxMessageTree * selectedMessageTree) = 0; virtual const ToolboxMessageTree * rootModel() = 0; virtual MessageTableCellWithMessage * leafCellAtIndex(int index) = 0; diff --git a/escher/include/escher/toolbox_message_tree.h b/escher/include/escher/toolbox_message_tree.h index 10e5cb336..48d027403 100644 --- a/escher/include/escher/toolbox_message_tree.h +++ b/escher/include/escher/toolbox_message_tree.h @@ -2,6 +2,7 @@ #define ESCHER_TOOLBOX_MESSAGE_TREE_H #include +#include class ToolboxMessageTree : public MessageTree { public: @@ -12,9 +13,9 @@ public: m_insertedText(insertedText) { }; - const MessageTree * children(int index) const override; - I18n::Message text() const; - I18n::Message insertedText() const; + const MessageTree * children(int index) const override { return &m_children[index]; } + I18n::Message text() const { return m_text; } + I18n::Message insertedText() const { return m_insertedText; } private: const ToolboxMessageTree * m_children; I18n::Message m_text; diff --git a/escher/src/even_odd_expression_cell.cpp b/escher/src/even_odd_expression_cell.cpp index 75e076768..4fb2e8739 100644 --- a/escher/src/even_odd_expression_cell.cpp +++ b/escher/src/even_odd_expression_cell.cpp @@ -19,8 +19,8 @@ void EvenOddExpressionCell::setEven(bool even) { m_expressionView.setBackgroundColor(backgroundColor()); } -void EvenOddExpressionCell::setExpression(ExpressionLayout * expressionLayout) { - m_expressionView.setExpression(expressionLayout); +void EvenOddExpressionCell::setExpressionLayout(ExpressionLayout * expressionLayout) { + m_expressionView.setExpressionLayout(expressionLayout); } void EvenOddExpressionCell::setBackgroundColor(KDColor backgroundColor) { diff --git a/escher/src/expression_field.cpp b/escher/src/expression_field.cpp new file mode 100644 index 000000000..5e9766583 --- /dev/null +++ b/escher/src/expression_field.cpp @@ -0,0 +1,131 @@ +#include +#include +#include + +ExpressionField::ExpressionField(Responder * parentResponder, char * textBuffer, int textBufferLength, Poincare::ExpressionLayout * layout, TextFieldDelegate * textFieldDelegate, ExpressionLayoutFieldDelegate * expressionLayoutFieldDelegate) : + Responder(parentResponder), + View(), + m_textField(parentResponder, textBuffer, textBuffer, textBufferLength, textFieldDelegate, false, KDText::FontSize::Large, 0.0f, 0.5f, KDColorBlack, KDColorWhite), + m_expressionLayoutField(parentResponder, layout, expressionLayoutFieldDelegate), + m_textBuffer(textBuffer), + m_textBufferLength(textBufferLength) +{ + // Initialize text field + m_textField.setMargins(0, k_horizontalMargin, 0, k_horizontalMargin); + m_textField.setBackgroundColor(KDColorWhite); + m_textField.setColorsBackground(true); + // Initialize layout field + m_expressionLayoutField.setMargins(k_verticalMargin, k_horizontalMargin, k_verticalMargin, k_horizontalMargin); + m_expressionLayoutField.setBackgroundColor(KDColorWhite); + m_expressionLayoutField.setColorsBackground(true); +} + +void ExpressionField::setEditing(bool isEditing, bool reinitDraftBuffer) { + if (editionIsInTextField()) { + m_textField.setEditing(isEditing, reinitDraftBuffer); + } else { + if (reinitDraftBuffer) { + m_expressionLayoutField.clearLayout(); + } + m_expressionLayoutField.setEditing(isEditing); + } +} + +bool ExpressionField::isEditing() const { + return editionIsInTextField() ? m_textField.isEditing() : m_expressionLayoutField.isEditing(); +} + +const char * ExpressionField::text() { + if (!editionIsInTextField()) { + m_expressionLayoutField.writeTextInBuffer(m_textBuffer, m_textBufferLength); + } + return m_textBuffer; +} + +void ExpressionField::setText(const char * text) { + if (editionIsInTextField()) { + m_textField.setText(text); + return; + } + m_expressionLayoutField.clearLayout(); + if (strlen(text) > 0) { + m_expressionLayoutField.insertLayoutFromTextAtCursor(text); + } +} + +void ExpressionField::insertText(const char * text) { + if (editionIsInTextField()) { + m_textField.handleEventWithText(text); + } else { + m_expressionLayoutField.setEditing(true); + m_expressionLayoutField.insertLayoutFromTextAtCursor(text); + } +} + +View * ExpressionField::subviewAtIndex(int index) { + assert(index == 0); + if (editionIsInTextField()) { + return &m_textField; + } + return &m_expressionLayoutField; +} + +void ExpressionField::layoutSubviews() { + KDRect inputViewFrame(0, k_separatorThickness, bounds().width(), bounds().height() - k_separatorThickness); + if (editionIsInTextField()) { + m_textField.setFrame(inputViewFrame); + m_expressionLayoutField.setFrame(KDRectZero); + return; + } + m_expressionLayoutField.setFrame(inputViewFrame); + m_textField.setFrame(KDRectZero); +} + +void ExpressionField::reload() { + layoutSubviews(); + markRectAsDirty(bounds()); +} + +void ExpressionField::drawRect(KDContext * ctx, KDRect rect) const { + // Draw the separator + ctx->fillRect(KDRect(0, 0, bounds().width(), k_separatorThickness), Palette::GreyMiddle); +} + +bool ExpressionField::handleEvent(Ion::Events::Event event) { + return editionIsInTextField() ? m_textField.handleEvent(event) : m_expressionLayoutField.handleEvent(event); +} + +KDSize ExpressionField::minimalSizeForOptimalDisplay() const { + return KDSize(0, inputViewHeight()); +} + +bool ExpressionField::editionIsInTextField() const { + return Poincare::Preferences::sharedPreferences()->editionMode() == Poincare::Preferences::EditionMode::Edition1D; +} + +bool ExpressionField::isEmpty() const { + return editionIsInTextField() ? (m_textField.draftTextLength() == 0) : !m_expressionLayoutField.hasText(); +} + +bool ExpressionField::heightIsMaximal() const { + return inputViewHeight() == k_separatorThickness + maximalHeight(); +} + +bool ExpressionField::handleEventWithText(const char * text, bool indentation) { + if (editionIsInTextField()) { + return m_textField.handleEventWithText(text, indentation); + } else { + return m_expressionLayoutField.handleEventWithText(text, indentation); + } +} + +KDCoordinate ExpressionField::inputViewHeight() const { + return k_separatorThickness + + (editionIsInTextField() ? k_textFieldHeight : + min(maximalHeight(), + max(k_textFieldHeight, m_expressionLayoutField.minimalSizeForOptimalDisplay().height() + 2*k_verticalMargin ))); +} + +KDCoordinate ExpressionField::maximalHeight() const { + return 0.6*Ion::Display::Height; +} diff --git a/escher/src/expression_layout_field.cpp b/escher/src/expression_layout_field.cpp new file mode 100644 index 000000000..1cd523a86 --- /dev/null +++ b/escher/src/expression_layout_field.cpp @@ -0,0 +1,334 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ExpressionLayoutField::ExpressionLayoutField(Responder * parentResponder, Poincare::ExpressionLayout * expressionLayout, ExpressionLayoutFieldDelegate * delegate) : + ScrollableView(parentResponder, &m_contentView, this), + m_contentView(expressionLayout), + m_delegate(delegate) +{ +} + +bool ExpressionLayoutField::isEditing() const { + return m_contentView.isEditing(); +} + +void ExpressionLayoutField::setEditing(bool isEditing) { + m_contentView.setEditing(isEditing); +} + +void ExpressionLayoutField::clearLayout() { + m_contentView.clearLayout(); +} + +void ExpressionLayoutField::scrollToCursor() { + scrollToBaselinedRect(m_contentView.cursorRect(), m_contentView.cursor()->baseline()); +} + +Toolbox * ExpressionLayoutField::toolbox() { + if (m_delegate) { + return m_delegate->toolboxForExpressionLayoutField(this); + } + return nullptr; +} + +bool ExpressionLayoutField::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 ExpressionLayoutField::expressionLayoutFieldShouldFinishEditing(Ion::Events::Event event) { + return m_delegate->expressionLayoutFieldShouldFinishEditing(this, event); +} + +KDSize ExpressionLayoutField::minimalSizeForOptimalDisplay() const { + KDSize contentViewSize = m_contentView.minimalSizeForOptimalDisplay(); + return KDSize(contentViewSize.width(), contentViewSize.height()); +} + +bool ExpressionLayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout) { + Poincare::ExpressionLayoutCursor 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()->pointedExpressionLayout()->removeGreySquaresFromAllMatrixAncestors()) { + *shouldRecomputeLayout = true; + } + result.setPointedExpressionLayout(m_contentView.expressionView()->expressionLayout()); + result.setPosition(Poincare::ExpressionLayoutCursor::Position::Left); + } else if (event == Ion::Events::ShiftRight) { + if (m_contentView.cursor()->pointedExpressionLayout()->removeGreySquaresFromAllMatrixAncestors()) { + *shouldRecomputeLayout = true; + } + result.setPointedExpressionLayout(m_contentView.expressionView()->expressionLayout()); + result.setPosition(Poincare::ExpressionLayoutCursor::Position::Right); + } + if (result.isDefined()) { + m_contentView.setCursor(result); + return true; + } + return false; +} + +bool ExpressionLayoutField::privateHandleEvent(Ion::Events::Event event) { + if (m_delegate && m_delegate->expressionLayoutFieldDidReceiveEvent(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() && expressionLayoutFieldShouldFinishEditing(event)) { + setEditing(false); + int bufferSize = TextField::maxBufferSize(); + char buffer[bufferSize]; + m_contentView.expressionView()->expressionLayout()->writeTextInBuffer(buffer, bufferSize); + if (m_delegate->expressionLayoutFieldDidFinishEditing(this, buffer, event)) { + clearLayout(); + } + return true; + } + if ((event == Ion::Events::OK || event == Ion::Events::EXE) && !isEditing()) { + setEditing(true); + m_contentView.cursor()->setPointedExpressionLayout(m_contentView.expressionView()->expressionLayout()); + m_contentView.cursor()->setPosition(Poincare::ExpressionLayoutCursor::Position::Right); + return true; + } + if (event == Ion::Events::Back && isEditing()) { + clearLayout(); + setEditing(false); + m_delegate->expressionLayoutFieldDidAbortEditing(this); + return true; + } + if (event == Ion::Events::Division) { + if (!isEditing()) { + setEditing(true); + } + m_contentView.cursor()->addFractionLayoutAndCollapseSiblings(); + return true; + } + if (event == Ion::Events::Exp) { + if (!isEditing()) { + setEditing(true); + } + m_contentView.cursor()->addEmptyExponentialLayout(); + return true; + } + if (event == Ion::Events::Power) { + if (!isEditing()) { + setEditing(true); + } + m_contentView.cursor()->addEmptyPowerLayout(); + return true; + } + if (event == Ion::Events::Sqrt) { + if (!isEditing()) { + setEditing(true); + } + m_contentView.cursor()->addEmptySquareRootLayout(); + return true; + } + if (event == Ion::Events::Square) { + if (!isEditing()) { + setEditing(true); + } + m_contentView.cursor()->addEmptySquarePowerLayout(); + return true; + } + if (event == Ion::Events::EE) { + if (!isEditing()) { + setEditing(true); + } + m_contentView.cursor()->addEmptyTenPowerLayout(); + return true; + } + if (event.hasText()) { + if (!isEditing()) { + setEditing(true); + } + const char * textToInsert = event.text(); + if (textToInsert[1] == 0 && (textToInsert[0] == '[' || textToInsert[0] == ']')) { + m_contentView.cursor()->addEmptyMatrixLayout(); + return true; + } + m_contentView.cursor()->insertText(textToInsert); + return true; + } + if (event == Ion::Events::Backspace) { + if (!isEditing()) { + setEditing(true); + } + m_contentView.cursor()->performBackspace(); + return true; + } + if (event == Ion::Events::Paste) { + if (!isEditing()) { + setEditing(true); + } + insertLayoutFromTextAtCursor(Clipboard::sharedClipboard()->storedText()); + return true; + } + if (event == Ion::Events::Clear && isEditing()) { + clearLayout(); + return true; + } + return false; +} + +void ExpressionLayoutField::insertLayoutAtCursor(Poincare::ExpressionLayout * layout, Poincare::ExpressionLayout * pointedLayout) { + if (layout == nullptr) { + return; + } + m_contentView.cursor()->showEmptyLayoutIfNeeded(); + bool layoutWillBeMerged = layout->isHorizontal(); + Poincare::ExpressionLayout * lastMergedLayoutChild = layoutWillBeMerged ? layout->editableChild(layout->numberOfChildren()-1) : nullptr; + m_contentView.cursor()->addLayoutAndMoveCursor(layout); + if (pointedLayout != nullptr && (pointedLayout != layout || !layoutWillBeMerged)) { + m_contentView.cursor()->setPointedExpressionLayout(pointedLayout); + m_contentView.cursor()->setPosition(Poincare::ExpressionLayoutCursor::Position::Right); + } else if (!layoutWillBeMerged) { + m_contentView.cursor()->setPointedExpressionLayout(layout->layoutToPointWhenInserting()); + m_contentView.cursor()->setPosition(Poincare::ExpressionLayoutCursor::Position::Right); + } + m_contentView.cursor()->pointedExpressionLayout()->addGreySquaresToAllMatrixAncestors(); + m_contentView.cursor()->hideEmptyLayoutIfNeeded(); + reload(); + if (!layoutWillBeMerged) { + scrollRightOfLayout(layout); + } else { + assert(lastMergedLayoutChild != nullptr); + scrollRightOfLayout(lastMergedLayoutChild); + } + scrollToCursor(); +} + +void ExpressionLayoutField::insertLayoutFromTextAtCursor(const char * text) { + m_contentView.cursor()->showEmptyLayoutIfNeeded(); + Poincare::Expression * expression = Poincare::Expression::parse(text); + if (expression != nullptr) { + Poincare::ExpressionLayout * layout = expression->createLayout(); + delete expression; + insertLayoutAtCursor(layout, layout); + } else { + m_contentView.cursor()->insertText(text); + } + m_contentView.cursor()->hideEmptyLayoutIfNeeded(); + reload(); +} + +void ExpressionLayoutField::reload() { + KDSize previousSize = minimalSizeForOptimalDisplay(); + m_contentView.expressionView()->expressionLayout()->invalidAllSizesPositionsAndBaselines(); + KDSize newSize = minimalSizeForOptimalDisplay(); + if (m_delegate && previousSize.height() != newSize.height()) { + m_delegate->expressionLayoutFieldDidChangeSize(this); + } + m_contentView.cursorPositionChanged(); + scrollToCursor(); + markRectAsDirty(bounds()); +} + +bool ExpressionLayoutField::hasText() const { + return m_contentView.expressionView()->expressionLayout()->hasText(); +} + +void ExpressionLayoutField::writeTextInBuffer(char * buffer, int bufferLength) { + m_contentView.expressionView()->expressionLayout()->writeTextInBuffer(buffer, bufferLength); +} + +bool ExpressionLayoutField::handleEventWithText(const char * text, bool indentation) { + Poincare::Expression * resultExpression = Poincare::Expression::parse(text); + if (resultExpression == nullptr) { + return false; + } + Poincare::ExpressionLayout * resultLayout = resultExpression->createLayout(); + delete resultExpression; + // Find the pointed layout. + Poincare::ExpressionLayout * pointedLayout = 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. */ + pointedLayout = resultLayout; + } else if (resultLayout->isHorizontal()) { + /* If the layout is horizontal, pick the first open parenthesis. For now, + * all horizontal layouts in MathToolbox have parentheses. */ + for (int i = 0; i < resultLayout->numberOfChildren(); i++) { + if (resultLayout->editableChild(i)->isLeftParenthesis()) { + pointedLayout = resultLayout->editableChild(i); + break; + } + } + } + /* Insert the layout. If pointedLayout is nullptr, the cursor will be on the + * right of the inserted layout. */ + insertLayoutAtCursor(resultLayout, pointedLayout); + return true; +} + +Poincare::ExpressionLayout * ExpressionLayoutField::expressionLayout() { + return m_contentView.expressionView()->expressionLayout(); +} + +char ExpressionLayoutField::XNTChar() { + return m_contentView.cursor()->pointedExpressionLayout()->XNTChar(); +} + +void ExpressionLayoutField::setBackgroundColor(KDColor c) { + ScrollableView::setBackgroundColor(c); + m_contentView.setBackgroundColor(c); +} + +void ExpressionLayoutField::scrollRightOfLayout(Poincare::ExpressionLayout * layout) { + KDRect layoutRect(layout->absoluteOrigin().translatedBy(m_contentView.expressionView()->drawingOrigin()), layout->size()); + scrollToBaselinedRect(layoutRect, layout->baseline()); +} + +void ExpressionLayoutField::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); +} diff --git a/escher/src/expression_layout_field_content_view.cpp b/escher/src/expression_layout_field_content_view.cpp new file mode 100644 index 000000000..8b4c5b0d6 --- /dev/null +++ b/escher/src/expression_layout_field_content_view.cpp @@ -0,0 +1,74 @@ +#include +#include +#include + +using namespace Poincare; + +ExpressionLayoutField::ContentView::ContentView(ExpressionLayout * expressionLayout) : + m_cursor(expressionLayout, ExpressionLayoutCursor::Position::Right), + m_expressionView(0.0f, 0.5f, KDColorBlack, KDColorWhite), + m_cursorView(), + m_isEditing(false) +{ + m_expressionView.setExpressionLayout(expressionLayout); +} + +void ExpressionLayoutField::ContentView::setEditing(bool isEditing) { + m_isEditing = isEditing; + markRectAsDirty(bounds()); + layoutSubviews(); +} + +void ExpressionLayoutField::ContentView::setBackgroundColor(KDColor c) { + m_expressionView.setBackgroundColor(c); +} + +void ExpressionLayoutField::ContentView::cursorPositionChanged() { + layoutCursorSubview(); +} + +KDRect ExpressionLayoutField::ContentView::cursorRect() { + return m_cursorView.frame(); +} + +void ExpressionLayoutField::ContentView::clearLayout() { + m_cursor.clearLayout(); +} + +KDSize ExpressionLayoutField::ContentView::minimalSizeForOptimalDisplay() const { + KDSize evSize = m_expressionView.minimalSizeForOptimalDisplay(); + return KDSize(evSize.width() + ExpressionLayoutCursor::k_cursorWidth, evSize.height()); +} + +View * ExpressionLayoutField::ContentView::subviewAtIndex(int index) { + assert(index >= 0 && index < 2); + View * m_views[] = {&m_expressionView, &m_cursorView}; + return m_views[index]; +} + +void ExpressionLayoutField::ContentView::layoutSubviews() { + m_expressionView.setFrame(bounds()); + layoutCursorSubview(); +} + +void ExpressionLayoutField::ContentView::layoutCursorSubview() { + if (!m_isEditing) { + m_cursorView.setFrame(KDRectZero); + return; + } + KDPoint expressionViewOrigin = m_expressionView.absoluteDrawingOrigin(); + ExpressionLayout * pointedLayout = m_cursor.pointedExpressionLayout(); + ExpressionLayoutCursor::Position cursorPosition = m_cursor.position(); + ExpressionLayoutCursor eqCursor = pointedLayout->equivalentCursor(m_cursor); + if (pointedLayout->hasChild(eqCursor.pointedExpressionLayout())) { + pointedLayout = eqCursor.pointedExpressionLayout(); + cursorPosition = eqCursor.position(); + } + KDPoint cursoredExpressionViewOrigin = pointedLayout->absoluteOrigin(); + KDCoordinate cursorX = expressionViewOrigin.x() + cursoredExpressionViewOrigin.x(); + if (cursorPosition == ExpressionLayoutCursor::Position::Right) { + cursorX += pointedLayout->size().width(); + } + KDPoint cursorTopLeftPosition(cursorX, expressionViewOrigin.y() + cursoredExpressionViewOrigin.y() + pointedLayout->baseline() - m_cursor.baseline()); + m_cursorView.setFrame(KDRect(cursorTopLeftPosition, ExpressionLayoutCursor::k_cursorWidth, m_cursor.cursorHeight())); +} diff --git a/escher/src/expression_table_cell.cpp b/escher/src/expression_table_cell.cpp index 136fddb41..2d27687ec 100644 --- a/escher/src/expression_table_cell.cpp +++ b/escher/src/expression_table_cell.cpp @@ -18,7 +18,7 @@ void ExpressionTableCell::setHighlighted(bool highlight) { m_labelExpressionView.setBackgroundColor(backgroundColor); } -void ExpressionTableCell::setExpression(Poincare::ExpressionLayout * expressionLayout) { - m_labelExpressionView.setExpression(expressionLayout); +void ExpressionTableCell::setExpressionLayout(Poincare::ExpressionLayout * expressionLayout) { + m_labelExpressionView.setExpressionLayout(expressionLayout); layoutSubviews(); } diff --git a/escher/src/expression_view.cpp b/escher/src/expression_view.cpp index cc5c27a3a..a89c3f6bb 100644 --- a/escher/src/expression_view.cpp +++ b/escher/src/expression_view.cpp @@ -15,7 +15,7 @@ ExpressionLayout * ExpressionView::expressionLayout() const { return m_expressionLayout; } -void ExpressionView::setExpression(ExpressionLayout * expressionLayout) { +void ExpressionView::setExpressionLayout(ExpressionLayout * expressionLayout) { m_expressionLayout = expressionLayout; markRectAsDirty(bounds()); } @@ -41,19 +41,24 @@ void ExpressionView::setAlignment(float horizontalAlignment, float verticalAlign } KDSize ExpressionView::minimalSizeForOptimalDisplay() const { - if (m_expressionLayout == nullptr) { + if (m_expressionLayout == nullptr) { return KDSizeZero; } return m_expressionLayout->size(); } +KDPoint ExpressionView::drawingOrigin() const { + KDSize expressionSize = m_expressionLayout->size(); + return KDPoint(m_horizontalAlignment*(m_frame.width() - expressionSize.width()), max(0, (m_frame.height() - expressionSize.height())/2)); +} + +KDPoint ExpressionView::absoluteDrawingOrigin() const { + return drawingOrigin().translatedBy(m_frame.topLeft()); +} + void ExpressionView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(rect, m_backgroundColor); if (m_expressionLayout != nullptr) { - //Position the origin of expression - KDSize expressionSize = m_expressionLayout->size(); - KDPoint origin(m_horizontalAlignment*(m_frame.width() - expressionSize.width()), - 0.5f*(m_frame.height() - expressionSize.height())); - m_expressionLayout->draw(ctx, origin, m_textColor, m_backgroundColor); + m_expressionLayout->draw(ctx, drawingOrigin(), m_textColor, m_backgroundColor); } } diff --git a/escher/src/input_view_controller.cpp b/escher/src/input_view_controller.cpp index df68abf35..c7bec3a3e 100644 --- a/escher/src/input_view_controller.cpp +++ b/escher/src/input_view_controller.cpp @@ -1,88 +1,52 @@ #include #include #include +#include #include -InputViewController::TextFieldController::ContentView::ContentView(Responder * parentResponder, TextFieldDelegate * textFieldDelegate) : - Responder(parentResponder), - View(), - m_textField(this, m_textBody, m_textBody, TextField::maxBufferSize(), textFieldDelegate, false) -{ - m_textBody[0] = 0; -} - -void InputViewController::TextFieldController::ContentView::didBecomeFirstResponder() { - app()->setFirstResponder(&m_textField); -} - -TextField * InputViewController::TextFieldController::ContentView::textField() { - return &m_textField; -} - -void InputViewController::TextFieldController::ContentView::drawRect(KDContext * ctx, KDRect rect) const { - ctx->fillRect(KDRect(0, 0, bounds().width(), k_separatorThickness), Palette::GreyMiddle); - ctx->fillRect(KDRect(0, k_separatorThickness, k_textMargin, bounds().height()-k_separatorThickness), m_textField.backgroundColor()); -} - -KDSize InputViewController::TextFieldController::ContentView::minimalSizeForOptimalDisplay() const { - return KDSize(0, k_inputHeight); -} - -int InputViewController::TextFieldController::ContentView::numberOfSubviews() const { - return 1; -} - -View * InputViewController::TextFieldController::ContentView::subviewAtIndex(int index) { - return &m_textField; -} - -void InputViewController::TextFieldController::ContentView::layoutSubviews() { - m_textField.setFrame(KDRect(k_textMargin, k_separatorThickness, bounds().width()-k_textMargin, bounds().height())); -} - -InputViewController::TextFieldController::TextFieldController(Responder * parentResponder, TextFieldDelegate * textFieldDelegate) : +InputViewController::ExpressionFieldController::ExpressionFieldController(Responder * parentResponder, TextFieldDelegate * textFieldDelegate, ExpressionLayoutFieldDelegate * expressionLayoutFieldDelegate) : ViewController(parentResponder), - m_view(this, textFieldDelegate) + m_layout(new Poincare::HorizontalLayout()), + m_expressionField(this, m_textBuffer, k_bufferLength, m_layout, textFieldDelegate, expressionLayoutFieldDelegate) { + m_textBuffer[0] = 0; } -View * InputViewController::TextFieldController::view() { - return &m_view; +InputViewController::ExpressionFieldController::~ExpressionFieldController() { + delete m_layout; } -void InputViewController::TextFieldController::didBecomeFirstResponder() { - app()->setFirstResponder(&m_view); +void InputViewController::ExpressionFieldController::didBecomeFirstResponder() { + app()->setFirstResponder(&m_expressionField); } -TextField * InputViewController::TextFieldController::textField() { - return m_view.textField(); -} - -InputViewController::InputViewController(Responder * parentResponder, ViewController * child, TextFieldDelegate * textFieldDelegate) : +InputViewController::InputViewController(Responder * parentResponder, ViewController * child, TextFieldDelegate * textFieldDelegate, ExpressionLayoutFieldDelegate * expressionLayoutFieldDelegate) : ModalViewController(parentResponder, child), - m_textFieldController(this, this), + m_expressionFieldController(this, this, this), m_successAction(Invocation(nullptr, nullptr)), m_failureAction(Invocation(nullptr, nullptr)), - m_textFieldDelegate(textFieldDelegate) + m_textFieldDelegate(textFieldDelegate), + m_expressionLayoutFieldDelegate(expressionLayoutFieldDelegate), + m_inputViewHeightIsMaximal(false) { } const char * InputViewController::textBody() { - return m_textFieldController.textField()->text(); + return m_expressionFieldController.expressionField()->text(); } void InputViewController::edit(Responder * caller, Ion::Events::Event event, void * context, const char * initialText, Invocation::Action successAction, Invocation::Action failureAction) { m_successAction = Invocation(successAction, context); m_failureAction = Invocation(failureAction, context); - displayModalViewController(&m_textFieldController, 1.0f, 1.0f); - m_textFieldController.textField()->handleEvent(event); + displayModalViewController(&m_expressionFieldController, 1.0f, 1.0f); if (initialText != nullptr) { - m_textFieldController.textField()->handleEventWithText(initialText); + m_expressionFieldController.expressionField()->setText(initialText); } + m_expressionFieldController.expressionField()->handleEvent(event); } -void InputViewController::abortTextFieldEditionAndDismiss() { - m_textFieldController.textField()->setEditing(false); +void InputViewController::abortEditionAndDismiss() { + m_expressionFieldController.expressionField()->setEditing(false); dismissModalViewController(); } @@ -91,15 +55,11 @@ bool InputViewController::textFieldShouldFinishEditing(TextField * textField, Io } bool InputViewController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { - m_successAction.perform(this); - dismissModalViewController(); - return true; + return inputViewDidFinishEditing(); } -bool InputViewController::textFieldDidAbortEditing(TextField * textField, const char * text) { - m_failureAction.perform(this); - dismissModalViewController(); - return true; +bool InputViewController::textFieldDidAbortEditing(TextField * textField) { + return inputViewDidAbortEditing(); } bool InputViewController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) { @@ -109,3 +69,45 @@ bool InputViewController::textFieldDidReceiveEvent(TextField * textField, Ion::E Toolbox * InputViewController::toolboxForTextInput(TextInput * input) { return m_textFieldDelegate->toolboxForTextInput(input); } + +bool InputViewController::expressionLayoutFieldShouldFinishEditing(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) { + return event == Ion::Events::OK || event == Ion::Events::EXE; +} + +bool InputViewController::expressionLayoutFieldDidReceiveEvent(ExpressionLayoutField * expressionLayoutField, Ion::Events::Event event) { + return m_expressionLayoutFieldDelegate->expressionLayoutFieldDidReceiveEvent(expressionLayoutField, event); +} + +bool InputViewController::expressionLayoutFieldDidFinishEditing(ExpressionLayoutField * expressionLayoutField, const char * text, Ion::Events::Event event) { + return inputViewDidFinishEditing(); +} + +bool InputViewController::expressionLayoutFieldDidAbortEditing(ExpressionLayoutField * expressionLayoutField) { + return inputViewDidAbortEditing(); +} + +void InputViewController::expressionLayoutFieldDidChangeSize(ExpressionLayoutField * expressionLayoutField) { + /* Reload the view only if the ExpressionField height actually changes, i.e. + * not if the height is already maximal and stays maximal. */ + bool newInputViewHeightIsMaximal = m_expressionFieldController.expressionField()->heightIsMaximal(); + if (!m_inputViewHeightIsMaximal || !newInputViewHeightIsMaximal) { + m_inputViewHeightIsMaximal = newInputViewHeightIsMaximal; + reloadModalViewController(); + } +} + +Toolbox * InputViewController::toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) { + return m_expressionLayoutFieldDelegate->toolboxForExpressionLayoutField(expressionLayoutField); +} + +bool InputViewController::inputViewDidFinishEditing() { + m_successAction.perform(this); + dismissModalViewController(); + return true; +} + +bool InputViewController::inputViewDidAbortEditing() { + m_failureAction.perform(this); + dismissModalViewController(); + return true; +} diff --git a/escher/src/message_table_cell_with_chevron_and_expression.cpp b/escher/src/message_table_cell_with_chevron_and_expression.cpp index d3e46ee75..9d8dc1cb0 100644 --- a/escher/src/message_table_cell_with_chevron_and_expression.cpp +++ b/escher/src/message_table_cell_with_chevron_and_expression.cpp @@ -17,8 +17,8 @@ void MessageTableCellWithChevronAndExpression::setHighlighted(bool highlight) { m_subtitleView.setBackgroundColor(backgroundColor); } -void MessageTableCellWithChevronAndExpression::setExpression(Poincare::ExpressionLayout * expressionLayout) { - m_subtitleView.setExpression(expressionLayout); +void MessageTableCellWithChevronAndExpression::setExpressionLayout(Poincare::ExpressionLayout * expressionLayout) { + m_subtitleView.setExpressionLayout(expressionLayout); reloadCell(); layoutSubviews(); } diff --git a/escher/src/modal_view_controller.cpp b/escher/src/modal_view_controller.cpp index 5780b448f..cd3ab4bd3 100644 --- a/escher/src/modal_view_controller.cpp +++ b/escher/src/modal_view_controller.cpp @@ -23,18 +23,12 @@ void ModalViewController::ContentView::setMainView(View * regularView) { } int ModalViewController::ContentView::numberOfSubviews() const { - KDRect regularFrame = bounds(); - KDRect modalFrame = frame(); - bool shouldDrawTheRegularViewBehind = !modalFrame.contains(regularFrame.topLeft()) || !modalFrame.contains(regularFrame.bottomRight()); - return 1 + (m_isDisplayingModal && shouldDrawTheRegularViewBehind); + return 1 + m_isDisplayingModal; } View * ModalViewController::ContentView::subviewAtIndex(int index) { switch (index) { case 0: - if (m_isDisplayingModal && numberOfSubviews() == 1) { - return m_currentModalView; - } return m_regularView; case 1: if (numberOfSubviews() == 2) { @@ -49,7 +43,7 @@ View * ModalViewController::ContentView::subviewAtIndex(int index) { } } -KDRect ModalViewController::ContentView::frame() const { +KDRect ModalViewController::ContentView::modalViewFrame() const { KDSize modalSize = m_isDisplayingModal ? m_currentModalView->minimalSizeForOptimalDisplay() : KDSize(0,0); KDCoordinate modalHeight = modalSize.height(); modalHeight = modalHeight == 0 ? bounds().height()-m_topMargin-m_bottomMargin : modalHeight; @@ -65,7 +59,11 @@ void ModalViewController::ContentView::layoutSubviews() { m_regularView->setFrame(bounds()); if (m_isDisplayingModal) { assert(m_currentModalView != nullptr); - m_currentModalView->setFrame(frame()); + m_currentModalView->setFrame(modalViewFrame()); + } else { + if (m_currentModalView) { + m_currentModalView->setFrame(KDRectZero); + } } } @@ -79,22 +77,25 @@ void ModalViewController::ContentView::presentModalView(View * modalView, float m_leftMargin = leftMargin; m_bottomMargin = bottomMargin; m_rightMargin = rightMargin; - markRectAsDirty(frame()); layoutSubviews(); } void ModalViewController::ContentView::dismissModalView() { m_isDisplayingModal = false; - markRectAsDirty(frame()); + layoutSubviews(); m_currentModalView->resetSuperview(); m_currentModalView = nullptr; - layoutSubviews(); } bool ModalViewController::ContentView::isDisplayingModal() const { return m_isDisplayingModal; } +void ModalViewController::ContentView::reload() { + markRectAsDirty(bounds()); + layoutSubviews(); +} + ModalViewController::ModalViewController(Responder * parentResponder, ViewController * child) : ViewController(parentResponder), m_contentView(), @@ -122,6 +123,10 @@ void ModalViewController::displayModalViewController(ViewController * vc, float app()->setFirstResponder(vc); } +void ModalViewController::reloadModalViewController() { + m_contentView.layoutSubviews(); +} + void ModalViewController::dismissModalViewController() { m_currentModalViewController->viewDidDisappear(); app()->setFirstResponder(m_previousResponder); @@ -162,3 +167,7 @@ void ModalViewController::viewDidDisappear() { } m_regularViewController->viewDidDisappear(); } + +void ModalViewController::reloadView() { + m_contentView.reload(); +} diff --git a/escher/src/scrollable_view.cpp b/escher/src/scrollable_view.cpp index 6db6475fc..bc9048715 100644 --- a/escher/src/scrollable_view.cpp +++ b/escher/src/scrollable_view.cpp @@ -52,8 +52,8 @@ void ScrollableView::reloadScroll(bool forceReLayout) { void ScrollableView::layoutSubviews() { KDSize viewSize = contentSize(); - KDCoordinate viewWidth = viewSize.width() < bounds().width() ? bounds().width() : viewSize.width(); - KDCoordinate viewHeight = viewSize.height() < bounds().height() ? bounds().height() : viewSize.height(); + KDCoordinate viewWidth = max(viewSize.width(), bounds().width() - leftMargin() - rightMargin()); + KDCoordinate viewHeight = max(viewSize.height(), bounds().height() - topMargin() - bottomMargin()); m_contentView->setSize(KDSize(viewWidth, viewHeight)); ScrollView::layoutSubviews(); } diff --git a/escher/src/stack_view.cpp b/escher/src/stack_view.cpp index 5f125bea5..bd667671d 100644 --- a/escher/src/stack_view.cpp +++ b/escher/src/stack_view.cpp @@ -37,7 +37,7 @@ void StackView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(KDRect(0, height-1, width, 1), m_separatorColor); // Write title KDSize textSize = KDText::stringSize(m_controller->title(), KDText::FontSize::Small); - KDPoint origin(0.5f*(m_frame.width() - textSize.width()),0.5f*(m_frame.height() - textSize.height())); + KDPoint origin((m_frame.width() - textSize.width())/2,(m_frame.height() - textSize.height())/2); ctx->drawString(m_controller->title(), origin, KDText::FontSize::Small, m_textColor, m_backgroundColor); } diff --git a/escher/src/tab_view_cell.cpp b/escher/src/tab_view_cell.cpp index 7bea92c38..43be0c1c8 100644 --- a/escher/src/tab_view_cell.cpp +++ b/escher/src/tab_view_cell.cpp @@ -48,7 +48,7 @@ void TabViewCell::drawRect(KDContext * ctx, KDRect rect) const { } // Write title KDSize textSize = KDText::stringSize(m_controller->title(), KDText::FontSize::Small); - KDPoint origin(0.5f*(m_frame.width() - textSize.width()),0.5f*(m_frame.height() - textSize.height())); + KDPoint origin((m_frame.width() - textSize.width())/2, (m_frame.height() - textSize.height())/2); ctx->drawString(m_controller->title(), origin, KDText::FontSize::Small, text, background); } diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index f47316aa2..016e3e5a3 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -293,8 +293,22 @@ TextArea::TextArea(Responder * parentResponder, char * textBuffer, bool TextArea::handleEventWithText(const char * text, bool indentation) { int nextCursorLocation = cursorLocation(); - if ((indentation && insertTextWithIndentation(text, cursorLocation())) || insertTextAtLocation(text, cursorLocation())) { - nextCursorLocation += TextInputHelpers::CursorIndexInCommand(text); + + int cursorIndexInCommand = TextInputHelpers::CursorIndexInCommand(text); + + size_t eventTextSize = strlen(text) + 1; + char buffer[eventTextSize]; + size_t bufferIndex = 0; + + // Remove EmptyChars + for (size_t i = bufferIndex; i < eventTextSize; i++) { + if (text[i] != Ion::Charset::Empty) { + buffer[bufferIndex++] = text[i]; + } + } + + if ((indentation && insertTextWithIndentation(buffer, cursorLocation())) || insertTextAtLocation(buffer, cursorLocation())) { + nextCursorLocation += cursorIndexInCommand; } setCursorLocation(nextCursorLocation); return true; @@ -344,7 +358,7 @@ void TextArea::setText(char * textBuffer, size_t textBufferSize) { bool TextArea::insertTextWithIndentation(const char * textBuffer, int location) { int indentation = indentationBeforeCursor(); - char spaceString[indentation+1]; // WOUHOU + char spaceString[indentation+1]; for (int i = 0; i < indentation; i++) { spaceString[i] = ' '; } diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index c483fadba..ae3ff78c5 100644 --- a/escher/src/text_field.cpp +++ b/escher/src/text_field.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include /* TextField::ContentView */ @@ -23,12 +24,12 @@ void TextField::ContentView::setDraftTextBuffer(char * draftTextBuffer) { } void TextField::ContentView::drawRect(KDContext * ctx, KDRect rect) const { - KDColor bckCol = m_backgroundColor; + KDColor backgroundColor = m_backgroundColor; if (m_isEditing) { - bckCol = KDColorWhite; + backgroundColor = KDColorWhite; } - ctx->fillRect(rect, bckCol); - ctx->drawString(text(), characterFrameAtIndex(0).origin(), m_fontSize, m_textColor, bckCol); + ctx->fillRect(bounds(), backgroundColor); + ctx->drawString(text(), characterFrameAtIndex(0).origin(), m_fontSize, m_textColor, backgroundColor); } const char * TextField::ContentView::text() const { @@ -162,7 +163,7 @@ TextField::TextField(Responder * parentResponder, char * textBuffer, char * draf size_t textBufferSize, TextFieldDelegate * delegate, bool hasTwoBuffers, KDText::FontSize size, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor) : TextInput(parentResponder, &m_contentView), - m_contentView(textBuffer, draftTextBuffer, textBufferSize, size,horizontalAlignment, verticalAlignment, textColor, backgroundColor), + m_contentView(textBuffer, draftTextBuffer, textBufferSize, size, horizontalAlignment, verticalAlignment, textColor, backgroundColor), m_hasTwoBuffers(hasTwoBuffers), m_delegate(delegate) { @@ -249,7 +250,7 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) { if (event == Ion::Events::Back && isEditing()) { setEditing(false); reloadScroll(); - m_delegate->textFieldDidAbortEditing(this, text()); + m_delegate->textFieldDidAbortEditing(this); return true; } if (event == Ion::Events::Clear && isEditing()) { @@ -302,15 +303,46 @@ void TextField::scrollToCursor() { bool TextField::handleEventWithText(const char * eventText, bool indentation) { size_t previousTextLength = strlen(text()); + + size_t eventTextSize = strlen(eventText) + 1; + char buffer[eventTextSize]; + size_t bufferIndex = 0; + + /* DIRTY + * We use the notation "_{}" to indicate a subscript layout. In a text field, + * such a subscript should be written using parentheses. For instance: "u_{n}" + * should be inserted as "u(n)". + * We thus remove underscores and changes brackets into parentheses. */ + for (size_t i = bufferIndex; i < eventTextSize; i++) { + if (eventText[i] == '{') { + buffer[bufferIndex++] = '('; + } else if (eventText[i] == '}') { + buffer[bufferIndex++] = ')'; + } else if (eventText[i] == '_') { + } else { + buffer[bufferIndex++] = eventText[i]; + } + } + + int cursorIndexInCommand = TextInputHelpers::CursorIndexInCommand(buffer); + + int newBufferIndex = 0; + // Remove EmptyChars + for (size_t i = newBufferIndex; i < bufferIndex; i++) { + if (buffer[i] != Ion::Charset::Empty) { + buffer[newBufferIndex++] = buffer[i]; + } + } + if (!isEditing()) { setEditing(true); } int nextCursorLocation = draftTextLength(); - if (insertTextAtLocation(eventText, cursorLocation())) { - /* The cursor position depends on the text as we sometimes want to - * position the cursor at the end of the text and sometimes after the - * first parenthesis. */ - nextCursorLocation = cursorLocation() + TextInputHelpers::CursorIndexInCommand(eventText); + if (insertTextAtLocation(buffer, cursorLocation())) { + /* The cursor position depends on the text as we sometimes want to position + * the cursor at the end of the text and sometimes after the first + * parenthesis. */ + nextCursorLocation = cursorLocation() + cursorIndexInCommand; } setCursorLocation(nextCursorLocation); return m_delegate->textFieldDidHandleEvent(this, true, strlen(text()) != previousTextLength); diff --git a/escher/src/text_input.cpp b/escher/src/text_input.cpp index 67ef0061a..4e27d0c41 100644 --- a/escher/src/text_input.cpp +++ b/escher/src/text_input.cpp @@ -70,6 +70,7 @@ Toolbox * TextInput::toolbox() { } void TextInput::setBackgroundColor(KDColor backgroundColor) { + ScrollView::setBackgroundColor(backgroundColor); contentView()->setBackgroundColor(backgroundColor); } diff --git a/escher/src/text_input_helpers.cpp b/escher/src/text_input_helpers.cpp index 1c6f88f0d..3a878afbe 100644 --- a/escher/src/text_input_helpers.cpp +++ b/escher/src/text_input_helpers.cpp @@ -1,20 +1,19 @@ #include +#include #include namespace TextInputHelpers { int CursorIndexInCommand(const char * text) { - for (size_t i = 0; i < strlen(text)-1; i++) { - if (text[i] == '(' || text[i] == '\'') { - if (text[i+1] == ')' || text[i+1] == '\'' || text[i+1] == ',') { - if (i >= strlen(k_random) && memcmp(&text[i-strlen(k_random)], k_random, strlen(k_random)) == 0) { - break; - } - return i + 1; - } + int textLength = strlen(text); + for (int i = 0; i < textLength - 1; i++) { + if (text[i] == '\'' && text[i+1] == '\'') { + return i + 1; + } else if (text[i] == Ion::Charset::Empty) { + return i; } } - return strlen(text); + return textLength; } } diff --git a/escher/src/toolbox.cpp b/escher/src/toolbox.cpp index 0aeae5f5b..a587ea251 100644 --- a/escher/src/toolbox.cpp +++ b/escher/src/toolbox.cpp @@ -96,10 +96,6 @@ Toolbox::Toolbox(Responder * parentResponder, const char * title) : m_selectableTableView.setShowsIndicators(false); } -void Toolbox::setSender(Responder * sender) { - m_sender = sender; -} - bool Toolbox::handleEvent(Ion::Events::Event event) { return handleEventForRow(event, selectedRow()); } @@ -234,7 +230,3 @@ bool Toolbox::returnToPreviousMenu() { app()->setFirstResponder(&m_listController); return true; } - -Responder * Toolbox::sender() { - return m_sender; -} diff --git a/escher/src/toolbox_message_tree.cpp b/escher/src/toolbox_message_tree.cpp deleted file mode 100644 index 595e7e300..000000000 --- a/escher/src/toolbox_message_tree.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include - -I18n::Message ToolboxMessageTree::text() const { - return m_text; -} - -I18n::Message ToolboxMessageTree::insertedText() const { - return m_insertedText; -} - -const MessageTree * ToolboxMessageTree::children(int index) const { - return &m_children[index]; -} diff --git a/escher/src/view.cpp b/escher/src/view.cpp index 46d3d9212..64cfcc62d 100644 --- a/escher/src/view.cpp +++ b/escher/src/view.cpp @@ -129,13 +129,12 @@ void View::setFrame(KDRect frame) { /* CAUTION: This code is not resilient to multiple consecutive setFrame() * calls without intermediate redraw() calls. */ - // TODO: Return if frame is equal to m_frame if (m_superview != nullptr) { /* We will move this view. This will leave a blank spot in its superview * were it previously was. - * At this point, we know that the only area that really needs to be redrawn - * in the superview is the value of m_frame at the start of that method. */ - m_superview->markRectAsDirty(m_frame); + * At this point, we know that the only area that needs to be redrawn in the + * superview is the old frame minus the part covered by the new frame.*/ + m_superview->markRectAsDirty(m_frame.differencedWith(frame)); } m_frame = frame; diff --git a/ion/include/ion/charset.h b/ion/include/ion/charset.h index fb463525f..333c2c4c6 100644 --- a/ion/include/ion/charset.h +++ b/ion/include/ion/charset.h @@ -24,7 +24,8 @@ enum Charset : char { GreaterEqual = (char)146, MultiplicationSign = (char)147, MiddleDot = (char)148, - AlmostEqual = (char)149 + AlmostEqual = (char)149, + Empty = (char)150 // This char is used to be parsed into EmptyExpression. }; } diff --git a/ion/src/shared/events.cpp b/ion/src/shared/events.cpp index 150cecdc6..b5300ec37 100644 --- a/ion/src/shared/events.cpp +++ b/ion/src/shared/events.cpp @@ -25,21 +25,32 @@ private: #define U() EventData::Undefined() #define T(x) EventData::Text(x) -static constexpr const char k_pi[2] = {Ion::Charset::SmallPi, 0}; -static constexpr const char k_root[4] = {Ion::Charset::Root, '(', ')', 0}; +static constexpr const char k_exponential[6] = {Ion::Charset::Exponential, '^', '(', Ion::Charset::Empty, ')', 0}; +static constexpr const char k_naperianLogarithm[6] = {'l', 'n', '(', Ion::Charset::Empty, ')', 0}; +static constexpr const char k_logarithm[7] = {'l', 'o', 'g', '(', Ion::Charset::Empty, ')', 0}; static constexpr const char k_complexI[2] = {Ion::Charset::IComplex, 0}; -static constexpr const char k_exponential[5] = {Ion::Charset::Exponential, '^', '(', ')', 0}; -static constexpr const char k_sto[2] = {Ion::Charset::Sto, 0}; -static constexpr const char k_exponent[2] = {Ion::Charset::Exponent, 0}; + +static constexpr const char k_sine[7] = {'s', 'i', 'n', '(', Ion::Charset::Empty, ')', 0}; +static constexpr const char k_cosine[7] = {'c', 'o', 's', '(', Ion::Charset::Empty, ')', 0}; +static constexpr const char k_tangent[7] = {'t', 'a', 'n', '(', Ion::Charset::Empty, ')', 0}; +static constexpr const char k_pi[2] = {Ion::Charset::SmallPi, 0}; +static constexpr const char k_root[5] = {Ion::Charset::Root, '(', Ion::Charset::Empty, ')', 0}; + static constexpr const char k_multiplicationSign[2] = {Ion::Charset::MultiplicationSign, 0}; +static constexpr const char k_exponent[2] = {Ion::Charset::Exponent, 0}; +static constexpr const char k_sto[2] = {Ion::Charset::Sto, 0}; + +static constexpr const char k_arcSine[8] = {'a', 's', 'i', 'n', '(', Ion::Charset::Empty, ')', 0}; +static constexpr const char k_arcCosine[8] = {'a', 'c', 'o', 's', '(', Ion::Charset::Empty, ')', 0}; +static constexpr const char k_arcTangent[8] = {'a', 't', 'a', 'n', '(', Ion::Charset::Empty, ')', 0}; static constexpr EventData s_dataForEvent[4*Event::PageSize] = { // Plain TL(), TL(), TL(), TL(), TL(), TL(), TL(), TL(), U(), U(), U(), U(), TL(), TL(), TL(), TL(), TL(), TL(), - T(k_exponential), T("ln()"), T("log()"), T(k_complexI), T(","), T("^"), - T("sin()"), T("cos()"), T("tan()"), T(k_pi), T(k_root), T("^2"), + T(k_exponential), T(k_naperianLogarithm), T(k_logarithm), T(k_complexI), T(","), T("^"), + T(k_sine), T(k_cosine), T(k_tangent), T(k_pi), T(k_root), T("^2"), T("7"), T("8"), T("9"), T("("), T(")"), U(), T("4"), T("5"), T("6"), T(k_multiplicationSign), T("/"), U(), T("1"), T("2"), T("3"), T("+"), T("-"), U(), @@ -49,7 +60,7 @@ static constexpr EventData s_dataForEvent[4*Event::PageSize] = { U(), U(), U(), U(), U(), U(), U(), U(), TL(), TL(), TL(), TL(), T("["), T("]"), T("{"), T("}"), T("_"), T(k_sto), - T("asin()"), T("acos()"), T("atan()"), T("="), T("<"), T(">"), + T(k_arcSine), T(k_arcCosine), T(k_arcTangent), T("="), T("<"), T(">"), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), @@ -145,7 +156,7 @@ static constexpr const char * s_nameForEvent[255] = { "One", "Two", "Three", "Plus", "Minus", nullptr, "Zero", "Dot", "EE", "Ans", "EXE", nullptr, //Shift, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "AlphaLock", "Cut", "Copy", "Paste", "Clear", "LeftBracket", "RightBracket", "LeftBrace", "RightBrace", "Underscore", "Sto", diff --git a/kandinsky/include/kandinsky/point.h b/kandinsky/include/kandinsky/point.h index 67003b8e7..892aa92c7 100644 --- a/kandinsky/include/kandinsky/point.h +++ b/kandinsky/include/kandinsky/point.h @@ -17,6 +17,7 @@ public: bool operator !=(const KDPoint &other) const { return !(operator ==(other)); } + uint16_t squareDistanceTo(KDPoint other) const; private: KDCoordinate m_x; KDCoordinate m_y; diff --git a/kandinsky/include/kandinsky/rect.h b/kandinsky/include/kandinsky/rect.h index b149a35cf..fc3fe47af 100644 --- a/kandinsky/include/kandinsky/rect.h +++ b/kandinsky/include/kandinsky/rect.h @@ -45,6 +45,8 @@ public: KDCoordinate left() const { return m_x; } KDPoint topLeft() const { return KDPoint(left(), top()); } + KDPoint topRight() const { return KDPoint(right(), top()); } + KDPoint bottomLeft() const { return KDPoint(left(), bottom()); } KDPoint bottomRight() const { return KDPoint(right(), bottom()); } bool operator ==(const KDRect &other) const { @@ -60,7 +62,10 @@ public: bool intersects(const KDRect & other) const; KDRect intersectedWith(const KDRect & other) const; KDRect unionedWith(const KDRect & other) const; // Returns the smallest rectangle containing r1 and r2 + KDRect differencedWith(const KDRect & other) const; // Returns the smallest rectangle containing r1\r2 bool contains(KDPoint p) const; + bool isAbove(KDPoint p) const; + bool isUnder(KDPoint p) const; bool isEmpty() const; private: diff --git a/kandinsky/src/point.cpp b/kandinsky/src/point.cpp index 4fe0a7afb..796232558 100644 --- a/kandinsky/src/point.cpp +++ b/kandinsky/src/point.cpp @@ -7,3 +7,7 @@ KDPoint KDPoint::translatedBy(KDPoint other) const { KDPoint KDPoint::opposite() const { return KDPoint(-m_x, -m_y); } + +uint16_t KDPoint::squareDistanceTo(KDPoint other) const { + return (m_x-other.x()) * (m_x-other.x()) + (m_y-other.y()) * (m_y-other.y()); +} diff --git a/kandinsky/src/rect.cpp b/kandinsky/src/rect.cpp index a9702ce45..4373d4f93 100644 --- a/kandinsky/src/rect.cpp +++ b/kandinsky/src/rect.cpp @@ -101,10 +101,59 @@ KDRect KDRect::unionedWith(const KDRect & other) const { ); } +KDRect KDRect::differencedWith(const KDRect & other) const { + if (this->isEmpty() || other.isEmpty()) { + return *this; + } + + KDRect intersection = intersectedWith(other); + if (intersection.isEmpty()) { + return *this; + } + + if (intersection == *this) { + return KDRectZero; + } + + KDCoordinate resultLeft = left(); + KDCoordinate resultTop = top(); + KDCoordinate resultRight = right(); + KDCoordinate resultBottom = bottom(); + + if (intersection.height() == height()) { + if (intersection.left() == left()) { + resultLeft = intersection.right() + 1; + } else if (intersection.right() == right()) { + resultRight = intersection.left() - 1; + } + } else if (intersection.width() == width()) { + if (intersection.top() == top()) { + resultTop = intersection.bottom() + 1; + } else if (intersection.bottom() == bottom()) { + resultBottom = intersection.top() - 1; + } + } + + return KDRect( + resultLeft, + resultTop, + resultRight-resultLeft+1, + resultBottom-resultTop+1 + ); +} + bool KDRect::contains(KDPoint p) const { return (p.x() >= x() && p.x() <= right() && p.y() >= y() && p.y() <= bottom()); } +bool KDRect::isAbove(KDPoint p) const { + return (p.y() >= y()); +} + +bool KDRect::isUnder(KDPoint p) const { + return (p.y() <= bottom()); +} + KDRect KDRect::translatedBy(KDPoint p) const { return KDRect(x() + p.x(), y() + p.y(), width(), height()); } diff --git a/kandinsky/test/rect.cpp b/kandinsky/test/rect.cpp index 284213a55..eb313a119 100644 --- a/kandinsky/test/rect.cpp +++ b/kandinsky/test/rect.cpp @@ -8,31 +8,17 @@ QUIZ_CASE(kandinsky_rect_intersect) { assert(a.intersects(b)); assert(b.intersects(a)); KDRect c = a.intersectedWith(b); - assert(c.x() == 0); - assert(c.y() == 0); - assert(c.width() == 5); - assert(c.height() == 5); + KDRect result(0, 0, 5, 5); + assert(c == result); c = b.intersectedWith(a); - assert(c.x() == 0); - assert(c.y() == 0); - assert(c.width() == 5); - assert(c.height() == 5); + assert(c == result); } QUIZ_CASE(kandinsky_rect_union) { KDRect a(-5, -5, 10, 10); KDRect b(0, 0, 10, 10); KDRect c = a.unionedWith(b); - assert(c.x() == -5); - assert(c.y() == -5); - assert(c.width() == 15); - assert(c.height() == 15); - - c = a.unionedWith(b); - assert(c.x() == -5); - assert(c.y() == -5); - assert(c.width() == 15); - assert(c.height() == 15); + assert(c == KDRect(-5, -5, 15, 15)); } QUIZ_CASE(kandinsky_rect_empty_union) { @@ -41,20 +27,53 @@ QUIZ_CASE(kandinsky_rect_empty_union) { KDRect c(-2, -1, 0, 1); KDRect t = a.unionedWith(b); - assert(t.x() == a.x()); - assert(t.y() == a.y()); - assert(t.width() == a.width()); - assert(t.height() == a.height()); + assert(t == a); t = b.unionedWith(a); - assert(t.x() == a.x()); - assert(t.y() == a.y()); - assert(t.width() == a.width()); - assert(t.height() == a.height()); + assert(t == a); t = a.unionedWith(c); - assert(t.x() == a.x()); - assert(t.y() == a.y()); - assert(t.width() == a.width()); - assert(t.height() == a.height()); + assert(t == a); +} + +QUIZ_CASE(kandinsky_rect_difference) { + KDRect a(-1, 0, 11, 2); + KDRect b(-4, -3, 4, 7); + KDRect c(3, -2, 1, 5); + KDRect d(7, -3, 5, 7); + KDRect e(-2, -1, 13, 5); + KDRect f(2, -4, 3, 3); + + KDRect t = e.differencedWith(a); + assert(t == e); + + t = a.differencedWith(e); + assert(t == KDRectZero); + + t = f.differencedWith(d); + assert(t == f); + + t = f.differencedWith(e); + assert(t == f); + + t = b.differencedWith(e); + assert(t == b); + + t = c.differencedWith(f); + assert(t == KDRect(3, -1, 1, 4)); + + t = c.differencedWith(a); + assert(t == c); + + t = c.differencedWith(e); + assert(t == KDRect(3, -2, 1, 1)); + + t = a.differencedWith(b); + assert(t == KDRect(0, 0, 10, 2)); + + t = a.differencedWith(c); + assert(t == a); + + t = a.differencedWith(d); + assert(t == KDRect(-1, 0, 8, 2)); } diff --git a/poincare/Makefile b/poincare/Makefile index 1a47014ce..4ef675962 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -26,6 +26,8 @@ objs += $(addprefix poincare/src/,\ division_remainder.o\ division.o\ dynamic_hierarchy.o\ + empty_expression.o\ + expression_layout_cursor.o\ expression_lexer.o\ expression_parser.o\ expression.o\ @@ -86,36 +88,52 @@ objs += $(addprefix poincare/src/,\ ) objs += $(addprefix poincare/src/layout/,\ - baseline_relative_layout.o\ + absolute_value_layout.o\ + binomial_coefficient_layout.o\ + bounded_static_layout_hierarchy.o\ bracket_layout.o\ + bracket_pair_layout.o\ + ceiling_layout.o\ + char_layout.o\ condensed_sum_layout.o\ conjugate_layout.o\ + dynamic_layout_hierarchy.o\ + empty_layout.o\ expression_layout.o\ + floor_layout.o\ fraction_layout.o\ grid_layout.o\ horizontal_layout.o\ integral_layout.o\ + left_parenthesis_layout.o\ + left_square_bracket_layout.o\ + matrix_layout.o\ nth_root_layout.o\ - parenthesis_layout.o\ product_layout.o\ + right_parenthesis_layout.o\ + right_square_bracket_layout.o\ sequence_layout.o\ - string_layout.o\ + static_layout_hierarchy.o\ sum_layout.o\ + vertical_offset_layout.o\ ) tests += $(addprefix poincare/test/,\ addition.cpp\ arithmetic.cpp\ + binomial_coefficient_layout.cpp\ complex.cpp\ convert_expression_to_text.cpp\ division.cpp\ factorial.cpp\ + fraction_layout.cpp\ function.cpp\ helper.cpp\ integer.cpp\ logarithm.cpp\ matrix.cpp\ multiplication.cpp\ + nth_root_layout.cpp\ parser.cpp\ power.cpp\ properties.cpp\ @@ -125,6 +143,7 @@ tests += $(addprefix poincare/test/,\ symbol.cpp\ store.cpp\ trigo.cpp\ + vertical_offset_layout.cpp\ ) # simplify_utils.cpp\ diff --git a/poincare/include/poincare.h b/poincare/include/poincare.h index c750becc4..89858c9ca 100644 --- a/poincare/include/poincare.h +++ b/poincare/include/poincare.h @@ -22,10 +22,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include #include diff --git a/poincare/include/poincare/bounded_static_layout_hierarchy.h b/poincare/include/poincare/bounded_static_layout_hierarchy.h new file mode 100644 index 000000000..a54036037 --- /dev/null +++ b/poincare/include/poincare/bounded_static_layout_hierarchy.h @@ -0,0 +1,22 @@ +#ifndef POINCARE_BOUNDED_STATIC_LAYOUT_HIERARCHY_H +#define POINCARE_BOUNDED_STATIC_LAYOUT_HIERARCHY_H + +#include + +namespace Poincare { + +template +class BoundedStaticLayoutHierarchy : public StaticLayoutHierarchy { +public: + BoundedStaticLayoutHierarchy(); + BoundedStaticLayoutHierarchy(const ExpressionLayout * expressionLayout, bool cloneOperands); // Specialized constructor for StaticLayoutHierarchy<2> + BoundedStaticLayoutHierarchy(const ExpressionLayout * expressionLayout1, const ExpressionLayout * expressionLayout2, bool cloneOperands); // Specialized constructor for StaticLayoutHierarchy<2> + BoundedStaticLayoutHierarchy(const ExpressionLayout * const * operands, int numberOfOperands, bool cloneOperands); + int numberOfChildren() const override { return m_numberOfChildren; } +private: + int m_numberOfChildren; +}; + +} + +#endif diff --git a/poincare/include/poincare/dynamic_layout_hierarchy.h b/poincare/include/poincare/dynamic_layout_hierarchy.h new file mode 100644 index 000000000..9d5830f11 --- /dev/null +++ b/poincare/include/poincare/dynamic_layout_hierarchy.h @@ -0,0 +1,44 @@ +#ifndef POINCARE_DYNAMIC_LAYOUT_HIERARCHY_H +#define POINCARE_DYNAMIC_LAYOUT_HIERARCHY_H + +#include +#include + +namespace Poincare { + +class DynamicLayoutHierarchy : public ExpressionLayout { +public: + DynamicLayoutHierarchy(); + DynamicLayoutHierarchy(const ExpressionLayout * const * operands, int numberOfOperands, bool cloneOperands); + DynamicLayoutHierarchy(const ExpressionLayout * operand, bool cloneOperands) : + DynamicLayoutHierarchy(ExpressionLayoutArray(operand).array(), 1, cloneOperands) {} + DynamicLayoutHierarchy(const ExpressionLayout * operand1, const ExpressionLayout * operand2, bool cloneOperands) : + DynamicLayoutHierarchy(ExpressionLayoutArray(operand1, operand2).array(), 2, cloneOperands) {} + DynamicLayoutHierarchy(const ExpressionLayout * operand1, const ExpressionLayout * operand2, const ExpressionLayout * operand3, bool cloneOperands) : + DynamicLayoutHierarchy(ExpressionLayoutArray(operand1, operand2, operand3).array(), 3, cloneOperands) {} + ~DynamicLayoutHierarchy(); + DynamicLayoutHierarchy(const DynamicLayoutHierarchy & other) = delete; + DynamicLayoutHierarchy(DynamicLayoutHierarchy && other) = delete; + DynamicLayoutHierarchy& operator=(const DynamicLayoutHierarchy & other) = delete; + DynamicLayoutHierarchy& operator=(DynamicLayoutHierarchy && other) = delete; + + int numberOfChildren() const override { return m_numberOfChildren; } + const ExpressionLayout * const * children() const override { return m_children; }; + + virtual void addChildrenAtIndex(const ExpressionLayout * const * operands, int numberOfOperands, int indexForInsertion, bool removeEmptyChildren); + bool addChildAtIndex(ExpressionLayout * operand, int index) override; + void removeChildAtIndex(int index, bool deleteAfterRemoval) override; + void removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor) override; + void removeAndDeleteChildren(); + virtual void mergeChildrenAtIndex(DynamicLayoutHierarchy * eL, int index, bool removeEmptyChildren); // WITHOUT delete. + +protected: + const ExpressionLayout ** m_children; + int m_numberOfChildren; +private: + void removeDetachedChildren(); +}; + +} + +#endif diff --git a/poincare/include/poincare/empty_expression.h b/poincare/include/poincare/empty_expression.h new file mode 100644 index 000000000..09d1cbb9d --- /dev/null +++ b/poincare/include/poincare/empty_expression.h @@ -0,0 +1,30 @@ +#ifndef POINCARE_EMPTY_EXPRESSION_H +#define POINCARE_EMPTY_EXPRESSION_H + +#include +#include + +namespace Poincare { + +/* An empty expression awaits completion by the user. */ + +class EmptyExpression : public StaticHierarchy<0> { +public: + Type type() const override { + return Type::EmptyExpression; + } + Expression * clone() const override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; +private: + /* Layout */ + ExpressionLayout * privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const override; + /* Evaluation */ + Expression * privateApproximate(SinglePrecision p, Context& context, AngleUnit angleUnit) const override { return templatedApproximate(context, angleUnit); } + Expression * privateApproximate(DoublePrecision p, Context& context, AngleUnit angleUnit) const override { return templatedApproximate(context, angleUnit); } + template Complex * templatedApproximate(Context& context, AngleUnit angleUnit) const; +}; + +} + +#endif + diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index c97ed7824..c8e2467c7 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -79,6 +79,7 @@ class Expression { friend class LayoutEngine; friend class Complex; friend class Complex; + friend class EmptyExpression; public: enum class Type : uint8_t { @@ -144,6 +145,7 @@ public: MatrixTranspose, PredictionInterval, SimplificationRoot, + EmptyExpression }; enum class ComplexFormat { Cartesian = 0, diff --git a/poincare/include/poincare/expression_layout.h b/poincare/include/poincare/expression_layout.h index 3d0e65d37..ba59e8a71 100644 --- a/poincare/include/poincare/expression_layout.h +++ b/poincare/include/poincare/expression_layout.h @@ -2,30 +2,162 @@ #define POINCARE_EXPRESSION_LAYOUT_H #include +#include +#include namespace Poincare { class ExpressionLayout { public: + enum class VerticalDirection { + Up, + Down + }; + enum class HorizontalDirection { + Left, + Right + }; + + /* Constructor & Destructor */ ExpressionLayout(); virtual ~ExpressionLayout() = default; + virtual ExpressionLayout * clone() const = 0; + /* Rendering */ void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); KDPoint origin(); + KDPoint absoluteOrigin(); KDSize size(); KDCoordinate baseline(); - void setParent(ExpressionLayout* parent); + virtual void invalidAllSizesPositionsAndBaselines(); + + /* Hierarchy */ + + // Children + virtual const ExpressionLayout * const * children() const = 0; + const ExpressionLayout * child(int i) const; + ExpressionLayout * editableChild(int i) { return const_cast(child(i)); } + virtual int numberOfChildren() const = 0; + bool hasChild(const ExpressionLayout * child) const; + int indexOfChild(const ExpressionLayout * child) const; + + // Parent + void setParent(ExpressionLayout * parent) { m_parent = parent; } + const ExpressionLayout * parent() const { return m_parent; } + ExpressionLayout * editableParent() { return m_parent; } + bool hasAncestor(const ExpressionLayout * e, bool includeSelf = false) const; + ExpressionLayout * editableRoot(); + + // Sibling + bool hasSibling(const ExpressionLayout * e) const; + + /* Dynamic Layout */ + + // Collapse + virtual void collapseSiblingsAndMoveCursor(ExpressionLayoutCursor * cursor) {} + + // Add + virtual bool addChildAtIndex(ExpressionLayout * child, int index) { return false; } + void addSibling(ExpressionLayoutCursor * cursor, ExpressionLayout * sibling); + void addSiblingAndMoveCursor(ExpressionLayoutCursor * cursor, ExpressionLayout * sibling); + + // Replace + virtual ExpressionLayout * replaceWith(ExpressionLayout * newChild, bool deleteAfterReplace); + ExpressionLayout * replaceWithAndMoveCursor(ExpressionLayout * newChild, bool deleteAfterReplace, ExpressionLayoutCursor * cursor); + virtual void replaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild); + virtual void replaceChildAndMoveCursor( + const ExpressionLayout * oldChild, + ExpressionLayout * newChild, + bool deleteOldChild, + ExpressionLayoutCursor * cursor); + + // Detach + void detachChild(const ExpressionLayout * e); // Detach a child WITHOUT deleting it + void detachChildren(); // Detach all children WITHOUT deleting them + + // Remove + virtual void removeChildAtIndex(int index, bool deleteAfterRemoval); + virtual void removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor); + + // User input + bool insertLayoutAtCursor(ExpressionLayout * newChild, ExpressionLayoutCursor * cursor); + virtual void deleteBeforeCursor(ExpressionLayoutCursor * cursor); + + /* Tree navigation */ + virtual ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) = 0; + virtual ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) = 0; + virtual ExpressionLayoutCursor cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false); + virtual ExpressionLayoutCursor cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false); + ExpressionLayoutCursor cursorInDescendantsAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout); + ExpressionLayoutCursor cursorInDescendantsUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout); + + /* Expression Engine */ + virtual int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const = 0; + + /* Cursor */ + virtual ExpressionLayoutCursor equivalentCursor(ExpressionLayoutCursor cursor); + + /* Other */ + virtual ExpressionLayout * layoutToPointWhenInserting(); + bool addGreySquaresToAllMatrixAncestors(); + bool removeGreySquaresFromAllMatrixAncestors(); + bool hasText() const; + virtual bool needsParenthesesWithParent() const { return false; } + virtual bool childMightNeedParentheses() const { return false; } + virtual bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { return true; } + /* isCollapsable is used when adding a sibling fraction: should the layout be + * inserted in the numerator (or denominator)? For instance, 1+2|3-4 should + * become 1+ 2/3 - 4 when pressing "Divide": a CharLayout is collapsable if + * its char is not +, -, or *. */ + bool canBeOmittedMultiplicationLeftFactor() const; + virtual bool canBeOmittedMultiplicationRightFactor() const; + /* canBeOmittedMultiplicationLeftFactor and RightFactor return true if the + * layout, next to another layout, might be the factor of a multiplication + * with an omitted multiplication sign. For instance, an absolute value layout + * returns true, because |3|2 means |3|*2. A '+' CharLayout returns false, + * because +'something' nevers means +*'something'. */ + virtual bool mustHaveLeftSibling() const { return false; } + virtual bool isVerticalOffset() const { return false; } + /* For now, mustHaveLeftSibling and isVerticalOffset behave the same, but code + * is clearer with different names. */ + virtual bool isHorizontal() const { return false; } + virtual bool isLeftParenthesis() const { return false; } + virtual bool isRightParenthesis() const { return false; } + virtual bool isLeftBracket() const { return false; } + virtual bool isRightBracket() const { return false; } + virtual bool isEmpty() const { return false; } + virtual bool isMatrix() const { return false; } + virtual bool hasUpperLeftIndex() const { return false; } + virtual char XNTChar() const; + protected: virtual void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) = 0; virtual KDSize computeSize() = 0; - virtual ExpressionLayout * child(uint16_t index) = 0; + virtual void computeBaseline() = 0; virtual KDPoint positionOfChild(ExpressionLayout * child) = 0; + void scoreCursorInDescendantsVerticalOf ( + VerticalDirection direction, + ExpressionLayoutCursor cursor, + bool * shouldRecomputeLayout, + ExpressionLayout ** childResult, + void * resultPosition, + int * resultScore); + virtual void privateAddSibling(ExpressionLayoutCursor * cursor, ExpressionLayout * sibling, bool moveCursor); + void collapseOnDirection(HorizontalDirection direction, int absorbingChildIndex); + virtual ExpressionLayoutCursor cursorVerticalOf(VerticalDirection direction, ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited); + ExpressionLayout * m_parent; KDCoordinate m_baseline; + /* m_baseline is the signed vertical distance from the top of the layout to + * the fraction bar of an hypothetical fraction sibling layout. If the top of + * the layout is under that bar, the baseline is negative. */ + bool m_sized; + bool m_baselined; + bool m_positioned; private: - KDPoint absoluteOrigin(); - //void computeLayout();//ExpressionLayout * parent, uint16_t childIndex); - ExpressionLayout* m_parent; - bool m_sized, m_positioned; + void detachChildAtIndex(int i); + ExpressionLayoutCursor cursorInDescendantsVerticalOf(VerticalDirection direction, ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout); + ExpressionLayout * replaceWithJuxtapositionOf(ExpressionLayout * leftChild, ExpressionLayout * rightChild, bool deleteAfterReplace); + bool changeGreySquaresOfAllMatrixAncestors(bool add); KDRect m_frame; }; diff --git a/poincare/include/poincare/expression_layout_array.h b/poincare/include/poincare/expression_layout_array.h new file mode 100644 index 000000000..6bdae090b --- /dev/null +++ b/poincare/include/poincare/expression_layout_array.h @@ -0,0 +1,20 @@ +#ifndef POINCARE_EXPRESSION_LAYOUT_ARRAY_H +#define POINCARE_EXPRESSION_LAYOUT_ARRAY_H + +#include + +namespace Poincare { + +class ExpressionLayoutArray { +public: + ExpressionLayoutArray(const ExpressionLayout * eL1 = nullptr, const ExpressionLayout * eL2 = nullptr, const ExpressionLayout * eL3 = nullptr) : + m_data{eL1, eL2, eL3} + {} + const ExpressionLayout * const * array() { return const_cast(m_data); } +private: + const ExpressionLayout * m_data[3]; +}; + +} + +#endif diff --git a/poincare/include/poincare/expression_layout_cursor.h b/poincare/include/poincare/expression_layout_cursor.h new file mode 100644 index 000000000..902062e68 --- /dev/null +++ b/poincare/include/poincare/expression_layout_cursor.h @@ -0,0 +1,74 @@ +#ifndef POINCARE_EXPRESSION_LAYOUT_CURSOR_H +#define POINCARE_EXPRESSION_LAYOUT_CURSOR_H + +#include + +namespace Poincare { + +class ExpressionLayout; + +class ExpressionLayoutCursor { +public: + constexpr static KDCoordinate k_cursorWidth = 1; + + enum class Position { + Left, + Right + }; + + constexpr ExpressionLayoutCursor(ExpressionLayout * layout = nullptr, Position position = Position::Right) : + m_pointedExpressionLayout(layout), + m_position(position) + {}; + + /* Definition */ + bool isDefined() const { return m_pointedExpressionLayout != nullptr; } + + /* Getters and setters */ + ExpressionLayout * pointedExpressionLayout() { return m_pointedExpressionLayout; } + void setPointedExpressionLayout(ExpressionLayout * expressionLayout) { m_pointedExpressionLayout = expressionLayout; } + Position position() const { return m_position; } + void setPosition(Position position) { m_position = position; } + KDCoordinate cursorHeight(); + KDCoordinate baseline(); + + /* Comparison */ + bool isEquivalentTo(ExpressionLayoutCursor cursor); + + /* Position */ + KDPoint middleLeftPoint(); + + /* Move */ + ExpressionLayoutCursor cursorOnLeft(bool * shouldRecomputeLayout); + ExpressionLayoutCursor cursorOnRight(bool * shouldRecomputeLayout); + ExpressionLayoutCursor cursorAbove(bool * shouldRecomputeLayout); + ExpressionLayoutCursor cursorUnder(bool * shouldRecomputeLayout); + + /* Edition */ + void addLayoutAndMoveCursor(ExpressionLayout * layout); + void addEmptyExponentialLayout(); + void addFractionLayoutAndCollapseSiblings(); + void addEmptyMatrixLayout(int numberOfRows = 1, int numberOfColumns = 1); + void addEmptyPowerLayout(); + void addEmptySquareRootLayout(); + void addEmptySquarePowerLayout(); + void addEmptyTenPowerLayout(); + void addXNTCharLayout(); + void insertText(const char * text); + void performBackspace(); + bool showEmptyLayoutIfNeeded(); + bool hideEmptyLayoutIfNeeded(); + void clearLayout(); + +private: + constexpr static KDCoordinate k_cursorHeight = 18; + bool baseForNewPowerLayout(); + bool privateShowHideEmptyLayoutIfNeeded(bool show); + KDCoordinate pointedLayoutHeight(); + ExpressionLayout * m_pointedExpressionLayout; + Position m_position; +}; + +} + +#endif diff --git a/poincare/include/poincare/integer.h b/poincare/include/poincare/integer.h index d33eb03bb..af28e53c1 100644 --- a/poincare/include/poincare/integer.h +++ b/poincare/include/poincare/integer.h @@ -3,10 +3,11 @@ #include #include -#include namespace Poincare { +class ExpressionLayout; + /* All algorithm should be improved with: * Modern Computer Arithmetic, Richard P. Brent and Paul Zimmermann */ diff --git a/poincare/include/poincare/layout_engine.h b/poincare/include/poincare/layout_engine.h index 5b0f7b87d..b70df9671 100644 --- a/poincare/include/poincare/layout_engine.h +++ b/poincare/include/poincare/layout_engine.h @@ -6,12 +6,56 @@ namespace Poincare { class LayoutEngine { + public: - static ExpressionLayout * createInfixLayout(const Expression * expression, PrintFloat::Mode floatDisplayMode, Expression::ComplexFormat complexFormat, const char * operatorName); + /* Expression to ExpressionLayout */ + static ExpressionLayout * createInfixLayout(const Expression * expression, PrintFloat::Mode floatDisplayMode, Expression::ComplexFormat complexFormat, const char * operatorName); static ExpressionLayout * createPrefixLayout(const Expression * expression, PrintFloat::Mode floatDisplayMode, Expression::ComplexFormat complexFormat, const char * operatorName); - static int writeInfixExpressionTextInBuffer(const Expression * expression, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName); - static int writePrefixExpressionTextInBuffer(const Expression * expression, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName); + /* Create special layouts */ + static ExpressionLayout * createParenthesedLayout(ExpressionLayout * layout, bool cloneLayout); + static ExpressionLayout * createStringLayout(const char * buffer, int bufferSize, KDText::FontSize fontSize = KDText::FontSize::Large); + static ExpressionLayout * createLogLayout(ExpressionLayout * argument, ExpressionLayout * index); + + /* Expression to Text */ + static int writeInfixExpressionTextInBuffer( + const Expression * expression, + char * buffer, + int bufferSize, + int numberOfDigits, + const char * operatorName); + static int writePrefixExpressionTextInBuffer( + const Expression * expression, + char * buffer, + int bufferSize, + int numberOfDigits, + const char * operatorName); + + /* ExpressionLayout to Text */ + static int writeInfixExpressionLayoutTextInBuffer( + const ExpressionLayout * expressionLayout, + char * buffer, + int bufferSize, + int numberOfDigits, + const char * operatorName, + int firstChildIndex = 0, + int lastChildIndex = -1); + static int writePrefixExpressionLayoutTextInBuffer( + const ExpressionLayout * expressionLayout, + char * buffer, + int bufferSize, + int numberOfDigits, + const char * operatorName, + bool writeFirstChild = true); + + /* Write one char in buffer */ + static int writeOneCharInBuffer(char * buffer, int bufferSize, char charToWrite); + +private: + static constexpr char divideChar = '/'; + // 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); + static int writePrefixExpressionOrExpressionLayoutTextInBuffer(const Expression * expression, const ExpressionLayout * expressionLayout, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName, bool writeFirstChild = true); }; } diff --git a/poincare/include/poincare/list_data.h b/poincare/include/poincare/list_data.h index dede2436e..87f6a8f2a 100644 --- a/poincare/include/poincare/list_data.h +++ b/poincare/include/poincare/list_data.h @@ -7,6 +7,7 @@ namespace Poincare { class ListData { public: + ListData(); ListData(Expression * operand); ~ListData(); ListData(const ListData& other) = delete; diff --git a/poincare/include/poincare/preferences.h b/poincare/include/poincare/preferences.h index fe751a825..3cc0e86df 100644 --- a/poincare/include/poincare/preferences.h +++ b/poincare/include/poincare/preferences.h @@ -7,12 +7,18 @@ namespace Poincare { class Preferences { public: + enum class EditionMode { + Edition1D, + Edition2D + }; Preferences(); static Preferences * sharedPreferences(); Expression::AngleUnit angleUnit() const; void setAngleUnit(Expression::AngleUnit angleUnit); PrintFloat::Mode displayMode() const; void setDisplayMode(PrintFloat::Mode mode); + EditionMode editionMode() const; + void setEditionMode(EditionMode editionMode); Expression::ComplexFormat complexFormat() const; void setComplexFormat(Expression::ComplexFormat complexFormat); char numberOfSignificantDigits() const; @@ -20,6 +26,7 @@ public: private: Expression::AngleUnit m_angleUnit; PrintFloat::Mode m_displayMode; + EditionMode m_editionMode; Expression::ComplexFormat m_complexFormat; char m_numberOfSignificantDigits; }; diff --git a/poincare/include/poincare/product.h b/poincare/include/poincare/product.h index ba5fe91c5..954a1aa20 100644 --- a/poincare/include/poincare/product.h +++ b/poincare/include/poincare/product.h @@ -13,7 +13,7 @@ public: private: const char * name() const override; int emptySequenceValue() const override; - ExpressionLayout * createSequenceLayoutWithArgumentLayouts(ExpressionLayout * subscriptLayout, ExpressionLayout * superscriptLayout, ExpressionLayout * argumentLayout) const override; + ExpressionLayout * createSequenceLayoutWithArgumentLayouts(ExpressionLayout * argumentLayout, ExpressionLayout * subscriptLayout, ExpressionLayout * superscriptLayout) const override; Expression * evaluateWithNextTerm(DoublePrecision p, Expression * a, Expression * b) const override { return templatedApproximateWithNextTerm(a, b); } diff --git a/poincare/include/poincare/static_layout_hierarchy.h b/poincare/include/poincare/static_layout_hierarchy.h new file mode 100644 index 000000000..57a31821a --- /dev/null +++ b/poincare/include/poincare/static_layout_hierarchy.h @@ -0,0 +1,31 @@ +#ifndef POINCARE_STATIC_LAYOUT_HIERARCHY_H +#define POINCARE_STATIC_LAYOUT_HIERARCHY_H + +#include + +namespace Poincare { + +template +class StaticLayoutHierarchy : public ExpressionLayout { +public: + StaticLayoutHierarchy(); + StaticLayoutHierarchy(const ExpressionLayout * const * operands, bool cloneOperands); + StaticLayoutHierarchy(const ExpressionLayout * expression, bool cloneOperands); // Specialized constructor for StaticLayoutHierarchy<1> + StaticLayoutHierarchy(const ExpressionLayout * expression1, const ExpressionLayout * expression2, bool cloneOperands); // Specialized constructor for StaticLayoutHierarchy<2> + StaticLayoutHierarchy(const ExpressionLayout * expression1, const ExpressionLayout * expression2, const ExpressionLayout * expression3, bool cloneOperands); // Specialized constructor for StaticLayoutHierarchy<3> + ~StaticLayoutHierarchy(); + StaticLayoutHierarchy(const StaticLayoutHierarchy & other) = delete; + StaticLayoutHierarchy(StaticLayoutHierarchy && other) = delete; + StaticLayoutHierarchy& operator=(const StaticLayoutHierarchy & other) = delete; + StaticLayoutHierarchy& operator=(StaticLayoutHierarchy && other) = delete; + + int numberOfChildren() const override { return T; } + const ExpressionLayout * const * children() const override { return m_children; } +protected: + void build(const ExpressionLayout * const * operands, int numberOfOperands, bool cloneOperands); + const ExpressionLayout * m_children[T]; +}; + +} + +#endif diff --git a/poincare/include/poincare/sum.h b/poincare/include/poincare/sum.h index 854e59d57..1aae88773 100644 --- a/poincare/include/poincare/sum.h +++ b/poincare/include/poincare/sum.h @@ -13,7 +13,7 @@ public: private: const char * name() const override; int emptySequenceValue() const override; - ExpressionLayout * createSequenceLayoutWithArgumentLayouts(ExpressionLayout * subscriptLayout, ExpressionLayout * superscriptLayout, ExpressionLayout * argumentLayout) const override; + ExpressionLayout * createSequenceLayoutWithArgumentLayouts(ExpressionLayout * argumentLayout, ExpressionLayout * subscriptLayout, ExpressionLayout * superscriptLayout) const override; Expression * evaluateWithNextTerm(DoublePrecision p, Expression * a, Expression * b) const override { return templatedApproximateWithNextTerm(a, b); } diff --git a/poincare/include/poincare_layouts.h b/poincare/include/poincare_layouts.h new file mode 100644 index 000000000..74a2dd1ca --- /dev/null +++ b/poincare/include/poincare_layouts.h @@ -0,0 +1,29 @@ +#ifndef POINCARE_LAYOUTS_H +#define POINCARE_LAYOUTS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/poincare/src/absolute_value.cpp b/poincare/src/absolute_value.cpp index ef9217f47..5751149b8 100644 --- a/poincare/src/absolute_value.cpp +++ b/poincare/src/absolute_value.cpp @@ -27,7 +27,7 @@ Expression * AbsoluteValue::setSign(Sign s, Context & context, AngleUnit angleUn ExpressionLayout * AbsoluteValue::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - return new AbsoluteValueLayout(operand(0)->createLayout(floatDisplayMode, complexFormat)); + return new AbsoluteValueLayout(operand(0)->createLayout(floatDisplayMode, complexFormat), false); } Expression * AbsoluteValue::shallowReduce(Context& context, AngleUnit angleUnit) { diff --git a/poincare/src/binomial_coefficient.cpp b/poincare/src/binomial_coefficient.cpp index 8dba72b53..51c0c62ba 100644 --- a/poincare/src/binomial_coefficient.cpp +++ b/poincare/src/binomial_coefficient.cpp @@ -2,8 +2,7 @@ #include #include #include -#include "layout/parenthesis_layout.h" -#include "layout/grid_layout.h" +#include "layout/binomial_coefficient_layout.h" extern "C" { #include @@ -76,10 +75,10 @@ Expression * BinomialCoefficient::shallowReduce(Context& context, AngleUnit angl ExpressionLayout * BinomialCoefficient::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - ExpressionLayout * childrenLayouts[2]; - childrenLayouts[0] = operand(0)->createLayout(floatDisplayMode, complexFormat); - childrenLayouts[1] = operand(1)->createLayout(floatDisplayMode, complexFormat); - return new ParenthesisLayout(new GridLayout(childrenLayouts, 2, 1)); + return new BinomialCoefficientLayout( + operand(0)->createLayout(floatDisplayMode, complexFormat), + operand(1)->createLayout(floatDisplayMode, complexFormat), + false); } template diff --git a/poincare/src/ceiling.cpp b/poincare/src/ceiling.cpp index a49969daf..4d8af70bb 100644 --- a/poincare/src/ceiling.cpp +++ b/poincare/src/ceiling.cpp @@ -1,13 +1,13 @@ #include #include "layout/ceiling_layout.h" +#include +#include #include #include #include -#include extern "C" { #include } -#include namespace Poincare { @@ -62,7 +62,7 @@ Complex Ceiling::computeOnComplex(const Complex c, AngleUnit angleUnit) { ExpressionLayout * Ceiling::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - return new CeilingLayout(m_operands[0]->createLayout(floatDisplayMode, complexFormat)); + return new CeilingLayout(m_operands[0]->createLayout(floatDisplayMode, complexFormat), false); } } diff --git a/poincare/src/complex.cpp b/poincare/src/complex.cpp index d221801b6..7ca360614 100644 --- a/poincare/src/complex.cpp +++ b/poincare/src/complex.cpp @@ -5,17 +5,20 @@ #include #include #include +#include +#include "layout/horizontal_layout.h" +#include "layout/vertical_offset_layout.h" + +#include +#include + extern "C" { #include +#include +#include #include #include -#include } -#include -#include "layout/string_layout.h" -#include "layout/baseline_relative_layout.h" -#include -#include namespace Poincare { @@ -312,7 +315,7 @@ ExpressionLayout * Complex::createPolarLayout(PrintFloat::Mode floatDisplayMo if (std::isnan(r()) || (std::isnan(th()) && r() != 0)) { numberOfCharInBase = PrintFloat::convertFloatToText(NAN, bufferBase, PrintFloat::k_maxComplexBufferLength, Preferences::sharedPreferences()->numberOfSignificantDigits(), floatDisplayMode); - return new StringLayout(bufferBase, numberOfCharInBase); + return LayoutEngine::createStringLayout(bufferBase, numberOfCharInBase); } if (r() != 1 || th() == 0) { numberOfCharInBase = PrintFloat::convertFloatToText(r(), bufferBase, PrintFloat::k_maxFloatBufferLength, Preferences::sharedPreferences()->numberOfSignificantDigits(), floatDisplayMode); @@ -332,16 +335,20 @@ ExpressionLayout * Complex::createPolarLayout(PrintFloat::Mode floatDisplayMo bufferSuperscript[numberOfCharInSuperscript] = 0; } if (numberOfCharInSuperscript == 0) { - return new StringLayout(bufferBase, numberOfCharInBase); + return LayoutEngine::createStringLayout(bufferBase, numberOfCharInBase); } - return new BaselineRelativeLayout(new StringLayout(bufferBase, numberOfCharInBase), new StringLayout(bufferSuperscript, numberOfCharInSuperscript), BaselineRelativeLayout::Type::Superscript); + ExpressionLayout * result = LayoutEngine::createStringLayout(bufferBase, numberOfCharInBase); + ExpressionLayout * exponentLayout = LayoutEngine::createStringLayout(bufferSuperscript, numberOfCharInSuperscript); + VerticalOffsetLayout * offsetLayout = new VerticalOffsetLayout(exponentLayout, VerticalOffsetLayout::Type::Superscript, false); + (static_cast(result))->addChildAtIndex(offsetLayout, result->numberOfChildren()); + return result; } template ExpressionLayout * Complex::createCartesianLayout(PrintFloat::Mode floatDisplayMode) const { char buffer[PrintFloat::k_maxComplexBufferLength]; int numberOfChars = convertComplexToText(buffer, PrintFloat::k_maxComplexBufferLength, Preferences::sharedPreferences()->numberOfSignificantDigits(), floatDisplayMode, Expression::ComplexFormat::Cartesian, Ion::Charset::MiddleDot); - return new StringLayout(buffer, numberOfChars); + return LayoutEngine::createStringLayout(buffer, numberOfChars); } template class Complex; diff --git a/poincare/src/conjugate.cpp b/poincare/src/conjugate.cpp index e2d697989..57461ae00 100644 --- a/poincare/src/conjugate.cpp +++ b/poincare/src/conjugate.cpp @@ -22,7 +22,7 @@ Expression * Conjugate::clone() const { ExpressionLayout * Conjugate::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - return new ConjugateLayout(operand(0)->createLayout(floatDisplayMode, complexFormat)); + return new ConjugateLayout(operand(0)->createLayout(floatDisplayMode, complexFormat), false); } Expression * Conjugate::shallowReduce(Context& context, AngleUnit angleUnit) { diff --git a/poincare/src/decimal.cpp b/poincare/src/decimal.cpp index 860f0e1b3..cf3fd3198 100644 --- a/poincare/src/decimal.cpp +++ b/poincare/src/decimal.cpp @@ -10,8 +10,6 @@ extern "C" { #include } -#include "layout/string_layout.h" - namespace Poincare { int Decimal::exponent(const char * integralPart, int integralPartLength, const char * fractionalPart, int fractionalPartLength, const char * exponent, int exponentLength, bool exponentNegative) { @@ -219,7 +217,7 @@ bool Decimal::needParenthesisWithParent(const Expression * e) const { ExpressionLayout * Decimal::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { char buffer[k_maxBufferSize]; int numberOfChars = convertToText(buffer, k_maxBufferSize, floatDisplayMode, PrintFloat::k_numberOfStoredSignificantDigits); - return new StringLayout(buffer, numberOfChars); + return LayoutEngine::createStringLayout(buffer, numberOfChars); } Expression * Decimal::shallowReduce(Context& context, AngleUnit angleUnit) { diff --git a/poincare/src/division.cpp b/poincare/src/division.cpp index 602597c88..d47288012 100644 --- a/poincare/src/division.cpp +++ b/poincare/src/division.cpp @@ -102,7 +102,7 @@ ExpressionLayout * Division::privateCreateLayout(PrintFloat::Mode floatDisplayMo assert(complexFormat != ComplexFormat::Default); const Expression * numerator = operand(0)->type() == Type::Parenthesis ? operand(0)->operand(0) : operand(0); const Expression * denominator = operand(1)->type() == Type::Parenthesis ? operand(1)->operand(0) : operand(1); - return new FractionLayout(numerator->createLayout(floatDisplayMode, complexFormat), denominator->createLayout(floatDisplayMode, complexFormat)); + return new FractionLayout(numerator->createLayout(floatDisplayMode, complexFormat), denominator->createLayout(floatDisplayMode, complexFormat), false); } template Matrix * Division::computeOnComplexAndMatrix(const Complex * c, const Matrix * n) { diff --git a/poincare/src/empty_expression.cpp b/poincare/src/empty_expression.cpp new file mode 100644 index 000000000..9e416269c --- /dev/null +++ b/poincare/src/empty_expression.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +extern "C" { +#include +} + +namespace Poincare { + +Expression * EmptyExpression::clone() const { + return new EmptyExpression(); +} + +int EmptyExpression::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { + return LayoutEngine::writeOneCharInBuffer(buffer, bufferSize, Ion::Charset::Empty); +} + +ExpressionLayout * EmptyExpression::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { + return new EmptyLayout(); +} + +template Complex * EmptyExpression::templatedApproximate(Context& context, AngleUnit angleUnit) const { + return new Complex(Complex::Float(NAN)); +} + +} diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index 77dfb9b14..61ba5af80 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -146,7 +146,6 @@ void Expression::swapOperands(int i, int j) { } bool Expression::hasAncestor(const Expression * e) const { - assert(m_parent != this); if (m_parent == e) { return true; } diff --git a/poincare/src/expression_layout_cursor.cpp b/poincare/src/expression_layout_cursor.cpp new file mode 100644 index 000000000..dff7a5fc5 --- /dev/null +++ b/poincare/src/expression_layout_cursor.cpp @@ -0,0 +1,315 @@ +#include +#include +#include +#include //TODO: finer include? +#include +#include + +namespace Poincare { + +static inline KDCoordinate max(KDCoordinate x, KDCoordinate y) { return (x>y ? x : y); } + +KDCoordinate ExpressionLayoutCursor::cursorHeight() { + KDCoordinate height = pointedLayoutHeight(); + return height == 0 ? k_cursorHeight : height; +} + +KDCoordinate ExpressionLayoutCursor::baseline() { + if (pointedLayoutHeight() == 0) { + return k_cursorHeight / 2; + } + KDCoordinate pointedLayoutBaseline = m_pointedExpressionLayout->baseline(); + ExpressionLayout * equivalentPointedLayout = m_pointedExpressionLayout->equivalentCursor(*this).pointedExpressionLayout(); + if (m_pointedExpressionLayout->hasChild(equivalentPointedLayout)) { + return equivalentPointedLayout->baseline(); + } else if (m_pointedExpressionLayout->hasSibling(equivalentPointedLayout)) { + return max(pointedLayoutBaseline, equivalentPointedLayout->baseline()); + } + return pointedLayoutBaseline; +} + +bool ExpressionLayoutCursor::isEquivalentTo(ExpressionLayoutCursor cursor) { + assert(isDefined()); + assert(cursor.isDefined()); + return middleLeftPoint() == cursor.middleLeftPoint(); +} + +KDPoint ExpressionLayoutCursor::middleLeftPoint() { + KDPoint layoutOrigin = m_pointedExpressionLayout->absoluteOrigin(); + KDCoordinate x = layoutOrigin.x() + (m_position == Position::Left ? 0 : m_pointedExpressionLayout->size().width()); + KDCoordinate y = layoutOrigin.y() + m_pointedExpressionLayout->baseline() - k_cursorHeight/2; + return KDPoint(x, y); +} + +ExpressionLayoutCursor ExpressionLayoutCursor::cursorOnLeft(bool * shouldRecomputeLayout) { + return m_pointedExpressionLayout->cursorLeftOf(*this, shouldRecomputeLayout); +} + +ExpressionLayoutCursor ExpressionLayoutCursor::cursorOnRight(bool * shouldRecomputeLayout) { + return m_pointedExpressionLayout->cursorRightOf(*this, shouldRecomputeLayout); +} + +ExpressionLayoutCursor ExpressionLayoutCursor::cursorAbove(bool * shouldRecomputeLayout) { + return m_pointedExpressionLayout->cursorAbove(*this, shouldRecomputeLayout); +} + +ExpressionLayoutCursor ExpressionLayoutCursor::cursorUnder(bool * shouldRecomputeLayout) { + return m_pointedExpressionLayout->cursorUnder(*this, shouldRecomputeLayout); +} + +void ExpressionLayoutCursor::addLayoutAndMoveCursor(ExpressionLayout * layout) { + bool layoutWillBeMerged = layout->isHorizontal(); + pointedExpressionLayout()->addSiblingAndMoveCursor(this, layout); + if (!layoutWillBeMerged) { + layout->collapseSiblingsAndMoveCursor(this); + } +} + +void ExpressionLayoutCursor::addEmptyExponentialLayout() { + CharLayout * child1 = new CharLayout(Ion::Charset::Exponential); + VerticalOffsetLayout * offsetLayout = new VerticalOffsetLayout(new EmptyLayout(), VerticalOffsetLayout::Type::Superscript, false); + HorizontalLayout * newChild = new HorizontalLayout(child1, offsetLayout, false); + pointedExpressionLayout()->addSibling(this, newChild); + setPointedExpressionLayout(offsetLayout->editableChild(0)); + setPosition(ExpressionLayoutCursor::Position::Right); +} + +void ExpressionLayoutCursor::addFractionLayoutAndCollapseSiblings() { + // Add a new FractionLayout + HorizontalLayout * child1 = new HorizontalLayout(new EmptyLayout(), false); + HorizontalLayout * child2 = new HorizontalLayout(new EmptyLayout(), false); + FractionLayout * newChild = new FractionLayout(child1, child2, false); + pointedExpressionLayout()->addSibling(this, newChild); + newChild->collapseSiblingsAndMoveCursor(this); +} + +void ExpressionLayoutCursor::addEmptyMatrixLayout(int numberOfRows, int numberOfColumns) { + assert(numberOfRows > 0); + assert(numberOfColumns > 0); + ExpressionLayout * children[(numberOfRows+1)*(numberOfColumns+1)]; + for (int i = 0; i < numberOfRows + 1; i++) { + for (int j = 0; j < numberOfColumns + 1; j++) { + if (i < numberOfRows && j < numberOfColumns) { + children[i*(numberOfColumns+1)+j] = new EmptyLayout(EmptyLayout::Color::Yellow); + } else { + children[i*(numberOfColumns+1)+j] = new EmptyLayout(EmptyLayout::Color::Grey); + } + } + } + ExpressionLayout * matrixLayout = new MatrixLayout(const_cast(const_cast(children)), numberOfRows+1, numberOfColumns+1, false); + m_pointedExpressionLayout->addSibling(this, matrixLayout); + setPointedExpressionLayout(matrixLayout->editableChild(0)); + setPosition(ExpressionLayoutCursor::Position::Right); +} + +void ExpressionLayoutCursor::addEmptyPowerLayout() { + VerticalOffsetLayout * offsetLayout = new VerticalOffsetLayout(new EmptyLayout(), VerticalOffsetLayout::Type::Superscript, false); + // If there is already a base + if (baseForNewPowerLayout()) { + m_pointedExpressionLayout->addSibling(this, offsetLayout); + setPointedExpressionLayout(offsetLayout->editableChild(0)); + setPosition(ExpressionLayoutCursor::Position::Right); + return; + } + // Else, add an empty base + EmptyLayout * child1 = new EmptyLayout(); + HorizontalLayout * newChild = new HorizontalLayout(child1, offsetLayout, false); + m_pointedExpressionLayout->addSibling(this, newChild); + setPointedExpressionLayout(child1); + setPosition(ExpressionLayoutCursor::Position::Right); +} + +void ExpressionLayoutCursor::addEmptySquareRootLayout() { + HorizontalLayout * child1 = new HorizontalLayout(new EmptyLayout(), false); + NthRootLayout * newChild = new NthRootLayout(child1, false); + m_pointedExpressionLayout->addSibling(this, newChild); + newChild->collapseSiblingsAndMoveCursor(this); +} + +void ExpressionLayoutCursor::addEmptySquarePowerLayout() { + CharLayout * indiceLayout = new CharLayout('2'); + VerticalOffsetLayout * offsetLayout = new VerticalOffsetLayout(indiceLayout, VerticalOffsetLayout::Type::Superscript, false); + // If there is already a base + if (baseForNewPowerLayout()) { + m_pointedExpressionLayout->addSibling(this, offsetLayout); + setPointedExpressionLayout(offsetLayout); + setPosition(ExpressionLayoutCursor::Position::Right); + return; + } + // Else, add an empty base + EmptyLayout * child1 = new EmptyLayout(); + HorizontalLayout * newChild = new HorizontalLayout(child1, offsetLayout, false); + m_pointedExpressionLayout->addSibling(this, newChild); + setPointedExpressionLayout(child1); + setPosition(ExpressionLayoutCursor::Position::Right); +} + +void ExpressionLayoutCursor::addEmptyTenPowerLayout() { + HorizontalLayout * newSibling = new HorizontalLayout(); + EmptyLayout * powerLayout = new EmptyLayout(); + ExpressionLayout * childLayouts[] = { + new CharLayout(Ion::Charset::MiddleDot), + new CharLayout('1'), + new CharLayout('0'), + new VerticalOffsetLayout(powerLayout, VerticalOffsetLayout::Type::Superscript, false)}; + newSibling->addChildrenAtIndex(childLayouts, 4, 0, false); + m_pointedExpressionLayout->addSibling(this, newSibling); + setPointedExpressionLayout(powerLayout); + setPosition(ExpressionLayoutCursor::Position::Right); +} + +void ExpressionLayoutCursor::addXNTCharLayout() { + CharLayout * newChild = new CharLayout(m_pointedExpressionLayout->XNTChar()); + m_pointedExpressionLayout->addSibling(this, newChild); + setPointedExpressionLayout(newChild); + setPosition(ExpressionLayoutCursor::Position::Right); +} + +void ExpressionLayoutCursor::insertText(const char * text) { + int textLength = strlen(text); + if (textLength <= 0) { + return; + } + ExpressionLayout * newChild = nullptr; + ExpressionLayout * pointedChild = nullptr; + for (int i = 0; i < textLength; i++) { + if (text[i] == Ion::Charset::Empty) { + continue; + } + if (text[i] == Ion::Charset::MultiplicationSign) { + newChild = new CharLayout(Ion::Charset::MiddleDot); + } else if (text[i] == '(') { + newChild = new LeftParenthesisLayout(); + if (pointedChild == nullptr) { + pointedChild = newChild; + } + } else if (text[i] == ')') { + newChild = new RightParenthesisLayout(); + } + + /* We never insert text with brackets for now. Removing this code saves the + * binary file 2K. */ +#if 0 + else if (text[i] == '[') { + newChild = new LeftSquareBracketLayout(); + } else if (text[i] == ']') { + newChild = new RightSquareBracketLayout(); + } +#endif + else { + newChild = new CharLayout(text[i]); + } + m_pointedExpressionLayout->addSibling(this, newChild); + m_pointedExpressionLayout = newChild; + m_position = Position::Right; + } + if (pointedChild != nullptr) { + m_pointedExpressionLayout = pointedChild; + } +} + +void ExpressionLayoutCursor::performBackspace() { + m_pointedExpressionLayout->deleteBeforeCursor(this); +} + +bool ExpressionLayoutCursor::showEmptyLayoutIfNeeded() { + return privateShowHideEmptyLayoutIfNeeded(true); +} + +bool ExpressionLayoutCursor::hideEmptyLayoutIfNeeded() { + return privateShowHideEmptyLayoutIfNeeded(false); +} + +void ExpressionLayoutCursor::clearLayout() { + ExpressionLayout * rootLayout = m_pointedExpressionLayout->editableRoot(); + assert(rootLayout->isHorizontal()); + static_cast(rootLayout)->removeAndDeleteChildren(); + m_pointedExpressionLayout = rootLayout; +} + +bool ExpressionLayoutCursor::privateShowHideEmptyLayoutIfNeeded(bool show) { + /* Find Empty layouts adjacent to the cursor: Check the pointed layout and the + * equivalent cursor positions */ + + ExpressionLayout * adjacentEmptyLayout = nullptr; + // Check the pointed layout + if (m_pointedExpressionLayout->isEmpty()) { + adjacentEmptyLayout = m_pointedExpressionLayout; + } else { + // Check the equivalent cursor position + ExpressionLayout * equivalentPointedLayout = m_pointedExpressionLayout->equivalentCursor(*this).pointedExpressionLayout(); + if (equivalentPointedLayout != nullptr && equivalentPointedLayout->isEmpty()) { + adjacentEmptyLayout = equivalentPointedLayout; + } + } + + if (adjacentEmptyLayout == nullptr) { + return false; + } + /* An EmptyLayout or HorizontalLayout with one child only, and this child is + * an EmptyLayout. */ + if (adjacentEmptyLayout->isHorizontal()) { + static_cast(adjacentEmptyLayout->editableChild(0))->setVisible(show); + } else { + static_cast(adjacentEmptyLayout)->setVisible(show); + } + return true; +} + +bool ExpressionLayoutCursor::baseForNewPowerLayout() { + /* Returns true if the layout on the left of the pointed layout is suitable to + * be the base of a new power layout: the base layout should be anything but + * an horizontal layout with no child. */ + if (m_position == Position::Right) { + if (m_pointedExpressionLayout->isHorizontal() && m_pointedExpressionLayout->numberOfChildren() == 0) { + return false; + } else { + return true; + } + } else { + assert(m_position == Position::Left); + if (m_pointedExpressionLayout->isHorizontal()) { + return false; + } + if (m_pointedExpressionLayout->isEmpty()) { + /* If the cursor is on the right of an EmptyLayout, move it to its right, + * make sure it is yellow, and this EmptyLayout will be the base of the + * new power layout. */ + m_position = Position::Right; + if (static_cast(m_pointedExpressionLayout)->color() == EmptyLayout::Color::Grey) { + static_cast(m_pointedExpressionLayout)->setColor(EmptyLayout::Color::Yellow); + int indexInParent = m_pointedExpressionLayout->parent()->indexOfChild(m_pointedExpressionLayout); + assert(indexInParent >= 0); + static_cast(m_pointedExpressionLayout->editableParent())->newRowOrColumnAtIndex(indexInParent); + } + return true; + } + ExpressionLayoutCursor equivalentLayoutCursor = m_pointedExpressionLayout->equivalentCursor(*this); + if (equivalentLayoutCursor.pointedExpressionLayout() == nullptr + || ( equivalentLayoutCursor.pointedExpressionLayout()->isHorizontal() + && equivalentLayoutCursor.position() == Position::Left)) + { + return false; + } + return true; + } +} + +KDCoordinate ExpressionLayoutCursor::pointedLayoutHeight() { + ExpressionLayout * equivalentPointedLayout = m_pointedExpressionLayout->equivalentCursor(*this).pointedExpressionLayout(); + if (m_pointedExpressionLayout->hasChild(equivalentPointedLayout)) { + return equivalentPointedLayout->size().height(); + } + KDCoordinate pointedLayoutHeight = m_pointedExpressionLayout->size().height(); + if (m_pointedExpressionLayout->hasSibling(equivalentPointedLayout)) { + KDCoordinate equivalentLayoutHeight = equivalentPointedLayout->size().height(); + KDCoordinate pointedLayoutBaseline = m_pointedExpressionLayout->baseline(); + KDCoordinate equivalentLayoutBaseline = equivalentPointedLayout->baseline(); + return max(pointedLayoutBaseline, equivalentLayoutBaseline) + + max(pointedLayoutHeight - pointedLayoutBaseline, equivalentLayoutHeight - equivalentLayoutBaseline); + } + return pointedLayoutHeight; +} + +} + diff --git a/poincare/src/expression_lexer.l b/poincare/src/expression_lexer.l index e6e34bca2..ea5065562 100644 --- a/poincare/src/expression_lexer.l +++ b/poincare/src/expression_lexer.l @@ -89,6 +89,10 @@ u\(n\) { poincare_expression_yylval.character = Symbol::SpecialSymbols::un; retu u\(n\+1\) { poincare_expression_yylval.character = Symbol::SpecialSymbols::un1; return SYMBOL; } v\(n\) { poincare_expression_yylval.character = Symbol::SpecialSymbols::vn; return SYMBOL; } v\(n\+1\) { poincare_expression_yylval.character = Symbol::SpecialSymbols::vn1; return SYMBOL; } +u\_\{n\} { poincare_expression_yylval.character = Symbol::SpecialSymbols::un; return SYMBOL; } +u\_\{n\+1\} { poincare_expression_yylval.character = Symbol::SpecialSymbols::un1; return SYMBOL; } +v\_\{n\} { poincare_expression_yylval.character = Symbol::SpecialSymbols::vn; return SYMBOL; } +v\_\{n\+1\} { poincare_expression_yylval.character = Symbol::SpecialSymbols::vn1; return SYMBOL; } acos { poincare_expression_yylval.expression = new ArcCosine(); return FUNCTION; } acosh { poincare_expression_yylval.expression = new HyperbolicArcCosine(); return FUNCTION; } abs { poincare_expression_yylval.expression = new AbsoluteValue(); return FUNCTION; } @@ -146,6 +150,7 @@ inf { poincare_expression_yylval.expression = new Undefined(); return UNDEFINED; \+ { return PLUS; } \- { return MINUS; } \x93 { return MULTIPLY; } +\x94 { return MULTIPLY; } \* { return MULTIPLY; } \/ { return DIVIDE; } \^ { return POW; } @@ -158,6 +163,8 @@ inf { poincare_expression_yylval.expression = new Undefined(); return UNDEFINED; \] { return RIGHT_BRACKET; } \, { return COMMA; } \. { return DOT; } +\_ { return UNDERSCORE; } +\x96 { poincare_expression_yylval.expression = new EmptyExpression(); return EMPTY; } [ ]+ /* Ignore whitespaces */ . { return UNDEFINED_SYMBOL; } diff --git a/poincare/src/expression_parser.y b/poincare/src/expression_parser.y index ac48cb3fe..027239f87 100644 --- a/poincare/src/expression_parser.y +++ b/poincare/src/expression_parser.y @@ -51,6 +51,7 @@ void poincare_expression_yyerror(Poincare::Expression ** expressionOutput, char %token SYMBOL %token FUNCTION %token UNDEFINED +%token EMPTY /* Operator tokens */ %token PLUS @@ -66,9 +67,9 @@ void poincare_expression_yyerror(Poincare::Expression ** expressionOutput, char %token LEFT_BRACKET %token RIGHT_BRACKET %token COMMA +%token UNDERSCORE %token DOT %token EE -%token ICOMPLEX %token STO %token UNDEFINED_SYMBOL @@ -92,27 +93,34 @@ void poincare_expression_yyerror(Poincare::Expression ** expressionOutput, char %nonassoc STO %left PLUS %left MINUS -%right UNARY_MINUS %left MULTIPLY %left DIVIDE %left IMPLICIT_MULTIPLY +%nonassoc UNARY_MINUS %right POW %left BANG %nonassoc LEFT_PARENTHESIS %nonassoc RIGHT_PARENTHESIS %nonassoc LEFT_BRACKET %nonassoc RIGHT_BRACKET +%nonassoc LEFT_BRACE +%nonassoc RIGHT_BRACE %nonassoc FUNCTION %left COMMA +%nonassoc UNDERSCORE %nonassoc DIGITS %nonassoc DOT %nonassoc EE -%nonassoc ICOMPLEX %nonassoc UNDEFINED %nonassoc SYMBOL +%nonassoc EMPTY /* The "exp" symbol uses the "expression" part of the union. */ %type final_exp; +%type term; +%type bang; +%type factor; +%type pow; %type exp; %type number; %type symb; @@ -125,7 +133,7 @@ void poincare_expression_yyerror(Poincare::Expression ** expressionOutput, char * have some heap-allocated data that need to be discarded. */ %destructor { delete $$; } FUNCTION -%destructor { delete $$; } UNDEFINED final_exp exp number +%destructor { delete $$; } UNDEFINED final_exp exp pow factor bang term number EMPTY %destructor { delete $$; } lstData /* MATRICES_ARE_DEFINED */ %destructor { delete $$; } mtxData @@ -137,64 +145,82 @@ Root: final_exp { *expressionOutput = $1; } + ; + +lstData: exp { $$ = new Poincare::ListData($1); } + | lstData COMMA exp { $$ = $1; $$->pushExpression($3); } + ; -lstData: - exp { $$ = new Poincare::ListData($1); } - | lstData COMMA exp { $$ = $1; $$->pushExpression($3); } /* MATRICES_ARE_DEFINED */ - mtxData: - LEFT_BRACKET lstData RIGHT_BRACKET { $$ = new Poincare::MatrixData($2, false); $2->detachOperands(); delete $2; } - | mtxData LEFT_BRACKET lstData RIGHT_BRACKET { if ($3->numberOfOperands() != $1->numberOfColumns()) { delete $1; delete $3; YYERROR; } ; $$ = $1; $$->pushListData($3, false); $3->detachOperands(); delete $3; } +mtxData: LEFT_BRACKET lstData RIGHT_BRACKET { $$ = new Poincare::MatrixData($2, false); $2->detachOperands(); delete $2; } + | mtxData LEFT_BRACKET lstData RIGHT_BRACKET { if ($3->numberOfOperands() != $1->numberOfColumns()) { delete $1; delete $3; YYERROR; } ; $$ = $1; $$->pushListData($3, false); $3->detachOperands(); delete $3; } + ; /* When approximating expressions to double, results are bounded by 1E308 (and * 1E-308 for small numbers). We thus accept decimals whose exponents are in * {-1000, 1000}. However, we have to compute the exponent first to decide - * wether to accept the decimal. The exponent of a Decimal is stored as an + * whether to accept the decimal. The exponent of a Decimal is stored as an * int32_t. We thus have to throw an error when the exponent computation might * overflow. Finally, we escape computation by throwing an error when the length * of the exponent digits is above 4 (0.00...-256 times-...01E1256=1E1000 is * accepted and 1000-...256times...-0E10000 = 1E10256, 10256 does not overflow * an int32_t). */ -number: - DIGITS { $$ = new Poincare::Rational(Poincare::Integer($1.address, false)); } - | DOT DIGITS { $$ = new Poincare::Decimal(Poincare::Decimal::mantissa(nullptr, 0, $2.address, $2.length, false), Poincare::Decimal::exponent(nullptr, 0, $2.address, $2.length, nullptr, 0, false)); } - | DIGITS DOT DIGITS { $$ = new Poincare::Decimal(Poincare::Decimal::mantissa($1.address, $1.length, $3.address, $3.length, false), Poincare::Decimal::exponent($1.address, $1.length, $3.address, $3.length, nullptr, 0, false)); } - | DOT DIGITS EE DIGITS { if ($4.length > 4) { YYERROR; }; int exponent = Poincare::Decimal::exponent(nullptr, 0, $2.address, $2.length, $4.address, $4.length, false); if (exponent > 1000 || exponent < -1000 ) { YYERROR; }; $$ = new Poincare::Decimal(Poincare::Decimal::mantissa(nullptr, 0, $2.address, $2.length, false), exponent); } - | DIGITS DOT DIGITS EE DIGITS { if ($5.length > 4) { YYERROR; }; int exponent = Poincare::Decimal::exponent($1.address, $1.length, $3.address, $3.length, $5.address, $5.length, false); if (exponent > 1000 || exponent < -1000 ) { YYERROR; }; $$ = new Poincare::Decimal(Poincare::Decimal::mantissa($1.address, $1.length, $3.address, $3.length, false), exponent); } - | DIGITS EE DIGITS { if ($3.length > 4) { YYERROR; }; int exponent = Poincare::Decimal::exponent($1.address, $1.length, nullptr, 0, $3.address, $3.length, false); if (exponent > 1000 || exponent < -1000 ) { YYERROR; }; $$ = new Poincare::Decimal(Poincare::Decimal::mantissa($1.address, $1.length, nullptr, 0, false), exponent); } - | DOT DIGITS EE MINUS DIGITS { if ($5.length > 4) { YYERROR; }; int exponent = Poincare::Decimal::exponent(nullptr, 0, $2.address, $2.length, $5.address, $5.length, true); if (exponent > 1000 || exponent < -1000 ) { YYERROR; }; $$ = new Poincare::Decimal(Poincare::Decimal::mantissa(nullptr, 0, $2.address, $2.length, false), exponent); } - | DIGITS DOT DIGITS EE MINUS DIGITS { if ($6.length > 4) { YYERROR; }; int exponent = Poincare::Decimal::exponent($1.address, $1.length, $3.address, $3.length, $6.address, $6.length, true); if (exponent > 1000 || exponent < -1000 ) { YYERROR; }; $$ = new Poincare::Decimal(Poincare::Decimal::mantissa($1.address, $1.length, $3.address, $3.length, false), exponent); } - | DIGITS EE MINUS DIGITS { if ($4.length > 4) { YYERROR; }; int exponent = Poincare::Decimal::exponent($1.address, $1.length, nullptr, 0, $4.address, $4.length, true); if (exponent > 1000 || exponent < -1000 ) { YYERROR; }; $$ = new Poincare::Decimal(Poincare::Decimal::mantissa($1.address, $1.length, nullptr, 0, false), exponent); } +number : DIGITS { $$ = new Poincare::Rational(Poincare::Integer($1.address, false)); } + | DOT DIGITS { $$ = new Poincare::Decimal(Poincare::Decimal::mantissa(nullptr, 0, $2.address, $2.length, false), Poincare::Decimal::exponent(nullptr, 0, $2.address, $2.length, nullptr, 0, false)); } + | DIGITS DOT DIGITS { $$ = new Poincare::Decimal(Poincare::Decimal::mantissa($1.address, $1.length, $3.address, $3.length, false), Poincare::Decimal::exponent($1.address, $1.length, $3.address, $3.length, nullptr, 0, false)); } + | DOT DIGITS EE DIGITS { if ($4.length > 4) { YYERROR; }; int exponent = Poincare::Decimal::exponent(nullptr, 0, $2.address, $2.length, $4.address, $4.length, false); if (exponent > 1000 || exponent < -1000 ) { YYERROR; }; $$ = new Poincare::Decimal(Poincare::Decimal::mantissa(nullptr, 0, $2.address, $2.length, false), exponent); } + | DIGITS DOT DIGITS EE DIGITS { if ($5.length > 4) { YYERROR; }; int exponent = Poincare::Decimal::exponent($1.address, $1.length, $3.address, $3.length, $5.address, $5.length, false); if (exponent > 1000 || exponent < -1000 ) { YYERROR; }; $$ = new Poincare::Decimal(Poincare::Decimal::mantissa($1.address, $1.length, $3.address, $3.length, false), exponent); } + | DIGITS EE DIGITS { if ($3.length > 4) { YYERROR; }; int exponent = Poincare::Decimal::exponent($1.address, $1.length, nullptr, 0, $3.address, $3.length, false); if (exponent > 1000 || exponent < -1000 ) { YYERROR; }; $$ = new Poincare::Decimal(Poincare::Decimal::mantissa($1.address, $1.length, nullptr, 0, false), exponent); } + | DOT DIGITS EE MINUS DIGITS { if ($5.length > 4) { YYERROR; }; int exponent = Poincare::Decimal::exponent(nullptr, 0, $2.address, $2.length, $5.address, $5.length, true); if (exponent > 1000 || exponent < -1000 ) { YYERROR; }; $$ = new Poincare::Decimal(Poincare::Decimal::mantissa(nullptr, 0, $2.address, $2.length, false), exponent); } + | DIGITS DOT DIGITS EE MINUS DIGITS { if ($6.length > 4) { YYERROR; }; int exponent = Poincare::Decimal::exponent($1.address, $1.length, $3.address, $3.length, $6.address, $6.length, true); if (exponent > 1000 || exponent < -1000 ) { YYERROR; }; $$ = new Poincare::Decimal(Poincare::Decimal::mantissa($1.address, $1.length, $3.address, $3.length, false), exponent); } + | DIGITS EE MINUS DIGITS { if ($4.length > 4) { YYERROR; }; int exponent = Poincare::Decimal::exponent($1.address, $1.length, nullptr, 0, $4.address, $4.length, true); if (exponent > 1000 || exponent < -1000 ) { YYERROR; }; $$ = new Poincare::Decimal(Poincare::Decimal::mantissa($1.address, $1.length, nullptr, 0, false), exponent); } + ; -symb: - SYMBOL { $$ = new Poincare::Symbol($1); } +symb : SYMBOL { $$ = new Poincare::Symbol($1); } + ; -/* The rules "exp MINUS exp" and "MINUS exp" are sometimes ambiguous. We want - * to favor "exp MINUS exp" over "MINUS exp". Bison by default resolves - * reduce/reduce conflicts in favor of the first grammar rule. Thus, the order - * of the grammar rules is here paramount: "MINUS exp" should always be after - * "exp MINUS exp". */ -exp: - UNDEFINED { $$ = $1; } - | exp BANG { $$ = new Poincare::Factorial($1, false); } - | number { $$ = $1; } - | symb { $$ = $1; } - | exp PLUS exp { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Addition(terms, 2, false); } - | exp MINUS exp { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Subtraction(terms, false); } - | exp MULTIPLY exp { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Multiplication(terms, 2, false); } - | exp exp %prec IMPLICIT_MULTIPLY { Poincare::Expression * terms[2] = {$1,$2}; $$ = new Poincare::Multiplication(terms, 2, false); } - | exp DIVIDE exp { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Division(terms, false); } - | exp POW exp { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Power(terms, false); } - | MINUS exp %prec UNARY_MINUS { Poincare::Expression * terms[1] = {$2}; $$ = new Poincare::Opposite(terms, false); } - | LEFT_PARENTHESIS exp RIGHT_PARENTHESIS { Poincare::Expression * terms[1] = {$2}; $$ = new Poincare::Parenthesis(terms, false); } +term : EMPTY { $$ = $1; } + | symb { $$ = $1; } + | UNDEFINED { $$ = $1; } + | number { $$ = $1; } + | FUNCTION UNDERSCORE LEFT_BRACE lstData RIGHT_BRACE LEFT_PARENTHESIS lstData RIGHT_PARENTHESIS { $$ = $1; int totalNumberOfArguments = $4->numberOfOperands()+$7->numberOfOperands(); +if (!$1->hasValidNumberOfOperands(totalNumberOfArguments)) { delete $1; delete $4; delete $7; YYERROR; }; +Poincare::ListData * arguments = new Poincare::ListData(); +for (int i = 0; i < $7->numberOfOperands(); i++) { arguments->pushExpression($7->operands()[i]); } +for (int i = 0; i < $4->numberOfOperands(); i++) { arguments->pushExpression($4->operands()[i]); } +$1->setArgument(arguments, totalNumberOfArguments, false); +$4->detachOperands(); delete $4; $7->detachOperands(); delete $7; arguments->detachOperands(); delete arguments;} + | FUNCTION LEFT_PARENTHESIS lstData RIGHT_PARENTHESIS { $$ = $1; if (!$1->hasValidNumberOfOperands($3->numberOfOperands())) { delete $1; delete $3; YYERROR; } ; $1->setArgument($3, $3->numberOfOperands(), false); $3->detachOperands(); delete $3; } + | FUNCTION LEFT_PARENTHESIS RIGHT_PARENTHESIS { $$ = $1; if (!$1->hasValidNumberOfOperands(0)) { delete $1; YYERROR; } ; } + | LEFT_PARENTHESIS exp RIGHT_PARENTHESIS { Poincare::Expression * terms[1] = {$2}; $$ = new Poincare::Parenthesis(terms, false); } /* MATRICES_ARE_DEFINED */ - | LEFT_BRACKET mtxData RIGHT_BRACKET { $$ = new Poincare::Matrix($2); delete $2; } - | FUNCTION LEFT_PARENTHESIS lstData RIGHT_PARENTHESIS { $$ = $1; if (!$1->hasValidNumberOfOperands($3->numberOfOperands())) { delete $1; delete $3; YYERROR; } ; $1->setArgument($3, $3->numberOfOperands(), false); $3->detachOperands(); delete $3; } - | FUNCTION LEFT_PARENTHESIS RIGHT_PARENTHESIS { $$ = $1; if (!$1->hasValidNumberOfOperands(0)) { delete $1; YYERROR; } ; } + | LEFT_BRACKET mtxData RIGHT_BRACKET { $$ = new Poincare::Matrix($2); delete $2; } + ; -final_exp: - exp { $$ = $1; } - | exp STO symb { if ($3->name() == Poincare::Symbol::SpecialSymbols::Ans) { delete $1; delete $3; YYERROR; } ; Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Store(terms, false); }; +bang : term { $$ = $1; } + | term BANG { $$ = new Poincare::Factorial($1, false); } + ; + +factor : bang { $$ = $1; } + | bang pow %prec IMPLICIT_MULTIPLY { Poincare::Expression * terms[2] = {$1,$2}; $$ = new Poincare::Multiplication(terms, 2, false); } + ; + +pow : factor { $$ = $1; } + | bang POW pow { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Power(terms, false); } + | bang POW MINUS pow { Poincare::Expression * terms1[1] = {$4}; Poincare::Expression * terms[2] = {$1,new Poincare::Opposite(terms1, false)}; $$ = new Poincare::Power(terms, false); } + ; + +exp : pow { $$ = $1; } + | exp DIVIDE exp { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Division(terms, false); } + | exp MULTIPLY exp { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Multiplication(terms, 2, false); } + | exp MINUS exp { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Subtraction(terms, false); } + | MINUS exp %prec UNARY_MINUS { Poincare::Expression * terms[1] = {$2}; $$ = new Poincare::Opposite(terms, false); } + | exp PLUS exp { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Addition(terms, 2, false); } + ; + +final_exp : exp { $$ = $1; } + | exp STO symb { if ($3->name() == Poincare::Symbol::SpecialSymbols::Ans) { delete $1; delete $3; YYERROR; } ; Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Store(terms, false); } + ; %% void poincare_expression_yyerror(Poincare::Expression ** expressionOutput, const char * msg) { diff --git a/poincare/src/factorial.cpp b/poincare/src/factorial.cpp index 8ab9b8408..bb1c048b7 100644 --- a/poincare/src/factorial.cpp +++ b/poincare/src/factorial.cpp @@ -1,5 +1,5 @@ #include -#include "layout/string_layout.h" +#include "layout/char_layout.h" #include "layout/horizontal_layout.h" #include #include @@ -95,10 +95,10 @@ Complex Factorial::computeOnComplex(const Complex c, AngleUnit angleUnit) ExpressionLayout * Factorial::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - ExpressionLayout * childrenLayouts[2]; - childrenLayouts[0] = operand(0)->createLayout(floatDisplayMode, complexFormat); - childrenLayouts[1] = new StringLayout("!", 1); - return new HorizontalLayout(childrenLayouts, 2); + HorizontalLayout * result = new HorizontalLayout(); + result->addOrMergeChildAtIndex(operand(0)->createLayout(floatDisplayMode, complexFormat), 0, false); + result->addChildAtIndex(new CharLayout('!'), result->numberOfChildren()); + return result; } int Factorial::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { diff --git a/poincare/src/floor.cpp b/poincare/src/floor.cpp index 4cfbceafe..fa9b02daa 100644 --- a/poincare/src/floor.cpp +++ b/poincare/src/floor.cpp @@ -59,7 +59,7 @@ Complex Floor::computeOnComplex(const Complex c, AngleUnit angleUnit) { ExpressionLayout * Floor::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - return new FloorLayout(m_operands[0]->createLayout(floatDisplayMode, complexFormat)); + return new FloorLayout(m_operands[0]->createLayout(floatDisplayMode, complexFormat), false); } } diff --git a/poincare/src/integer.cpp b/poincare/src/integer.cpp index df3ff433b..a55a19dac 100644 --- a/poincare/src/integer.cpp +++ b/poincare/src/integer.cpp @@ -1,14 +1,14 @@ #include +#include +#include +#include +#include +#include extern "C" { #include #include #include } -#include -#include -#include -#include "layout/string_layout.h" -#include namespace Poincare { @@ -603,7 +603,7 @@ int Integer::writeTextInBuffer(char * buffer, int bufferSize) const { ExpressionLayout * Integer::createLayout() const { char buffer[255]; int numberOfChars = writeTextInBuffer(buffer, 255); - return new StringLayout(buffer, numberOfChars); + return LayoutEngine::createStringLayout(buffer, numberOfChars); } template float Poincare::Integer::approximate() const; diff --git a/poincare/src/integral.cpp b/poincare/src/integral.cpp index 5f2d76989..8536c9cb5 100644 --- a/poincare/src/integral.cpp +++ b/poincare/src/integral.cpp @@ -8,7 +8,6 @@ extern "C" { #include #include } -#include "layout/string_layout.h" #include "layout/integral_layout.h" #include "layout/horizontal_layout.h" @@ -71,10 +70,11 @@ Complex * Integral::templatedApproximate(Context & context, AngleUnit angleUn ExpressionLayout * Integral::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - ExpressionLayout * childrenLayouts[2]; - childrenLayouts[0] = operand(0)->createLayout(floatDisplayMode, complexFormat); - childrenLayouts[1] = new StringLayout("dx", 2); - return new IntegralLayout(operand(1)->createLayout(floatDisplayMode, complexFormat), operand(2)->createLayout(floatDisplayMode, complexFormat), new HorizontalLayout(childrenLayouts, 2)); + return new IntegralLayout( + operand(0)->createLayout(floatDisplayMode, complexFormat), + operand(1)->createLayout(floatDisplayMode, complexFormat), + operand(2)->createLayout(floatDisplayMode, complexFormat), + false); } template diff --git a/poincare/src/layout/absolute_value_layout.cpp b/poincare/src/layout/absolute_value_layout.cpp new file mode 100644 index 000000000..32fbf72f2 --- /dev/null +++ b/poincare/src/layout/absolute_value_layout.cpp @@ -0,0 +1,10 @@ +#include "absolute_value_layout.h" + +namespace Poincare { + +ExpressionLayout * AbsoluteValueLayout::clone() const { + AbsoluteValueLayout * layout = new AbsoluteValueLayout(const_cast(this)->operandLayout(), true); + return layout; +} + +} diff --git a/poincare/src/layout/absolute_value_layout.h b/poincare/src/layout/absolute_value_layout.h index 8f31b387c..c9ad4a6a0 100644 --- a/poincare/src/layout/absolute_value_layout.h +++ b/poincare/src/layout/absolute_value_layout.h @@ -1,22 +1,24 @@ #ifndef POINCARE_ABSOLUTE_VALUE_LAYOUT_H #define POINCARE_ABSOLUTE_VALUE_LAYOUT_H -#include "bracket_layout.h" +#include "bracket_pair_layout.h" +#include +#include namespace Poincare { -class AbsoluteValueLayout : public BracketLayout { +class AbsoluteValueLayout : public BracketPairLayout { public: - AbsoluteValueLayout(ExpressionLayout * operandLayout) : BracketLayout(operandLayout) {} - ~AbsoluteValueLayout() {} - AbsoluteValueLayout(const AbsoluteValueLayout& other) = delete; - AbsoluteValueLayout(AbsoluteValueLayout&& other) = delete; - AbsoluteValueLayout& operator=(const AbsoluteValueLayout& other) = delete; - AbsoluteValueLayout& operator=(AbsoluteValueLayout&& other) = delete; + using BracketPairLayout::BracketPairLayout; + ExpressionLayout * clone() const override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writePrefixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, "abs"); + } protected: - KDCoordinate widthMargin() const { return 2; } - bool renderTopBar() const { return false; } - bool renderBottomBar() const { return false; } + KDCoordinate widthMargin() const override { return 2; } + virtual KDCoordinate verticalExternMargin() const override { return 1; } + bool renderTopBar() const override { return false; } + bool renderBottomBar() const override { return false; } }; } diff --git a/poincare/src/layout/baseline_relative_layout.cpp b/poincare/src/layout/baseline_relative_layout.cpp deleted file mode 100644 index da55937a9..000000000 --- a/poincare/src/layout/baseline_relative_layout.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "baseline_relative_layout.h" -#include -#include - -namespace Poincare { - -BaselineRelativeLayout::BaselineRelativeLayout(ExpressionLayout * baseLayout, ExpressionLayout * indiceLayout, Type type) : - ExpressionLayout(), - m_baseLayout(baseLayout), - m_indiceLayout(indiceLayout), - m_type(type) -{ - m_baseLayout->setParent(this); - m_indiceLayout->setParent(this); - m_baseline = type == Type::Subscript ? m_baseLayout->baseline() : - m_indiceLayout->size().height() + m_baseLayout->baseline() - k_indiceHeight; -} - -BaselineRelativeLayout::~BaselineRelativeLayout() { - delete m_baseLayout; - delete m_indiceLayout; -} - -ExpressionLayout * BaselineRelativeLayout::baseLayout() { - return m_baseLayout; -} - -ExpressionLayout * BaselineRelativeLayout::indiceLayout() { - return m_indiceLayout; -} - -void BaselineRelativeLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - // There is nothing to draw for a subscript/superscript, only the position of the children matters -} - -KDSize BaselineRelativeLayout::computeSize() { - KDSize baseSize = m_baseLayout->size(); - KDSize indiceSize = m_indiceLayout->size(); - return KDSize(baseSize.width() + indiceSize.width(), baseSize.height() + indiceSize.height() - k_indiceHeight); -} - -ExpressionLayout * BaselineRelativeLayout::child(uint16_t index) { - switch (index) { - case 0: - return m_baseLayout; - case 1: - return m_indiceLayout; - default: - return nullptr; - } -} - -KDPoint BaselineRelativeLayout::positionOfChild(ExpressionLayout * child) { - KDCoordinate x = 0; - KDCoordinate y = 0; - if (child == m_baseLayout && m_type == Type::Superscript) { - x = 0; - y = m_indiceLayout->size().height() - k_indiceHeight; - } - if (child == m_indiceLayout) { - x = m_baseLayout->size().width(); - y = m_type == Type::Superscript ? 0 : m_baseLayout->size().height() - k_indiceHeight; - } - return KDPoint(x,y); -} - -} - diff --git a/poincare/src/layout/baseline_relative_layout.h b/poincare/src/layout/baseline_relative_layout.h deleted file mode 100644 index f313d9212..000000000 --- a/poincare/src/layout/baseline_relative_layout.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef POINCARE_BASELINE_RELATIVE_LAYOUT_H -#define POINCARE_BASELINE_RELATIVE_LAYOUT_H - -#include -#include - -namespace Poincare { - -class BaselineRelativeLayout : public ExpressionLayout { -public: - enum class Type { - Subscript, - Superscript - }; - BaselineRelativeLayout(ExpressionLayout * baseLayout, ExpressionLayout * indiceLayout, Type type); - ~BaselineRelativeLayout(); - BaselineRelativeLayout(const BaselineRelativeLayout& other) = delete; - BaselineRelativeLayout(BaselineRelativeLayout&& other) = delete; - BaselineRelativeLayout& operator=(const BaselineRelativeLayout& other) = delete; - BaselineRelativeLayout& operator=(BaselineRelativeLayout&& other) = delete; - ExpressionLayout * baseLayout(); - ExpressionLayout * indiceLayout(); -protected: - void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; - KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; - KDPoint positionOfChild(ExpressionLayout * child) override; -private: - constexpr static KDCoordinate k_indiceHeight = 5; - ExpressionLayout * m_baseLayout; - ExpressionLayout * m_indiceLayout; - Type m_type; -}; - -} - -#endif diff --git a/poincare/src/layout/binomial_coefficient_layout.cpp b/poincare/src/layout/binomial_coefficient_layout.cpp new file mode 100644 index 000000000..1ac2f96ae --- /dev/null +++ b/poincare/src/layout/binomial_coefficient_layout.cpp @@ -0,0 +1,119 @@ +#include "binomial_coefficient_layout.h" +#include "empty_layout.h" +#include "grid_layout.h" +#include "horizontal_layout.h" +#include "left_parenthesis_layout.h" +#include "parenthesis_layout.h" +#include "right_parenthesis_layout.h" +#include +#include +extern "C" { +#include +#include +} + +namespace Poincare { + +ExpressionLayout * BinomialCoefficientLayout::clone() const { + BinomialCoefficientLayout * layout = new BinomialCoefficientLayout(const_cast(this)->nLayout(), const_cast(this)->kLayout(), true); + return layout; +} + +ExpressionLayoutCursor BinomialCoefficientLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Left of the children. Go Left. + if (cursor.position() == ExpressionLayoutCursor::Position::Left + && (cursor.pointedExpressionLayout() == nLayout() + || cursor.pointedExpressionLayout() == kLayout())) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + + assert(cursor.pointedExpressionLayout() == this); + // Case: Right. Go to the kLayout. + if (cursor.position() == ExpressionLayoutCursor::Position::Right) { + assert(kLayout() != nullptr); + return ExpressionLayoutCursor(kLayout(), ExpressionLayoutCursor::Position::Right); + } + + // Case: Left. Ask the parent. + assert(cursor.position() == ExpressionLayoutCursor::Position::Left); + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor BinomialCoefficientLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Right of the children. Go Right. + if (cursor.position() == ExpressionLayoutCursor::Position::Right + && (cursor.pointedExpressionLayout() == nLayout() + || cursor.pointedExpressionLayout() == kLayout())) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); + } + + assert(cursor.pointedExpressionLayout() == this); + // Case: Left. Go Left of the nLayout. + if (cursor.position() == ExpressionLayoutCursor::Position::Left) { + assert(nLayout() != nullptr); + return ExpressionLayoutCursor(nLayout(), ExpressionLayoutCursor::Position::Left); + } + assert(cursor.position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. Ask the parent. + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor BinomialCoefficientLayout::cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + // Case: kLayout. Move to nLayout. + if (cursor.pointedExpressionLayout()->hasAncestor(kLayout(), true)) { + return nLayout()->cursorInDescendantsAbove(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::cursorAbove(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +ExpressionLayoutCursor BinomialCoefficientLayout::cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + // Case: nLayout. Move to kLayout. + if (cursor.pointedExpressionLayout()->hasAncestor(nLayout(), true)) { + return kLayout()->cursorInDescendantsUnder(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::cursorUnder(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +void BinomialCoefficientLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + // Render the parentheses. + LeftParenthesisLayout * dummyLeftParenthesis = new LeftParenthesisLayout(); + RightParenthesisLayout * dummyRightParenthesis = new RightParenthesisLayout(); + GridLayout * dummyGridLayout = new GridLayout(ExpressionLayoutArray(nLayout(), kLayout()).array(), 2, 1, true); + HorizontalLayout dummyLayout(dummyLeftParenthesis, dummyGridLayout, dummyRightParenthesis, false); + KDPoint leftParenthesisPoint = dummyLayout.positionOfChild(dummyLeftParenthesis); + KDPoint rightParenthesisPoint = dummyLayout.positionOfChild(dummyRightParenthesis); + dummyLeftParenthesis->render(ctx, p.translatedBy(leftParenthesisPoint), expressionColor, backgroundColor); + dummyRightParenthesis->render(ctx, p.translatedBy(rightParenthesisPoint), expressionColor, backgroundColor); +} + +KDSize BinomialCoefficientLayout::computeSize() { + KDSize coefficientsSize = GridLayout(ExpressionLayoutArray(nLayout(), kLayout()).array(), 2, 1, true).size(); + return KDSize(coefficientsSize.width() + 2*ParenthesisLayout::parenthesisWidth(), coefficientsSize.height()); +} + +void BinomialCoefficientLayout::computeBaseline() { + m_baseline = GridLayout(ExpressionLayoutArray(nLayout(), kLayout()).array(), 2, 1, true).baseline(); + m_baselined = true; +} + +KDPoint BinomialCoefficientLayout::positionOfChild(ExpressionLayout * child) { + LeftParenthesisLayout * dummyLeftParenthesis = new LeftParenthesisLayout(); + RightParenthesisLayout * dummyRightParenthesis = new RightParenthesisLayout(); + GridLayout * dummyGridLayout = new GridLayout(ExpressionLayoutArray(nLayout(), kLayout()).array(), 2, 1, true); + HorizontalLayout dummyLayout(dummyLeftParenthesis, dummyGridLayout, dummyRightParenthesis, false); + if (child == nLayout()) { + return dummyGridLayout->positionOfChild(dummyGridLayout->editableChild(0)).translatedBy(dummyLayout.positionOfChild(dummyGridLayout)); + } + assert(child == kLayout()); + return dummyGridLayout->positionOfChild(dummyGridLayout->editableChild(1)).translatedBy(dummyLayout.positionOfChild(dummyGridLayout)); +} + +} diff --git a/poincare/src/layout/binomial_coefficient_layout.h b/poincare/src/layout/binomial_coefficient_layout.h new file mode 100644 index 000000000..f53ac8baf --- /dev/null +++ b/poincare/src/layout/binomial_coefficient_layout.h @@ -0,0 +1,38 @@ +#ifndef POINCARE_BINOMIAL_COEFFICIENT_LAYOUT_H +#define POINCARE_BINOMIAL_COEFFICIENT_LAYOUT_H + +#include +#include +#include + +namespace Poincare { + +class BinomialCoefficientLayout : public StaticLayoutHierarchy<2> { +public: + using StaticLayoutHierarchy::StaticLayoutHierarchy; + ExpressionLayout * clone() const override; + + // Tree navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + ExpressionLayoutCursor cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writePrefixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, "binomial"); + } + +protected: + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + KDSize computeSize() override; + void computeBaseline() override; + KDPoint positionOfChild(ExpressionLayout * child) override; +private: + ExpressionLayout * nLayout() { return editableChild(0); } + ExpressionLayout * kLayout() { return editableChild(1); } +}; + +} + +#endif diff --git a/poincare/src/layout/bounded_static_layout_hierarchy.cpp b/poincare/src/layout/bounded_static_layout_hierarchy.cpp new file mode 100644 index 000000000..12f986e61 --- /dev/null +++ b/poincare/src/layout/bounded_static_layout_hierarchy.cpp @@ -0,0 +1,37 @@ +#include +#include +extern "C" { +#include +} + +namespace Poincare { + +template +BoundedStaticLayoutHierarchy::BoundedStaticLayoutHierarchy() : + StaticLayoutHierarchy(), + m_numberOfChildren(0) +{ +} + +template +BoundedStaticLayoutHierarchy::BoundedStaticLayoutHierarchy(const ExpressionLayout * const * operands, int numberOfOperands, bool cloneOperands) : + m_numberOfChildren(numberOfOperands) +{ + StaticLayoutHierarchy::build(operands, numberOfOperands, cloneOperands); +} + +template<> +BoundedStaticLayoutHierarchy<2>::BoundedStaticLayoutHierarchy(const ExpressionLayout * e, bool cloneOperands) : + BoundedStaticLayoutHierarchy((ExpressionLayout **)&e, 1, cloneOperands) +{ +} + +template<> +BoundedStaticLayoutHierarchy<2>::BoundedStaticLayoutHierarchy(const ExpressionLayout * e1, const ExpressionLayout * e2, bool cloneOperands) : + BoundedStaticLayoutHierarchy(ExpressionLayoutArray(e1, e2).array(), 2, cloneOperands) +{ +} + +template class Poincare::BoundedStaticLayoutHierarchy<2>; + +} diff --git a/poincare/src/layout/bracket_layout.cpp b/poincare/src/layout/bracket_layout.cpp index dddd78aa9..5c92c029b 100644 --- a/poincare/src/layout/bracket_layout.cpp +++ b/poincare/src/layout/bracket_layout.cpp @@ -1,4 +1,6 @@ #include "bracket_layout.h" +#include +#include extern "C" { #include #include @@ -6,46 +8,157 @@ extern "C" { namespace Poincare { -BracketLayout::BracketLayout(ExpressionLayout * operandLayout) : - ExpressionLayout(), - m_operandLayout(operandLayout) +static inline KDCoordinate max(KDCoordinate x, KDCoordinate y) { return (x>y ? x : y); } + +BracketLayout::BracketLayout() : + StaticLayoutHierarchy<0>(), + m_operandHeightComputed(false) { - m_operandLayout->setParent(this); - m_baseline = m_operandLayout->baseline(); } -BracketLayout::~BracketLayout() { - delete m_operandLayout; +void BracketLayout::invalidAllSizesPositionsAndBaselines() { + m_operandHeightComputed = false; + ExpressionLayout::invalidAllSizesPositionsAndBaselines(); } -void BracketLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - KDSize operandSize = m_operandLayout->size(); - ctx->fillRect(KDRect(p.x(), p.y(), k_lineThickness, m_operandLayout->size().height()), expressionColor); - ctx->fillRect(KDRect(p.x()+operandSize.width()+2*widthMargin()+k_lineThickness, p.y(), k_lineThickness, m_operandLayout->size().height()), expressionColor); - if (renderTopBar()) { - ctx->fillRect(KDRect(p.x(), p.y(), k_bracketWidth, k_lineThickness), expressionColor); - ctx->fillRect(KDRect(p.x()+2*k_lineThickness+operandSize.width()+2*widthMargin()-k_bracketWidth, p.y(), k_bracketWidth, k_lineThickness), expressionColor); +ExpressionLayoutCursor BracketLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + assert(cursor.pointedExpressionLayout() == this); + // Case: Right. Go Left. + if (cursor.position() == ExpressionLayoutCursor::Position::Right) { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); } - if (renderBottomBar()) { - ctx->fillRect(KDRect(p.x(), p.y()+operandSize.height()-k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); - ctx->fillRect(KDRect(p.x()+2*k_lineThickness+operandSize.width()+2*widthMargin()-k_bracketWidth, p.y()+operandSize.height()-k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); + assert(cursor.position() == ExpressionLayoutCursor::Position::Left); + // Case: Left. Ask the parent. + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); } + return ExpressionLayoutCursor(); } -KDSize BracketLayout::computeSize() { - KDSize operandSize = m_operandLayout->size(); - return KDSize(operandSize.width() + 2*widthMargin() + 2*k_lineThickness, operandSize.height()); +ExpressionLayoutCursor BracketLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + assert(cursor.pointedExpressionLayout() == this); + // Case: Left. Go Right. + if (cursor.position() == ExpressionLayoutCursor::Position::Left) { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); + } + assert(cursor.position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. Ask the parent. + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); } -ExpressionLayout * BracketLayout::child(uint16_t index) { - if (index == 0) { - return m_operandLayout; +void BracketLayout::computeBaseline() { + assert(m_parent != nullptr); + int indexInParent = m_parent->indexOfChild(this); + int numberOfSiblings = m_parent->numberOfChildren(); + if (((isLeftParenthesis() || isLeftBracket()) && indexInParent == numberOfSiblings - 1) + || ((isRightParenthesis() || isRightBracket()) && indexInParent == 0) + || ((isLeftParenthesis() || isLeftBracket()) && indexInParent < numberOfSiblings - 1 && m_parent->child(indexInParent + 1)->isVerticalOffset())) + { + /* The bracket does not have siblings on its open direction, or it is a left + * bracket that is base of a superscript layout. In the latter case, it + * should have a default baseline, else it creates an infinite loop as the + * bracket needs the superscript baseline, which needs the bracket baseline.*/ + m_baseline = size().height()/2; + m_baselined = true; + return; } - return nullptr; + + int currentNumberOfOpenBrackets = 1; + m_baseline = 0; + int increment = (isLeftParenthesis() || isLeftBracket()) ? 1 : -1; + for (int i = indexInParent + increment; i >= 0 && i < numberOfSiblings; i+=increment) { + ExpressionLayout * sibling = m_parent->editableChild(i); + if ((isLeftParenthesis() && sibling->isRightParenthesis()) + || (isLeftBracket() && sibling->isRightBracket()) + || (isRightParenthesis() && sibling->isLeftParenthesis()) + || (isRightBracket() && sibling->isLeftBracket())) + { + if (i == indexInParent + increment) { + /* If the bracket is immediately closed, we set the baseline to half the + * bracket height. */ + m_baseline = size().height()/2; + m_baselined = true; + return; + } + currentNumberOfOpenBrackets--; + if (currentNumberOfOpenBrackets == 0) { + break; + } + } else if ((isLeftParenthesis() && sibling->isLeftParenthesis()) + || (isLeftBracket() && sibling->isLeftBracket()) + || (isRightParenthesis() && sibling->isRightParenthesis()) + || (isRightBracket() && sibling->isRightBracket())) + { + currentNumberOfOpenBrackets++; + } + m_baseline = max(m_baseline, sibling->baseline()); + } + m_baseline += (size().height() - operandHeight()) / 2; + m_baselined = true; +} + +KDCoordinate BracketLayout::operandHeight() { + if (!m_operandHeightComputed) { + computeOperandHeight(); + } + return m_operandHeight; +} + +void BracketLayout::computeOperandHeight() { + assert(m_parent != nullptr); + m_operandHeight = Metric::MinimalBracketAndParenthesisHeight; + int indexInParent = m_parent->indexOfChild(this); + int numberOfSiblings = m_parent->numberOfChildren(); + if ((isLeftParenthesis() || isLeftBracket()) + && indexInParent < numberOfSiblings - 1 + && m_parent->child(indexInParent + 1)->isVerticalOffset()) + { + /* If a left bracket is the base of a superscript layout, it should have a + * a default height, else it creates an infinite loop because the bracket + * needs the superscript height, which needs the bracket height. */ + m_operandHeightComputed = true; + return; + } + + KDCoordinate maxUnderBaseline = 0; + KDCoordinate maxAboveBaseline = 0; + + int currentNumberOfOpenBrackets = 1; + int increment = (isLeftParenthesis() || isLeftBracket()) ? 1 : -1; + for (int i = indexInParent + increment; i >= 0 && i < numberOfSiblings; i+= increment) { + ExpressionLayout * sibling = m_parent->editableChild(i); + if ((isLeftParenthesis() && sibling->isRightParenthesis()) + || (isLeftBracket() && sibling->isRightBracket()) + || (isRightParenthesis() && sibling->isLeftParenthesis()) + || (isRightBracket() && sibling->isLeftBracket())) + { + currentNumberOfOpenBrackets--; + if (currentNumberOfOpenBrackets == 0) { + break; + } + } else if ((isLeftParenthesis() && sibling->isLeftParenthesis()) + || (isLeftBracket() && sibling->isLeftBracket()) + || (isRightParenthesis() && sibling->isRightParenthesis()) + || (isRightBracket() && sibling->isRightBracket())) + { + currentNumberOfOpenBrackets++; + } + KDCoordinate siblingHeight = sibling->size().height(); + KDCoordinate siblingBaseline = sibling->baseline(); + maxUnderBaseline = max(maxUnderBaseline, siblingHeight - siblingBaseline); + maxAboveBaseline = max(maxAboveBaseline, siblingBaseline); + } + m_operandHeight = max(m_operandHeight, maxUnderBaseline + maxAboveBaseline); + m_operandHeightComputed = true; + } KDPoint BracketLayout::positionOfChild(ExpressionLayout * child) { - return KDPoint(widthMargin()+k_lineThickness, 0); + assert(false); + return KDPointZero; } } diff --git a/poincare/src/layout/bracket_layout.h b/poincare/src/layout/bracket_layout.h index 4fca30e92..8cfed2a61 100644 --- a/poincare/src/layout/bracket_layout.h +++ b/poincare/src/layout/bracket_layout.h @@ -1,34 +1,24 @@ #ifndef POINCARE_BRACKET_LAYOUT_H #define POINCARE_BRACKET_LAYOUT_H -#include -#include +#include namespace Poincare { -class BracketLayout : public ExpressionLayout { +class BracketLayout : public StaticLayoutHierarchy<0> { public: - BracketLayout(ExpressionLayout * operandLayout); - ~BracketLayout(); - BracketLayout(const BracketLayout& other) = delete; - BracketLayout(BracketLayout&& other) = delete; - BracketLayout& operator=(const BracketLayout& other) = delete; - BracketLayout& operator=(BracketLayout&& other) = delete; + BracketLayout(); + void invalidAllSizesPositionsAndBaselines() override; + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; protected: - virtual KDCoordinate widthMargin() const { return 5; } - virtual bool renderTopBar() const { return true; } - virtual bool renderBottomBar() const { return true; } - void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; - KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; + void computeBaseline() override; + KDCoordinate operandHeight(); + void computeOperandHeight(); KDPoint positionOfChild(ExpressionLayout * child) override; -private: - constexpr static KDCoordinate k_bracketWidth = 5; - constexpr static KDCoordinate k_lineThickness = 1; - ExpressionLayout * m_operandLayout; + bool m_operandHeightComputed; + uint16_t m_operandHeight; }; - } #endif - diff --git a/poincare/src/layout/bracket_pair_layout.cpp b/poincare/src/layout/bracket_pair_layout.cpp new file mode 100644 index 000000000..96a6c97ec --- /dev/null +++ b/poincare/src/layout/bracket_pair_layout.cpp @@ -0,0 +1,139 @@ +#include "bracket_pair_layout.h" +#include "horizontal_layout.h" +#include +#include +extern "C" { +#include +#include +} + +namespace Poincare { + +ExpressionLayout * BracketPairLayout::clone() const { + BracketPairLayout * layout = new BracketPairLayout(const_cast(this)->operandLayout(), true); + return layout; +} + +void BracketPairLayout::collapseSiblingsAndMoveCursor(ExpressionLayoutCursor * cursor) { + // If the operand layout is not an HorizontalLayout, replace it with one. + if (!operandLayout()->isHorizontal()) { + ExpressionLayout * previousOperand = operandLayout(); + HorizontalLayout * horizontalOperandLayout = new HorizontalLayout(previousOperand, false); + replaceChild(previousOperand, horizontalOperandLayout, false); + } + ExpressionLayout::collapseOnDirection(HorizontalDirection::Right, 0); + cursor->setPointedExpressionLayout(operandLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); +} + +void BracketPairLayout::deleteBeforeCursor(ExpressionLayoutCursor * cursor) { + if (cursor->isEquivalentTo(ExpressionLayoutCursor(operandLayout(), ExpressionLayoutCursor::Position::Left))) { + // Case: Left of the operand. Delete the layout, keep the operand. + replaceWithAndMoveCursor(operandLayout(), true, cursor); + return; + } + ExpressionLayout::deleteBeforeCursor(cursor); +} + +ExpressionLayoutCursor BracketPairLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Left of the operand. Go Left of the brackets. + if (operandLayout() + && cursor.pointedExpressionLayout() == operandLayout() + && cursor.position() == ExpressionLayoutCursor::Position::Left) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Right of the brackets. Go Right of the operand. + if (cursor.position() == ExpressionLayoutCursor::Position::Right) { + assert(operandLayout() != nullptr); + return ExpressionLayoutCursor(operandLayout(), ExpressionLayoutCursor::Position::Right); + } + assert(cursor.position() == ExpressionLayoutCursor::Position::Left); + // Case: Left of the brackets. Ask the parent. + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor BracketPairLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Right of the operand. Go Right of the brackets. + if (operandLayout() + && cursor.pointedExpressionLayout() == operandLayout() + && cursor.position() == ExpressionLayoutCursor::Position::Right) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Left of the brackets. Go Left of the operand. + if (cursor.position() == ExpressionLayoutCursor::Position::Left) { + assert(operandLayout() != nullptr); + return ExpressionLayoutCursor(operandLayout(), ExpressionLayoutCursor::Position::Left); + } + assert(cursor.position() == ExpressionLayoutCursor::Position::Right); + // Case: Right of the brackets. Ask the parent. + cursor.setPointedExpressionLayout(this); + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +int BracketPairLayout::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { + if (bufferSize == 0) { + return -1; + } + buffer[bufferSize-1] = 0; + + // Write the opening bracket + int numberOfChar = 0; + buffer[numberOfChar++] = '['; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + + // Write the argument + numberOfChar += const_cast(this)->operandLayout()->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the closing bracket + buffer[numberOfChar++] = ']'; + buffer[numberOfChar] = 0; + return numberOfChar; +} + +void BracketPairLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + const KDCoordinate k_widthMargin = widthMargin(); + const KDCoordinate k_externWidthMargin = externWidthMargin(); + KDSize operandSize = operandLayout()->size(); + KDCoordinate verticalBarHeight = operandLayout()->size().height() + 2*k_verticalMargin; + ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y()+verticalExternMargin(), k_lineThickness, verticalBarHeight), expressionColor); + ctx->fillRect(KDRect(p.x()+k_externWidthMargin+operandSize.width()+2*k_widthMargin+k_lineThickness, p.y()+verticalExternMargin(), k_lineThickness, verticalBarHeight), expressionColor); + if (renderTopBar()) { + ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y()+verticalExternMargin(), k_bracketWidth, k_lineThickness), expressionColor); + ctx->fillRect(KDRect(p.x()+k_externWidthMargin+2*k_lineThickness+operandSize.width()+2*k_widthMargin-k_bracketWidth, p.y() + verticalExternMargin(), k_bracketWidth, k_lineThickness), expressionColor); + } + if (renderBottomBar()) { + ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y()+verticalExternMargin()+verticalBarHeight-k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); + ctx->fillRect(KDRect(p.x()+k_externWidthMargin+2*k_lineThickness+operandSize.width()+2*k_widthMargin-k_bracketWidth, p.y()+verticalExternMargin()+verticalBarHeight-k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); + } +} + +KDSize BracketPairLayout::computeSize() { + const KDCoordinate k_widthMargin = widthMargin(); + const KDCoordinate k_externWidthMargin = externWidthMargin(); + KDSize operandSize = operandLayout()->size(); + return KDSize(operandSize.width() + 2*k_externWidthMargin + 2*k_widthMargin + 2*k_lineThickness, operandSize.height() + 2 * k_verticalMargin + 2*verticalExternMargin()); +} + +void BracketPairLayout::computeBaseline() { + m_baseline = operandLayout()->baseline() + k_verticalMargin + verticalExternMargin(); + m_baselined = true; +} + +KDPoint BracketPairLayout::positionOfChild(ExpressionLayout * child) { + const KDCoordinate k_widthMargin = widthMargin(); + const KDCoordinate k_externWidthMargin = externWidthMargin(); + return KDPoint(k_widthMargin+k_externWidthMargin+k_lineThickness, k_verticalMargin + verticalExternMargin()); +} + +} diff --git a/poincare/src/layout/bracket_pair_layout.h b/poincare/src/layout/bracket_pair_layout.h new file mode 100644 index 000000000..25fbef9b9 --- /dev/null +++ b/poincare/src/layout/bracket_pair_layout.h @@ -0,0 +1,47 @@ +#ifndef POINCARE_BRACKET_PAIR_LAYOUT_H +#define POINCARE_BRACKET_PAIR_LAYOUT_H + +#include +#include + +namespace Poincare { + +class BracketPairLayout : public StaticLayoutHierarchy<1> { + friend class MatrixLayout; +public: + using StaticLayoutHierarchy::StaticLayoutHierarchy; + ExpressionLayout * clone() const override; + + // Collapse + void collapseSiblingsAndMoveCursor(ExpressionLayoutCursor * cursor) override; + + // User input + void deleteBeforeCursor(ExpressionLayoutCursor * cursor) override; + + // Tree navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; +protected: + ExpressionLayout * operandLayout() { return editableChild(0); } + KDCoordinate externWidthMargin() const { return 2; } + virtual KDCoordinate widthMargin() const { return 5; } + virtual KDCoordinate verticalExternMargin() const { return 0; } + virtual bool renderTopBar() const { return true; } + virtual bool renderBottomBar() const { return true; } + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + KDSize computeSize() override; + void computeBaseline() override; + KDPoint positionOfChild(ExpressionLayout * child) override; +private: + constexpr static KDCoordinate k_bracketWidth = 5; + constexpr static KDCoordinate k_lineThickness = 1; + constexpr static KDCoordinate k_verticalMargin = 1; +}; + +} + +#endif + diff --git a/poincare/src/layout/ceiling_layout.cpp b/poincare/src/layout/ceiling_layout.cpp new file mode 100644 index 000000000..0e04baf2b --- /dev/null +++ b/poincare/src/layout/ceiling_layout.cpp @@ -0,0 +1,10 @@ +#include "ceiling_layout.h" + +namespace Poincare { + +ExpressionLayout * CeilingLayout::clone() const { + CeilingLayout * layout = new CeilingLayout(const_cast(this)->operandLayout(), true); + return layout; +} + +} diff --git a/poincare/src/layout/ceiling_layout.h b/poincare/src/layout/ceiling_layout.h index 6fe8d60a8..0a456ed6a 100644 --- a/poincare/src/layout/ceiling_layout.h +++ b/poincare/src/layout/ceiling_layout.h @@ -1,18 +1,18 @@ #ifndef POINCARE_CEILING_LAYOUT_H #define POINCARE_CEILING_LAYOUT_H -#include "bracket_layout.h" +#include "bracket_pair_layout.h" +#include namespace Poincare { -class CeilingLayout : public BracketLayout { +class CeilingLayout : public BracketPairLayout { public: - CeilingLayout(ExpressionLayout * operandLayout) : BracketLayout(operandLayout) {} - ~CeilingLayout() {} - CeilingLayout(const CeilingLayout& other) = delete; - CeilingLayout(CeilingLayout&& other) = delete; - CeilingLayout& operator=(const CeilingLayout& other) = delete; - CeilingLayout& operator=(CeilingLayout&& other) = delete; + using BracketPairLayout::BracketPairLayout; + ExpressionLayout * clone() const override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writePrefixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, "ceil"); + } protected: bool renderBottomBar() const override { return false; } }; diff --git a/poincare/src/layout/char_layout.cpp b/poincare/src/layout/char_layout.cpp new file mode 100644 index 000000000..0a4cd9018 --- /dev/null +++ b/poincare/src/layout/char_layout.cpp @@ -0,0 +1,76 @@ +#include "char_layout.h" +#include +#include +#include +#include + +namespace Poincare { + +CharLayout::CharLayout(char c, KDText::FontSize fontSize) : + StaticLayoutHierarchy<0>(), + m_char(c), + m_fontSize(fontSize) +{ +} + +ExpressionLayout * CharLayout::clone() const { + CharLayout * layout = new CharLayout(m_char, m_fontSize); + return layout; +} + +ExpressionLayoutCursor CharLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + assert(cursor.pointedExpressionLayout() == this); + // Case: Right. Go Left. + if (cursor.position() == ExpressionLayoutCursor::Position::Right) { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + // Case: Left. Ask the parent. + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor CharLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + assert(cursor.pointedExpressionLayout() == this); + // Case: Left. Go Right. + if (cursor.position() == ExpressionLayoutCursor::Position::Left) { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); + } + // Case: Right. Ask the parent. + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +bool CharLayout::isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { + if (*numberOfOpenParenthesis <= 0 + && (m_char == '+' + || m_char == '-' + || m_char == '*' + || m_char == Ion::Charset::MultiplicationSign + || m_char == Ion::Charset::MiddleDot + || m_char == ',')) + { + return false; + } + return true; +} + +void CharLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + char string[2] = {m_char, 0}; + ctx->drawString(string, p, m_fontSize, expressionColor, backgroundColor); +} + +KDSize CharLayout::computeSize() { + return KDText::charSize(m_fontSize); +} + +void CharLayout::computeBaseline() { + // Half height of the font. + m_baseline = (KDText::charSize(m_fontSize).height()+1)/2; + m_baselined = true; +} + +} diff --git a/poincare/src/layout/char_layout.h b/poincare/src/layout/char_layout.h new file mode 100644 index 000000000..79da87bd2 --- /dev/null +++ b/poincare/src/layout/char_layout.h @@ -0,0 +1,45 @@ +#ifndef POINCARE_CHAR_LAYOUT_H +#define POINCARE_CHAR_LAYOUT_H + +#include +#include +#include +#include + +namespace Poincare { + +class CharLayout : public StaticLayoutHierarchy<0> { +public: + CharLayout(char c, KDText::FontSize fontSize = KDText::FontSize::Large); + ExpressionLayout * clone() const override; + + // CharLayout + char character() const { return m_char; } + KDText::FontSize fontSize() const { return m_fontSize; } + + // Tree navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writeOneCharInBuffer(buffer, bufferSize, m_char); + } + + // Other + bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; +protected: + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + KDPoint positionOfChild(ExpressionLayout * child) override { + assert(false); + return KDPointZero; + } + KDSize computeSize() override; + void computeBaseline() override; + char m_char; + KDText::FontSize m_fontSize; +}; + +} + +#endif diff --git a/poincare/src/layout/condensed_sum_layout.cpp b/poincare/src/layout/condensed_sum_layout.cpp index e2c20e853..1be4db0b5 100644 --- a/poincare/src/layout/condensed_sum_layout.cpp +++ b/poincare/src/layout/condensed_sum_layout.cpp @@ -1,69 +1,41 @@ #include "condensed_sum_layout.h" +#include #include #include namespace Poincare { -CondensedSumLayout::CondensedSumLayout(ExpressionLayout * baseLayout, ExpressionLayout * subscriptLayout, ExpressionLayout * superscriptLayout) : - ExpressionLayout(), - m_baseLayout(baseLayout), - m_subscriptLayout(subscriptLayout), - m_superscriptLayout(superscriptLayout) -{ - m_baseLayout->setParent(this); - m_subscriptLayout->setParent(this); - if (m_superscriptLayout) { - m_superscriptLayout->setParent(this); - } - KDSize superscriptSize = m_superscriptLayout == nullptr ? KDSizeZero : m_superscriptLayout->size(); - m_baseline = m_baseLayout->baseline() + max(0, superscriptSize.height() - m_baseLayout->size().height()/2); -} - -CondensedSumLayout::~CondensedSumLayout() { - delete m_baseLayout; - delete m_subscriptLayout; - if (m_superscriptLayout) { - delete m_superscriptLayout; - } -} - -void CondensedSumLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - // Nothing to draw +ExpressionLayout * CondensedSumLayout::clone() const { + CondensedSumLayout * layout = new CondensedSumLayout(const_cast(this)->baseLayout(), const_cast(this)->subscriptLayout(), const_cast(this)->superscriptLayout(), true); + return layout; } KDSize CondensedSumLayout::computeSize() { - KDSize baseSize = m_baseLayout->size(); - KDSize subscriptSize = m_subscriptLayout->size(); - KDSize superscriptSize = m_superscriptLayout == nullptr ? KDSizeZero : m_superscriptLayout->size(); + KDSize baseSize = baseLayout()->size(); + KDSize subscriptSize = subscriptLayout()->size(); + KDSize superscriptSize = superscriptLayout() == nullptr ? KDSizeZero : superscriptLayout()->size(); return KDSize(baseSize.width() + max(subscriptSize.width(), superscriptSize.width()), max(baseSize.height()/2, subscriptSize.height()) + max(baseSize.height()/2, superscriptSize.height())); } -ExpressionLayout * CondensedSumLayout::child(uint16_t index) { - switch (index) { - case 0: - return m_baseLayout; - case 1: - return m_subscriptLayout; - case 2: - return m_superscriptLayout; - default: - return nullptr; - } +void CondensedSumLayout::computeBaseline() { + KDSize superscriptSize = superscriptLayout() == nullptr ? KDSizeZero : superscriptLayout()->size(); + m_baseline = baseLayout()->baseline() + max(0, superscriptSize.height() - baseLayout()->size().height()/2); + m_baselined = true; } KDPoint CondensedSumLayout::positionOfChild(ExpressionLayout * child) { KDCoordinate x = 0; KDCoordinate y = 0; - KDSize baseSize = m_baseLayout->size(); - KDSize superscriptSize = m_superscriptLayout == nullptr ? KDSizeZero : m_superscriptLayout->size(); - if (child == m_baseLayout) { + KDSize baseSize = baseLayout()->size(); + KDSize superscriptSize = superscriptLayout() == nullptr ? KDSizeZero : superscriptLayout()->size(); + if (child == baseLayout()) { y = max(0, superscriptSize.height() - baseSize.height()/2); } - if (child == m_subscriptLayout) { + if (child == subscriptLayout()) { x = baseSize.width(); y = max(baseSize.height()/2, superscriptSize.height()); } - if (child == m_superscriptLayout) { + if (child == superscriptLayout()) { x = baseSize.width(); } return KDPoint(x,y); diff --git a/poincare/src/layout/condensed_sum_layout.h b/poincare/src/layout/condensed_sum_layout.h index dc52f4e5f..372c9d6e8 100644 --- a/poincare/src/layout/condensed_sum_layout.h +++ b/poincare/src/layout/condensed_sum_layout.h @@ -1,28 +1,56 @@ #ifndef POINCARE_CONDENSED_SUM_LAYOUT_H #define POINCARE_CONDENSED_SUM_LAYOUT_H -#include -#include +#include +#include +#include namespace Poincare { -class CondensedSumLayout : public ExpressionLayout { +class CondensedSumLayout : public StaticLayoutHierarchy<3> { public: - CondensedSumLayout(ExpressionLayout * baseLayout, ExpressionLayout * subscriptLayout, ExpressionLayout * superscriptLayout = nullptr); - ~CondensedSumLayout(); - CondensedSumLayout(const CondensedSumLayout& other) = delete; - CondensedSumLayout(CondensedSumLayout&& other) = delete; - CondensedSumLayout& operator=(const CondensedSumLayout& other) = delete; - CondensedSumLayout& operator=(CondensedSumLayout&& other) = delete; + using StaticLayoutHierarchy::StaticLayoutHierarchy; + ExpressionLayout * clone() const override; + + /* Tree navigation + * CondensedSumLayout is only used in apps/shared/sum_graph_controller.cpp, in + * a view with no cursor. */ + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override { + assert(false); + return ExpressionLayoutCursor(); + } + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override { + assert(false); + return ExpressionLayoutCursor(); + } + ExpressionLayoutCursor cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override { + assert(false); + return ExpressionLayoutCursor(); + } + ExpressionLayoutCursor cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override { + assert(false); + return ExpressionLayoutCursor(); + } + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writePrefixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, "sum"); + } + + // Other + ExpressionLayout * layoutToPointWhenInserting() override { + assert(false); + return nullptr; + } protected: - void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override {} KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; + void computeBaseline() override; KDPoint positionOfChild(ExpressionLayout * child) override; private: - ExpressionLayout * m_baseLayout; - ExpressionLayout * m_subscriptLayout; - ExpressionLayout * m_superscriptLayout; + ExpressionLayout * baseLayout() { return editableChild(0); } + ExpressionLayout * subscriptLayout() { return editableChild(1); } + ExpressionLayout * superscriptLayout() { return editableChild(2); } }; } diff --git a/poincare/src/layout/conjugate_layout.cpp b/poincare/src/layout/conjugate_layout.cpp index ca0aa4cef..135fb5150 100644 --- a/poincare/src/layout/conjugate_layout.cpp +++ b/poincare/src/layout/conjugate_layout.cpp @@ -1,4 +1,8 @@ #include "conjugate_layout.h" +#include "empty_layout.h" +#include "horizontal_layout.h" +#include +#include extern "C" { #include #include @@ -6,36 +10,101 @@ extern "C" { namespace Poincare { -ConjugateLayout::ConjugateLayout(ExpressionLayout * operandLayout) : - ExpressionLayout(), - m_operandLayout(operandLayout) -{ - m_operandLayout->setParent(this); - m_baseline = m_operandLayout->baseline()+k_overlineWidth+k_overlineMargin; +ExpressionLayout * ConjugateLayout::clone() const { + ConjugateLayout * layout = new ConjugateLayout(const_cast(this)->operandLayout(), true); + return layout; } -ConjugateLayout::~ConjugateLayout() { - delete m_operandLayout; +void ConjugateLayout::collapseSiblingsAndMoveCursor(ExpressionLayoutCursor * cursor) { + // If the operand layouts is not HorizontalLayouts, replace it with one. + if (!operandLayout()->isHorizontal()) { + ExpressionLayout * previousOperand = operandLayout(); + HorizontalLayout * horizontalOperandLayout = new HorizontalLayout(previousOperand, false); + replaceChild(previousOperand, horizontalOperandLayout, false); + } + ExpressionLayout::collapseOnDirection(HorizontalDirection::Right, 0); + cursor->setPointedExpressionLayout(operandLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); +} + +void ConjugateLayout::replaceChildAndMoveCursor(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) { + assert(oldChild == operandLayout()); + if (newChild->isEmpty()) { + if (!deleteOldChild) { + detachChild(oldChild); + } + replaceWithAndMoveCursor(newChild, true, cursor); + return; + } + ExpressionLayout::replaceChildAndMoveCursor(oldChild, newChild, deleteOldChild, cursor); +} + +void ConjugateLayout::removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor) { + assert(index >= 0 && index < numberOfChildren()); + assert(cursor->pointedExpressionLayout()->hasAncestor(child(index), true)); + replaceChildAndMoveCursor(child(index), new EmptyLayout(), deleteAfterRemoval, cursor); +} + +ExpressionLayoutCursor ConjugateLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Left of the operand. Move Left. + if (operandLayout() + && cursor.pointedExpressionLayout() == operandLayout() + && cursor.position() == ExpressionLayoutCursor::Position::Left) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Right. Go to the operand. + if (cursor.position() == ExpressionLayoutCursor::Position::Right) { + assert(operandLayout() != nullptr); + return ExpressionLayoutCursor(operandLayout(), ExpressionLayoutCursor::Position::Right); + } + // Case: Left. Ask the parent. + assert(cursor.position() == ExpressionLayoutCursor::Position::Left); + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor ConjugateLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Right of the operand. Move Right. + if (operandLayout() + && cursor.pointedExpressionLayout() == operandLayout() + && cursor.position() == ExpressionLayoutCursor::Position::Right) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Left. Go to the operand. + if (cursor.position() == ExpressionLayoutCursor::Position::Left) { + assert(operandLayout() != nullptr); + return ExpressionLayoutCursor(operandLayout(), ExpressionLayoutCursor::Position::Left); + } + // Case: Right. Ask the parent. + assert(cursor.position() == ExpressionLayoutCursor::Position::Right); + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); } void ConjugateLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - ctx->fillRect(KDRect(p.x(), p.y(), m_operandLayout->size().width(), k_overlineWidth), expressionColor); + ctx->fillRect(KDRect(p.x()+Metric::FractionAndConjugateHorizontalMargin, p.y(), operandLayout()->size().width()+2*Metric::FractionAndConjugateHorizontalOverflow, k_overlineWidth), expressionColor); } KDSize ConjugateLayout::computeSize() { - KDSize operandSize = m_operandLayout->size(); - return KDSize(operandSize.width(), operandSize.height()+k_overlineWidth+k_overlineMargin); + KDSize operandSize = operandLayout()->size(); + return KDSize(Metric::FractionAndConjugateHorizontalMargin+Metric::FractionAndConjugateHorizontalOverflow+operandSize.width()+Metric::FractionAndConjugateHorizontalOverflow+Metric::FractionAndConjugateHorizontalMargin, operandSize.height()+k_overlineWidth+k_overlineVerticalMargin); } -ExpressionLayout * ConjugateLayout::child(uint16_t index) { - if (index == 0) { - return m_operandLayout; - } - return nullptr; +void ConjugateLayout::computeBaseline() { + m_baseline = operandLayout()->baseline()+k_overlineWidth+k_overlineVerticalMargin; + m_baselined = true; } KDPoint ConjugateLayout::positionOfChild(ExpressionLayout * child) { - return KDPoint(0, k_overlineWidth+k_overlineMargin); + return KDPoint(Metric::FractionAndConjugateHorizontalMargin+Metric::FractionAndConjugateHorizontalOverflow, k_overlineWidth+k_overlineVerticalMargin); } } diff --git a/poincare/src/layout/conjugate_layout.h b/poincare/src/layout/conjugate_layout.h index 29ebdfaa3..e29f3fc8a 100644 --- a/poincare/src/layout/conjugate_layout.h +++ b/poincare/src/layout/conjugate_layout.h @@ -1,28 +1,40 @@ #ifndef POINCARE_CONJUGATE_LAYOUT_H #define POINCARE_CONJUGATE_LAYOUT_H -#include -#include +#include +#include namespace Poincare { -class ConjugateLayout : public ExpressionLayout { +class ConjugateLayout : public StaticLayoutHierarchy<1> { public: - ConjugateLayout(ExpressionLayout * operandLayout); - ~ConjugateLayout(); - ConjugateLayout(const ConjugateLayout& other) = delete; - ConjugateLayout(ConjugateLayout&& other) = delete; - ConjugateLayout& operator=(const ConjugateLayout& other) = delete; - ConjugateLayout& operator=(ConjugateLayout&& other) = delete; + using StaticLayoutHierarchy::StaticLayoutHierarchy; + ExpressionLayout * clone() const override; + + // Collapse + void collapseSiblingsAndMoveCursor(ExpressionLayoutCursor * cursor) override; + // Replace + void replaceChildAndMoveCursor(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) override; + // Remove + void removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor) override; + + // Tree navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writePrefixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, "conj"); + } protected: void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; + void computeBaseline() override; KDPoint positionOfChild(ExpressionLayout * child) override; private: constexpr static KDCoordinate k_overlineWidth = 1; - constexpr static KDCoordinate k_overlineMargin = 3; - ExpressionLayout * m_operandLayout; + constexpr static KDCoordinate k_overlineVerticalMargin = 1; + ExpressionLayout * operandLayout() { return editableChild(0); } }; } diff --git a/poincare/src/layout/dynamic_layout_hierarchy.cpp b/poincare/src/layout/dynamic_layout_hierarchy.cpp new file mode 100644 index 000000000..ba55192b8 --- /dev/null +++ b/poincare/src/layout/dynamic_layout_hierarchy.cpp @@ -0,0 +1,173 @@ +#include +#include "empty_layout.h" +#include +extern "C" { +#include +#include +} + +namespace Poincare { + +DynamicLayoutHierarchy::DynamicLayoutHierarchy() : + ExpressionLayout(), + m_children(nullptr), + m_numberOfChildren(0) +{ +} + +DynamicLayoutHierarchy::DynamicLayoutHierarchy(const ExpressionLayout * const * children, int numberOfChildren, bool cloneChildren) : + ExpressionLayout(), + m_numberOfChildren(numberOfChildren) +{ + assert(children != nullptr); + m_children = new const ExpressionLayout * [numberOfChildren]; + for (int i = 0; i < numberOfChildren; i++) { + assert(children[i] != nullptr); + if (cloneChildren) { + m_children[i] = children[i]->clone(); + } else { + m_children[i] = children[i]; + } + const_cast(m_children[i])->setParent(this); + } +} + +DynamicLayoutHierarchy::~DynamicLayoutHierarchy() { + removeAndDeleteChildren(); +} + +void DynamicLayoutHierarchy::mergeChildrenAtIndex(DynamicLayoutHierarchy * eL, int index, bool removeEmptyChildren) { + int indexForInsertion = index; + int indexOfEL = indexOfChild(eL); + if (indexOfEL >= 0) { + removeChildAtIndex(indexOfEL, false); + if (indexOfEL < index) { + indexForInsertion--; + } + } + addChildrenAtIndex(eL->children(), eL->numberOfChildren(), indexForInsertion, removeEmptyChildren); + eL->removeDetachedChildren(); + delete eL; +} + +void DynamicLayoutHierarchy::addChildrenAtIndex(const ExpressionLayout * const * operands, int numberOfOperands, int indexForInsertion, bool removeEmptyChildren) { + assert(numberOfOperands > 0); + const ExpressionLayout ** newOperands = new const ExpressionLayout * [m_numberOfChildren+numberOfOperands]; + int currentIndex = 0; + assert(indexForInsertion >= 0 && indexForInsertion <= m_numberOfChildren); + for (int i = 0; i < indexForInsertion; i++) { + newOperands[currentIndex++] = m_children[i]; + } + for (int i = 0; i < numberOfOperands; i++) { + if (i == 0 + && operands[0]->isEmpty() + && numberOfOperands > 1 + && operands[1]->mustHaveLeftSibling() + && indexForInsertion > 0) + { + /* If the first added operand is Empty because its right sibling needs a + * left sibling, remove it if any previous child could be such a left + * sibling. */ + continue; + } + if (!removeEmptyChildren + || !operands[i]->isEmpty() + || (i < numberOfOperands-1 && operands[i+1]->mustHaveLeftSibling())) + { + const_cast(operands[i])->setParent(this); + newOperands[currentIndex++] = operands[i]; + } + } + for (int i = indexForInsertion; i < m_numberOfChildren; i++) { + newOperands[currentIndex++] = m_children[i]; + } + if (m_children != nullptr) { + delete[] m_children; + } + m_children = newOperands; + m_numberOfChildren = currentIndex; +} + +bool DynamicLayoutHierarchy::addChildAtIndex(ExpressionLayout * child, int index) { + assert(index >= 0 && index <= m_numberOfChildren); + const ExpressionLayout ** newChildren = new const ExpressionLayout * [m_numberOfChildren+1]; + int j = 0; + for (int i = 0; i <= m_numberOfChildren; i++) { + if (i == index) { + child->setParent(this); + newChildren[i] = child; + } else { + newChildren[i] = m_children[j++]; + } + } + delete[] m_children; + m_children = newChildren; + m_numberOfChildren += 1; + return true; +} + +void DynamicLayoutHierarchy::removeChildAtIndex(int index, bool deleteAfterRemoval) { + assert(index >= 0 && index < m_numberOfChildren); + if (deleteAfterRemoval) { + delete m_children[index]; + } else { + const_cast(m_children[index])->setParent(nullptr); + } + m_numberOfChildren--; + for (int j=index; j= 0 && index < numberOfChildren()); + assert(cursor->pointedExpressionLayout()->hasAncestor(child(index), true)); + if (numberOfChildren() == 1) { + if (m_parent) { + if (!deleteAfterRemoval) { + detachChild(editableChild(0)); + } + m_parent->removePointedChildAtIndexAndMoveCursor(m_parent->indexOfChild(this), true, cursor); + return; + } + removeChildAtIndex(index, deleteAfterRemoval); + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return; + } + removeChildAtIndex(index, deleteAfterRemoval); + if (index < numberOfChildren()) { + cursor->setPointedExpressionLayout(editableChild(index)); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return; + } + int indexOfNewPointedLayout = index - 1; + assert(indexOfNewPointedLayout >= 0); + assert(indexOfNewPointedLayout < numberOfChildren()); + cursor->setPointedExpressionLayout(editableChild(indexOfNewPointedLayout)); +} + +void DynamicLayoutHierarchy::removeAndDeleteChildren() { + if (m_children != nullptr) { + for (int i = 0; i < m_numberOfChildren; i++) { + if (m_children[i] != nullptr) { + delete m_children[i]; + } + } + } + delete[] m_children; + m_children = nullptr; + m_numberOfChildren = 0; +} + +void DynamicLayoutHierarchy::removeDetachedChildren() { + int currentIndex = 0; + for (int i = 0; i < m_numberOfChildren; i++) { + if (m_children[i] != nullptr && m_children[i]->parent() == this) { + m_children[currentIndex++] = m_children[i]; + } + } + m_numberOfChildren = currentIndex; +} + +} diff --git a/poincare/src/layout/empty_layout.cpp b/poincare/src/layout/empty_layout.cpp new file mode 100644 index 000000000..535599ec5 --- /dev/null +++ b/poincare/src/layout/empty_layout.cpp @@ -0,0 +1,109 @@ +#include "empty_layout.h" +#include "matrix_layout.h" +#include +#include +#include + +namespace Poincare { + +EmptyLayout::EmptyLayout(Color color, bool visible, KDText::FontSize size, bool margins) : + StaticLayoutHierarchy(), + m_isVisible(visible), + m_color(color), + m_size(size), + m_margins(margins) +{ +} + +ExpressionLayout * EmptyLayout::clone() const { + EmptyLayout * layout = new EmptyLayout(m_color, m_isVisible, m_size, m_margins); + return layout; +} + +void EmptyLayout::deleteBeforeCursor(ExpressionLayoutCursor * cursor) { + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + if (m_parent) { + return m_parent->deleteBeforeCursor(cursor); + } +} + +ExpressionLayoutCursor EmptyLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + assert(cursor.pointedExpressionLayout() == this); + // Ask the parent. + if (m_parent) { + cursor.setPosition(ExpressionLayoutCursor::Position::Left); + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor EmptyLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + assert(cursor.pointedExpressionLayout() == this); + // Ask the parent. + if (m_parent) { + cursor.setPosition(ExpressionLayoutCursor::Position::Right); + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +int EmptyLayout::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { + if (bufferSize == 0) { + return -1; + } + buffer[0] = 0; + return 0; +} + +void EmptyLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + if (m_isVisible) { + KDColor fillColor = m_color == Color::Yellow ? Palette::YellowDark : Palette::GreyBright; + ctx->fillRect(KDRect(p.x()+(m_margins ? k_marginWidth : 0), p.y()+(m_margins ? k_marginHeight : 0), width(), height()), fillColor); + ctx->fillRect(KDRect(p.x()+(m_margins ? k_marginWidth : 0), p.y()+(m_margins ? k_marginHeight : 0), width(), height()), fillColor); + } +} + +KDSize EmptyLayout::computeSize() { + KDCoordinate sizeWidth = m_isVisible ? width() + 2*(m_margins ? k_marginWidth : 0) : 0; + return KDSize(sizeWidth, height() + 2*(m_margins ? k_marginHeight : 0)); +} + +void EmptyLayout::computeBaseline() { + m_baseline = (m_margins ? k_marginHeight : 0) + height()/2; + m_baselined = true; +} + +void EmptyLayout::privateAddSibling(ExpressionLayoutCursor * cursor, ExpressionLayout * sibling, bool moveCursor) { + Color currentColor = m_color; + int indexInParent = m_parent->indexOfChild(this); + ExpressionLayout * parent = m_parent; + if (sibling->mustHaveLeftSibling()) { + m_color = Color::Yellow; + ExpressionLayout::privateAddSibling(cursor, sibling, moveCursor); + } else { + if (moveCursor) { + replaceWithAndMoveCursor(sibling, true, cursor); + } else { + replaceWith(sibling, true); + } + } + if (currentColor == Color::Grey) { + // The parent is a MatrixLayout. + static_cast(parent)->newRowOrColumnAtIndex(indexInParent); + } +} + +ExpressionLayoutCursor EmptyLayout::cursorVerticalOf(VerticalDirection direction, ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + /* The two cursor positions around an EmptyLayout are equivalent, so both + * should be checked. */ + assert(cursor.pointedExpressionLayout() == this); + ExpressionLayoutCursor cursorResult = ExpressionLayout::cursorVerticalOf(direction, cursor, shouldRecomputeLayout, equivalentPositionVisited); + if (cursorResult.isDefined()) { + return cursorResult; + } + ExpressionLayoutCursor::Position newPosition = cursor.position() == ExpressionLayoutCursor::Position::Left ? ExpressionLayoutCursor::Position::Right : ExpressionLayoutCursor::Position::Left; + cursor.setPosition(newPosition); + return ExpressionLayout::cursorVerticalOf(direction, cursor, shouldRecomputeLayout, false); +} + +} diff --git a/poincare/src/layout/empty_layout.h b/poincare/src/layout/empty_layout.h new file mode 100644 index 000000000..77ce71b3a --- /dev/null +++ b/poincare/src/layout/empty_layout.h @@ -0,0 +1,62 @@ +#ifndef POINCARE_empty_layout_H +#define POINCARE_empty_layout_H + +#include +#include +#include + +namespace Poincare { + +class EmptyLayout : public StaticLayoutHierarchy<0> { +public: + enum class Color { + Yellow, + Grey + }; + EmptyLayout(Color color = Color::Yellow, bool visible = true, KDText::FontSize size = KDText::FontSize::Large, bool margins = true); + ExpressionLayout * clone() const override; + + // EmptyLayout + Color color() const { return m_color; } + void setColor(Color color) { m_color = color; } + bool isVisible() const { return m_isVisible; } + void setVisible(bool visible) { m_isVisible = visible; } + + // User input + void deleteBeforeCursor(ExpressionLayoutCursor * cursor) override; + + // Tree navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; + + // Other + bool isEmpty() const override { return true; } + +protected: + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + KDSize computeSize() override; + void computeBaseline() override; + KDPoint positionOfChild(ExpressionLayout * child) override { + assert(false); + return KDPointZero; + } + void privateAddSibling(ExpressionLayoutCursor * cursor, ExpressionLayout * sibling, bool moveCursor) override; +private: + constexpr static KDCoordinate k_marginWidth = 1; + constexpr static KDCoordinate k_marginHeight = 3; + constexpr static KDCoordinate k_lineThickness = 1; + ExpressionLayoutCursor cursorVerticalOf(VerticalDirection direction, ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) override; + KDCoordinate height() const { return KDText::charSize(m_size).height() - 2*k_marginHeight; } + KDCoordinate width() const { return KDText::charSize(m_size).width() - 2*k_marginWidth; } + bool m_isVisible; + Color m_color; + KDText::FontSize m_size; + bool m_margins; +}; + +} + +#endif diff --git a/poincare/src/layout/expression_layout.cpp b/poincare/src/layout/expression_layout.cpp index 560eed6f8..18dd90ed1 100644 --- a/poincare/src/layout/expression_layout.cpp +++ b/poincare/src/layout/expression_layout.cpp @@ -1,19 +1,31 @@ +#include +#include "empty_layout.h" +#include "horizontal_layout.h" +#include "matrix_layout.h" +#include #include #include -#include "string_layout.h" +#include +#include namespace Poincare { ExpressionLayout::ExpressionLayout() : - m_baseline(0), m_parent(nullptr), + m_baseline(0), m_sized(false), + m_baselined(false), m_positioned(false), - m_frame(KDRectZero) { + m_frame(KDRectZero) +{ } -KDCoordinate ExpressionLayout::baseline() { - return m_baseline; +void ExpressionLayout::draw(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + int i = 0; + while (ExpressionLayout * c = editableChild(i++)) { + c->draw(ctx, p, expressionColor, backgroundColor); + } + render(ctx, absoluteOrigin().translatedBy(p), expressionColor, backgroundColor); } KDPoint ExpressionLayout::origin() { @@ -25,14 +37,6 @@ KDPoint ExpressionLayout::origin() { } } -void ExpressionLayout::draw(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - int i = 0; - while (ExpressionLayout * c = child(i++)) { - c->draw(ctx, p, expressionColor, backgroundColor); - } - render(ctx, absoluteOrigin().translatedBy(p), expressionColor, backgroundColor); -} - KDPoint ExpressionLayout::absoluteOrigin() { if (!m_positioned) { if (m_parent != nullptr) { @@ -53,8 +57,484 @@ KDSize ExpressionLayout::size() { return m_frame.size(); } -void ExpressionLayout::setParent(ExpressionLayout* parent) { - m_parent = parent; +KDCoordinate ExpressionLayout::baseline() { + if (!m_baselined) { + computeBaseline(); + m_baselined = true; + } + return m_baseline; +} + +void ExpressionLayout::invalidAllSizesPositionsAndBaselines() { + m_sized = false; + m_positioned = false; + m_baselined = false; + for (int i = 0; i < numberOfChildren(); i++) { + editableChild(i)->invalidAllSizesPositionsAndBaselines(); + } +} + +const ExpressionLayout * ExpressionLayout::child(int i) const { + assert(i >= 0); + if (i < numberOfChildren()) { + assert(children()[i]->parent() == nullptr || children()[i]->parent() == this); + return children()[i]; + } + return nullptr; +} + +bool ExpressionLayout::hasChild(const ExpressionLayout * child) const { + if (child == nullptr) { + return false; + } + return child->parent() == this; +} + +int ExpressionLayout::indexOfChild(const ExpressionLayout * child) const { + if (child == nullptr || !hasChild(child)) { + return -1; + } + for (int i = 0; i < numberOfChildren(); i++) { + if (children()[i] == child) { + return i; + } + } + assert(false); + return -1; +} + +bool ExpressionLayout::hasAncestor(const ExpressionLayout * e, bool includeSelf) const { + if (includeSelf && e == this) { + return true; + } + if (m_parent == e) { + return true; + } + if (m_parent == nullptr) { + return false; + } + return m_parent->hasAncestor(e); +} + +ExpressionLayout * ExpressionLayout::editableRoot() { + if (m_parent == nullptr) { + return this; + } + return m_parent->editableRoot(); +} + +bool ExpressionLayout::hasSibling(const ExpressionLayout * e) const { + if (e == nullptr) { + return false; + } + return m_parent == e->parent(); +} + +void ExpressionLayout::addSibling(ExpressionLayoutCursor * cursor, ExpressionLayout * sibling) { + privateAddSibling(cursor, sibling, false); +} + +void ExpressionLayout::addSiblingAndMoveCursor(ExpressionLayoutCursor * cursor, ExpressionLayout * sibling) { + privateAddSibling(cursor, sibling, true); +} + +ExpressionLayout * ExpressionLayout::replaceWith(ExpressionLayout * newChild, bool deleteAfterReplace) { + assert(m_parent != nullptr); + m_parent->replaceChild(this, newChild, deleteAfterReplace); + return newChild; +} + +ExpressionLayout * ExpressionLayout::replaceWithAndMoveCursor(ExpressionLayout * newChild, bool deleteAfterReplace, ExpressionLayoutCursor * cursor) { + assert(m_parent != nullptr); + m_parent->replaceChildAndMoveCursor(this, newChild, deleteAfterReplace, cursor); + return newChild; +} + +void ExpressionLayout::replaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild) { + assert(newChild != nullptr); + // Caution: handle the case where we replace an operand with a descendant of ours. + if (newChild->hasAncestor(this)) { + newChild->editableParent()->detachChild(newChild); + } + ExpressionLayout ** op = const_cast(children()); + for (int i = 0; i < numberOfChildren(); i++) { + if (op[i] == oldChild) { + if (oldChild != nullptr && oldChild->parent() == this) { + const_cast(oldChild)->setParent(nullptr); + } + if (deleteOldChild) { + delete oldChild; + } + if (newChild != nullptr) { + const_cast(newChild)->setParent(this); + } + op[i] = newChild; + break; + } + } +} + +void ExpressionLayout::replaceChildAndMoveCursor(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) { + assert(indexOfChild(oldChild) >= 0); + if (!newChild->hasAncestor(oldChild)) { + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + } + replaceChild(oldChild, newChild, deleteOldChild); + cursor->setPointedExpressionLayout(newChild); +} + +void ExpressionLayout::detachChild(const ExpressionLayout * e) { + assert(hasChild(e)); + detachChildAtIndex(indexOfChild(e)); +} + +void ExpressionLayout::detachChildren() { + for (int i = 0; i < numberOfChildren(); i++) { + detachChildAtIndex(i); + } +} + +void ExpressionLayout::removeChildAtIndex(int index, bool deleteAfterRemoval) { + assert(index >= 0 && index < numberOfChildren()); + replaceChild(editableChild(index), new EmptyLayout(), deleteAfterRemoval); +} + +void ExpressionLayout::removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor) { + assert(index >= 0 && index < numberOfChildren()); + assert(cursor->pointedExpressionLayout()->hasAncestor(child(index), true)); + removeChildAtIndex(index, deleteAfterRemoval); + if (index < numberOfChildren()) { + cursor->setPointedExpressionLayout(editableChild(index)); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return; + } + int indexOfNewPointedLayout = index - 1; + assert(indexOfNewPointedLayout >= 0); + assert(indexOfNewPointedLayout < numberOfChildren()); + cursor->setPointedExpressionLayout(editableChild(indexOfNewPointedLayout)); +} + + +bool ExpressionLayout::insertLayoutAtCursor(ExpressionLayout * newChild, ExpressionLayoutCursor * cursor) { + cursor->pointedExpressionLayout()->addSibling(cursor, newChild); + return true; +} + +void ExpressionLayout::deleteBeforeCursor(ExpressionLayoutCursor * cursor) { + int indexOfPointedExpression = indexOfChild(cursor->pointedExpressionLayout()); + if (indexOfPointedExpression >= 0) { + // Case: The pointed layout is a child. Move Left. + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + bool shouldRecomputeLayout = false; + ExpressionLayoutCursor newCursor = cursor->cursorOnLeft(&shouldRecomputeLayout); + cursor->setPointedExpressionLayout(newCursor.pointedExpressionLayout()); + cursor->setPosition(newCursor.position()); + return; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: this is the pointed layout. + if (m_parent == nullptr) { + // Case: No parent. Return. + return; + } + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + // Case: Left. Ask the parent. + if (m_parent) { + m_parent->deleteBeforeCursor(cursor); + } + return; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. Delete the layout. + m_parent->removePointedChildAtIndexAndMoveCursor(m_parent->indexOfChild(this), true, cursor); +} + +char ExpressionLayout::XNTChar() const { + if (m_parent == nullptr) { + return Ion::Charset::Empty; + } + return m_parent->XNTChar(); +} + +ExpressionLayoutCursor ExpressionLayout::cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + return cursorVerticalOf(VerticalDirection::Up, cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +ExpressionLayoutCursor ExpressionLayout::cursorInDescendantsAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + return cursorInDescendantsVerticalOf(VerticalDirection::Up, cursor, shouldRecomputeLayout); +} + +ExpressionLayoutCursor ExpressionLayout::cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + return cursorVerticalOf(VerticalDirection::Down, cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +ExpressionLayoutCursor ExpressionLayout::cursorInDescendantsUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + return cursorInDescendantsVerticalOf(VerticalDirection::Down, cursor, shouldRecomputeLayout); +} + +ExpressionLayoutCursor ExpressionLayout::equivalentCursor(ExpressionLayoutCursor cursor) { + // Only HorizontalLayout may not have a parent, and it overload this function + assert(m_parent); + if (cursor.pointedExpressionLayout() == this) { + return m_parent->equivalentCursor(cursor); + } else { + return ExpressionLayoutCursor(); + } +} + +ExpressionLayout * ExpressionLayout::layoutToPointWhenInserting() { + if (numberOfChildren() > 0) { + return editableChild(0); + } + return this; +} + +bool ExpressionLayout::addGreySquaresToAllMatrixAncestors() { + return changeGreySquaresOfAllMatrixAncestors(true); +} + +bool ExpressionLayout::removeGreySquaresFromAllMatrixAncestors() { + return changeGreySquaresOfAllMatrixAncestors(false); +} + +bool ExpressionLayout::hasText() const { + /* A layout has text if it is not empty and it is not an horizontal layout + * with no child or with one child with no text. */ + return !isEmpty() && !(isHorizontal() && (numberOfChildren() == 0 || (numberOfChildren() == 1 && !child(0)->hasText()))); +} + +bool ExpressionLayout::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 ExpressionLayout::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(); +} + +void ExpressionLayout::detachChildAtIndex(int i) { + ExpressionLayout ** op = const_cast(children()); + if (op[i] != nullptr && op[i]->parent() == this) { + const_cast(op[i])->setParent(nullptr); + } + op[i] = nullptr; +} + +ExpressionLayoutCursor ExpressionLayout::cursorInDescendantsVerticalOf(VerticalDirection direction, ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + ExpressionLayout * chilResult = nullptr; + ExpressionLayout ** childResultPtr = &chilResult; + ExpressionLayoutCursor::Position resultPosition = ExpressionLayoutCursor::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; + + scoreCursorInDescendantsVerticalOf(direction, cursor, shouldRecomputeLayout, childResultPtr, &resultPosition, &resultScore); + + // If there is a valid result + if (*childResultPtr == nullptr) { + return ExpressionLayoutCursor(); + } + *shouldRecomputeLayout = (*childResultPtr)->addGreySquaresToAllMatrixAncestors(); + return ExpressionLayoutCursor(*childResultPtr, resultPosition); +} + +ExpressionLayoutCursor ExpressionLayout::cursorVerticalOf(VerticalDirection direction, ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + if (!equivalentPositionVisited) { + ExpressionLayoutCursor cursorEquivalent = equivalentCursor(cursor); + if (cursorEquivalent.isDefined()) { + cursor.setPointedExpressionLayout(cursorEquivalent.pointedExpressionLayout()); + cursor.setPosition(cursorEquivalent.position()); + if (direction == VerticalDirection::Up) { + return cursor.pointedExpressionLayout()->cursorAbove(cursor, shouldRecomputeLayout, true); + } else { + return cursor.pointedExpressionLayout()->cursorUnder(cursor, shouldRecomputeLayout, true); + } + } + } + if (m_parent) { + if (direction == VerticalDirection::Up) { + return m_parent->cursorAbove(cursor, shouldRecomputeLayout, true); + } else { + return m_parent->cursorUnder(cursor, shouldRecomputeLayout, true); + } + } + return ExpressionLayoutCursor(); +} + +void ExpressionLayout::scoreCursorInDescendantsVerticalOf ( + VerticalDirection direction, + ExpressionLayoutCursor cursor, + bool * shouldRecomputeLayout, + ExpressionLayout ** childResult, + void * resultPosition, + int * resultScore) +{ + ExpressionLayoutCursor::Position * castedResultPosition = static_cast(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 = ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left).middleLeftPoint().squareDistanceTo(cursorMiddleLeft); + if (currentDistance <= *resultScore ){ + *childResult = this; + *castedResultPosition = ExpressionLayoutCursor::Position::Left; + *resultScore = currentDistance; + } + + // Check the distance to a Right cursor. + currentDistance = ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right).middleLeftPoint().squareDistanceTo(cursorMiddleLeft); + if (currentDistance < *resultScore) { + *childResult = this; + *castedResultPosition = ExpressionLayoutCursor::Position::Right; + *resultScore = currentDistance; + } + } + if (layoutIsUnderOrAbove || layoutContains) { + int childIndex = 0; + while (child(childIndex++)) { + editableChild(childIndex-1)->scoreCursorInDescendantsVerticalOf(direction, cursor, shouldRecomputeLayout, childResult, castedResultPosition, resultScore); + } + } +} + +void ExpressionLayout::privateAddSibling(ExpressionLayoutCursor * cursor, ExpressionLayout * sibling, bool moveCursor) { + /* The layout must have a parent, because HorizontalLayout overrides + * privateAddSibling and only an HorizontalLayout can be the root layout. */ + assert(m_parent); + if (m_parent->isHorizontal()) { + int indexInParent = m_parent->indexOfChild(this); + int siblingIndex = cursor->position() == ExpressionLayoutCursor::Position::Left ? indexInParent : indexInParent + 1; + + /* Special case: If the neighbour sibling is a VerticalOffsetLayout, let it + * handle the insertion of the new sibling. Jump special case if this is a + * VerticalOffsetLayout to avoid infinite loop.*/ + if (!isVerticalOffset()) { + ExpressionLayout * neighbour = nullptr; + if (cursor->position() == ExpressionLayoutCursor::Position::Left && indexInParent > 0) { + neighbour = m_parent->editableChild(indexInParent - 1); + } else if (cursor->position() == ExpressionLayoutCursor::Position::Right && indexInParent < m_parent->numberOfChildren() - 1) { + neighbour = m_parent->editableChild(indexInParent + 1); + } + if (neighbour != nullptr && neighbour->isVerticalOffset()) { + cursor->setPointedExpressionLayout(neighbour); + cursor->setPosition(cursor->position() == ExpressionLayoutCursor::Position::Left ? ExpressionLayoutCursor::Position::Right : ExpressionLayoutCursor::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 < m_parent->numberOfChildren()) { + cursor->setPointedExpressionLayout(m_parent->editableChild(siblingIndex)); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + } else { + cursor->setPointedExpressionLayout(m_parent); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + } + } + static_cast(m_parent)->addOrMergeChildAtIndex(sibling, siblingIndex, true); + return; + } + ExpressionLayout * juxtapositionLayout = nullptr; + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + juxtapositionLayout = replaceWithJuxtapositionOf(sibling, this, false); + } else { + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + juxtapositionLayout = replaceWithJuxtapositionOf(this, sibling, false); + } + if (moveCursor) { + cursor->setPointedExpressionLayout(juxtapositionLayout); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + } +} + +void ExpressionLayout::collapseOnDirection(HorizontalDirection direction, int absorbingChildIndex) { + if (!parent() || !parent()->isHorizontal()) { + return; + } + int indexInParent = parent()->indexOfChild(this); + int numberOfSiblings = parent()->numberOfChildren(); + int numberOfOpenParenthesis = 0; + bool canCollapse = true; + ExpressionLayout * absorbingChild = editableChild(absorbingChildIndex); + if (!absorbingChild || !absorbingChild->isHorizontal()) { + return; + } + HorizontalLayout * horizontalAbsorbingChild = static_cast(absorbingChild); + if (direction == HorizontalDirection::Right && indexInParent < numberOfSiblings - 1) { + canCollapse = !(editableParent()->editableChild(indexInParent+1)->mustHaveLeftSibling()); + } + ExpressionLayout * sibling = nullptr; + bool forceCollapse = false; + while (canCollapse) { + if (direction == HorizontalDirection::Right && indexInParent == numberOfSiblings - 1) { + break; + } + if (direction == HorizontalDirection::Left && indexInParent == 0) { + break; + } + int siblingIndex = direction == HorizontalDirection::Right ? indexInParent+1 : indexInParent-1; + sibling = editableParent()->editableChild(siblingIndex); + if (forceCollapse || sibling->isCollapsable(&numberOfOpenParenthesis, direction == HorizontalDirection::Left)) { + /* If the collapse direction is Left and the next sibling to be collapsed + * must have a left sibling, force the collapsing of this needed left + * sibling. */ + forceCollapse = direction == HorizontalDirection::Left && sibling->mustHaveLeftSibling(); + editableParent()->removeChildAtIndex(siblingIndex, false); + int newIndex = direction == HorizontalDirection::Right ? absorbingChild->numberOfChildren() : 0; + horizontalAbsorbingChild->addOrMergeChildAtIndex(sibling, newIndex, true); + numberOfSiblings--; + if (direction == HorizontalDirection::Left) { + indexInParent--; + } + } else { + break; + } + } +} + +ExpressionLayout * ExpressionLayout::replaceWithJuxtapositionOf(ExpressionLayout * leftChild, ExpressionLayout * rightChild, bool deleteAfterReplace) { + assert(m_parent != nullptr); + assert(!m_parent->isHorizontal()); + /* One of the children to juxtapose might be "this", so we first have to + * replace "this" with an horizontal layout, then add "this" to the layout. */ + ExpressionLayout * layout = new HorizontalLayout(); + m_parent->replaceChild(this, layout, deleteAfterReplace); + layout->addChildAtIndex(leftChild, 0); + layout->addChildAtIndex(rightChild, 1); + return layout; +} + +bool ExpressionLayout::changeGreySquaresOfAllMatrixAncestors(bool add) { + bool changedSquares = false; + ExpressionLayout * currentAncestor = m_parent; + while (currentAncestor != nullptr) { + if (currentAncestor->isMatrix()) { + if (add) { + static_cast(currentAncestor)->addGreySquares(); + } else { + static_cast(currentAncestor)->removeGreySquares(); + } + changedSquares = true; + } + currentAncestor = currentAncestor->editableParent(); + } + return changedSquares; } } diff --git a/poincare/src/layout/floor_layout.cpp b/poincare/src/layout/floor_layout.cpp new file mode 100644 index 000000000..598cfea39 --- /dev/null +++ b/poincare/src/layout/floor_layout.cpp @@ -0,0 +1,10 @@ +#include "floor_layout.h" + +namespace Poincare { + +ExpressionLayout * FloorLayout::clone() const { + FloorLayout * layout = new FloorLayout(const_cast(this)->operandLayout(), true); + return layout; +} + +} diff --git a/poincare/src/layout/floor_layout.h b/poincare/src/layout/floor_layout.h index 17c994eb5..bd5300b1d 100644 --- a/poincare/src/layout/floor_layout.h +++ b/poincare/src/layout/floor_layout.h @@ -1,18 +1,18 @@ #ifndef POINCARE_FLOOR_LAYOUT_H #define POINCARE_FLOOR_LAYOUT_H -#include "bracket_layout.h" +#include "bracket_pair_layout.h" +#include namespace Poincare { -class FloorLayout : public BracketLayout { +class FloorLayout : public BracketPairLayout { public: - FloorLayout(ExpressionLayout * operandLayout) : BracketLayout(operandLayout) {} - ~FloorLayout() {} - FloorLayout(const FloorLayout& other) = delete; - FloorLayout(FloorLayout&& other) = delete; - FloorLayout& operator=(const FloorLayout& other) = delete; - FloorLayout& operator=(FloorLayout&& other) = delete; + using BracketPairLayout::BracketPairLayout; + ExpressionLayout * clone() const override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writePrefixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, "floor"); + } protected: bool renderTopBar() const override { return false; } }; diff --git a/poincare/src/layout/fraction_layout.cpp b/poincare/src/layout/fraction_layout.cpp index e71a263db..8782aafb5 100644 --- a/poincare/src/layout/fraction_layout.cpp +++ b/poincare/src/layout/fraction_layout.cpp @@ -1,55 +1,267 @@ +#include "fraction_layout.h" +#include "empty_layout.h" +#include "horizontal_layout.h" +#include +#include +#include #include #include -#include "fraction_layout.h" namespace Poincare { -FractionLayout::FractionLayout(ExpressionLayout * numerator_layout, ExpressionLayout * denominator_layout) : -ExpressionLayout(), m_numerator_layout(numerator_layout), m_denominator_layout(denominator_layout) { - m_numerator_layout->setParent(this); - m_denominator_layout->setParent(this); - m_baseline = m_numerator_layout->size().height() - + k_fractionLineMargin + k_fractionLineHeight; +ExpressionLayout * FractionLayout::clone() const { + FractionLayout * layout = new FractionLayout(const_cast(this)->numeratorLayout(), const_cast(this)->denominatorLayout(), true); + return layout; } -FractionLayout::~FractionLayout() { - delete m_denominator_layout; - delete m_numerator_layout; +void FractionLayout::collapseSiblingsAndMoveCursor(ExpressionLayoutCursor * cursor) { + /* If the numerator or denominator layouts are not HorizontalLayouts, replace + * them with one. */ + if (!numeratorLayout()->isHorizontal()) { + ExpressionLayout * previousNumerator = numeratorLayout(); + HorizontalLayout * horizontalNumeratorLayout = new HorizontalLayout(previousNumerator, false); + replaceChild(previousNumerator, horizontalNumeratorLayout, false); + } + if (!denominatorLayout()->isHorizontal()) { + ExpressionLayout * previousDenominator = denominatorLayout(); + HorizontalLayout * horizontalDenominatorLayout = new HorizontalLayout(previousDenominator, false); + replaceChild(previousDenominator, horizontalDenominatorLayout, false); + } + ExpressionLayout::collapseOnDirection(HorizontalDirection::Right, 1); + ExpressionLayout::collapseOnDirection(HorizontalDirection::Left, 0); + cursor->setPointedExpressionLayout(denominatorLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); +} + +void FractionLayout::deleteBeforeCursor(ExpressionLayoutCursor * cursor) { + /* Case: Left of the denominator. + * Replace the fraction with a horizontal juxtaposition of the numerator and + * the denominator. */ + if (cursor->pointedExpressionLayout() == denominatorLayout()) { + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + if (numeratorLayout()->isEmpty() && denominatorLayout()->isEmpty()) { + /* Case: Numerator and denominator are empty. + * Move the cursor then replace the fraction with an empty layout. */ + replaceWithAndMoveCursor(new EmptyLayout(), true, cursor); + return; + } + + /* Else, replace the fraction with a juxtaposition of the numerator and + * denominator. Place the cursor in the middle of the juxtaposition, which + * is right of the numerator. */ + + // Juxtapose. + ExpressionLayout * numerator = numeratorLayout(); + ExpressionLayout * denominator = denominatorLayout(); + detachChild(numerator); + detachChild(denominator); + HorizontalLayout * newLayout = new HorizontalLayout(); + // Prepare the cursor position. + ExpressionLayout * nextPointedLayout = nullptr; + ExpressionLayoutCursor::Position nextPosition = ExpressionLayoutCursor::Position::Left; + // Add the denominator before the numerator to have the right order. + if (!denominator->isEmpty()) { + newLayout->addOrMergeChildAtIndex(denominator, 0, true); + /* The cursor should point to the left of denominator. However, if the + * pointed expression is an empty layout, it might disappear when merging + * the numerator, we therefore point to the next child which is visually + * equivalent. */ + int indexPointedLayout = newLayout->editableChild(0)->isEmpty() ? 1 : 0; + assert(newLayout->numberOfChildren() > indexPointedLayout); + nextPointedLayout = newLayout->editableChild(indexPointedLayout); + } else { + delete denominator; + } + if (!numerator->isEmpty()) { + newLayout->addOrMergeChildAtIndex(numerator, 0, true); + if (nextPointedLayout == nullptr) { + /* If nextPointedLayout is not defined yet, the denominator was empty + * and we want to point to the right of the numerator. It is asserted + * not to be empty because we previously handled the case of both + * numerator and denominator empty and the rightest child of a + * horizontal layout cannot be empty. */ + assert(newLayout->numberOfChildren() > 0); + nextPointedLayout = newLayout->editableChild(newLayout->numberOfChildren() - 1); + nextPosition = ExpressionLayoutCursor::Position::Right; + assert(!nextPointedLayout->isEmpty()); + } + } else { + delete numerator; + } + replaceWith(newLayout, true); + cursor->setPointedExpressionLayout(nextPointedLayout); + cursor->setPosition(nextPosition); + return; + } + + /* Case: Right. + * Move Right of the denominator. */ + if (cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + cursor->setPointedExpressionLayout(denominatorLayout()); + return; + } + ExpressionLayout::deleteBeforeCursor(cursor); +} + +ExpressionLayoutCursor FractionLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Left of the numerator or the denominator. Go Left of the fraction. + if (((numeratorLayout() && cursor.pointedExpressionLayout() == numeratorLayout()) + || (denominatorLayout() && cursor.pointedExpressionLayout() == denominatorLayout())) + && cursor.position() == ExpressionLayoutCursor::Position::Left) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Right. Go to the denominator. + if (cursor.position() == ExpressionLayoutCursor::Position::Right) { + assert(denominatorLayout() != nullptr); + return ExpressionLayoutCursor(denominatorLayout(), ExpressionLayoutCursor::Position::Right); + } + // Case: Left. Ask the parent. + assert(cursor.position() == ExpressionLayoutCursor::Position::Left); + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor FractionLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Right of the numerator or the denominator. Go Right of the fraction. + if (((numeratorLayout() && cursor.pointedExpressionLayout() == numeratorLayout()) + || (denominatorLayout() && cursor.pointedExpressionLayout() == denominatorLayout())) + && cursor.position() == ExpressionLayoutCursor::Position::Right) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Left. Go to the numerator. + if (cursor.position() == ExpressionLayoutCursor::Position::Left) { + assert(numeratorLayout() != nullptr); + return ExpressionLayoutCursor(numeratorLayout(), ExpressionLayoutCursor::Position::Left); + } + // Case: Right. Ask the parent. + assert(cursor.position() == ExpressionLayoutCursor::Position::Right); + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor FractionLayout::cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + // If the cursor is inside denominator, move it to the numerator. + if (denominatorLayout() && cursor.pointedExpressionLayout()->hasAncestor(denominatorLayout(), true)) { + assert(numeratorLayout() != nullptr); + return numeratorLayout()->cursorInDescendantsAbove(cursor, shouldRecomputeLayout); + } + // If the cursor is Left or Right, move it to the numerator. + if (cursor.pointedExpressionLayout() == this){ + assert(numeratorLayout() != nullptr); + return ExpressionLayoutCursor(numeratorLayout(), cursor.position()); + } + return ExpressionLayout::cursorAbove(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +ExpressionLayoutCursor FractionLayout::cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + // If the cursor is inside numerator, move it to the denominator. + if (numeratorLayout() && cursor.pointedExpressionLayout()->hasAncestor(numeratorLayout(), true)) { + assert(denominatorLayout() != nullptr); + return denominatorLayout()->cursorInDescendantsUnder(cursor, shouldRecomputeLayout); + } + // If the cursor is Left or Right, move it to the denominator. + if (cursor.pointedExpressionLayout() == this){ + assert(denominatorLayout() != nullptr); + return ExpressionLayoutCursor(denominatorLayout(), cursor.position()); + } + return ExpressionLayout::cursorUnder(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +int FractionLayout::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { + if (bufferSize == 0) { + return -1; + } + buffer[bufferSize-1] = 0; + int numberOfChar = 0; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + + int indexInParent = -1; + if (m_parent) { + indexInParent = m_parent->indexOfChild(this); + } + + // Add a multiplication if omitted. + if (indexInParent > 0 && m_parent->isHorizontal() && m_parent->child(indexInParent - 1)->canBeOmittedMultiplicationLeftFactor()) { + buffer[numberOfChar++] = Ion::Charset::MiddleDot; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + } + + bool addParenthesis = false; + if (indexInParent >= 0 && indexInParent < (m_parent->numberOfChildren() - 1) && m_parent->isHorizontal() && m_parent->child(indexInParent + 1)->isVerticalOffset()) { + addParenthesis = true; + // Add parenthesis + buffer[numberOfChar++] = '('; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + } + + // Write the content of the fraction + numberOfChar += LayoutEngine::writeInfixExpressionLayoutTextInBuffer(this, buffer+numberOfChar, bufferSize-numberOfChar, numberOfSignificantDigits, "/"); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + if (addParenthesis) { + // Add parenthesis + buffer[numberOfChar++] = ')'; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + } + + // Add a multiplication if omitted. + if (indexInParent >= 0 && indexInParent < (m_parent->numberOfChildren() - 1) && m_parent->isHorizontal() && m_parent->child(indexInParent + 1)->canBeOmittedMultiplicationRightFactor()) { + buffer[numberOfChar++] = Ion::Charset::MiddleDot; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + } + + buffer[numberOfChar] = 0; + return numberOfChar; +} + +ExpressionLayout * FractionLayout::layoutToPointWhenInserting() { + if (numeratorLayout()->isEmpty()){ + return numeratorLayout(); + } + if (denominatorLayout()->isEmpty()){ + return denominatorLayout(); + } + return this; } void FractionLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - KDCoordinate fractionLineY = p.y() + m_numerator_layout->size().height() + k_fractionLineMargin; - ctx->fillRect(KDRect(p.x()+k_fractionBorderMargin, fractionLineY, size().width()-2*k_fractionBorderMargin, 1), expressionColor); + KDCoordinate fractionLineY = p.y() + numeratorLayout()->size().height() + k_fractionLineMargin; + ctx->fillRect(KDRect(p.x()+Metric::FractionAndConjugateHorizontalMargin, fractionLineY, size().width()-2*Metric::FractionAndConjugateHorizontalMargin, k_fractionLineHeight), expressionColor); } KDSize FractionLayout::computeSize() { - KDCoordinate width = max(m_numerator_layout->size().width(), m_denominator_layout->size().width()) - + 2*k_fractionBorderLength+2*k_fractionBorderMargin; - KDCoordinate height = m_numerator_layout->size().height() + KDCoordinate width = max(numeratorLayout()->size().width(), denominatorLayout()->size().width()) + + 2*Metric::FractionAndConjugateHorizontalOverflow+2*Metric::FractionAndConjugateHorizontalMargin; + KDCoordinate height = numeratorLayout()->size().height() + k_fractionLineMargin + k_fractionLineHeight + k_fractionLineMargin - + m_denominator_layout->size().height(); + + denominatorLayout()->size().height(); return KDSize(width, height); } -ExpressionLayout * FractionLayout::child(uint16_t index) { - switch (index) { - case 0: - return m_numerator_layout; - case 1: - return m_denominator_layout; - default: - return nullptr; - } +void FractionLayout::computeBaseline() { + m_baseline = numeratorLayout()->size().height() + + k_fractionLineMargin + k_fractionLineHeight; + m_baselined = true; } KDPoint FractionLayout::positionOfChild(ExpressionLayout * child) { KDCoordinate x = 0; KDCoordinate y = 0; - if (child == m_numerator_layout) { - x = (KDCoordinate)((size().width() - m_numerator_layout->size().width())/2); - } else if (child == m_denominator_layout) { - x = (KDCoordinate)((size().width() - m_denominator_layout->size().width())/2); - y = (KDCoordinate)(m_numerator_layout->size().height() + 2*k_fractionLineMargin + k_fractionLineHeight); + if (child == numeratorLayout()) { + x = (KDCoordinate)((size().width() - numeratorLayout()->size().width())/2); + } else if (child == denominatorLayout()) { + x = (KDCoordinate)((size().width() - denominatorLayout()->size().width())/2); + y = (KDCoordinate)(numeratorLayout()->size().height() + 2*k_fractionLineMargin + k_fractionLineHeight); } else { assert(false); // Should not happen } diff --git a/poincare/src/layout/fraction_layout.h b/poincare/src/layout/fraction_layout.h index 4a01a60ae..6ababe90e 100644 --- a/poincare/src/layout/fraction_layout.h +++ b/poincare/src/layout/fraction_layout.h @@ -1,31 +1,50 @@ #ifndef POINCARE_FRACTION_LAYOUT_H #define POINCARE_FRACTION_LAYOUT_H -#include -#include +#include +#include namespace Poincare { -class FractionLayout : public ExpressionLayout { +class FractionLayout : public StaticLayoutHierarchy<2> { public: - FractionLayout(ExpressionLayout * numerator, ExpressionLayout * denominator); - ~FractionLayout(); - FractionLayout(const FractionLayout& other) = delete; - FractionLayout(FractionLayout&& other) = delete; - FractionLayout& operator=(const FractionLayout& other) = delete; - FractionLayout& operator=(FractionLayout&& other) = delete; + using StaticLayoutHierarchy::StaticLayoutHierarchy; + ExpressionLayout * clone() const override; + + // Collapse + void collapseSiblingsAndMoveCursor(ExpressionLayoutCursor * cursor) override; + + // User input + void deleteBeforeCursor(ExpressionLayoutCursor * cursor) override; + + // Tree navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + ExpressionLayoutCursor cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; + + // Other + ExpressionLayout * layoutToPointWhenInserting() override; + bool canBeOmittedMultiplicationRightFactor() const override { return false; } + /* WARNING: We need to override this function, else 1/2 3/4 would be + * serialized as 1/2**3/4, as the two Fraction layouts think their sibling is + * an omitted multiplication layout factor. We have the same problem with + * 2^3 1/2 being serialized as 2^3**1/2, so must override the Right version + * and not canBeOmittedMultiplicationLeftFactor. */ + bool childMightNeedParentheses() const override { return true; } protected: void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; + void computeBaseline() override; KDPoint positionOfChild(ExpressionLayout * child) override; private: - constexpr static KDCoordinate k_fractionBorderLength = 2; - constexpr static KDCoordinate k_fractionBorderMargin = 2; constexpr static KDCoordinate k_fractionLineMargin = 2; - constexpr static KDCoordinate k_fractionLineHeight = 2; - ExpressionLayout * m_numerator_layout; - ExpressionLayout * m_denominator_layout; + constexpr static KDCoordinate k_fractionLineHeight = 1; + ExpressionLayout * numeratorLayout() { return editableChild(0); } + ExpressionLayout * denominatorLayout() { return editableChild(1); } }; } diff --git a/poincare/src/layout/grid_layout.cpp b/poincare/src/layout/grid_layout.cpp index 92dae7a72..7f152288d 100644 --- a/poincare/src/layout/grid_layout.cpp +++ b/poincare/src/layout/grid_layout.cpp @@ -1,4 +1,7 @@ #include "grid_layout.h" +#include "empty_layout.h" +#include +#include extern "C" { #include #include @@ -6,40 +9,114 @@ extern "C" { namespace Poincare { -GridLayout::GridLayout(ExpressionLayout ** entryLayouts, int numberOfRows, int numberOfColumns) : - ExpressionLayout(), +GridLayout::GridLayout(const ExpressionLayout * const * entryLayouts, int numberOfRows, int numberOfColumns, bool cloneOperands) : + DynamicLayoutHierarchy(entryLayouts, numberOfRows*numberOfColumns, cloneOperands), m_numberOfRows(numberOfRows), m_numberOfColumns(numberOfColumns) { - m_entryLayouts = new ExpressionLayout *[numberOfColumns*numberOfRows]; - for (int i = 0; i < m_numberOfRows*m_numberOfColumns; i++) { - m_entryLayouts[i] = entryLayouts[i]; - m_entryLayouts[i]->setParent(this); - } - m_baseline = (height()+1)/2; } -GridLayout::~GridLayout() { - for (int i=0; i(children()), m_numberOfRows, m_numberOfColumns, true); + return layout; +} + +ExpressionLayoutCursor GridLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Right. Go to the last entry. + if (cursor.pointedExpressionLayout() == this + && cursor.position() == ExpressionLayoutCursor::Position::Right) + { + ExpressionLayout * lastChild = editableChild(m_numberOfColumns*m_numberOfRows-1); + assert(lastChild != nullptr); + return ExpressionLayoutCursor(lastChild, ExpressionLayoutCursor::Position::Right); } - delete[] m_entryLayouts; + // Case: The cursor points to a grid's child. + int childIndex = indexOfChild(cursor.pointedExpressionLayout()); + if (childIndex >- 1 && cursor.position() == ExpressionLayoutCursor::Position::Left) { + if (childIsLeftOfGrid(childIndex)) { + // Case: Left of a child on the left of the grid. Go Left of the grid + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + // Case: Left of another child. Go Right of its sibling on the left. + return ExpressionLayoutCursor(editableChild(childIndex-1), ExpressionLayoutCursor::Position::Right); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Left. Ask the parent. + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor GridLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Left. Go to the first entry. + if (cursor.pointedExpressionLayout() == this + && cursor.position() == ExpressionLayoutCursor::Position::Left) + { + assert(m_numberOfColumns*m_numberOfRows >= 1); + ExpressionLayout * firstChild = editableChild(0); + assert(firstChild != nullptr); + return ExpressionLayoutCursor(firstChild, ExpressionLayoutCursor::Position::Left); + } + // Case: The cursor points to a grid's child. + int childIndex = indexOfChild(cursor.pointedExpressionLayout()); + if (childIndex >- 1 && cursor.position() == ExpressionLayoutCursor::Position::Right) { + if (childIsRightOfGrid(childIndex)) { + // Case: Right of a child on the right of the grid. Go Right of the grid. + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); + } + // Case: Right of another child. Go Left of its sibling on the right. + return ExpressionLayoutCursor(editableChild(childIndex+1), ExpressionLayoutCursor::Position::Left); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Right. Ask the parent. + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor GridLayout::cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + /* If the cursor is child that is not on the top row, move it inside its upper + * neighbour.*/ + int childIndex = m_numberOfColumns; + while (childIndex < numberOfChildren()) { + if (cursor.pointedExpressionLayout()->hasAncestor(child(childIndex), true)) { + return editableChild(childIndex - m_numberOfColumns)->cursorInDescendantsAbove(cursor, shouldRecomputeLayout); + } + childIndex++; + } + return ExpressionLayout::cursorAbove(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +ExpressionLayoutCursor GridLayout::cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + int childIndex = 0; + while (childIndex < numberOfChildren() - m_numberOfColumns) { + if (cursor.pointedExpressionLayout()->hasAncestor(child(childIndex), true)) { + return editableChild(childIndex + m_numberOfColumns)->cursorInDescendantsUnder(cursor, shouldRecomputeLayout); + } + childIndex++; + } + return ExpressionLayout::cursorUnder(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +void GridLayout::removeChildAtIndex(int index, bool deleteAfterRemoval) { + ExpressionLayout::removeChildAtIndex(index, deleteAfterRemoval); } KDCoordinate GridLayout::rowBaseline(int i) { KDCoordinate rowBaseline = 0; for (int j = 0; j < m_numberOfColumns; j++) { - rowBaseline = max(rowBaseline, m_entryLayouts[i*m_numberOfColumns+j]->baseline()); + rowBaseline = max(rowBaseline, editableChild(i*m_numberOfColumns+j)->baseline()); } return rowBaseline; } - KDCoordinate GridLayout::rowHeight(int i) { KDCoordinate rowHeight = 0; KDCoordinate baseline = rowBaseline(i); for (int j = 0; j < m_numberOfColumns; j++) { - rowHeight = max(rowHeight, m_entryLayouts[i*m_numberOfColumns+j]->size().height() - m_entryLayouts[i*m_numberOfColumns+j]->baseline()); + rowHeight = max(rowHeight, editableChild(i*m_numberOfColumns+j)->size().height() - editableChild(i*m_numberOfColumns+j)->baseline()); } return baseline+rowHeight; } @@ -56,7 +133,7 @@ KDCoordinate GridLayout::height() { KDCoordinate GridLayout::columnWidth(int j) { KDCoordinate columnWidth = 0; for (int i = 0; i < m_numberOfRows; i++) { - columnWidth = max(columnWidth, m_entryLayouts[i*m_numberOfColumns+j]->size().width()); + columnWidth = max(columnWidth, editableChild(i*m_numberOfColumns+j)->size().width()); } return columnWidth; } @@ -78,11 +155,9 @@ KDSize GridLayout::computeSize() { return KDSize(width(), height()); } -ExpressionLayout * GridLayout::child(uint16_t index) { - if (index < m_numberOfColumns*m_numberOfRows) { - return m_entryLayouts[index]; - } - return nullptr; +void GridLayout::computeBaseline() { + m_baseline = (height()+1)/2; + m_baselined = true; } KDPoint GridLayout::positionOfChild(ExpressionLayout * child) { @@ -90,7 +165,7 @@ KDPoint GridLayout::positionOfChild(ExpressionLayout * child) { int columnIndex = 0; for (int i = 0; i < m_numberOfRows; i++) { for (int j = 0; j < m_numberOfColumns; j++) { - if (child == m_entryLayouts[i*m_numberOfColumns+j]) { + if (child == editableChild(i*m_numberOfColumns+j)) { rowIndex = i; columnIndex = j; break; @@ -110,4 +185,72 @@ KDPoint GridLayout::positionOfChild(ExpressionLayout * child) { return KDPoint(x, y); } +void GridLayout::addEmptyRow(EmptyLayout::Color color) { + ExpressionLayout * newChildren[m_numberOfColumns]; + for (int i = 0; i < m_numberOfColumns; i++) { + newChildren[i] = new EmptyLayout(color); + } + addChildrenAtIndex(const_cast(const_cast(newChildren)), m_numberOfColumns, numberOfChildren(), false); + m_numberOfRows++; +} + +void GridLayout::addEmptyColumn(EmptyLayout::Color color) { + m_numberOfColumns++; + for (int i = 0; i < m_numberOfRows; i++) { + addChildAtIndex(new EmptyLayout(color), i*m_numberOfColumns + m_numberOfColumns-1); + } +} + +void GridLayout::deleteRowAtIndex(int index) { + assert(index >= 0 && index < m_numberOfRows); + for (int i = 0; i < m_numberOfColumns; i++) { + DynamicLayoutHierarchy::removeChildAtIndex(index * m_numberOfColumns, true); + } + m_numberOfRows--; +} + +void GridLayout::deleteColumnAtIndex(int index) { + assert(index >= 0 && index < m_numberOfColumns); + for (int i = (m_numberOfRows - 1) * m_numberOfColumns + index; i > -1; i-= m_numberOfColumns) { + DynamicLayoutHierarchy::removeChildAtIndex(i, true); + } + m_numberOfColumns--; +} + +bool GridLayout::childIsLeftOfGrid(int index) const { + assert(index >= 0 && index < m_numberOfRows*m_numberOfColumns); + return columnAtChildIndex(index) == 0; +} + +bool GridLayout::childIsRightOfGrid(int index) const { + assert(index >= 0 && index < m_numberOfRows*m_numberOfColumns); + return columnAtChildIndex(index) == m_numberOfColumns - 1; +} + +bool GridLayout::childIsTopOfGrid(int index) const { + assert(index >= 0 && index < m_numberOfRows*m_numberOfColumns); + return rowAtChildIndex(index) == 0; +} + +bool GridLayout::childIsBottomOfGrid(int index) const { + assert(index >= 0 && index < m_numberOfRows*m_numberOfColumns); + return rowAtChildIndex(index) == m_numberOfRows - 1; +} + +int GridLayout::rowAtChildIndex(int index) const { + assert(index >= 0 && index < m_numberOfRows*m_numberOfColumns); + return (int)(index / m_numberOfColumns); +} + +int GridLayout::columnAtChildIndex(int index) const { + assert(index >= 0 && index < m_numberOfRows*m_numberOfColumns); + return index - m_numberOfColumns * rowAtChildIndex(index); +} + +int GridLayout::indexAtRowColumn(int rowIndex, int columnIndex) const { + assert(rowIndex >= 0 && rowIndex < m_numberOfRows); + assert(columnIndex >= 0 && columnIndex < m_numberOfColumns); + return rowIndex * m_numberOfColumns + columnIndex; +} + } diff --git a/poincare/src/layout/grid_layout.h b/poincare/src/layout/grid_layout.h index f252f28df..7dd23a0e1 100644 --- a/poincare/src/layout/grid_layout.h +++ b/poincare/src/layout/grid_layout.h @@ -1,24 +1,49 @@ #ifndef POINCARE_GRID_LAYOUT_H #define POINCARE_GRID_LAYOUT_H -#include -#include +#include +#include "empty_layout.h" namespace Poincare { -class GridLayout : public ExpressionLayout { +class GridLayout : public DynamicLayoutHierarchy { + friend class BinomialCoefficientLayout; public: - GridLayout(ExpressionLayout ** entryLayouts, int numberOfRows, int numberOfColumns); - ~GridLayout(); - GridLayout(const GridLayout& other) = delete; - GridLayout(GridLayout&& other) = delete; - GridLayout& operator=(const GridLayout& other) = delete; - GridLayout& operator=(GridLayout&& other) = delete; + GridLayout(const ExpressionLayout * const * entryLayouts, int numberOfRows, int numberOfColumns, bool cloneOperands); + ExpressionLayout * clone() const override; + + // Tree navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + ExpressionLayoutCursor cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + + // Remove + void removeChildAtIndex(int index, bool deleteAfterRemoval) override; + /* This function replaces the child with an EmptyLayout. To delete the grid's + * children, do not call this function. */ + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { return 0; } + protected: void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; + void computeBaseline() override; KDPoint positionOfChild(ExpressionLayout * child) override; + void addEmptyRow(EmptyLayout::Color color); + void addEmptyColumn(EmptyLayout::Color color); + 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; + int indexAtRowColumn(int rowIndex, int columnIndex) const; + int m_numberOfRows; + int m_numberOfColumns; private: constexpr static KDCoordinate k_gridEntryMargin = 6; KDCoordinate rowBaseline(int i); @@ -26,9 +51,6 @@ private: KDCoordinate height(); KDCoordinate columnWidth(int j); KDCoordinate width(); - ExpressionLayout ** m_entryLayouts; - int m_numberOfRows; - int m_numberOfColumns; }; } diff --git a/poincare/src/layout/horizontal_layout.cpp b/poincare/src/layout/horizontal_layout.cpp index 804ef7bd6..a9ea44039 100644 --- a/poincare/src/layout/horizontal_layout.cpp +++ b/poincare/src/layout/horizontal_layout.cpp @@ -1,34 +1,353 @@ +#include "horizontal_layout.h" +#include "empty_layout.h" extern "C" { #include #include #include } -#include "horizontal_layout.h" -#include "string_layout.h" namespace Poincare { -HorizontalLayout::HorizontalLayout(ExpressionLayout ** children_layouts, int number_of_children) : - ExpressionLayout(), m_number_of_children(number_of_children) { - assert(number_of_children > 0); - m_children_layouts = new ExpressionLayout *[number_of_children]; - for (int i=0; isetParent(this); - if (m_children_layouts[i]->baseline() > m_baseline) { - m_baseline = m_children_layouts[i]->baseline(); +ExpressionLayout * HorizontalLayout::clone() const { + HorizontalLayout * layout = new HorizontalLayout(const_cast(children()), numberOfChildren(), true); + return layout; +} + +void HorizontalLayout::deleteBeforeCursor(ExpressionLayoutCursor * cursor) { + if (cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Left + && m_parent == nullptr) + { + // Case: Left and this is the main layout. Return. + return; + } + if (cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Right + && m_parent == nullptr + && numberOfChildren() == 0) + { + // Case: Right and this is the main layout with no children. Return. + return; + } + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + int indexOfPointedExpression = indexOfChild(cursor->pointedExpressionLayout()); + if (indexOfPointedExpression >= 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 (indexOfPointedExpression == 0) { + cursor->setPointedExpressionLayout(this); + } else if (indexOfPointedExpression > 0) { + cursor->setPointedExpressionLayout(editableChild(indexOfPointedExpression - 1)); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + } + cursor->performBackspace(); + return; } } -} - -HorizontalLayout::~HorizontalLayout() { - for (int i=0; ipointedExpressionLayout() == this); + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + // Case: Right. Point to the last child and perform backspace. + cursor->setPointedExpressionLayout(editableChild(numberOfChildren() - 1)); + cursor->performBackspace(); + return; } - delete[] m_children_layouts; + ExpressionLayout::deleteBeforeCursor(cursor); } -void HorizontalLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { +void HorizontalLayout::replaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild) { + privateReplaceChild(oldChild, newChild, deleteOldChild, nullptr); +} + +void HorizontalLayout::replaceChildAndMoveCursor(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) { + privateReplaceChild(oldChild, newChild, deleteOldChild, cursor); +} + +void HorizontalLayout::privateReplaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) { + bool oldWasAncestorOfNewLayout = false; + if (newChild->hasAncestor(this)) { + newChild->editableParent()->detachChild(newChild); + oldWasAncestorOfNewLayout = true; + } + int oldChildIndex = indexOfChild(oldChild); + if (newChild->isEmpty()) { + if (numberOfChildren() > 1) { + /* If the new layout is empty and the horizontal layout has other + * children, just delete the old child. */ + if (!newChild->hasAncestor(oldChild)) { + delete newChild; + } + removeChildAtIndex(oldChildIndex, deleteOldChild); + if (cursor == nullptr) { + return; + } + if (oldChildIndex == 0) { + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return; + } + cursor->setPointedExpressionLayout(editableChild(oldChildIndex -1)); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return; + } + /* If the new layout is empty and it was the only horizontal layout child, + * 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, 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. */ + 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() == ExpressionLayoutCursor::Position::Right) { + if (oldChildIndex == numberOfChildren() - 1) { + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + } else { + cursor->setPointedExpressionLayout(editableChild(oldChildIndex + 1)); + cursor->setPosition(ExpressionLayoutCursor::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->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + } else { + cursor->setPointedExpressionLayout(editableChild(oldChildIndex - 1)); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + } + } + } + bool oldChildRemovedAtMerge = oldChild->isEmpty(); + mergeChildrenAtIndex(static_cast(newChild), indexForInsertion + 1, true); + if (!oldChildRemovedAtMerge) { + removeChildAtIndex(indexForInsertion, deleteOldChild); + } + return; + } + // Else, just replace the child. + if (cursor != nullptr && !oldWasAncestorOfNewLayout) { + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + } + ExpressionLayout::replaceChild(oldChild, newChild, deleteOldChild); + if (cursor == nullptr) { + return; + } + cursor->setPointedExpressionLayout(newChild); +} + +void HorizontalLayout::addOrMergeChildAtIndex(ExpressionLayout * eL, int index, bool removeEmptyChildren) { + if (eL->isHorizontal()) { + mergeChildrenAtIndex(static_cast(eL), index, removeEmptyChildren); + } else { + addChildAtIndex(eL, index); + } +} + +ExpressionLayoutCursor HorizontalLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Left. Ask the parent. + if (cursor.pointedExpressionLayout() == this) { + if (cursor.position() == ExpressionLayoutCursor::Position::Left) { + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); + } + assert(cursor.position() == ExpressionLayoutCursor::Position::Right); + /* Case: Right. + * Go to the last child if there is one, and move Left. + * Else go Left and ask the parent. */ + if (numberOfChildren() < 1) { + cursor.setPosition(ExpressionLayoutCursor::Position::Left); + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); + } + ExpressionLayout * lastChild = editableChild(numberOfChildren()-1); + assert(lastChild != nullptr); + cursor.setPointedExpressionLayout(lastChild); + return lastChild->cursorLeftOf(cursor, shouldRecomputeLayout); + } + + // Case: The cursor is Left of a child. + assert(cursor.position() == ExpressionLayoutCursor::Position::Left); + int childIndex = indexOfChild(cursor.pointedExpressionLayout()); + assert(childIndex >= 0); + if (childIndex == 0) { + // Case: the child is the leftmost. Ask the parent. + if (m_parent) { + cursor.setPointedExpressionLayout(this); + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); + } + // Case: the child is not the leftmost. Go to its left sibling and move Left. + cursor.setPointedExpressionLayout(editableChild(childIndex-1)); + cursor.setPosition(ExpressionLayoutCursor::Position::Right); + return editableChild(childIndex-1)->cursorLeftOf(cursor, shouldRecomputeLayout); +} + +ExpressionLayoutCursor HorizontalLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Right. Ask the parent. + if (cursor.pointedExpressionLayout() == this) { + if (cursor.position() == ExpressionLayoutCursor::Position::Right) { + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); + } + assert(cursor.position() == ExpressionLayoutCursor::Position::Left); + /* Case: Left. + * Go to the first child if there is one, and move Right. + * Else go Right and ask the parent. */ + if (numberOfChildren() < 1) { + cursor.setPosition(ExpressionLayoutCursor::Position::Right); + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); + } + ExpressionLayout * firstChild = editableChild(0); + assert(firstChild != nullptr); + cursor.setPointedExpressionLayout(firstChild); + return firstChild->cursorRightOf(cursor, shouldRecomputeLayout); + } + + // Case: The cursor is Right of a child. + assert(cursor.position() == ExpressionLayoutCursor::Position::Right); + int childIndex = indexOfChild(cursor.pointedExpressionLayout()); + assert(childIndex >= 0); + if (childIndex == numberOfChildren() - 1) { + // Case: the child is the rightmost. Ask the parent. + if (m_parent) { + cursor.setPointedExpressionLayout(this); + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); + } + /* Case: the child is not the rightmost. Go to its right sibling and move + * Right. */ + cursor.setPointedExpressionLayout(editableChild(childIndex+1)); + cursor.setPosition(ExpressionLayoutCursor::Position::Left); + return editableChild(childIndex+1)->cursorRightOf(cursor, shouldRecomputeLayout); +} + +void HorizontalLayout::addChildrenAtIndex(const ExpressionLayout * const * operands, int numberOfOperands, int indexForInsertion, bool removeEmptyChildren) { + int newIndex = removeEmptyChildBeforeInsertionAtIndex(indexForInsertion, !operands[0]->mustHaveLeftSibling()); + DynamicLayoutHierarchy::addChildrenAtIndex(operands, numberOfOperands, newIndex, removeEmptyChildren); +} + +bool HorizontalLayout::addChildAtIndex(ExpressionLayout * operand, int index) { + int newIndex = removeEmptyChildBeforeInsertionAtIndex(index, !operand->mustHaveLeftSibling()); + return DynamicLayoutHierarchy::addChildAtIndex(operand, newIndex); +} + +void HorizontalLayout::removeChildAtIndex(int index, bool deleteAfterRemoval) { + privateRemoveChildAtIndex(index, deleteAfterRemoval, false); +} + +void HorizontalLayout::mergeChildrenAtIndex(DynamicLayoutHierarchy * eL, int index, bool removeEmptyChildren) { + /* Before the merge, remove ay empty child that would be on the left or on the + * right of 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 = eL->numberOfChildren() > 0 ? !(eL->child(0)->mustHaveLeftSibling()) : true; + int newIndex = removeEmptyChildBeforeInsertionAtIndex(index, shouldRemoveOnLeft); + DynamicLayoutHierarchy::mergeChildrenAtIndex(eL, newIndex, removeEmptyChildren); +} + +int HorizontalLayout::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { + if (numberOfChildren() == 0) { + if (bufferSize == 0) { + return -1; + } + buffer[0] = 0; + return 0; + } + return LayoutEngine::writeInfixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, ""); +} + +ExpressionLayoutCursor HorizontalLayout::equivalentCursor(ExpressionLayoutCursor cursor) { + ExpressionLayoutCursor result; + ExpressionLayout * newPointedLayout = nullptr; + ExpressionLayoutCursor::Position newPosition = ExpressionLayoutCursor::Position::Left; + if (cursor.pointedExpressionLayout() == this) { + // First or last child, if any + if(numberOfChildren() == 0) { + return result; + } else { + newPointedLayout = editableChild(cursor.position() == ExpressionLayoutCursor::Position::Left ? 0 : numberOfChildren() - 1); + newPosition = cursor.position(); + } + } else { + // Left or right child + int indexOfPointedLayout = indexOfChild(cursor.pointedExpressionLayout()); + if (indexOfPointedLayout < 0) { + return result; + } else if (cursor.position() == ExpressionLayoutCursor::Position::Left) { + if (indexOfPointedLayout == 0) { + newPointedLayout = this; + newPosition = ExpressionLayoutCursor::Position::Left; + } else { + newPointedLayout = editableChild(indexOfPointedLayout - 1); + newPosition = ExpressionLayoutCursor::Position::Right; + } + } else { + assert(cursor.position() == ExpressionLayoutCursor::Position::Right); + if (indexOfPointedLayout == numberOfChildren() - 1) { + newPointedLayout = this; + newPosition = ExpressionLayoutCursor::Position::Right; + } else { + newPointedLayout = editableChild(indexOfPointedLayout + 1); + newPosition = ExpressionLayoutCursor::Position::Left; + } + } + } + result.setPointedExpressionLayout(newPointedLayout); + result.setPosition(newPosition); + return result; +} + +bool HorizontalLayout::needsParenthesesWithParent() const { + if (!parent()->childMightNeedParentheses()) { + return false; + } + int numberOfOpenParenthesis = 0; + for (int i = 0; i < numberOfChildren(); i++) { + if (!child(i)->isCollapsable(&numberOfOpenParenthesis, true)) { + return true; + } + } + return false; } KDSize HorizontalLayout::computeSize() { @@ -36,7 +355,7 @@ KDSize HorizontalLayout::computeSize() { int i = 0; KDCoordinate max_under_baseline = 0; KDCoordinate max_above_baseline = 0; - while (ExpressionLayout * c = child(i++)) { + while (ExpressionLayout * c = editableChild(i++)) { KDSize childSize = c->size(); totalWidth += childSize.width(); if (childSize.height() - c->baseline() > max_under_baseline) { @@ -49,32 +368,103 @@ KDSize HorizontalLayout::computeSize() { return KDSize(totalWidth, max_under_baseline + max_above_baseline); } -ExpressionLayout * HorizontalLayout::child(uint16_t index) { - assert(index <= (unsigned int) m_number_of_children); - if (index < (unsigned int) m_number_of_children) { - return m_children_layouts[index]; - } else { - return nullptr; +void HorizontalLayout::computeBaseline() { + m_baseline = 0; + for (int i = 0; i < numberOfChildren(); i++) { + if (editableChild(i)->baseline() > m_baseline) { + m_baseline = editableChild(i)->baseline(); + } } + m_baselined = true; } KDPoint HorizontalLayout::positionOfChild(ExpressionLayout * child) { KDCoordinate x = 0; KDCoordinate y = 0; - uint16_t index = 0; - for (int i=0;i 0) { - ExpressionLayout * previousChild = m_children_layouts[index-1]; + ExpressionLayout * previousChild = editableChild(index-1); assert(previousChild != nullptr); x = previousChild->origin().x() + previousChild->size().width(); } - y = m_baseline - child->baseline(); + y = baseline() - child->baseline(); return KDPoint(x, y); } +void HorizontalLayout::privateAddSibling(ExpressionLayoutCursor * cursor, ExpressionLayout * sibling, bool moveCursor) { + // Add the "sibling" as a child. + if (cursor->position() == ExpressionLayoutCursor::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 (numberOfChildren() > 0 && editableChild(0)->isEmpty()) { + if (sibling->mustHaveLeftSibling()) { + indexForInsertion = 1; + } else { + /* We force the removing 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, true); + } + } + if (moveCursor) { + if (numberOfChildren() > indexForInsertion) { + cursor->setPointedExpressionLayout(editableChild(indexForInsertion)); + } else { + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + } + } + addOrMergeChildAtIndex(sibling, indexForInsertion, false); + return; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + // If the last child is empty, remove it before adding the layout. + int childrenCount = numberOfChildren(); + if (childrenCount > 0 && editableChild(childrenCount - 1)->isEmpty() && !sibling->mustHaveLeftSibling()) { + /* Force remove the last child. */ + privateRemoveChildAtIndex(childrenCount - 1, true, true); + } + addOrMergeChildAtIndex(sibling, numberOfChildren(), false); + if (moveCursor) { + cursor->setPointedExpressionLayout(this); + } +} + +void HorizontalLayout::privateRemoveChildAtIndex(int index, bool deleteAfterRemoval, 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. */ + DynamicLayoutHierarchy::removeChildAtIndex(index, deleteAfterRemoval); + /* 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); + } +} + +int HorizontalLayout::removeEmptyChildBeforeInsertionAtIndex(int index, bool shouldRemoveOnLeft) { + int newIndex = index; + /* If empty, remove the child that would be on the right of the inserted + * layout. */ + if (newIndex < numberOfChildren() + && child(newIndex)->isEmpty()) + { + privateRemoveChildAtIndex(newIndex, true, true); + } + /* If empty, remove the child that would be on the left of the inserted + * layout. */ + if (shouldRemoveOnLeft + && newIndex - 1 >= 0 + && newIndex - 1 <= numberOfChildren() -1 + && child(newIndex - 1)->isEmpty()) + { + privateRemoveChildAtIndex(newIndex-1, true, true); + newIndex = index - 1; + } + return newIndex; +} + } diff --git a/poincare/src/layout/horizontal_layout.h b/poincare/src/layout/horizontal_layout.h index 1770eee1f..d70a9d826 100644 --- a/poincare/src/layout/horizontal_layout.h +++ b/poincare/src/layout/horizontal_layout.h @@ -1,27 +1,61 @@ #ifndef POINCARE_HORIZONTAL_LAYOUT_H #define POINCARE_HORIZONTAL_LAYOUT_H -#include -#include +#include +#include +#include namespace Poincare { -class HorizontalLayout : public ExpressionLayout { +/* WARNING: A Horizontal Layout should never have a Horizontal Layout child. + * For instance, use addOrMergeChildAtIndex to add an ExpressionLayout safely. */ + +class HorizontalLayout : public DynamicLayoutHierarchy { + friend class BinomialCoefficientLayout; + friend class IntegralLayout; + friend class MatrixLayout; + friend class SequenceLayout; public: - HorizontalLayout(ExpressionLayout ** layouts, int number_of_children); - ~HorizontalLayout(); - HorizontalLayout(const HorizontalLayout& other) = delete; - HorizontalLayout(HorizontalLayout&& other) = delete; - HorizontalLayout& operator=(const HorizontalLayout& other) = delete; - HorizontalLayout& operator=(HorizontalLayout&& other) = delete; + using DynamicLayoutHierarchy::DynamicLayoutHierarchy; + ExpressionLayout * clone() const override; + void deleteBeforeCursor(ExpressionLayoutCursor * cursor) override; + + // Replace + void replaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild) override; + void replaceChildAndMoveCursor(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) override; + void addOrMergeChildAtIndex(ExpressionLayout * eL, int index, bool removeEmptyChildren); + + // Tree navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + + // Dynamic layout + void addChildrenAtIndex(const ExpressionLayout * const * operands, int numberOfOperands, int indexForInsertion, bool removeEmptyChildren) override; + bool addChildAtIndex(ExpressionLayout * operand, int index) override; + void removeChildAtIndex(int index, bool deleteAfterRemoval) override; + void mergeChildrenAtIndex(DynamicLayoutHierarchy * eL, int index, bool removeEmptyChildren) override; + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; + + // Cursor + ExpressionLayoutCursor equivalentCursor(ExpressionLayoutCursor cursor) override; + + // Other + bool isHorizontal() const override { return true; } + bool isEmpty() const override { return m_numberOfChildren == 1 && child(0)->isEmpty(); } + bool needsParenthesesWithParent() const override; + bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override { return m_numberOfChildren != 0; } protected: - void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override {} KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; + void computeBaseline() override; KDPoint positionOfChild(ExpressionLayout * child) override; + void privateAddSibling(ExpressionLayoutCursor * cursor, ExpressionLayout * sibling, bool moveCursor) override; private: - int m_number_of_children; - ExpressionLayout ** m_children_layouts; + void privateReplaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor); + void privateRemoveChildAtIndex(int index, bool deleteAfterRemoval, bool forceRemove); + int removeEmptyChildBeforeInsertionAtIndex(int index, bool shouldRemoveOnLeft); }; } diff --git a/poincare/src/layout/integral_layout.cpp b/poincare/src/layout/integral_layout.cpp index 6b91dc738..81d4b91b0 100644 --- a/poincare/src/layout/integral_layout.cpp +++ b/poincare/src/layout/integral_layout.cpp @@ -1,4 +1,7 @@ #include "integral_layout.h" +#include "char_layout.h" +#include "horizontal_layout.h" +#include #include #include @@ -18,28 +21,168 @@ const uint8_t bottomSymbolPixel[IntegralLayout::k_symbolHeight][IntegralLayout:: {0xFF, 0xFF, 0x00, 0x00}, }; -IntegralLayout::IntegralLayout(ExpressionLayout * lowerBoundLayout, ExpressionLayout * upperBoundLayout, ExpressionLayout * integrandLayout) : - ExpressionLayout(), - m_lowerBoundLayout(lowerBoundLayout), - m_upperBoundLayout(upperBoundLayout), - m_integrandLayout(integrandLayout) -{ - m_lowerBoundLayout->setParent(this); - m_upperBoundLayout->setParent(this); - m_integrandLayout->setParent(this); - m_baseline = m_upperBoundLayout->size().height() + k_integrandHeigthMargin + m_integrandLayout->baseline(); +ExpressionLayout * IntegralLayout::clone() const { + IntegralLayout * layout = new IntegralLayout(const_cast(this)->integrandLayout(), const_cast(this)->lowerBoundLayout(), const_cast(this)->upperBoundLayout(), true); + return layout; } -IntegralLayout::~IntegralLayout() { - delete m_lowerBoundLayout; - delete m_upperBoundLayout; - delete m_integrandLayout; +void IntegralLayout::deleteBeforeCursor(ExpressionLayoutCursor * cursor) { + if (cursor->isEquivalentTo(ExpressionLayoutCursor(integrandLayout(), ExpressionLayoutCursor::Position::Left))) { + // Case: Left of the integrand. + // Delete the layout, keep the integrand. + replaceWithAndMoveCursor(integrandLayout(), true, cursor); + return; + } + ExpressionLayout::deleteBeforeCursor(cursor); +} + +ExpressionLayoutCursor IntegralLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Left the upper or lower bound. Go Left of the integral. + if (((upperBoundLayout() + && cursor.pointedExpressionLayout() == upperBoundLayout()) + || (lowerBoundLayout() + && cursor.pointedExpressionLayout() == lowerBoundLayout())) + && cursor.position() == ExpressionLayoutCursor::Position::Left) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + // Case: Left the integrand. Go Right of the lower bound. + if (integrandLayout() + && cursor.pointedExpressionLayout() == integrandLayout() + && cursor.position() == ExpressionLayoutCursor::Position::Left) + { + assert(lowerBoundLayout() != nullptr); + return ExpressionLayoutCursor(lowerBoundLayout(), ExpressionLayoutCursor::Position::Right); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Right of the integral. Go to the integrand. + if (cursor.position() == ExpressionLayoutCursor::Position::Right) { + assert(integrandLayout() != nullptr); + return ExpressionLayoutCursor(integrandLayout(), ExpressionLayoutCursor::Position::Right); + } + assert(cursor.position() == ExpressionLayoutCursor::Position::Left); + // Case: Left of the brackets. Ask the parent. + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor IntegralLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Right the upper or lower bound. + // Go Left of the integrand. + if (((upperBoundLayout() + && cursor.pointedExpressionLayout() == upperBoundLayout()) + || (lowerBoundLayout() + && cursor.pointedExpressionLayout() == lowerBoundLayout())) + && cursor.position() == ExpressionLayoutCursor::Position::Right) + { + assert(integrandLayout() != nullptr); + return ExpressionLayoutCursor(integrandLayout(), ExpressionLayoutCursor::Position::Left); + } + // Case: Right the integrand. Go Right. + if (integrandLayout() + && cursor.pointedExpressionLayout() == integrandLayout() + && cursor.position() == ExpressionLayoutCursor::Position::Right) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Left of the integral. Go to the upper bound. + if (cursor.position() == ExpressionLayoutCursor::Position::Left) { + assert(upperBoundLayout() != nullptr); + return ExpressionLayoutCursor(upperBoundLayout(), ExpressionLayoutCursor::Position::Left); + } + assert(cursor.position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. Ask the parent. + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor IntegralLayout::cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + // If the cursor is inside the lower bound, move it to the upper bound. + if (lowerBoundLayout() && cursor.pointedExpressionLayout()->hasAncestor(lowerBoundLayout(), true)) { + assert(upperBoundLayout() != nullptr); + return upperBoundLayout()->cursorInDescendantsAbove(cursor, shouldRecomputeLayout); + } + // If the cursor is Left of the integrand, move it to the upper bound. + if (integrandLayout() + && cursor.isEquivalentTo(ExpressionLayoutCursor(integrandLayout(), ExpressionLayoutCursor::Position::Left))) + { + assert(upperBoundLayout() != nullptr); + return upperBoundLayout()->cursorInDescendantsAbove(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::cursorAbove(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +ExpressionLayoutCursor IntegralLayout::cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + // If the cursor is inside the upper bound, move it to the lower bound. + if (upperBoundLayout() && cursor.pointedExpressionLayout()->hasAncestor(upperBoundLayout(), true)) { + assert(lowerBoundLayout() != nullptr); + return lowerBoundLayout()->cursorInDescendantsUnder(cursor, shouldRecomputeLayout); + } + // If the cursor is Left of the integrand, move it to the lower bound. + if (integrandLayout() + && cursor.isEquivalentTo(ExpressionLayoutCursor(integrandLayout(), ExpressionLayoutCursor::Position::Left))) + { + assert(lowerBoundLayout() != nullptr); + return lowerBoundLayout()->cursorInDescendantsUnder(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::cursorUnder(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +int IntegralLayout::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { + if (bufferSize == 0) { + return -1; + } + buffer[bufferSize-1] = 0; + + // Write the operator name + int numberOfChar = strlcpy(buffer, "int", bufferSize); + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; + } + + // Write the opening parenthesis + buffer[numberOfChar++] = '('; + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; + } + + // Write the argument + numberOfChar += const_cast(this)->integrandLayout()->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfSignificantDigits); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the comma + buffer[numberOfChar++] = ','; + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the lower bound + numberOfChar += const_cast(this)->lowerBoundLayout()->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfSignificantDigits); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the comma + buffer[numberOfChar++] = ','; + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the upper bound + numberOfChar += const_cast(this)->upperBoundLayout()->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfSignificantDigits); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the closing parenthesis + buffer[numberOfChar++] = ')'; + buffer[numberOfChar] = 0; + return numberOfChar; } void IntegralLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - KDSize integrandSize = m_integrandLayout->size(); - KDSize upperBoundSize = m_upperBoundLayout->size(); + KDSize integrandSize = integrandLayout()->size(); + KDSize upperBoundSize = upperBoundLayout()->size(); KDColor workingBuffer[k_symbolWidth*k_symbolHeight]; + + // Render the integral symbol. KDRect topSymbolFrame(p.x() + k_symbolWidth + k_lineThickness, p.y() + upperBoundSize.height() - k_boundHeightMargin, k_symbolWidth, k_symbolHeight); ctx->blendRectWithMask(topSymbolFrame, expressionColor, (const uint8_t *)topSymbolPixel, (KDColor *)workingBuffer); @@ -49,43 +192,42 @@ void IntegralLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, ctx->blendRectWithMask(bottomSymbolFrame, expressionColor, (const uint8_t *)bottomSymbolPixel, (KDColor *)workingBuffer); ctx->fillRect(KDRect(p.x() + k_symbolWidth, p.y() + upperBoundSize.height() - k_boundHeightMargin, k_lineThickness, 2*k_boundHeightMargin+2*k_integrandHeigthMargin+integrandSize.height()), expressionColor); + + // Render "dx". + CharLayout * dummydx = new CharLayout('d'); + HorizontalLayout dummyLayout(integrandLayout()->clone(), dummydx, false); + KDPoint dxPosition = dummyLayout.positionOfChild(dummydx); + ctx->drawString("dx", dxPosition.translatedBy(p).translatedBy(positionOfChild(integrandLayout())), dummydx->fontSize(), expressionColor, backgroundColor); } KDSize IntegralLayout::computeSize() { - KDSize integrandSize = m_integrandLayout->size(); - KDSize lowerBoundSize = m_lowerBoundLayout->size(); - KDSize upperBoundSize = m_upperBoundLayout->size(); + KDSize dxSize = HorizontalLayout(new CharLayout('d'), new CharLayout('x'), false).size(); + KDSize integrandSize = integrandLayout()->size(); + KDSize lowerBoundSize = lowerBoundLayout()->size(); + KDSize upperBoundSize = upperBoundLayout()->size(); return KDSize( - k_symbolWidth+k_lineThickness+k_boundWidthMargin+max(lowerBoundSize.width(), upperBoundSize.width())+k_integrandWidthMargin+integrandSize.width(), - upperBoundSize.height()+ 2*k_integrandHeigthMargin+integrandSize.height()+lowerBoundSize.height()); + k_symbolWidth+k_lineThickness+k_boundWidthMargin+max(lowerBoundSize.width(), upperBoundSize.width())+k_integrandWidthMargin+integrandSize.width()+dxSize.width(), + upperBoundSize.height()+ 2*k_integrandHeigthMargin+max(integrandSize.height(), dxSize.height())+lowerBoundSize.height()); } -ExpressionLayout * IntegralLayout::child(uint16_t index) { - switch (index) { - case 0: - return m_upperBoundLayout; - case 1: - return m_lowerBoundLayout; - case 2: - return m_integrandLayout; - default: - return nullptr; - } +void IntegralLayout::computeBaseline() { + m_baseline = upperBoundLayout()->size().height() + k_integrandHeigthMargin + integrandLayout()->baseline(); + m_baselined = true; } KDPoint IntegralLayout::positionOfChild(ExpressionLayout * child) { - KDSize integrandSize = m_integrandLayout->size(); - KDSize lowerBoundSize = m_lowerBoundLayout->size(); - KDSize upperBoundSize = m_upperBoundLayout->size(); + KDSize integrandSize = integrandLayout()->size(); + KDSize lowerBoundSize = lowerBoundLayout()->size(); + KDSize upperBoundSize = upperBoundLayout()->size(); KDCoordinate x = 0; KDCoordinate y = 0; - if (child == m_lowerBoundLayout) { + if (child == lowerBoundLayout()) { x = k_symbolWidth+k_lineThickness+k_boundWidthMargin; y = upperBoundSize.height()+2*k_integrandHeigthMargin+integrandSize.height(); - } else if (child == m_upperBoundLayout) { + } else if (child == upperBoundLayout()) { x = k_symbolWidth+k_lineThickness+k_boundWidthMargin;; y = 0; - } else if (child == m_integrandLayout) { + } else if (child == integrandLayout()) { x = k_symbolWidth +k_lineThickness+ k_boundWidthMargin+max(lowerBoundSize.width(), upperBoundSize.width())+k_integrandWidthMargin; y = upperBoundSize.height()+k_integrandHeigthMargin; } else { diff --git a/poincare/src/layout/integral_layout.h b/poincare/src/layout/integral_layout.h index 2e7059acd..f577fb382 100644 --- a/poincare/src/layout/integral_layout.h +++ b/poincare/src/layout/integral_layout.h @@ -1,25 +1,41 @@ #ifndef POINCARE_INTEGRAL_LAYOUT_H #define POINCARE_INTEGRAL_LAYOUT_H -#include -#include +#include +#include +#include namespace Poincare { -class IntegralLayout : public ExpressionLayout { +class IntegralLayout : public StaticLayoutHierarchy<3> { public: - IntegralLayout(ExpressionLayout * lowerBoundLayout, ExpressionLayout * upperBoundLayout, ExpressionLayout * integrandLayout); - ~IntegralLayout(); - IntegralLayout(const IntegralLayout& other) = delete; - IntegralLayout(IntegralLayout&& other) = delete; - IntegralLayout& operator=(const IntegralLayout& other) = delete; - IntegralLayout& operator=(IntegralLayout&& other) = delete; constexpr static KDCoordinate k_symbolHeight = 4; constexpr static KDCoordinate k_symbolWidth = 4; + using StaticLayoutHierarchy::StaticLayoutHierarchy; + ExpressionLayout * clone() const override; + + // Dynamic Layout + void deleteBeforeCursor(ExpressionLayoutCursor * cursor) override; + + // Tree navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + ExpressionLayoutCursor cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; + + // Other + ExpressionLayout * layoutToPointWhenInserting() override { + assert(lowerBoundLayout() != nullptr); + return lowerBoundLayout(); + } + char XNTChar() const override { return 'x'; } protected: void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; + void computeBaseline() override; KDPoint positionOfChild(ExpressionLayout * child) override; private: constexpr static KDCoordinate k_boundHeightMargin = 8; @@ -27,9 +43,9 @@ private: constexpr static KDCoordinate k_integrandWidthMargin = 2; constexpr static KDCoordinate k_integrandHeigthMargin = 2; constexpr static KDCoordinate k_lineThickness = 1; - ExpressionLayout * m_lowerBoundLayout; - ExpressionLayout * m_upperBoundLayout; - ExpressionLayout * m_integrandLayout; + ExpressionLayout * integrandLayout() { return editableChild(0); } + ExpressionLayout * lowerBoundLayout() { return editableChild(1); } + ExpressionLayout * upperBoundLayout() { return editableChild(2); } }; } diff --git a/poincare/src/layout/left_parenthesis_layout.cpp b/poincare/src/layout/left_parenthesis_layout.cpp new file mode 100644 index 000000000..880225bb6 --- /dev/null +++ b/poincare/src/layout/left_parenthesis_layout.cpp @@ -0,0 +1,64 @@ +#include "left_parenthesis_layout.h" +extern "C" { +#include +#include +} + +namespace Poincare { + +const uint8_t topLeftCurve[ParenthesisLayout::k_parenthesisCurveHeight][ParenthesisLayout::k_parenthesisCurveWidth] = { + {0xFF, 0xFF, 0xFF, 0xF9, 0x66}, + {0xFF, 0xFF, 0xEB, 0x40, 0x9A}, + {0xFF, 0xF2, 0x40, 0xBF, 0xFF}, + {0xFF, 0x49, 0xB6, 0xFF, 0xFF}, + {0xA9, 0x5A, 0xFF, 0xFF, 0xFF}, + {0x45, 0xBE, 0xFF, 0xFF, 0xFF}, + {0x11, 0xEE, 0xFF, 0xFF, 0xFF}, +}; + +const uint8_t bottomLeftCurve[ParenthesisLayout::k_parenthesisCurveHeight][ParenthesisLayout::k_parenthesisCurveWidth] = { + {0x11, 0xEE, 0xFF, 0xFF, 0xFF}, + {0x45, 0xBE, 0xFF, 0xFF, 0xFF}, + {0xA9, 0x5A, 0xFF, 0xFF, 0xFF}, + {0xFF, 0x49, 0xB6, 0xFF, 0xFF}, + {0xFF, 0xF2, 0x40, 0xBF, 0xFF}, + {0xFF, 0xFF, 0xEB, 0x40, 0x9A}, + {0xFF, 0xFF, 0xFF, 0xF9, 0x66}, +}; + +ExpressionLayout * LeftParenthesisLayout::clone() const { + LeftParenthesisLayout * layout = new LeftParenthesisLayout(); + return layout; +} + +bool LeftParenthesisLayout::isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { + if (*numberOfOpenParenthesis == 0 && goingLeft) { + return false; + } + *numberOfOpenParenthesis = goingLeft ? *numberOfOpenParenthesis - 1 : *numberOfOpenParenthesis + 1; + return true; +} + +void LeftParenthesisLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + KDRect frame(p.x()+ParenthesisLayout::k_externWidthMargin, + p.y()+ParenthesisLayout::k_externHeightMargin, + ParenthesisLayout::k_parenthesisCurveWidth, + ParenthesisLayout::k_parenthesisCurveHeight); + + ctx->blendRectWithMask(frame, expressionColor, (const uint8_t *)topLeftCurve, (KDColor *)(ParenthesisLayout::s_parenthesisWorkingBuffer)); + + frame = KDRect(p.x()+ParenthesisLayout::k_externWidthMargin, + p.y() + size().height() - ParenthesisLayout::k_parenthesisCurveHeight - ParenthesisLayout::k_externHeightMargin, + ParenthesisLayout::k_parenthesisCurveWidth, + ParenthesisLayout::k_parenthesisCurveHeight); + + ctx->blendRectWithMask(frame, expressionColor, (const uint8_t *)bottomLeftCurve, (KDColor *)(ParenthesisLayout::s_parenthesisWorkingBuffer)); + + ctx->fillRect(KDRect(p.x()+ParenthesisLayout::k_externWidthMargin, + p.y()+ParenthesisLayout::k_parenthesisCurveHeight+ParenthesisLayout::k_externHeightMargin, + ParenthesisLayout::k_lineThickness, + size().height() - 2*(ParenthesisLayout::k_parenthesisCurveHeight+ParenthesisLayout::k_externHeightMargin)), + expressionColor); +} + +} diff --git a/poincare/src/layout/left_parenthesis_layout.h b/poincare/src/layout/left_parenthesis_layout.h new file mode 100644 index 000000000..2d9878c5c --- /dev/null +++ b/poincare/src/layout/left_parenthesis_layout.h @@ -0,0 +1,26 @@ +#ifndef POINCARE_LEFT_PARENTHESIS_LAYOUT_H +#define POINCARE_LEFT_PARENTHESIS_LAYOUT_H + +#include +#include + +namespace Poincare { + +class LeftParenthesisLayout : public ParenthesisLayout { + friend class BinomialCoefficientLayout; + friend class SequenceLayout; +public: + using ParenthesisLayout::ParenthesisLayout; + ExpressionLayout * clone() const override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writeOneCharInBuffer(buffer, bufferSize, '('); + } + bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; + bool isLeftParenthesis() const override { return true; } +protected: + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; +}; + +} + +#endif diff --git a/poincare/src/layout/left_square_bracket_layout.cpp b/poincare/src/layout/left_square_bracket_layout.cpp new file mode 100644 index 000000000..fd82e06b5 --- /dev/null +++ b/poincare/src/layout/left_square_bracket_layout.cpp @@ -0,0 +1,15 @@ +#include "left_square_bracket_layout.h" + +namespace Poincare { + +ExpressionLayout * LeftSquareBracketLayout::clone() const { + return new LeftSquareBracketLayout(); +} + +void LeftSquareBracketLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y(), k_lineThickness, operandHeight()), expressionColor); + ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y(), k_bracketWidth, k_lineThickness), expressionColor); + ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y() + operandHeight(), k_bracketWidth, k_lineThickness), expressionColor); +} + +} diff --git a/poincare/src/layout/left_square_bracket_layout.h b/poincare/src/layout/left_square_bracket_layout.h new file mode 100644 index 000000000..b07709fda --- /dev/null +++ b/poincare/src/layout/left_square_bracket_layout.h @@ -0,0 +1,23 @@ +#ifndef POINCARE_LEFT_SQUARE_BRACKET_LAYOUT_H +#define POINCARE_LEFT_SQUARE_BRACKET_LAYOUT_H + +#include +#include + +namespace Poincare { + +class LeftSquareBracketLayout : public SquareBracketLayout { +public: + using SquareBracketLayout::SquareBracketLayout; + ExpressionLayout * clone() const override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writeOneCharInBuffer(buffer, bufferSize, '['); + } + bool isLeftBracket() const override { return true; } +protected: + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; +}; + +} + +#endif diff --git a/poincare/src/layout/matrix_layout.cpp b/poincare/src/layout/matrix_layout.cpp new file mode 100644 index 000000000..903ff6b45 --- /dev/null +++ b/poincare/src/layout/matrix_layout.cpp @@ -0,0 +1,271 @@ +#include "matrix_layout.h" +#include "empty_layout.h" +#include "bracket_pair_layout.h" +#include +#include +extern "C" { +#include +#include +} + +namespace Poincare { + +ExpressionLayout * MatrixLayout::clone() const { + MatrixLayout * layout = new MatrixLayout(children(), m_numberOfRows, m_numberOfColumns, true); + return layout; +} + +ExpressionLayoutCursor MatrixLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + 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(); + *shouldRecomputeLayout = true; + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + /* 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(); + *shouldRecomputeLayout = true; + ExpressionLayout * lastChild = editableChild((m_numberOfColumns-1)*(m_numberOfRows-1)); + assert(lastChild != nullptr); + return ExpressionLayoutCursor(lastChild, ExpressionLayoutCursor::Position::Right); + } + return GridLayout::cursorLeftOf(cursor, shouldRecomputeLayout); +} + +ExpressionLayoutCursor MatrixLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // 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(); + *shouldRecomputeLayout = true; + assert(m_numberOfColumns*m_numberOfRows >= 1); + ExpressionLayout * firstChild = editableChild(0); + assert(firstChild != nullptr); + return ExpressionLayoutCursor(firstChild, ExpressionLayoutCursor::Position::Left); + } + // 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 right of the grid. */ + assert(hasGreySquares()); + removeGreySquares(); + *shouldRecomputeLayout = true; + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); + + } + return GridLayout::cursorRightOf(cursor, shouldRecomputeLayout); +} + +ExpressionLayoutCursor MatrixLayout::cursorVerticalOf(VerticalDirection direction, ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + bool shouldRemoveGreySquares = false; + int firstIndex = direction == VerticalDirection::Up ? 0 : numberOfChildren() - m_numberOfColumns; + int lastIndex = direction == VerticalDirection::Up ? m_numberOfColumns : numberOfChildren(); + for (int childIndex = firstIndex; childIndex < lastIndex; childIndex++) { + if (cursor.pointedExpressionLayout()->hasAncestor(child(childIndex), true)) { + // The cursor is leaving the matrix, so remove the grey squares. + shouldRemoveGreySquares = true; + break; + } + } + ExpressionLayoutCursor resultCursor = GridLayout::cursorVerticalOf(direction, cursor, shouldRecomputeLayout, equivalentPositionVisited); + if (resultCursor.isDefined() && shouldRemoveGreySquares) { + assert(hasGreySquares()); + removeGreySquares(); + *shouldRecomputeLayout = true; + } + return resultCursor; +} + +void MatrixLayout::replaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild) { + int oldChildIndex = indexOfChild(oldChild); + GridLayout::replaceChild(oldChild, newChild, deleteOldChild); + childWasReplacedAtIndex(oldChildIndex); +} + +void MatrixLayout::replaceChildAndMoveCursor(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) { + int oldChildIndex = indexOfChild(oldChild); + int rowIndex = rowAtChildIndex(oldChildIndex); + int columnIndex = columnAtChildIndex(oldChildIndex); + replaceChild(oldChild, newChild, deleteOldChild); + int newIndex = indexAtRowColumn(rowIndex, columnIndex); + if (newIndex < numberOfChildren()) { + cursor->setPointedExpressionLayout(editableChild(newIndex)); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return; + } + cursor->setPointedExpressionLayout(editableChild(numberOfChildren())); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); +} + +void MatrixLayout::removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor) { + assert(index >= 0 && index < numberOfChildren()); + assert(cursor->pointedExpressionLayout()->hasAncestor(child(index), true)); + replaceChildAndMoveCursor(child(index), new EmptyLayout(), deleteAfterRemoval, cursor); +} + +int MatrixLayout::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { + // The grid is a matrix. + if (bufferSize == 0) { + return -1; + } + buffer[bufferSize-1] = 0; + int numberOfChar = 0; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + + buffer[numberOfChar++] = '['; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + + 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, numberOfSignificantDigits, ",", i*m_numberOfColumns, i* m_numberOfColumns + maxColumnIndex); + + buffer[numberOfChar++] = ']'; + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + } + buffer[numberOfChar++] = ']'; + buffer[numberOfChar] = 0; + return numberOfChar; +} + +void MatrixLayout::newRowOrColumnAtIndex(int index) { + assert(index >= 0 && index < m_numberOfColumns*m_numberOfRows); + bool shouldAddNewRow = GridLayout::childIsBottomOfGrid(index); + int correspondingRow = rowAtChildIndex(index); + // We need to compute this boolean before modifying the layout + if (GridLayout::childIsRightOfGrid(index)) { + // Color the grey EmptyLayouts of the column in yellow. + int correspondingColumn = m_numberOfColumns - 1; + for (int i = 0; i < m_numberOfRows - 1; i++) { + ExpressionLayout * lastLayoutOfRow = editableChild(i*m_numberOfColumns+correspondingColumn); + if (lastLayoutOfRow->isEmpty()) { + static_cast(lastLayoutOfRow)->setColor(EmptyLayout::Color::Yellow); + } + } + // Add a column of grey EmptyLayouts on the right. + addEmptyColumn(EmptyLayout::Color::Grey); + } + if (shouldAddNewRow) { + // Color the grey EmptyLayouts of the row in yellow. + for (int i = 0; i < m_numberOfColumns - 1; i++) { + ExpressionLayout * lastLayoutOfColumn = editableChild(correspondingRow*m_numberOfColumns+i); + if (lastLayoutOfColumn->isEmpty()) { + static_cast(lastLayoutOfColumn)->setColor(EmptyLayout::Color::Yellow); + } + } + // Add a row of grey EmptyLayouts at the bottom. + addEmptyRow(EmptyLayout::Color::Grey); + } +} + +void MatrixLayout::childWasReplacedAtIndex(int index) { + assert(index >= 0 && index < m_numberOfColumns*m_numberOfRows); + int rowIndex = rowAtChildIndex(index); + int columnIndex = columnAtChildIndex(index); + bool rowIsEmpty = isRowEmpty(rowIndex); + bool columnIsEmpty = isColumnEmpty(columnIndex); + if (rowIsEmpty && m_numberOfRows > 2) { + deleteRowAtIndex(rowIndex); + } + if (columnIsEmpty && m_numberOfColumns > 2) { + deleteColumnAtIndex(columnIndex); + } + if (!rowIsEmpty && !columnIsEmpty) { + ExpressionLayout * newChild = editableChild(index); + if (newChild->isEmpty() + && (childIsRightOfGrid(index) + || childIsBottomOfGrid(index))) + { + static_cast(newChild)->setColor(EmptyLayout::Color::Grey); + } + } +} + +void MatrixLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + const ExpressionLayout * dummyGridLayout = new GridLayout(children(), m_numberOfRows, m_numberOfColumns, true); + BracketPairLayout dummyLayout(dummyGridLayout, false); + dummyLayout.render(ctx, p, expressionColor, backgroundColor); +} + +KDSize MatrixLayout::computeSize() { + const ExpressionLayout * dummyGridLayout = new GridLayout(children(), m_numberOfRows, m_numberOfColumns, true); + BracketPairLayout dummyLayout(dummyGridLayout, false); + return dummyLayout.size(); +} + +KDPoint MatrixLayout::positionOfChild(ExpressionLayout * child) { + assert(indexOfChild(child) > -1); + ExpressionLayout * dummyGridLayout = new GridLayout(children(), m_numberOfRows, m_numberOfColumns, true); + BracketPairLayout dummyLayout(dummyGridLayout, false); + return GridLayout::positionOfChild(child).translatedBy(dummyLayout.positionOfChild(dummyGridLayout)); +} + +bool MatrixLayout::isRowEmpty(int index) const { + assert(index >= 0 && index < m_numberOfRows); + for (int i = index * m_numberOfColumns; i < (index+1) * m_numberOfColumns; i++) { + if (!child(i)->isEmpty()) { + return false; + } + } + return true; +} + +bool MatrixLayout::isColumnEmpty(int index) const { + assert(index >= 0 && index < m_numberOfColumns); + for (int i = index; i < m_numberOfRows * m_numberOfColumns; i+= m_numberOfColumns) { + if (!child(i)->isEmpty()) { + return false; + } + } + return true; +} + +void MatrixLayout::addGreySquares() { + if (!hasGreySquares()) { + addEmptyRow(EmptyLayout::Color::Grey); + addEmptyColumn(EmptyLayout::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() == EmptyLayout::Color::Grey) + { + assert(isRowEmpty(m_numberOfRows - 1)); + assert(isColumnEmpty(m_numberOfColumns - 1)); + return true; + } + return false; +} + +} diff --git a/poincare/src/layout/matrix_layout.h b/poincare/src/layout/matrix_layout.h new file mode 100644 index 000000000..91b74fbf3 --- /dev/null +++ b/poincare/src/layout/matrix_layout.h @@ -0,0 +1,47 @@ +#ifndef POINCARE_MATRIX_LAYOUT_H +#define POINCARE_MATRIX_LAYOUT_H + +#include + +namespace Poincare { + +class MatrixLayout : public GridLayout { +public: + using GridLayout::GridLayout; + ExpressionLayout * clone() const override; + + // MatrixLayout + void newRowOrColumnAtIndex(int index); + void addGreySquares(); + void removeGreySquares(); + + // Tree Navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + + // Replace & remove + void replaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild) override; + void replaceChildAndMoveCursor(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) override; + void removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor) override; + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; + + // Other + bool isMatrix() const override { return true; } + +protected: + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + KDSize computeSize() override; + KDPoint positionOfChild(ExpressionLayout * child) override; + ExpressionLayoutCursor cursorVerticalOf(VerticalDirection direction, ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) override; +private: + void childWasReplacedAtIndex(int index); + bool isRowEmpty(int index) const; + bool isColumnEmpty(int index) const; + bool hasGreySquares() const; +}; + +} + +#endif diff --git a/poincare/src/layout/nth_root_layout.cpp b/poincare/src/layout/nth_root_layout.cpp index c9b75fbae..987862239 100644 --- a/poincare/src/layout/nth_root_layout.cpp +++ b/poincare/src/layout/nth_root_layout.cpp @@ -1,9 +1,14 @@ +#include "nth_root_layout.h" +#include "horizontal_layout.h" +#include +#include #include #include -#include "nth_root_layout.h" namespace Poincare { +static inline uint16_t max(uint16_t x, uint16_t y) { return (x>y ? x : y); } + const uint8_t radixPixel[NthRootLayout::k_leftRadixHeight][NthRootLayout::k_leftRadixWidth] = { {0x00, 0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0x00, 0xFF, 0xFF, 0xFF}, @@ -15,47 +20,198 @@ const uint8_t radixPixel[NthRootLayout::k_leftRadixHeight][NthRootLayout::k_left {0xFF, 0xFF, 0xFF, 0xFF, 0x00}, }; -NthRootLayout::NthRootLayout(ExpressionLayout * radicandLayout, ExpressionLayout * indexLayout) : - ExpressionLayout(), - m_radicandLayout(radicandLayout), - m_indexLayout(indexLayout) -{ - m_radicandLayout->setParent(this); - if (m_indexLayout != nullptr) { - m_indexLayout->setParent(this); - m_baseline = max(m_radicandLayout->baseline() + k_radixLineThickness + k_heightMargin, - m_indexLayout->size().height() + k_indexHeight); - } else { - m_baseline = m_radicandLayout->baseline() + k_radixLineThickness + k_heightMargin; +ExpressionLayout * NthRootLayout::clone() const { + if (numberOfChildren() == 1) { + return new NthRootLayout(const_cast(this)->radicandLayout(), true); } + assert(numberOfChildren() == 2); + return new NthRootLayout(const_cast(this)->radicandLayout(), const_cast(this)->indexLayout(), true); } -NthRootLayout::~NthRootLayout() { - delete m_radicandLayout; - if (m_indexLayout != nullptr) { - delete m_indexLayout; +void NthRootLayout::collapseSiblingsAndMoveCursor(ExpressionLayoutCursor * cursor) { + // If the radicand layout is not an HorizontalLayout, replace it with one. + if (!radicandLayout()->isHorizontal()) { + ExpressionLayout * previousRadicand = radicandLayout(); + HorizontalLayout * horizontalRadicandLayout = new HorizontalLayout(previousRadicand, false); + replaceChild(previousRadicand, horizontalRadicandLayout, false); } + ExpressionLayout::collapseOnDirection(HorizontalDirection::Right, 0); + cursor->setPointedExpressionLayout(radicandLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); +} + +void NthRootLayout::deleteBeforeCursor(ExpressionLayoutCursor * cursor) { + if (cursor->pointedExpressionLayout() == radicandLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Left) + { + // Case: Left of the radicand. Delete the layout, keep the radicand. + replaceWithAndMoveCursor(radicandLayout(), true, cursor); + return; + } + ExpressionLayout::deleteBeforeCursor(cursor); +} + +ExpressionLayoutCursor NthRootLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Left of the radicand. Go the index if any, else go Left of the root. + if (radicandLayout() + && cursor.pointedExpressionLayout() == radicandLayout() + && cursor.position() == ExpressionLayoutCursor::Position::Left) + { + if (indexLayout()) { + return ExpressionLayoutCursor(indexLayout(), ExpressionLayoutCursor::Position::Right); + } + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + // Case: Left of the index. Go Left of the root. + if (indexLayout() + && cursor.pointedExpressionLayout() == indexLayout() + && cursor.position() == ExpressionLayoutCursor::Position::Left) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Right. Go Right of the radicand. + if (cursor.position() == ExpressionLayoutCursor::Position::Right) { + assert(radicandLayout() != nullptr); + return ExpressionLayoutCursor(radicandLayout(), ExpressionLayoutCursor::Position::Right); + } + assert(cursor.position() == ExpressionLayoutCursor::Position::Left); + // Case: Left. Ask the parent. + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor NthRootLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Right of the radicand. Go the Right of the root. + if (radicandLayout() + && cursor.pointedExpressionLayout() == radicandLayout() + && cursor.position() == ExpressionLayoutCursor::Position::Right) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); + } + // Case: Right of the index. Go Left of the integrand. + if (indexLayout() + && cursor.pointedExpressionLayout() == indexLayout() + && cursor.position() == ExpressionLayoutCursor::Position::Right) + { + assert(radicandLayout() != nullptr); + return ExpressionLayoutCursor(radicandLayout(), ExpressionLayoutCursor::Position::Left); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Left. Go index if there is one, else go to the radicand. + if (cursor.position() == ExpressionLayoutCursor::Position::Left) { + if (indexLayout()) { + return ExpressionLayoutCursor(indexLayout(), ExpressionLayoutCursor::Position::Left); + } + assert(radicandLayout() != nullptr); + return ExpressionLayoutCursor(radicandLayout(), ExpressionLayoutCursor::Position::Left); + } + assert(cursor.position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. Ask the parent. + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor NthRootLayout::cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + // If the cursor is Left of the radicand, move it to the index. + if (indexLayout() + && radicandLayout() + && cursor.isEquivalentTo(ExpressionLayoutCursor(radicandLayout(), ExpressionLayoutCursor::Position::Left))) + { + return ExpressionLayoutCursor(indexLayout(), ExpressionLayoutCursor::Position::Right); + } + // If the cursor is Left, move it to the index. + if (indexLayout() + && cursor.pointedExpressionLayout() == this + && cursor.position() == ExpressionLayoutCursor::Position::Left) + { + return ExpressionLayoutCursor(indexLayout(), ExpressionLayoutCursor::Position::Left); + } + return ExpressionLayout::cursorAbove(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +ExpressionLayoutCursor NthRootLayout::cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + if (indexLayout() && cursor.pointedExpressionLayout()->hasAncestor(indexLayout(), true)) { + // If the cursor is Right of the index, move it to the radicand. + if (cursor.isEquivalentTo(ExpressionLayoutCursor(indexLayout(), ExpressionLayoutCursor::Position::Right))) { + assert(radicandLayout() != nullptr); + return ExpressionLayoutCursor(radicandLayout(), ExpressionLayoutCursor::Position::Left); + } + // If the cursor is Left of the index, move it Left . + if (cursor.isEquivalentTo(ExpressionLayoutCursor(indexLayout(), ExpressionLayoutCursor::Position::Left))) { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + } + return ExpressionLayout::cursorUnder(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +static_assert('\x90' == Ion::Charset::Root, "Unicode error"); +int NthRootLayout::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { + // Case: root(x,n) + if (numberOfChildren() == 2 + && (const_cast(this))->indexLayout() + && !(const_cast(this))->indexLayout()->isEmpty()) + { + return LayoutEngine::writePrefixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, "root"); + } + // Case: squareRoot(x) + if (numberOfChildren() == 1) { + return LayoutEngine::writePrefixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, "\x90"); + } + // Case: root(x,empty) + // Write "'SquareRootSymbol'('radicandLayout')". + assert((const_cast(this))->indexLayout() && (const_cast(this))->indexLayout()->isEmpty()); + if (bufferSize == 0) { + return -1; + } + buffer[bufferSize-1] = 0; + int numberOfChar = 0; + + buffer[numberOfChar++] = '\x90'; + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; + } + + buffer[numberOfChar++] = '('; + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; + } + + numberOfChar += (const_cast(this))->radicandLayout()->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfSignificantDigits); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + buffer[numberOfChar++] = ')'; + buffer[numberOfChar] = 0; + return numberOfChar; } void NthRootLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - KDSize radicandSize = m_radicandLayout->size(); - KDSize indexSize = m_indexLayout != nullptr ? m_indexLayout->size() : KDSize(k_leftRadixWidth,0); + KDSize radicandSize = radicandLayout()->size(); + KDSize indexSize = adjustedIndexSize(); KDColor workingBuffer[k_leftRadixWidth*k_leftRadixHeight]; KDRect leftRadixFrame(p.x() + indexSize.width() + k_widthMargin - k_leftRadixWidth, - p.y() + m_baseline + radicandSize.height() - m_radicandLayout->baseline() + k_heightMargin - k_leftRadixHeight, + p.y() + baseline() + radicandSize.height() - radicandLayout()->baseline() + k_heightMargin - k_leftRadixHeight, k_leftRadixWidth, k_leftRadixHeight); ctx->blendRectWithMask(leftRadixFrame, expressionColor, (const uint8_t *)radixPixel, (KDColor *)workingBuffer); - if (indexSize.height() + k_indexHeight > m_radicandLayout->baseline() + k_radixLineThickness + k_heightMargin) { + // If the indice is higher than the root. + if (indexSize.height() + k_indexHeight > radicandLayout()->baseline() + k_radixLineThickness + k_heightMargin) { + // Vertical radix bar ctx->fillRect(KDRect(p.x() + indexSize.width() + k_widthMargin, - p.y() + indexSize.height() + k_indexHeight - m_radicandLayout->baseline() - k_radixLineThickness - k_heightMargin, + p.y() + indexSize.height() + k_indexHeight - radicandLayout()->baseline() - k_radixLineThickness - k_heightMargin, k_radixLineThickness, radicandSize.height() + 2*k_heightMargin + k_radixLineThickness), expressionColor); + // Horizontal radix bar ctx->fillRect(KDRect(p.x() + indexSize.width() + k_widthMargin, - p.y() + indexSize.height() + k_indexHeight - m_radicandLayout->baseline() - k_radixLineThickness - k_heightMargin, - radicandSize.width() + 2*k_widthMargin, + p.y() + indexSize.height() + k_indexHeight - radicandLayout()->baseline() - k_radixLineThickness - k_heightMargin, + radicandSize.width() + 2*k_widthMargin + k_radixHorizontalOverflow, k_radixLineThickness), expressionColor); - ctx->fillRect(KDRect(p.x() + indexSize.width() + k_widthMargin + radicandSize.width() + 2*k_widthMargin, - p.y() + indexSize.height() + k_indexHeight - m_radicandLayout->baseline() - k_radixLineThickness - k_heightMargin, + // Right radix bar + ctx->fillRect(KDRect(p.x() + indexSize.width() + k_widthMargin + radicandSize.width() + 2*k_widthMargin + k_radixHorizontalOverflow, + p.y() + indexSize.height() + k_indexHeight - radicandLayout()->baseline() - k_radixLineThickness - k_heightMargin, k_radixLineThickness, k_rightRadixHeight + k_radixLineThickness), expressionColor); } else { @@ -65,9 +221,9 @@ void NthRootLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, radicandSize.height() + 2*k_heightMargin + k_radixLineThickness), expressionColor); ctx->fillRect(KDRect(p.x() + indexSize.width() + k_widthMargin, p.y(), - radicandSize.width() + 2*k_widthMargin, + radicandSize.width() + 2*k_widthMargin + k_radixHorizontalOverflow, k_radixLineThickness), expressionColor); - ctx->fillRect(KDRect(p.x() + indexSize.width() + k_widthMargin + radicandSize.width() + 2*k_widthMargin, + ctx->fillRect(KDRect(p.x() + indexSize.width() + k_widthMargin + radicandSize.width() + 2*k_widthMargin + k_radixHorizontalOverflow, p.y(), k_radixLineThickness, k_rightRadixHeight + k_radixLineThickness), expressionColor); @@ -76,40 +232,42 @@ void NthRootLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, } KDSize NthRootLayout::computeSize() { - KDSize radicandSize = m_radicandLayout->size(); - KDSize indexSize = m_indexLayout != nullptr ? m_indexLayout->size() : KDSize(k_leftRadixWidth,0); + KDSize radicandSize = radicandLayout()->size(); + KDSize indexSize = adjustedIndexSize(); return KDSize( - indexSize.width() + 3*k_widthMargin + 2*k_radixLineThickness + radicandSize.width(), - m_baseline + radicandSize.height() - m_radicandLayout->baseline() + k_heightMargin + indexSize.width() + 3*k_widthMargin + 2*k_radixLineThickness + radicandSize.width() + k_radixHorizontalOverflow, + baseline() + radicandSize.height() - radicandLayout()->baseline() + k_heightMargin ); } -ExpressionLayout * NthRootLayout::child(uint16_t index) { - switch (index) { - case 0: - return m_radicandLayout; - case 1: - return m_indexLayout; - default: - return nullptr; +void NthRootLayout::computeBaseline() { + if (indexLayout() != nullptr) { + m_baseline = max(radicandLayout()->baseline() + k_radixLineThickness + k_heightMargin, + indexLayout()->size().height() + k_indexHeight); + } else { + m_baseline = radicandLayout()->baseline() + k_radixLineThickness + k_heightMargin; } + m_baselined = true; } KDPoint NthRootLayout::positionOfChild(ExpressionLayout * child) { KDCoordinate x = 0; KDCoordinate y = 0; - KDSize indexSize = m_indexLayout != nullptr ? m_indexLayout->size() : KDSize(k_leftRadixWidth,0); - if (child == m_indexLayout) { - x = 0; - y = m_baseline - indexSize.height() - k_indexHeight; - } else if (child == m_radicandLayout) { + KDSize indexSize = adjustedIndexSize(); + if (child == radicandLayout()) { x = indexSize.width() + 2*k_widthMargin + k_radixLineThickness; - y = m_baseline - m_radicandLayout->baseline(); + y = baseline() - radicandLayout()->baseline(); + } else if (indexLayout() && child == indexLayout()) { + x = 0; + y = baseline() - indexSize.height() - k_indexHeight; } else { assert(false); } return KDPoint(x,y); } +KDSize NthRootLayout::adjustedIndexSize() { + return indexLayout() != nullptr ? KDSize(max(k_leftRadixWidth, indexLayout()->size().width()), indexLayout()->size().height()) : KDSize(k_leftRadixWidth,0); } +} diff --git a/poincare/src/layout/nth_root_layout.h b/poincare/src/layout/nth_root_layout.h index b75cebdb0..4af03b082 100644 --- a/poincare/src/layout/nth_root_layout.h +++ b/poincare/src/layout/nth_root_layout.h @@ -1,34 +1,50 @@ #ifndef POINCARE_NTH_ROOT_LAYOUT_H #define POINCARE_NTH_ROOT_LAYOUT_H -#include -#include +#include +#include namespace Poincare { -class NthRootLayout : public ExpressionLayout { +class NthRootLayout : public BoundedStaticLayoutHierarchy<2> { public: - NthRootLayout(ExpressionLayout * radicandLayout, ExpressionLayout * indexLayout); - ~NthRootLayout(); - NthRootLayout(const NthRootLayout& other) = delete; - NthRootLayout(NthRootLayout&& other) = delete; - NthRootLayout& operator=(const NthRootLayout& other) = delete; - NthRootLayout& operator=(NthRootLayout&& other) = delete; constexpr static KDCoordinate k_leftRadixHeight = 8; constexpr static KDCoordinate k_leftRadixWidth = 5; + using BoundedStaticLayoutHierarchy::BoundedStaticLayoutHierarchy; + ExpressionLayout * clone() const override; + + // Collapse + void collapseSiblingsAndMoveCursor(ExpressionLayoutCursor * cursor) override; + + // User input + void deleteBeforeCursor(ExpressionLayoutCursor * cursor) override; + + // Tree navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + ExpressionLayoutCursor cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; + + // Other + bool hasUpperLeftIndex() const override { return numberOfChildren() > 1; } protected: void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; + void computeBaseline() override; KDPoint positionOfChild(ExpressionLayout * child) override; private: constexpr static KDCoordinate k_rightRadixHeight = 2; - constexpr static KDCoordinate k_indexHeight = 5; + constexpr static KDCoordinate k_radixHorizontalOverflow = 2; + constexpr static KDCoordinate k_indexHeight = 4; constexpr static KDCoordinate k_heightMargin = 2; - constexpr static KDCoordinate k_widthMargin = 1; + constexpr static KDCoordinate k_widthMargin = 2; constexpr static KDCoordinate k_radixLineThickness = 1; - ExpressionLayout * m_radicandLayout; - ExpressionLayout * m_indexLayout; + KDSize adjustedIndexSize(); + ExpressionLayout * radicandLayout() { return editableChild(0); } + ExpressionLayout * indexLayout() { return numberOfChildren() > 1 ? editableChild(1) : nullptr; } }; } diff --git a/poincare/src/layout/parenthesis_layout.cpp b/poincare/src/layout/parenthesis_layout.cpp deleted file mode 100644 index 1c5814b60..000000000 --- a/poincare/src/layout/parenthesis_layout.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "parenthesis_layout.h" -extern "C" { -#include -#include -} - -namespace Poincare { - -const uint8_t topLeftCurve[ParenthesisLayout::k_parenthesisCurveHeight][ParenthesisLayout::k_parenthesisCurveWidth] = { - {0xFF, 0xFF, 0xFF, 0xF9, 0x66}, - {0xFF, 0xFF, 0xEB, 0x40, 0x9A}, - {0xFF, 0xF2, 0x40, 0xBF, 0xFF}, - {0xFF, 0x49, 0xB6, 0xFF, 0xFF}, - {0xA9, 0x5A, 0xFF, 0xFF, 0xFF}, - {0x45, 0xBE, 0xFF, 0xFF, 0xFF}, - {0x11, 0xEE, 0xFF, 0xFF, 0xFF}, - -}; - -const uint8_t bottomLeftCurve[ParenthesisLayout::k_parenthesisCurveHeight][ParenthesisLayout::k_parenthesisCurveWidth] = { - {0x11, 0xEE, 0xFF, 0xFF, 0xFF}, - {0x45, 0xBE, 0xFF, 0xFF, 0xFF}, - {0xA9, 0x5A, 0xFF, 0xFF, 0xFF}, - {0xFF, 0x49, 0xB6, 0xFF, 0xFF}, - {0xFF, 0xF2, 0x40, 0xBF, 0xFF}, - {0xFF, 0xFF, 0xEB, 0x40, 0x9A}, - {0xFF, 0xFF, 0xFF, 0xF9, 0x66}, - -}; - -const uint8_t topRightCurve[ParenthesisLayout::k_parenthesisCurveHeight][ParenthesisLayout::k_parenthesisCurveWidth] = { - {0x66, 0xF9, 0xFF, 0xFF, 0xFF}, - {0x9A, 0x40, 0xEB, 0xFF, 0xFF}, - {0xFF, 0xBF, 0x40, 0xF2, 0xFF}, - {0xFF, 0xFF, 0xB6, 0x49, 0xFF}, - {0xFF, 0xFF, 0xFF, 0x5A, 0xA9}, - {0xFF, 0xFF, 0xFF, 0xBE, 0x45}, - {0xFF, 0xFF, 0xFF, 0xEE, 0x11}, -}; - -const uint8_t bottomRightCurve[ParenthesisLayout::k_parenthesisCurveHeight][ParenthesisLayout::k_parenthesisCurveWidth] = { - {0xFF, 0xFF, 0xFF, 0xEE, 0x11}, - {0xFF, 0xFF, 0xFF, 0xBE, 0x45}, - {0xFF, 0xFF, 0xFF, 0x5A, 0xA9}, - {0xFF, 0xFF, 0xB6, 0x49, 0xFF}, - {0xFF, 0xBF, 0x40, 0xF2, 0xFF}, - {0x9A, 0x40, 0xEB, 0xFF, 0xFF}, - {0x66, 0xF9, 0xFF, 0xFF, 0xFF}, - -}; - -ParenthesisLayout::ParenthesisLayout(ExpressionLayout * operandLayout) : - ExpressionLayout(), - m_operandLayout(operandLayout) -{ - if (m_operandLayout) { - m_operandLayout->setParent(this); - m_baseline = m_operandLayout->baseline(); - } else { - m_baseline = (KDText::charSize(KDText::FontSize::Large).height()+1)/2; - } -} - -ParenthesisLayout::~ParenthesisLayout() { - if (m_operandLayout) { - delete m_operandLayout; - } -} - -KDColor s_parenthesisWorkingBuffer[ParenthesisLayout::k_parenthesisCurveHeight*ParenthesisLayout::k_parenthesisCurveWidth]; - -void ParenthesisLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - KDRect frame(p.x()+k_externWidthMargin, p.y()+k_externHeightMargin, k_parenthesisCurveWidth, k_parenthesisCurveHeight); - ctx->blendRectWithMask(frame, expressionColor, (const uint8_t *)topLeftCurve, (KDColor *)s_parenthesisWorkingBuffer); - frame = KDRect(p.x()+k_externWidthMargin, p.y() + operandSize().height() - k_parenthesisCurveHeight - k_externHeightMargin, - k_parenthesisCurveWidth, k_parenthesisCurveHeight); - ctx->blendRectWithMask(frame, expressionColor, (const uint8_t *)bottomLeftCurve, (KDColor *)s_parenthesisWorkingBuffer); - frame = KDRect(p.x()+k_externWidthMargin + operandSize().width() + 2*k_widthMargin + 2*k_lineThickness - k_parenthesisCurveWidth, p.y() + k_externHeightMargin, - k_parenthesisCurveWidth, k_parenthesisCurveHeight); - ctx->blendRectWithMask(frame, expressionColor, (const uint8_t *)topRightCurve, (KDColor *)s_parenthesisWorkingBuffer); - frame = KDRect(p.x() +k_externWidthMargin + operandSize().width() + 2*k_widthMargin + 2*k_lineThickness - k_parenthesisCurveWidth, p.y() + operandSize().height() - k_parenthesisCurveHeight - k_externHeightMargin, - k_parenthesisCurveWidth, k_parenthesisCurveHeight); - ctx->blendRectWithMask(frame, expressionColor, (const uint8_t *)bottomRightCurve, (KDColor *)s_parenthesisWorkingBuffer); - - ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y()+k_parenthesisCurveHeight+k_externHeightMargin, k_lineThickness, operandSize().height() - 2*(k_parenthesisCurveHeight+k_externHeightMargin)), expressionColor); - ctx->fillRect(KDRect(p.x()+k_externWidthMargin+operandSize().width()+2*k_widthMargin+k_lineThickness, p.y()+k_parenthesisCurveHeight+2, k_lineThickness, operandSize().height()- 2*(k_parenthesisCurveHeight+k_externHeightMargin)), expressionColor); -} - -KDSize ParenthesisLayout::computeSize() { - return KDSize(operandSize().width() + 2*k_widthMargin + 2*k_lineThickness+2*k_externWidthMargin, operandSize().height()); -} - -ExpressionLayout * ParenthesisLayout::child(uint16_t index) { - if (index == 0) { - return m_operandLayout; - } - return nullptr; -} - -KDPoint ParenthesisLayout::positionOfChild(ExpressionLayout * child) { - return KDPoint(k_widthMargin+k_lineThickness+k_externWidthMargin, 0); -} - -KDSize ParenthesisLayout::operandSize() { - return (m_operandLayout ? m_operandLayout->size() : KDSize(0, KDText::charSize(KDText::FontSize::Large).height())); -} - -} diff --git a/poincare/src/layout/parenthesis_layout.h b/poincare/src/layout/parenthesis_layout.h index 9f4772e58..7b826287a 100644 --- a/poincare/src/layout/parenthesis_layout.h +++ b/poincare/src/layout/parenthesis_layout.h @@ -1,33 +1,26 @@ #ifndef POINCARE_PARENTHESIS_LAYOUT_H #define POINCARE_PARENTHESIS_LAYOUT_H -#include -#include +#include "bracket_layout.h" namespace Poincare { -class ParenthesisLayout : public ExpressionLayout { +class ParenthesisLayout : public BracketLayout { public: - ParenthesisLayout(ExpressionLayout * operandLayout); - ~ParenthesisLayout(); - ParenthesisLayout(const ParenthesisLayout& other) = delete; - ParenthesisLayout(ParenthesisLayout&& other) = delete; - ParenthesisLayout& operator=(const ParenthesisLayout& other) = delete; - ParenthesisLayout& operator=(ParenthesisLayout&& other) = delete; + using BracketLayout::BracketLayout; + constexpr static KDCoordinate parenthesisWidth() { return k_widthMargin + k_lineThickness + k_externWidthMargin; } constexpr static KDCoordinate k_parenthesisCurveWidth = 5; constexpr static KDCoordinate k_parenthesisCurveHeight = 7; -protected: - void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; - KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; - KDPoint positionOfChild(ExpressionLayout * child) override; -private: constexpr static KDCoordinate k_externWidthMargin = 1; constexpr static KDCoordinate k_externHeightMargin = 2; constexpr static KDCoordinate k_widthMargin = 5; constexpr static KDCoordinate k_lineThickness = 1; - KDSize operandSize(); - ExpressionLayout * m_operandLayout; + constexpr static KDCoordinate k_verticalMargin = 4; +protected: + KDColor s_parenthesisWorkingBuffer[k_parenthesisCurveHeight*k_parenthesisCurveWidth]; + KDSize computeSize() override { + return KDSize(parenthesisWidth(), operandHeight() + k_verticalMargin); + } }; } diff --git a/poincare/src/layout/product_layout.cpp b/poincare/src/layout/product_layout.cpp index cb573cf4b..f850b8860 100644 --- a/poincare/src/layout/product_layout.cpp +++ b/poincare/src/layout/product_layout.cpp @@ -1,21 +1,36 @@ #include "product_layout.h" -#include -#include +#include "char_layout.h" +#include "horizontal_layout.h" namespace Poincare { +ExpressionLayout * ProductLayout::clone() const { + ProductLayout * layout = new ProductLayout(const_cast(this)->argumentLayout(), const_cast(this)->lowerBoundLayout(), const_cast(this)->upperBoundLayout(), true); + return layout; +} + +int ProductLayout::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { + return SequenceLayout::writeDerivedClassInBuffer("product", buffer, bufferSize, numberOfSignificantDigits); +} + void ProductLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - KDSize upperBoundSize = m_upperBoundLayout->size(); - KDSize lowerBoundSize = m_lowerBoundLayout->size(); - ctx->fillRect(KDRect(p.x() + max(max(0, (upperBoundSize.width()-k_symbolWidth)/2), (lowerBoundSize.width()-k_symbolWidth)/2), - p.y() + max(upperBoundSize.height()+k_boundHeightMargin, m_argumentLayout->baseline()-(k_symbolHeight+1)/2), + // Compute sizes. + KDSize upperBoundSize = upperBoundLayout()->size(); + KDSize lowerBoundSizeWithNEquals = HorizontalLayout(new CharLayout('n'), new CharLayout('='), lowerBoundLayout()->clone(), false).size(); + + // Render the Product symbol. + ctx->fillRect(KDRect(p.x() + max(max(0, (upperBoundSize.width()-k_symbolWidth)/2), (lowerBoundSizeWithNEquals.width()-k_symbolWidth)/2), + p.y() + max(upperBoundSize.height()+k_boundHeightMargin, argumentLayout()->baseline()-(k_symbolHeight+1)/2), k_lineThickness, k_symbolHeight), expressionColor); - ctx->fillRect(KDRect(p.x() + max(max(0, (upperBoundSize.width()-k_symbolWidth)/2), (lowerBoundSize.width()-k_symbolWidth)/2), - p.y() + max(upperBoundSize.height()+k_boundHeightMargin, m_argumentLayout->baseline()-(k_symbolHeight+1)/2), + ctx->fillRect(KDRect(p.x() + max(max(0, (upperBoundSize.width()-k_symbolWidth)/2), (lowerBoundSizeWithNEquals.width()-k_symbolWidth)/2), + p.y() + max(upperBoundSize.height()+k_boundHeightMargin, argumentLayout()->baseline()-(k_symbolHeight+1)/2), k_symbolWidth, k_lineThickness), expressionColor); - ctx->fillRect(KDRect(p.x() + max(max(0, (upperBoundSize.width()-k_symbolWidth)/2), (lowerBoundSize.width()-k_symbolWidth)/2)+k_symbolWidth, - p.y() + max(upperBoundSize.height()+k_boundHeightMargin, m_argumentLayout->baseline()-(k_symbolHeight+1)/2), + ctx->fillRect(KDRect(p.x() + max(max(0, (upperBoundSize.width()-k_symbolWidth)/2), (lowerBoundSizeWithNEquals.width()-k_symbolWidth)/2)+k_symbolWidth, + p.y() + max(upperBoundSize.height()+k_boundHeightMargin, argumentLayout()->baseline()-(k_symbolHeight+1)/2), k_lineThickness, k_symbolHeight), expressionColor); + + // Render the "n=" and the parentheses. + SequenceLayout::render(ctx, p, expressionColor, backgroundColor); } } diff --git a/poincare/src/layout/product_layout.h b/poincare/src/layout/product_layout.h index 4aca54737..090aab63d 100644 --- a/poincare/src/layout/product_layout.h +++ b/poincare/src/layout/product_layout.h @@ -2,12 +2,15 @@ #define POINCARE_PRODUCT_LAYOUT_H #include "sequence_layout.h" +#include namespace Poincare { class ProductLayout : public SequenceLayout { public: using SequenceLayout::SequenceLayout; + ExpressionLayout * clone() const override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; protected: void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; private: diff --git a/poincare/src/layout/right_parenthesis_layout.cpp b/poincare/src/layout/right_parenthesis_layout.cpp new file mode 100644 index 000000000..369cca075 --- /dev/null +++ b/poincare/src/layout/right_parenthesis_layout.cpp @@ -0,0 +1,71 @@ +#include "right_parenthesis_layout.h" +extern "C" { +#include +#include +} + +namespace Poincare { + +const uint8_t topRightCurve[ParenthesisLayout::k_parenthesisCurveHeight][ParenthesisLayout::k_parenthesisCurveWidth] = { + {0x66, 0xF9, 0xFF, 0xFF, 0xFF}, + {0x9A, 0x40, 0xEB, 0xFF, 0xFF}, + {0xFF, 0xBF, 0x40, 0xF2, 0xFF}, + {0xFF, 0xFF, 0xB6, 0x49, 0xFF}, + {0xFF, 0xFF, 0xFF, 0x5A, 0xA9}, + {0xFF, 0xFF, 0xFF, 0xBE, 0x45}, + {0xFF, 0xFF, 0xFF, 0xEE, 0x11}, +}; + +const uint8_t bottomRightCurve[ParenthesisLayout::k_parenthesisCurveHeight][ParenthesisLayout::k_parenthesisCurveWidth] = { + {0xFF, 0xFF, 0xFF, 0xEE, 0x11}, + {0xFF, 0xFF, 0xFF, 0xBE, 0x45}, + {0xFF, 0xFF, 0xFF, 0x5A, 0xA9}, + {0xFF, 0xFF, 0xB6, 0x49, 0xFF}, + {0xFF, 0xBF, 0x40, 0xF2, 0xFF}, + {0x9A, 0x40, 0xEB, 0xFF, 0xFF}, + {0x66, 0xF9, 0xFF, 0xFF, 0xFF}, +}; + +ExpressionLayout * RightParenthesisLayout::clone() const { + RightParenthesisLayout * layout = new RightParenthesisLayout(); + return layout; +} + +bool RightParenthesisLayout::isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { + if (*numberOfOpenParenthesis == 0 && !goingLeft) { + return false; + } + *numberOfOpenParenthesis = goingLeft ? *numberOfOpenParenthesis + 1 : *numberOfOpenParenthesis - 1; + return true; +} + +void RightParenthesisLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + KDRect frame = KDRect(p.x() + ParenthesisLayout::k_widthMargin + ParenthesisLayout::k_lineThickness - ParenthesisLayout::k_parenthesisCurveWidth, + p.y() + ParenthesisLayout::k_externHeightMargin, + ParenthesisLayout::k_parenthesisCurveWidth, + ParenthesisLayout::k_parenthesisCurveHeight); + + ctx->blendRectWithMask(frame, expressionColor, (const uint8_t *)topRightCurve, (KDColor *)(ParenthesisLayout::s_parenthesisWorkingBuffer)); + + frame = KDRect(p.x() + ParenthesisLayout::k_widthMargin + ParenthesisLayout::k_lineThickness - ParenthesisLayout::k_parenthesisCurveWidth, + p.y() + size().height() - ParenthesisLayout::k_parenthesisCurveHeight - ParenthesisLayout::k_externHeightMargin, + ParenthesisLayout::k_parenthesisCurveWidth, + ParenthesisLayout::k_parenthesisCurveHeight); + + ctx->blendRectWithMask(frame, expressionColor, (const uint8_t *)bottomRightCurve, (KDColor *)(ParenthesisLayout::s_parenthesisWorkingBuffer)); + + ctx->fillRect(KDRect(p.x()+ParenthesisLayout::k_widthMargin, + p.y()+ParenthesisLayout::k_parenthesisCurveHeight+2, + ParenthesisLayout::k_lineThickness, + size().height() - 2*(ParenthesisLayout::k_parenthesisCurveHeight+ParenthesisLayout::k_externHeightMargin)), + expressionColor); +} + +} + + + + + + + diff --git a/poincare/src/layout/right_parenthesis_layout.h b/poincare/src/layout/right_parenthesis_layout.h new file mode 100644 index 000000000..a4606acce --- /dev/null +++ b/poincare/src/layout/right_parenthesis_layout.h @@ -0,0 +1,26 @@ +#ifndef POINCARE_RIGHT_PARENTHESIS_LAYOUT_H +#define POINCARE_RIGHT_PARENTHESIS_LAYOUT_H + +#include +#include + +namespace Poincare { + +class RightParenthesisLayout : public ParenthesisLayout { + friend class BinomialCoefficientLayout; + friend class SequenceLayout; +public: + using ParenthesisLayout::ParenthesisLayout; + ExpressionLayout * clone() const override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writeOneCharInBuffer(buffer, bufferSize, ')'); + } + bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; + bool isRightParenthesis() const override { return true; } +protected: + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; +}; + +} + +#endif diff --git a/poincare/src/layout/right_square_bracket_layout.cpp b/poincare/src/layout/right_square_bracket_layout.cpp new file mode 100644 index 000000000..019464bc6 --- /dev/null +++ b/poincare/src/layout/right_square_bracket_layout.cpp @@ -0,0 +1,15 @@ +#include "right_square_bracket_layout.h" + +namespace Poincare { + +ExpressionLayout * RightSquareBracketLayout::clone() const { + return new RightSquareBracketLayout(); +} + +void RightSquareBracketLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + ctx->fillRect(KDRect(p.x()+k_widthMargin, p.y(), k_lineThickness, operandHeight()), expressionColor); + ctx->fillRect(KDRect(p.x()+k_widthMargin-k_bracketWidth+1, p.y(), k_bracketWidth, k_lineThickness), expressionColor); + ctx->fillRect(KDRect(p.x()+k_widthMargin-k_bracketWidth+1, p.y() + operandHeight(), k_bracketWidth, k_lineThickness), expressionColor); +} + +} diff --git a/poincare/src/layout/right_square_bracket_layout.h b/poincare/src/layout/right_square_bracket_layout.h new file mode 100644 index 000000000..d99d3e6e4 --- /dev/null +++ b/poincare/src/layout/right_square_bracket_layout.h @@ -0,0 +1,23 @@ +#ifndef POINCARE_RIGHT_SQUARE_BRACKET_LAYOUT_H +#define POINCARE_RIGHT_SQUARE_BRACKET_LAYOUT_H + +#include +#include + +namespace Poincare { + +class RightSquareBracketLayout : public SquareBracketLayout { +public: + using SquareBracketLayout::SquareBracketLayout; + ExpressionLayout * clone() const override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writeOneCharInBuffer(buffer, bufferSize, ']'); + } + bool isRightBracket() const override { return true; } +protected: + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; +}; + +} + +#endif diff --git a/poincare/src/layout/sequence_layout.cpp b/poincare/src/layout/sequence_layout.cpp index 52ed5dc95..b95f2191a 100644 --- a/poincare/src/layout/sequence_layout.cpp +++ b/poincare/src/layout/sequence_layout.cpp @@ -1,68 +1,219 @@ #include "sequence_layout.h" -#include +#include "char_layout.h" +#include "horizontal_layout.h" +#include "left_parenthesis_layout.h" +#include "right_parenthesis_layout.h" +#include #include namespace Poincare { -SequenceLayout::SequenceLayout(ExpressionLayout * lowerBoundLayout, ExpressionLayout * upperBoundLayout, ExpressionLayout * argumentLayout) : - ExpressionLayout(), - m_lowerBoundLayout(lowerBoundLayout), - m_upperBoundLayout(upperBoundLayout), - m_argumentLayout(argumentLayout) -{ - m_lowerBoundLayout->setParent(this); - m_upperBoundLayout->setParent(this); - m_argumentLayout->setParent(this); - m_baseline = max(m_upperBoundLayout->size().height()+k_boundHeightMargin+(k_symbolHeight+1)/2, m_argumentLayout->baseline()); +void SequenceLayout::deleteBeforeCursor(ExpressionLayoutCursor * cursor) { + if (cursor->isEquivalentTo(ExpressionLayoutCursor(argumentLayout(), ExpressionLayoutCursor::Position::Left))) { + // Case: Left of the argument. Delete the layout, keep the argument. + replaceWithAndMoveCursor(argumentLayout(), true, cursor); + return; + } + ExpressionLayout::deleteBeforeCursor(cursor); } -SequenceLayout::~SequenceLayout() { - delete m_lowerBoundLayout; - delete m_upperBoundLayout; - delete m_argumentLayout; +ExpressionLayoutCursor SequenceLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Left of the bounds. Go Left of the sequence. + if (cursor.position() == ExpressionLayoutCursor::Position::Left + && ((lowerBoundLayout() + && cursor.pointedExpressionLayout() == lowerBoundLayout()) + || (upperBoundLayout() + && cursor.pointedExpressionLayout() == upperBoundLayout()))) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + // Case: Left of the argument. Go Right of the lower bound. + if (cursor.position() == ExpressionLayoutCursor::Position::Left + && argumentLayout() + && cursor.pointedExpressionLayout() == argumentLayout()) + { + assert(lowerBoundLayout() != nullptr); + return ExpressionLayoutCursor(lowerBoundLayout(), ExpressionLayoutCursor::Position::Right); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Right. Go to the argument and move Left. + if (cursor.position() == ExpressionLayoutCursor::Position::Right) { + assert(argumentLayout() != nullptr); + return ExpressionLayoutCursor(argumentLayout(), ExpressionLayoutCursor::Position::Right); + } + assert(cursor.position() == ExpressionLayoutCursor::Position::Left); + // Case: Left. Ask the parent. + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor SequenceLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Right of the bounds. Go Left of the argument. + if (cursor.position() == ExpressionLayoutCursor::Position::Right + && ((lowerBoundLayout() + && cursor.pointedExpressionLayout() == lowerBoundLayout()) + || (upperBoundLayout() + && cursor.pointedExpressionLayout() == upperBoundLayout()))) + { + assert(argumentLayout() != nullptr); + return ExpressionLayoutCursor(argumentLayout(), ExpressionLayoutCursor::Position::Left); + } + // Case: Right of the argument. Go Right. + if (cursor.position() == ExpressionLayoutCursor::Position::Right + && argumentLayout() + && cursor.pointedExpressionLayout() == argumentLayout()) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); + } + assert(cursor.pointedExpressionLayout() == this); + // Case: Left. Go to the upper bound + if (cursor.position() == ExpressionLayoutCursor::Position::Left) { + assert(upperBoundLayout() != nullptr); + return ExpressionLayoutCursor(upperBoundLayout(), ExpressionLayoutCursor::Position::Left); + } + assert(cursor.position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. Ask the parent + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor SequenceLayout::cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + // If the cursor is inside the lower bound, move it to the upper bound + if (lowerBoundLayout() && cursor.pointedExpressionLayout()->hasAncestor(lowerBoundLayout(), true)) { + assert(upperBoundLayout() != nullptr); + return upperBoundLayout()->cursorInDescendantsAbove(cursor, shouldRecomputeLayout); + } + // If the cursor is Left of the argument, move it to the upper bound + if (argumentLayout() + && cursor.isEquivalentTo(ExpressionLayoutCursor(argumentLayout(), ExpressionLayoutCursor::Position::Left))) + { + assert(upperBoundLayout() != nullptr); + return upperBoundLayout()->cursorInDescendantsAbove(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::cursorAbove(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +ExpressionLayoutCursor SequenceLayout::cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + // If the cursor is inside the upper bound, move it to the lower bound + if (upperBoundLayout() && cursor.pointedExpressionLayout()->hasAncestor(upperBoundLayout(), true)) { + assert(lowerBoundLayout() != nullptr); + return lowerBoundLayout()->cursorInDescendantsUnder(cursor, shouldRecomputeLayout); + } + // If the cursor is Left of the argument, move it to the lower bound + if (argumentLayout() + && cursor.isEquivalentTo(ExpressionLayoutCursor(argumentLayout(), ExpressionLayoutCursor::Position::Left))) + { + assert(lowerBoundLayout() != nullptr); + return lowerBoundLayout()->cursorInDescendantsUnder(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::cursorUnder(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +int SequenceLayout::writeDerivedClassInBuffer(const char * operatorName, char * buffer, int bufferSize, int numberOfSignificantDigits) const { + assert(operatorName != nullptr); + if (bufferSize == 0) { + return -1; + } + buffer[bufferSize-1] = 0; + + // Write the operator name + int numberOfChar = strlcpy(buffer, operatorName, bufferSize); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the opening parenthesis + buffer[numberOfChar++] = '('; + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the argument + numberOfChar += const_cast(this)->argumentLayout()->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfSignificantDigits); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the comma + buffer[numberOfChar++] = ','; + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the lower bound + numberOfChar += const_cast(this)->lowerBoundLayout()->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfSignificantDigits); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the comma + buffer[numberOfChar++] = ','; + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the upper bound + numberOfChar += const_cast(this)->upperBoundLayout()->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfSignificantDigits); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Write the closing parenthesis + buffer[numberOfChar++] = ')'; + buffer[numberOfChar] = 0; + return numberOfChar; } KDSize SequenceLayout::computeSize() { - KDSize argumentSize = m_argumentLayout->size(); - KDSize lowerBoundSize = m_lowerBoundLayout->size(); - KDSize upperBoundSize = m_upperBoundLayout->size(); + KDSize lowerBoundSizeWithNEquals = HorizontalLayout(new CharLayout('n'), new CharLayout('='), lowerBoundLayout()->clone(), false).size(); + LeftParenthesisLayout * dummyLeftParenthesis = new LeftParenthesisLayout(); + RightParenthesisLayout * dummyRightParenthesis = new RightParenthesisLayout(); + HorizontalLayout dummyLayout2(dummyLeftParenthesis, argumentLayout()->clone(), dummyRightParenthesis, false); + KDSize dummyLayoutSize = dummyLayout2.size(); + KDSize upperBoundSize = upperBoundLayout()->size(); return KDSize( - max(max(k_symbolWidth, lowerBoundSize.width()), upperBoundSize.width())+k_argumentWidthMargin+argumentSize.width(), - m_baseline + max(k_symbolHeight/2+k_boundHeightMargin+lowerBoundSize.height(), argumentSize.height() - m_argumentLayout->baseline()) + max(max(k_symbolWidth, lowerBoundSizeWithNEquals.width()), upperBoundSize.width())+k_argumentWidthMargin+dummyLayoutSize.width(), + baseline() + max(k_symbolHeight/2+k_boundHeightMargin+lowerBoundSizeWithNEquals.height(), dummyLayoutSize.height() - argumentLayout()->baseline()) ); } -ExpressionLayout * SequenceLayout::child(uint16_t index) { - switch (index) { - case 0: - return m_upperBoundLayout; - case 1: - return m_lowerBoundLayout; - case 2: - return m_argumentLayout; - default: - return nullptr; - } -} - -KDPoint SequenceLayout::positionOfChild(ExpressionLayout * child) { - KDSize lowerBoundSize = m_lowerBoundLayout->size(); - KDSize upperBoundSize = m_upperBoundLayout->size(); +KDPoint SequenceLayout::positionOfChild(ExpressionLayout * eL) { + ExpressionLayout * lowerBoundClone = lowerBoundLayout()->clone(); + HorizontalLayout dummyLayout1(new CharLayout('n'), new CharLayout('='), lowerBoundClone, false); + KDSize lowerBoundSizeWithNEquals = dummyLayout1.size(); + KDSize upperBoundSize = upperBoundLayout()->size(); + LeftParenthesisLayout * dummyLeftParenthesis = new LeftParenthesisLayout(); + HorizontalLayout dummyLayout2(dummyLeftParenthesis, argumentLayout()->clone(), false); KDCoordinate x = 0; KDCoordinate y = 0; - if (child == m_lowerBoundLayout) { - x = max(max(0, (k_symbolWidth-lowerBoundSize.width())/2), (upperBoundSize.width()-lowerBoundSize.width())/2); - y = m_baseline + k_symbolHeight/2 + k_boundHeightMargin; - } else if (child == m_upperBoundLayout) { - x = max(max(0, (k_symbolWidth-upperBoundSize.width())/2), (lowerBoundSize.width()-upperBoundSize.width())/2); - y = m_baseline - (k_symbolHeight+1)/2- k_boundHeightMargin-upperBoundSize.height(); - } else if (child == m_argumentLayout) { - x = max(max(k_symbolWidth, lowerBoundSize.width()), upperBoundSize.width())+k_argumentWidthMargin; - y = m_baseline - m_argumentLayout->baseline(); + if (eL == lowerBoundLayout()) { + x = dummyLayout1.positionOfChild(lowerBoundClone).x() + +max(max(0, (k_symbolWidth-lowerBoundSizeWithNEquals.width())/2), + (upperBoundSize.width()-lowerBoundSizeWithNEquals.width())/2); + y = baseline() + k_symbolHeight/2 + k_boundHeightMargin; + } else if (eL == upperBoundLayout()) { + x = max(max(0, (k_symbolWidth-upperBoundSize.width())/2), (lowerBoundSizeWithNEquals.width()-upperBoundSize.width())/2); + y = baseline() - (k_symbolHeight+1)/2- k_boundHeightMargin-upperBoundSize.height(); + } else if (eL == argumentLayout()) { + x = max(max(k_symbolWidth, lowerBoundSizeWithNEquals.width()), upperBoundSize.width())+k_argumentWidthMargin+dummyLeftParenthesis->size().width(); + y = baseline() - argumentLayout()->baseline(); } else { assert(false); } return KDPoint(x,y); } +void SequenceLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + // Render the "n=" + CharLayout * dummyN = new CharLayout('n'); + ExpressionLayout * lowerBoundClone = lowerBoundLayout()->clone(); + HorizontalLayout dummyLayout(dummyN, new CharLayout('='), lowerBoundClone, false); + KDPoint nEqualsPosition = positionOfChild(lowerBoundLayout()).translatedBy((dummyLayout.positionOfChild(lowerBoundClone)).opposite()).translatedBy(dummyLayout.positionOfChild(dummyN)); + ctx->drawString("n=", p.translatedBy(nEqualsPosition), dummyN->fontSize(), expressionColor, backgroundColor); + + // Render the parentheses + LeftParenthesisLayout * dummyLeftParenthesis = new LeftParenthesisLayout(); + RightParenthesisLayout * dummyRightParenthesis = new RightParenthesisLayout(); + HorizontalLayout dummyLayout2(dummyLeftParenthesis, argumentLayout()->clone(), dummyRightParenthesis, false); + KDPoint leftParenthesisPoint = positionOfChild(argumentLayout()).translatedBy(dummyLayout2.positionOfChild(dummyLeftParenthesis)).translatedBy(dummyLayout2.positionOfChild(dummyLayout2.editableChild(1)).opposite()); + KDPoint rightParenthesisPoint = positionOfChild(argumentLayout()).translatedBy(dummyLayout2.positionOfChild(dummyRightParenthesis)).translatedBy(dummyLayout2.positionOfChild(dummyLayout2.editableChild(1)).opposite()); + dummyLeftParenthesis->render(ctx, p.translatedBy(leftParenthesisPoint), expressionColor, backgroundColor); + dummyRightParenthesis->render(ctx, p.translatedBy(rightParenthesisPoint), expressionColor, backgroundColor); +} + +void SequenceLayout::computeBaseline() { + m_baseline = max(upperBoundLayout()->size().height()+k_boundHeightMargin+(k_symbolHeight+1)/2, argumentLayout()->baseline()); + m_baselined = true; +} + } diff --git a/poincare/src/layout/sequence_layout.h b/poincare/src/layout/sequence_layout.h index 41f3b17bb..da9f619a6 100644 --- a/poincare/src/layout/sequence_layout.h +++ b/poincare/src/layout/sequence_layout.h @@ -1,31 +1,44 @@ #ifndef POINCARE_SEQUENCE_LAYOUT_H #define POINCARE_SEQUENCE_LAYOUT_H -#include -#include +#include +#include namespace Poincare { -class SequenceLayout : public ExpressionLayout { +class SequenceLayout : public StaticLayoutHierarchy<3> { public: - SequenceLayout(ExpressionLayout * lowerBoundLayout, ExpressionLayout * upperBoundLayout, ExpressionLayout * argumentLayout); - ~SequenceLayout(); - SequenceLayout(const SequenceLayout& other) = delete; - SequenceLayout(SequenceLayout&& other) = delete; - SequenceLayout& operator=(const SequenceLayout& other) = delete; - SequenceLayout& operator=(SequenceLayout&& other) = delete; + using StaticLayoutHierarchy::StaticLayoutHierarchy; constexpr static KDCoordinate k_symbolHeight = 15; constexpr static KDCoordinate k_symbolWidth = 9; + + // User input + void deleteBeforeCursor(ExpressionLayoutCursor * cursor) override; + + // Tree navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + ExpressionLayoutCursor cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + + // Other + ExpressionLayout * layoutToPointWhenInserting() override { + assert(lowerBoundLayout() != nullptr); + return lowerBoundLayout(); + } + char XNTChar() const override { return 'n'; } protected: constexpr static KDCoordinate k_boundHeightMargin = 2; - ExpressionLayout * m_lowerBoundLayout; - ExpressionLayout * m_upperBoundLayout; - ExpressionLayout * m_argumentLayout; -private: - KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; - KDPoint positionOfChild(ExpressionLayout * child) override; constexpr static KDCoordinate k_argumentWidthMargin = 2; + int writeDerivedClassInBuffer(const char * operatorName, char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const; + KDSize computeSize() override; + KDPoint positionOfChild(ExpressionLayout * eL) override; + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + ExpressionLayout * upperBoundLayout() { return editableChild(2); } + ExpressionLayout * lowerBoundLayout() { return editableChild(1); } + ExpressionLayout * argumentLayout() { return editableChild(0); } +private: + void computeBaseline() override; }; } diff --git a/poincare/src/layout/square_bracket_layout.h b/poincare/src/layout/square_bracket_layout.h new file mode 100644 index 000000000..79ac9b05c --- /dev/null +++ b/poincare/src/layout/square_bracket_layout.h @@ -0,0 +1,22 @@ +#ifndef POINCARE_SQUARE_BRACKET_LAYOUT_H +#define POINCARE_SQUARE_BRACKET_LAYOUT_H + +#include "bracket_layout.h" + +namespace Poincare { + +class SquareBracketLayout : public BracketLayout { +public: + using BracketLayout::BracketLayout; +protected: + constexpr static KDCoordinate k_bracketWidth = 5; + constexpr static KDCoordinate k_lineThickness = 1; + constexpr static KDCoordinate k_widthMargin = 5; + constexpr static KDCoordinate k_externWidthMargin = 2; + KDSize computeSize() override { + return KDSize(k_externWidthMargin + k_lineThickness + k_widthMargin, operandHeight() + k_lineThickness); + } +}; +} + +#endif diff --git a/poincare/src/layout/static_layout_hierarchy.cpp b/poincare/src/layout/static_layout_hierarchy.cpp new file mode 100644 index 000000000..0a2df93ae --- /dev/null +++ b/poincare/src/layout/static_layout_hierarchy.cpp @@ -0,0 +1,71 @@ +#include +#include +extern "C" { +#include +} + +namespace Poincare { + +template +StaticLayoutHierarchy::StaticLayoutHierarchy() : + ExpressionLayout(), + m_children{} +{ +} + +template +StaticLayoutHierarchy::StaticLayoutHierarchy(const ExpressionLayout * const * children, bool cloneChildren) : + ExpressionLayout() +{ + build(children, T, cloneChildren); +} + +template<> +StaticLayoutHierarchy<1>::StaticLayoutHierarchy(const ExpressionLayout * e, bool cloneChildren) : + StaticLayoutHierarchy((ExpressionLayout **)&e, cloneChildren) +{ +} + +template<> +StaticLayoutHierarchy<2>::StaticLayoutHierarchy(const ExpressionLayout * e1, const ExpressionLayout * e2, bool cloneChildren) : + StaticLayoutHierarchy(ExpressionLayoutArray(e1, e2).array(), cloneChildren) +{ +} + +template<> +StaticLayoutHierarchy<3>::StaticLayoutHierarchy(const ExpressionLayout * e1, const ExpressionLayout * e2, const ExpressionLayout * e3, bool cloneChildren) : + StaticLayoutHierarchy(ExpressionLayoutArray(e1, e2, e3).array(), cloneChildren) +{ +} + +template +StaticLayoutHierarchy::~StaticLayoutHierarchy() { + for (int i = 0; i < T; i++) { + if (m_children[i] != nullptr) { + delete m_children[i]; + } + } +} + +template +void StaticLayoutHierarchy::build(const ExpressionLayout * const * operands, int numberOfOperands, bool cloneOperands) { + assert(operands != nullptr); + assert(numberOfOperands <= T); + for (int i = 0; i < numberOfOperands; i++) { + assert(operands[i] != nullptr); + if (cloneOperands) { + m_children[i] = operands[i]->clone(); + } else { + m_children[i] = operands[i]; + } + const_cast(m_children[i])->setParent(this); + } +} + +template class Poincare::StaticLayoutHierarchy<0>; +template class Poincare::StaticLayoutHierarchy<1>; +template class Poincare::StaticLayoutHierarchy<2>; +template class Poincare::StaticLayoutHierarchy<3>; + +} + diff --git a/poincare/src/layout/string_layout.cpp b/poincare/src/layout/string_layout.cpp deleted file mode 100644 index 62bbbb68c..000000000 --- a/poincare/src/layout/string_layout.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#include "string_layout.h" - -namespace Poincare { - -StringLayout::StringLayout(const char * string, size_t length, KDText::FontSize fontSize) : - ExpressionLayout(), - m_fontSize(fontSize) -{ - m_string = new char[length+1]; - memcpy(m_string, string, length); - m_string[length] = 0; - // Half height of the font. - m_baseline = (KDText::charSize(m_fontSize).height()+1)/2; -} - -StringLayout::~StringLayout() { - delete[] m_string; -} - -char * StringLayout::text() { - return m_string; -} - -ExpressionLayout * StringLayout::child(uint16_t index) { - return nullptr; -} - -void StringLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - ctx->drawString(m_string, p, m_fontSize, expressionColor, backgroundColor); -} - -KDPoint StringLayout::positionOfChild(ExpressionLayout * child) { - assert(false); // We should never be here - return KDPointZero; -} - -KDSize StringLayout::computeSize() { - return KDText::stringSize(m_string, m_fontSize); -} - -} diff --git a/poincare/src/layout/string_layout.h b/poincare/src/layout/string_layout.h deleted file mode 100644 index 4aa4b584e..000000000 --- a/poincare/src/layout/string_layout.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef POINCARE_STRING_LAYOUT_H -#define POINCARE_STRING_LAYOUT_H - -#include -#include - -namespace Poincare { - -class StringLayout : public ExpressionLayout { -public: - // Here the inverse is a uint8_t instead of a bool, because the size of a bool is - // not standardized, thus since we call a foreign C function with this value we want to be - // sure about compatibility. - StringLayout(const char * string, size_t length, KDText::FontSize fontSize = KDText::FontSize::Large); - ~StringLayout(); - StringLayout(const StringLayout& other) = delete; - StringLayout(StringLayout&& other) = delete; - StringLayout& operator=(const StringLayout& other) = delete; - StringLayout& operator=(StringLayout&& other) = delete; - char * text(); -protected: - void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; - KDSize computeSize() override; - ExpressionLayout * child(uint16_t index) override; - KDPoint positionOfChild(ExpressionLayout * child) override; -private: - char * m_string; - KDText::FontSize m_fontSize; -}; - -} - -#endif diff --git a/poincare/src/layout/sum_layout.cpp b/poincare/src/layout/sum_layout.cpp index 95c376bee..41c274556 100644 --- a/poincare/src/layout/sum_layout.cpp +++ b/poincare/src/layout/sum_layout.cpp @@ -1,6 +1,6 @@ #include "sum_layout.h" -#include -#include +#include "char_layout.h" +#include "horizontal_layout.h" namespace Poincare { @@ -22,14 +22,29 @@ const uint8_t symbolPixel[SumLayout::k_symbolHeight][SumLayout::k_symbolWidth] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, }; +ExpressionLayout * SumLayout::clone() const { + SumLayout * layout = new SumLayout(const_cast(this)->argumentLayout(), const_cast(this)->lowerBoundLayout(), const_cast(this)->upperBoundLayout(), true); + return layout; +} + +int SumLayout::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { + return SequenceLayout::writeDerivedClassInBuffer("sum", buffer, bufferSize, numberOfSignificantDigits); +} + void SumLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - KDSize upperBoundSize = m_upperBoundLayout->size(); - KDSize lowerBoundSize = m_lowerBoundLayout->size(); + // Computes sizes. + KDSize upperBoundSize = upperBoundLayout()->size(); + KDSize lowerBoundSizeWithNEquals = HorizontalLayout(new CharLayout('n'), new CharLayout('='), lowerBoundLayout()->clone(), false).size(); + + // Render the Sum symbol. KDColor workingBuffer[k_symbolWidth*k_symbolHeight]; - KDRect symbolFrame(p.x() + max(max(0, (upperBoundSize.width()-k_symbolWidth)/2), (lowerBoundSize.width()-k_symbolWidth)/2), - p.y() + max(upperBoundSize.height()+k_boundHeightMargin, m_argumentLayout->baseline()-(k_symbolHeight+1)/2), - k_symbolWidth, k_symbolHeight); + KDRect symbolFrame(p.x() + max(max(0, (upperBoundSize.width()-k_symbolWidth)/2), (lowerBoundSizeWithNEquals.width()-k_symbolWidth)/2), + p.y() + max(upperBoundSize.height()+k_boundHeightMargin, argumentLayout()->baseline()-(k_symbolHeight+1)/2), + k_symbolWidth, k_symbolHeight); ctx->blendRectWithMask(symbolFrame, expressionColor, (const uint8_t *)symbolPixel, (KDColor *)workingBuffer); + + // Render the "n=" and the parentheses. + SequenceLayout::render(ctx, p, expressionColor, backgroundColor); } } diff --git a/poincare/src/layout/sum_layout.h b/poincare/src/layout/sum_layout.h index e09e447cf..205ee0831 100644 --- a/poincare/src/layout/sum_layout.h +++ b/poincare/src/layout/sum_layout.h @@ -2,12 +2,15 @@ #define POINCARE_SUM_LAYOUT_H #include "sequence_layout.h" +#include namespace Poincare { class SumLayout : public SequenceLayout { public: using SequenceLayout::SequenceLayout; + ExpressionLayout * clone() const override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; private: void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; }; diff --git a/poincare/src/layout/vertical_offset_layout.cpp b/poincare/src/layout/vertical_offset_layout.cpp new file mode 100644 index 000000000..ef34b5350 --- /dev/null +++ b/poincare/src/layout/vertical_offset_layout.cpp @@ -0,0 +1,286 @@ +#include "vertical_offset_layout.h" +#include "empty_layout.h" +#include "left_parenthesis_layout.h" +#include "right_parenthesis_layout.h" +#include +#include +#include +#include +#include + +namespace Poincare { + +VerticalOffsetLayout::VerticalOffsetLayout(ExpressionLayout * indice, Type type, bool cloneOperands) : + StaticLayoutHierarchy(indice, cloneOperands), + m_type(type) +{ +} + +ExpressionLayout * VerticalOffsetLayout::clone() const { + VerticalOffsetLayout * layout = new VerticalOffsetLayout(const_cast(this)->indiceLayout(), m_type, true); + return layout; +} + +void VerticalOffsetLayout::deleteBeforeCursor(ExpressionLayoutCursor * cursor) { + if (cursor->pointedExpressionLayout() == indiceLayout()) { + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + ExpressionLayout * base = baseLayout(); + if (indiceLayout()->isEmpty()) { + int indexInParent = m_parent->indexOfChild(this); + if (base->isEmpty()) { + // Case: Empty base and indice. + // Remove both the base and the indice layout. + ExpressionLayout * parent = m_parent; + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + parent->removePointedChildAtIndexAndMoveCursor(indexInParent, true, cursor); + cursor->setPointedExpressionLayout(parent->editableChild(indexInParent-1)); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + parent->removePointedChildAtIndexAndMoveCursor(indexInParent-1, true, cursor); + return; + } + // Case: Empty indice only. + // Delete the layout. + cursor->setPointedExpressionLayout(base); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + m_parent->removeChildAtIndex(indexInParent, true); + return; + } + // Case: Non-empty indice. + // Move Left of the VerticalOffsetLayout. + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return; + } + if (cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + // Case: Right. + // Move to the indice. + cursor->setPointedExpressionLayout(indiceLayout()); + return; + } + ExpressionLayout::deleteBeforeCursor(cursor); +} + +ExpressionLayoutCursor VerticalOffsetLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Left of the indice. Go Left. + if (indiceLayout() + && cursor.pointedExpressionLayout() == indiceLayout() + && cursor.position() == ExpressionLayoutCursor::Position::Left) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); + } + + assert(cursor.pointedExpressionLayout() == this); + // Case: Right. Go to the indice. + if (cursor.position() == ExpressionLayoutCursor::Position::Right) { + assert(indiceLayout() != nullptr); + return ExpressionLayoutCursor(indiceLayout(), ExpressionLayoutCursor::Position::Right); + } + // Case: Left. Ask the parent. + assert(cursor.position() == ExpressionLayoutCursor::Position::Left); + if (m_parent) { + return m_parent->cursorLeftOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor VerticalOffsetLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { + // Case: Right of the indice. Go Right. + if (indiceLayout() + && cursor.pointedExpressionLayout() == indiceLayout() + && cursor.position() == ExpressionLayoutCursor::Position::Right) + { + return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); + } + + assert(cursor.pointedExpressionLayout() == this); + // Case: Left. Go to the indice. + if (cursor.position() == ExpressionLayoutCursor::Position::Left) { + assert(indiceLayout() != nullptr); + return ExpressionLayoutCursor(indiceLayout(), ExpressionLayoutCursor::Position::Left); + } + // Case: Right. Ask the parent. + assert(cursor.position() == ExpressionLayoutCursor::Position::Right); + if (m_parent) { + return m_parent->cursorRightOf(cursor, shouldRecomputeLayout); + } + return ExpressionLayoutCursor(); +} + +ExpressionLayoutCursor VerticalOffsetLayout::cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + // Case: Superscript. + if (m_type == VerticalOffsetLayout::Type::Superscript) { + // Case: Right. Move to the indice. + if (cursor.isEquivalentTo(ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right))) { + assert(indiceLayout() != nullptr); + return ExpressionLayoutCursor(indiceLayout(), ExpressionLayoutCursor::Position::Right); + } + // Case: Left. Move to the indice. + if (cursor.isEquivalentTo(ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left))) { + assert(indiceLayout() != nullptr); + return ExpressionLayoutCursor(indiceLayout(), ExpressionLayoutCursor::Position::Left); + } + } + /* Case: Subscript, Left or Right of the indice. Put the cursor at the same + * position, pointing this. */ + if (m_type == VerticalOffsetLayout::Type::Subscript + && indiceLayout() != nullptr + && (cursor.isEquivalentTo(ExpressionLayoutCursor(indiceLayout(), ExpressionLayoutCursor::Position::Left)) + || cursor.isEquivalentTo(ExpressionLayoutCursor(indiceLayout(), ExpressionLayoutCursor::Position::Right)))) + { + return ExpressionLayoutCursor(this, cursor.position()); + } + return ExpressionLayout::cursorAbove(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +ExpressionLayoutCursor VerticalOffsetLayout::cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { + // Case: Subscript. + if (m_type == VerticalOffsetLayout::Type::Subscript) { + // Case: Right. Move to the indice. + if (cursor.isEquivalentTo(ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right))) { + assert(indiceLayout() != nullptr); + return ExpressionLayoutCursor(indiceLayout(), ExpressionLayoutCursor::Position::Right); + } + // Case: Left. Move to the indice. + if (cursor.isEquivalentTo(ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left))) { + assert(indiceLayout() != nullptr); + return ExpressionLayoutCursor(indiceLayout(), ExpressionLayoutCursor::Position::Left); + } + } + /* Case: Superscript, Left or Right of the indice. Put the cursor at the same + * position, pointing this. */ + if (m_type == VerticalOffsetLayout::Type::Superscript + && indiceLayout() != nullptr + && cursor.pointedExpressionLayout() == indiceLayout()) + { + return ExpressionLayoutCursor(this, cursor.position()); + } + return ExpressionLayout::cursorUnder(cursor, shouldRecomputeLayout, equivalentPositionVisited); +} + +int VerticalOffsetLayout::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { + if (m_type == Type::Subscript) { + if (bufferSize == 0) { + return -1; + } + buffer[bufferSize-1] = 0; + if (bufferSize == 1) { + return 0; + } + // If the layout is a subscript, write "_{indice}" + int numberOfChar = LayoutEngine::writeOneCharInBuffer(buffer, bufferSize, '_'); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + numberOfChar += LayoutEngine::writeOneCharInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, '{'); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + numberOfChar += const_cast(this)->indiceLayout()->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfSignificantDigits); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + numberOfChar += LayoutEngine::writeOneCharInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, '}'); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + return numberOfChar; + } + assert(m_type == Type::Superscript); + // If the layout is a superscript, write "^indice", with parentheses if needed + int numberOfChar = LayoutEngine::writeOneCharInBuffer(buffer, bufferSize, '^'); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + numberOfChar += LayoutEngine::writeInfixExpressionLayoutTextInBuffer(this, buffer+numberOfChar, bufferSize-numberOfChar, numberOfSignificantDigits, "^"); + if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + + // Add a multiplication if omitted. + int indexInParent = -1; + if (m_parent) { + indexInParent = m_parent->indexOfChild(this); + } + if (indexInParent >= 0 && indexInParent < (m_parent->numberOfChildren() - 1) && m_parent->isHorizontal() && m_parent->child(indexInParent + 1)->canBeOmittedMultiplicationRightFactor()) { + buffer[numberOfChar++] = Ion::Charset::MiddleDot; + if (numberOfChar >= bufferSize-1) { return bufferSize-1;} + } + buffer[numberOfChar] = 0; + return numberOfChar; +} + +ExpressionLayout * VerticalOffsetLayout::baseLayout() { + int indexInParent = parent()->indexOfChild(this); + assert(indexInParent > 0); + return editableParent()->editableChild(indexInParent - 1); +} + +KDSize VerticalOffsetLayout::computeSize() { + KDSize indiceSize = indiceLayout()->size(); + KDCoordinate width = indiceSize.width(); + if (m_type == Type::Superscript) { + assert(m_parent != nullptr); + assert(m_parent->isHorizontal()); + int indexInParent = m_parent->indexOfChild(this); + if (indexInParent < m_parent-> numberOfChildren() - 1 && m_parent->editableChild(indexInParent + 1)->hasUpperLeftIndex()) { + width += k_separationMargin; + } + } + KDCoordinate height = baseLayout()->size().height() - k_indiceHeight + indiceLayout()->size().height(); + return KDSize(width, height); +} + +void VerticalOffsetLayout::computeBaseline() { + if (m_type == Type::Subscript) { + m_baseline = baseLayout()->baseline(); + } else { + m_baseline = indiceLayout()->size().height() - k_indiceHeight + baseLayout()->baseline(); + } + m_baselined = true; +} + +KDPoint VerticalOffsetLayout::positionOfChild(ExpressionLayout * child) { + assert(child == indiceLayout()); + if (m_type == Type::Superscript) { + return KDPointZero; + } + assert(m_type == Type::Subscript); + ExpressionLayout * base = baseLayout(); + return KDPoint(0, base->size().height() - k_indiceHeight); +} + +void VerticalOffsetLayout::privateAddSibling(ExpressionLayoutCursor * cursor, ExpressionLayout * sibling, bool moveCursor) { + if (sibling->isVerticalOffset()) { + VerticalOffsetLayout * verticalOffsetSibling = static_cast(sibling); + if (verticalOffsetSibling->type() == Type::Superscript) { + assert(m_parent->isHorizontal()); + // Add the Left parenthesis + int indexInParent = m_parent->indexOfChild(this); + int leftParenthesisIndex = indexInParent; + LeftParenthesisLayout * leftParenthesis = new LeftParenthesisLayout(); + int numberOfOpenParenthesis = 0; + while (leftParenthesisIndex > 0 + && editableParent()->editableChild(leftParenthesisIndex-1)->isCollapsable(&numberOfOpenParenthesis, true)) + { + leftParenthesisIndex--; + } + m_parent->addChildAtIndex(leftParenthesis, leftParenthesisIndex); + indexInParent = m_parent->indexOfChild(this); + + // Add the Right parenthesis + RightParenthesisLayout * rightParenthesis = new RightParenthesisLayout(); + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + m_parent->addChildAtIndex(rightParenthesis, indexInParent + 1); + } else { + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + m_parent->addChildAtIndex(rightParenthesis, indexInParent); + } + cursor->setPointedExpressionLayout(rightParenthesis); + if (moveCursor) { + rightParenthesis->addSiblingAndMoveCursor(cursor, sibling); + } else { + rightParenthesis->addSibling(cursor, sibling); + } + return; + } + } + ExpressionLayout::privateAddSibling(cursor, sibling, moveCursor); +} + + +} diff --git a/poincare/src/layout/vertical_offset_layout.h b/poincare/src/layout/vertical_offset_layout.h new file mode 100644 index 000000000..8741c49ce --- /dev/null +++ b/poincare/src/layout/vertical_offset_layout.h @@ -0,0 +1,52 @@ +#ifndef POINCARE_VERTICAL_OFFSET_LAYOUT_H +#define POINCARE_VERTICAL_OFFSET_LAYOUT_H + +#include + +namespace Poincare { + +class VerticalOffsetLayout : public StaticLayoutHierarchy<1> { +public: + enum class Type { + Subscript, + Superscript + }; + VerticalOffsetLayout(ExpressionLayout * indice, Type type, bool cloneOperands); + ExpressionLayout * clone() const override; + + // VerticalOffsetLayout + Type type() const { return m_type; } + + // User input + void deleteBeforeCursor(ExpressionLayoutCursor * cursor) override; + + // Tree navigation + ExpressionLayoutCursor cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) override; + ExpressionLayoutCursor cursorAbove(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + ExpressionLayoutCursor cursorUnder(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; + + // Serialization + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; + + // Other + bool mustHaveLeftSibling() const override { return true; } + bool isVerticalOffset() const override { return true; } + bool childMightNeedParentheses() const override { return m_type == Type::Superscript; } +protected: + ExpressionLayout * indiceLayout() { return editableChild(0);} + ExpressionLayout * baseLayout(); + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override {} + KDSize computeSize() override; + void computeBaseline() override; + KDPoint positionOfChild(ExpressionLayout * child) override; + void privateAddSibling(ExpressionLayoutCursor * cursor, ExpressionLayout * sibling, bool moveCursor) override; + Type m_type; +private: + constexpr static KDCoordinate k_indiceHeight = 5; + constexpr static KDCoordinate k_separationMargin = 5; +}; + +} + +#endif diff --git a/poincare/src/layout_engine.cpp b/poincare/src/layout_engine.cpp index 3c4dc5a01..51faab43b 100644 --- a/poincare/src/layout_engine.cpp +++ b/poincare/src/layout_engine.cpp @@ -1,7 +1,9 @@ #include -#include "layout/string_layout.h" -#include "layout/parenthesis_layout.h" +#include "layout/char_layout.h" #include "layout/horizontal_layout.h" +#include "layout/left_parenthesis_layout.h" +#include "layout/right_parenthesis_layout.h" +#include "layout/vertical_offset_layout.h" extern "C" { #include } @@ -13,72 +15,138 @@ ExpressionLayout * LayoutEngine::createInfixLayout(const Expression * expression assert(complexFormat != Expression::ComplexFormat::Default); int numberOfOperands = expression->numberOfOperands(); assert(numberOfOperands > 1); - ExpressionLayout** children_layouts = new ExpressionLayout * [2*numberOfOperands-1]; - children_layouts[0] = expression->operand(0)->createLayout(); - for (int i=1; ioperand(i)->type() == Expression::Type::Opposite ? new ParenthesisLayout(expression->operand(i)->createLayout(floatDisplayMode, complexFormat)) : expression->operand(i)->createLayout(floatDisplayMode, complexFormat); + HorizontalLayout * result = new HorizontalLayout(); + result->addOrMergeChildAtIndex(expression->operand(0)->createLayout(), 0, true); + for (int i = 1; i < numberOfOperands; i++) { + result->addOrMergeChildAtIndex(createStringLayout(operatorName, strlen(operatorName)), result->numberOfChildren(), true); + result->addOrMergeChildAtIndex( + expression->operand(i)->type() == Expression::Type::Opposite ? + createParenthesedLayout(expression->operand(i)->createLayout(floatDisplayMode, complexFormat), false) : + expression->operand(i)->createLayout(floatDisplayMode, complexFormat), + result->numberOfChildren(), true); } - /* HorizontalLayout holds the children layouts so they do not need to be - * deleted here. */ - ExpressionLayout * layout = new HorizontalLayout(children_layouts, 2*numberOfOperands-1); - delete[] children_layouts; - return layout; + return result; } ExpressionLayout * LayoutEngine::createPrefixLayout(const Expression * expression, PrintFloat::Mode floatDisplayMode, Expression::ComplexFormat complexFormat, const char * operatorName) { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != Expression::ComplexFormat::Default); + HorizontalLayout * result = new HorizontalLayout(); + + // Add the operator name. + result->addOrMergeChildAtIndex(createStringLayout(operatorName, strlen(operatorName)), 0, true); + + // Create the layout of arguments separated by commas. + ExpressionLayout * args = nullptr; int numberOfOperands = expression->numberOfOperands(); - ExpressionLayout * argumentLayouts = nullptr; if (numberOfOperands > 0) { - ExpressionLayout ** grandChildrenLayouts = new ExpressionLayout *[2*numberOfOperands-1]; - int layoutIndex = 0; - grandChildrenLayouts[layoutIndex++] = expression->operand(0)->createLayout(floatDisplayMode, complexFormat); + args = new HorizontalLayout(); + HorizontalLayout * horizontalArgs = static_cast(args); + horizontalArgs->addOrMergeChildAtIndex(expression->operand(0)->createLayout(floatDisplayMode, complexFormat), 0, true); for (int i = 1; i < numberOfOperands; i++) { - grandChildrenLayouts[layoutIndex++] = new StringLayout(",", 1); - grandChildrenLayouts[layoutIndex++] = expression->operand(i)->createLayout(floatDisplayMode, complexFormat); + horizontalArgs->addChildAtIndex(new CharLayout(','), args->numberOfChildren()); + horizontalArgs->addOrMergeChildAtIndex(expression->operand(i)->createLayout(floatDisplayMode, complexFormat), horizontalArgs->numberOfChildren(), true); } - /* HorizontalLayout holds the grand children layouts so they do not need to - * be deleted */ - argumentLayouts = new HorizontalLayout(grandChildrenLayouts, 2*numberOfOperands-1); - delete [] grandChildrenLayouts; } - ExpressionLayout * childrenLayouts[2]; - childrenLayouts[0] = new StringLayout(operatorName, strlen(operatorName)); - childrenLayouts[1] = new ParenthesisLayout(argumentLayouts); - /* Same comment as above */ - return new HorizontalLayout(childrenLayouts, 2); + // Add the parenthesed arguments. + result->addOrMergeChildAtIndex(createParenthesedLayout(args, false), result->numberOfChildren(), true); + return result; +} + +ExpressionLayout * LayoutEngine::createParenthesedLayout(ExpressionLayout * layout, bool cloneLayout) { + HorizontalLayout * result = new HorizontalLayout(); + result->addChildAtIndex(new LeftParenthesisLayout(), 0); + if (layout != nullptr) { + result->addOrMergeChildAtIndex(cloneLayout ? layout->clone() : layout, 1, true); + } + result->addChildAtIndex(new RightParenthesisLayout(), result->numberOfChildren()); + return result; +} + +ExpressionLayout * LayoutEngine::createStringLayout(const char * buffer, int bufferSize, KDText::FontSize fontSize) { + assert(bufferSize > 0); + HorizontalLayout * resultLayout = new HorizontalLayout(); + for (int i = 0; i < bufferSize; i++) { + resultLayout->addChildAtIndex(new CharLayout(buffer[i], fontSize), i); + } + return resultLayout; +} + +ExpressionLayout * LayoutEngine::createLogLayout(ExpressionLayout * argument, ExpressionLayout * index) { + HorizontalLayout * resultLayout = static_cast(createStringLayout("log", 3)); + VerticalOffsetLayout * offsetLayout = new VerticalOffsetLayout(index, VerticalOffsetLayout::Type::Subscript, false); + resultLayout->addChildAtIndex(offsetLayout, resultLayout->numberOfChildren()); + resultLayout->addOrMergeChildAtIndex(createParenthesedLayout(argument, false), resultLayout->numberOfChildren(), true); + return resultLayout; } int LayoutEngine::writeInfixExpressionTextInBuffer(const Expression * expression, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName) { + return writeInfixExpressionOrExpressionLayoutTextInBuffer(expression, nullptr, buffer, bufferSize, numberOfDigits, operatorName, 0, -1); +} + +int LayoutEngine::writePrefixExpressionTextInBuffer(const Expression * expression, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName) { + return writePrefixExpressionOrExpressionLayoutTextInBuffer(expression, nullptr, buffer, bufferSize, numberOfDigits, operatorName); +} + +int LayoutEngine::writeInfixExpressionLayoutTextInBuffer(const ExpressionLayout * expressionLayout, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName, int firstChildIndex, int lastChildIndex) { + return writeInfixExpressionOrExpressionLayoutTextInBuffer(nullptr, expressionLayout, buffer, bufferSize, numberOfDigits, operatorName, firstChildIndex, lastChildIndex); +} + +int LayoutEngine::writePrefixExpressionLayoutTextInBuffer(const ExpressionLayout * expressionLayout, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName, bool writeFirstChild) { + return writePrefixExpressionOrExpressionLayoutTextInBuffer(nullptr, expressionLayout, buffer, bufferSize, numberOfDigits, operatorName, writeFirstChild); +} + +int LayoutEngine::writeInfixExpressionOrExpressionLayoutTextInBuffer(const Expression * expression, const ExpressionLayout * expressionLayout, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName, int firstChildIndex, int lastChildIndex) { + assert(expression != nullptr || expressionLayout != nullptr); if (bufferSize == 0) { return -1; } buffer[bufferSize-1] = 0; int numberOfChar = 0; - int numberOfOperands = expression->numberOfOperands(); - assert(numberOfOperands > 1); - if (numberOfChar >= bufferSize-1) { return bufferSize-1; } - if (expression->operand(0)->needParenthesisWithParent(expression)) { + int numberOfOperands = (expression != nullptr) ? expression->numberOfOperands() : expressionLayout->numberOfChildren(); + assert(numberOfOperands > 0); + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; + } + + bool currentChildNeedsParentheses = + (expression != nullptr + && expression->operand(firstChildIndex)->needParenthesisWithParent(expression)) + || (expression == nullptr + && expressionLayout->child(firstChildIndex)->needsParenthesesWithParent()); + + // Write first operand + if (currentChildNeedsParentheses){ buffer[numberOfChar++] = '('; if (numberOfChar >= bufferSize-1) { return bufferSize-1; } } - numberOfChar += expression->operand(0)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits); - if (expression->operand(0)->needParenthesisWithParent(expression)) { + numberOfChar += (expression != nullptr) ? expression->operand(firstChildIndex)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits) : expressionLayout->child(firstChildIndex)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits); + if (currentChildNeedsParentheses){ buffer[numberOfChar++] = ')'; if (numberOfChar >= bufferSize-1) { return bufferSize-1; } } - for (int i=1; i= bufferSize-1) { return bufferSize-1; } + + // Write operator numberOfChar += strlcpy(buffer+numberOfChar, operatorName, bufferSize-numberOfChar); if (numberOfChar >= bufferSize-1) { return bufferSize-1; } - if (expression->operand(i)->needParenthesisWithParent(expression)) { + + // Write next operand + currentChildNeedsParentheses = + (expression != nullptr + && expression->operand(i)->needParenthesisWithParent(expression)) + || (expression == nullptr + && expressionLayout->child(i)->needsParenthesesWithParent()); + + if (currentChildNeedsParentheses) { buffer[numberOfChar++] = '('; if (numberOfChar >= bufferSize-1) { return bufferSize-1; } } - numberOfChar += expression->operand(i)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits); - if (expression->operand(i)->needParenthesisWithParent(expression)) { + numberOfChar += (expression != nullptr) ? expression->operand(i)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits) : expressionLayout->child(i)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits); + if (currentChildNeedsParentheses) { buffer[numberOfChar++] = ')'; if (numberOfChar >= bufferSize-1) { return bufferSize-1; } } @@ -87,24 +155,34 @@ int LayoutEngine::writeInfixExpressionTextInBuffer(const Expression * expression return numberOfChar; } -int LayoutEngine::writePrefixExpressionTextInBuffer(const Expression * expression, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName) { +int LayoutEngine::writePrefixExpressionOrExpressionLayoutTextInBuffer(const Expression * expression, const ExpressionLayout * expressionLayout, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName, bool writeFirstChild) { + assert(expression != nullptr || expressionLayout != nullptr); if (bufferSize == 0) { return -1; } buffer[bufferSize-1] = 0; - int numberOfChar = 0; - numberOfChar += strlcpy(buffer, operatorName, bufferSize); - if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + int numberOfChar = strlcpy(buffer, operatorName, bufferSize); + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; + } + buffer[numberOfChar++] = '('; - if (numberOfChar >= bufferSize-1) { return bufferSize-1; } - int numberOfOperands = expression->numberOfOperands(); + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; + } + + int numberOfOperands = (expression != nullptr) ? expression->numberOfOperands() : expressionLayout->numberOfChildren(); if (numberOfOperands > 0) { - numberOfChar += expression->operand(0)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits); - for (int i = 1; i < numberOfOperands; i++) { + if (!writeFirstChild) { + assert(numberOfOperands > 1); + } + int firstOperandIndex = writeFirstChild ? 0 : 1; + numberOfChar += (expression != nullptr) ? expression->operand(firstOperandIndex)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits) : expressionLayout->child(firstOperandIndex)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits); + for (int i = firstOperandIndex + 1; i < numberOfOperands; i++) { if (numberOfChar >= bufferSize-1) { return bufferSize-1; } buffer[numberOfChar++] = ','; if (numberOfChar >= bufferSize-1) { return bufferSize-1; } - numberOfChar += expression->operand(i)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits); + numberOfChar += (expression != nullptr) ? expression->operand(i)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar) : expressionLayout->child(i)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar); } if (numberOfChar >= bufferSize-1) { return bufferSize-1; } } @@ -113,4 +191,17 @@ int LayoutEngine::writePrefixExpressionTextInBuffer(const Expression * expressio return numberOfChar; } +int LayoutEngine::writeOneCharInBuffer(char * buffer, int bufferSize, char charToWrite) { + if (bufferSize == 0) { + return -1; + } + buffer[bufferSize-1] = 0; + if (bufferSize == 1) { + return 0; + } + buffer[0] = charToWrite; + buffer[1] = 0; + return 1; +} + } diff --git a/poincare/src/list_data.cpp b/poincare/src/list_data.cpp index 110d6163c..c49ff5ff2 100644 --- a/poincare/src/list_data.cpp +++ b/poincare/src/list_data.cpp @@ -6,6 +6,11 @@ extern "C" { namespace Poincare { +ListData::ListData() : + m_numberOfOperands(0), + m_operands(new Expression*[0]) +{} + ListData::ListData(Expression * operand) : m_numberOfOperands(1), m_operands(new Expression*[1]) diff --git a/poincare/src/logarithm.cpp b/poincare/src/logarithm.cpp index 820a27274..a8e979dc0 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -1,25 +1,23 @@ #include -#include -#include -#include +#include "layout/horizontal_layout.h" +#include "layout/vertical_offset_layout.h" #include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include #include +#include +#include #include #include extern "C" { #include #include } -#include "layout/baseline_relative_layout.h" -#include "layout/horizontal_layout.h" -#include "layout/parenthesis_layout.h" -#include "layout/string_layout.h" namespace Poincare { @@ -236,10 +234,7 @@ ExpressionLayout * Logarithm::privateCreateLayout(PrintFloat::Mode floatDisplayM if (numberOfOperands() == 1) { return LayoutEngine::createPrefixLayout(this, floatDisplayMode, complexFormat, "log"); } - ExpressionLayout * childrenLayouts[2]; - childrenLayouts[0] = new BaselineRelativeLayout(new StringLayout("log", strlen("log")), operand(1)->createLayout(floatDisplayMode, complexFormat), BaselineRelativeLayout::Type::Subscript); - childrenLayouts[1] = new ParenthesisLayout(operand(0)->createLayout(floatDisplayMode, complexFormat)); - return new HorizontalLayout(childrenLayouts, 2); + return LayoutEngine::createLogLayout(operand(0)->createLayout(floatDisplayMode, complexFormat), operand(1)->createLayout(floatDisplayMode, complexFormat)); } } diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index 73ee8c88e..c8310bec4 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -7,8 +7,7 @@ extern "C" { #include #include #include -#include "layout/grid_layout.h" -#include "layout/bracket_layout.h" +#include "layout/matrix_layout.h" #include #include #include @@ -106,7 +105,7 @@ ExpressionLayout * Matrix::privateCreateLayout(PrintFloat::Mode floatDisplayMode for (int i = 0; i < numberOfOperands(); i++) { childrenLayouts[i] = operand(i)->createLayout(floatDisplayMode, complexFormat); } - ExpressionLayout * layout = new BracketLayout(new GridLayout(childrenLayouts, numberOfRows(), numberOfColumns())); + ExpressionLayout * layout = new MatrixLayout(childrenLayouts, numberOfRows(), numberOfColumns(), false); delete [] childrenLayouts; return layout; } diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 3be01b639..ad04e6e16 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -1,26 +1,22 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include extern "C" { #include #include } -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "layout/string_layout.h" -#include "layout/horizontal_layout.h" -#include "layout/parenthesis_layout.h" namespace Poincare { diff --git a/poincare/src/naperian_logarithm.cpp b/poincare/src/naperian_logarithm.cpp index 6b23eb695..9a4ff62b0 100644 --- a/poincare/src/naperian_logarithm.cpp +++ b/poincare/src/naperian_logarithm.cpp @@ -8,9 +8,6 @@ extern "C" { } #include #include -#include "layout/horizontal_layout.h" -#include "layout/parenthesis_layout.h" -#include "layout/string_layout.h" namespace Poincare { diff --git a/poincare/src/nth_root.cpp b/poincare/src/nth_root.cpp index bafba43e3..8f8005aec 100644 --- a/poincare/src/nth_root.cpp +++ b/poincare/src/nth_root.cpp @@ -41,7 +41,7 @@ Expression * NthRoot::shallowReduce(Context& context, AngleUnit angleUnit) { ExpressionLayout * NthRoot::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - return new NthRootLayout(operand(0)->createLayout(floatDisplayMode, complexFormat), operand(1)->createLayout(floatDisplayMode, complexFormat)); + return new NthRootLayout(operand(0)->createLayout(floatDisplayMode, complexFormat), operand(1)->createLayout(floatDisplayMode, complexFormat), false); } template diff --git a/poincare/src/opposite.cpp b/poincare/src/opposite.cpp index a394e6f4f..249ad84ce 100644 --- a/poincare/src/opposite.cpp +++ b/poincare/src/opposite.cpp @@ -1,16 +1,16 @@ #include +#include "layout/char_layout.h" +#include "layout/horizontal_layout.h" +#include #include +#include #include -#include #include +#include extern "C" { #include #include } -#include -#include "layout/horizontal_layout.h" -#include "layout/parenthesis_layout.h" -#include "layout/string_layout.h" namespace Poincare { @@ -69,11 +69,14 @@ Expression * Opposite::shallowReduce(Context& context, AngleUnit angleUnit) { ExpressionLayout * Opposite::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - ExpressionLayout * children_layouts[2]; - char string[2] = {'-', '\0'}; - children_layouts[0] = new StringLayout(string, 1); - children_layouts[1] = operand(0)->type() == Type::Opposite ? new ParenthesisLayout(operand(0)->createLayout(floatDisplayMode, complexFormat)) : operand(0)->createLayout(floatDisplayMode, complexFormat); - return new HorizontalLayout(children_layouts, 2); + HorizontalLayout * result = new HorizontalLayout(new CharLayout('-'), false); + if (operand(0)->type() == Type::Opposite) { + result->addOrMergeChildAtIndex(LayoutEngine::createParenthesedLayout(operand(0)->createLayout(floatDisplayMode, complexFormat), false), 1, false); + } else { + result->addOrMergeChildAtIndex(operand(0)->createLayout(floatDisplayMode, complexFormat), 1, false); + } + return result; + } int Opposite::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { diff --git a/poincare/src/parenthesis.cpp b/poincare/src/parenthesis.cpp index f312ba6f6..09240a108 100644 --- a/poincare/src/parenthesis.cpp +++ b/poincare/src/parenthesis.cpp @@ -1,9 +1,9 @@ +#include + extern "C" { #include #include } -#include -#include "layout/parenthesis_layout.h" namespace Poincare { @@ -24,7 +24,7 @@ int Parenthesis::polynomialDegree(char symbolName) const { ExpressionLayout * Parenthesis::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - return new ParenthesisLayout(operand(0)->createLayout(floatDisplayMode, complexFormat)); + return LayoutEngine::createParenthesedLayout(operand(0)->createLayout(floatDisplayMode, complexFormat), false); } Expression * Parenthesis::shallowReduce(Context& context, AngleUnit angleUnit) { diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 359ddd42f..6531d27d6 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -1,29 +1,34 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "layout/horizontal_layout.h" +#include "layout/vertical_offset_layout.h" + +#include +#include +#include + extern "C" { #include #include } -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "layout/baseline_relative_layout.h" + namespace Poincare { @@ -146,7 +151,14 @@ ExpressionLayout * Power::privateCreateLayout(PrintFloat::Mode floatDisplayMode, if (m_operands[1]->type() == Type::Parenthesis) { indiceOperand = m_operands[1]->operand(0); } - return new BaselineRelativeLayout(m_operands[0]->createLayout(floatDisplayMode, complexFormat),indiceOperand->createLayout(floatDisplayMode, complexFormat), BaselineRelativeLayout::Type::Superscript); + HorizontalLayout * result = new HorizontalLayout(); + result->addOrMergeChildAtIndex(m_operands[0]->createLayout(floatDisplayMode, complexFormat), 0, false); + result->addChildAtIndex(new VerticalOffsetLayout( + indiceOperand->createLayout(floatDisplayMode, complexFormat), + VerticalOffsetLayout::Type::Superscript, + false), + result->numberOfChildren()); + return result; } int Power::simplificationOrderSameType(const Expression * e, bool canBeInterrupted) const { diff --git a/poincare/src/preferences.cpp b/poincare/src/preferences.cpp index 17294df3e..c9918d13b 100644 --- a/poincare/src/preferences.cpp +++ b/poincare/src/preferences.cpp @@ -6,6 +6,7 @@ namespace Poincare { Preferences::Preferences() : m_angleUnit(Expression::AngleUnit::Degree), m_displayMode(PrintFloat::Mode::Decimal), + m_editionMode(EditionMode::Edition1D), m_complexFormat(Expression::ComplexFormat::Cartesian), m_numberOfSignificantDigits(PrintFloat::k_numberOfPrintedSignificantDigits) { @@ -32,6 +33,14 @@ void Preferences::setDisplayMode(PrintFloat::Mode mode) { m_displayMode = mode; } +Preferences::EditionMode Preferences::editionMode() const { + return m_editionMode; +} + +void Preferences::setEditionMode(Preferences::EditionMode editionMode) { + m_editionMode = editionMode; +} + Expression::ComplexFormat Preferences::complexFormat() const { return m_complexFormat; } diff --git a/poincare/src/product.cpp b/poincare/src/product.cpp index d202cd807..ab07fbc0d 100644 --- a/poincare/src/product.cpp +++ b/poincare/src/product.cpp @@ -26,8 +26,8 @@ int Product::emptySequenceValue() const { return 1; } -ExpressionLayout * Product::createSequenceLayoutWithArgumentLayouts(ExpressionLayout * subscriptLayout, ExpressionLayout * superscriptLayout, ExpressionLayout * argumentLayout) const { - return new ProductLayout(subscriptLayout, superscriptLayout, argumentLayout); +ExpressionLayout * Product::createSequenceLayoutWithArgumentLayouts(ExpressionLayout * argumentLayout, ExpressionLayout * subscriptLayout, ExpressionLayout * superscriptLayout) const { + return new ProductLayout(argumentLayout, subscriptLayout, superscriptLayout, false); } template diff --git a/poincare/src/rational.cpp b/poincare/src/rational.cpp index a58225f45..03e9c1954 100644 --- a/poincare/src/rational.cpp +++ b/poincare/src/rational.cpp @@ -7,7 +7,6 @@ extern "C" { } #include #include -#include "layout/string_layout.h" #include "layout/fraction_layout.h" namespace Poincare { @@ -158,7 +157,7 @@ ExpressionLayout * Rational::privateCreateLayout(PrintFloat::Mode floatDisplayMo return numeratorLayout; } ExpressionLayout * denominatorLayout = m_denominator.createLayout(); - return new FractionLayout(numeratorLayout, denominatorLayout); + return new FractionLayout(numeratorLayout, denominatorLayout, false); } int Rational::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { diff --git a/poincare/src/sequence.cpp b/poincare/src/sequence.cpp index 76df88d4c..00f48111b 100644 --- a/poincare/src/sequence.cpp +++ b/poincare/src/sequence.cpp @@ -1,10 +1,8 @@ #include -#include #include -#include +#include #include -#include "layout/string_layout.h" -#include "layout/horizontal_layout.h" +#include extern "C" { #include #include @@ -16,10 +14,7 @@ namespace Poincare { ExpressionLayout * Sequence::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - ExpressionLayout * childrenLayouts[2]; - childrenLayouts[0] = new StringLayout("n=", 2); - childrenLayouts[1] = operand(1)->createLayout(floatDisplayMode, complexFormat); - return createSequenceLayoutWithArgumentLayouts(new HorizontalLayout(childrenLayouts, 2), operand(2)->createLayout(floatDisplayMode, complexFormat), operand(0)->createLayout(floatDisplayMode, complexFormat)); + return createSequenceLayoutWithArgumentLayouts(operand(0)->createLayout(floatDisplayMode, complexFormat), operand(1)->createLayout(floatDisplayMode, complexFormat), operand(2)->createLayout(floatDisplayMode, complexFormat)); } template diff --git a/poincare/src/square_root.cpp b/poincare/src/square_root.cpp index 4c06e70fe..2681a8037 100644 --- a/poincare/src/square_root.cpp +++ b/poincare/src/square_root.cpp @@ -52,7 +52,7 @@ Expression * SquareRoot::shallowReduce(Context& context, AngleUnit angleUnit) { ExpressionLayout * SquareRoot::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - return new NthRootLayout(operand(0)->createLayout(floatDisplayMode, complexFormat),nullptr); + return new NthRootLayout(operand(0)->createLayout(floatDisplayMode, complexFormat), false); } } diff --git a/poincare/src/store.cpp b/poincare/src/store.cpp index 2fa5c0d35..778d2639f 100644 --- a/poincare/src/store.cpp +++ b/poincare/src/store.cpp @@ -7,8 +7,8 @@ extern "C" { #include #include #include +#include "layout/char_layout.h" #include "layout/horizontal_layout.h" -#include "layout/string_layout.h" namespace Poincare { @@ -37,12 +37,11 @@ Expression * Store::shallowReduce(Context& context, AngleUnit angleUnit) { ExpressionLayout * Store::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); - ExpressionLayout * childrenLayouts[3]; - childrenLayouts[0] = value()->createLayout(floatDisplayMode, complexFormat); - const char stoSymbol[2] = {Ion::Charset::Sto, 0}; - childrenLayouts[1] = new StringLayout(stoSymbol, 1); - childrenLayouts[2] = symbol()->createLayout(floatDisplayMode, complexFormat); - return new HorizontalLayout(childrenLayouts, 3); + HorizontalLayout * result = new HorizontalLayout(); + result->addOrMergeChildAtIndex(value()->createLayout(floatDisplayMode, complexFormat), 0, false); + result->addChildAtIndex(new CharLayout(Ion::Charset::Sto), result->numberOfChildren()); + result->addOrMergeChildAtIndex(symbol()->createLayout(floatDisplayMode, complexFormat), result->numberOfChildren(), false); + return result; } template diff --git a/poincare/src/subtraction.cpp b/poincare/src/subtraction.cpp index 9a4b2ca7e..e517138f4 100644 --- a/poincare/src/subtraction.cpp +++ b/poincare/src/subtraction.cpp @@ -1,18 +1,14 @@ +#include +#include +#include +#include +#include +#include extern "C" { #include #include } -#include -#include -#include -#include -#include -#include -#include "layout/horizontal_layout.h" -#include "layout/string_layout.h" -#include "layout/parenthesis_layout.h" - namespace Poincare { Expression::Type Subtraction::type() const { diff --git a/poincare/src/sum.cpp b/poincare/src/sum.cpp index 70a5168b7..fe973047a 100644 --- a/poincare/src/sum.cpp +++ b/poincare/src/sum.cpp @@ -26,8 +26,8 @@ int Sum::emptySequenceValue() const { return 0; } -ExpressionLayout * Sum::createSequenceLayoutWithArgumentLayouts(ExpressionLayout * subscriptLayout, ExpressionLayout * superscriptLayout, ExpressionLayout * argumentLayout) const { - return new SumLayout(subscriptLayout, superscriptLayout, argumentLayout); +ExpressionLayout * Sum::createSequenceLayoutWithArgumentLayouts(ExpressionLayout * argumentLayout, ExpressionLayout * subscriptLayout, ExpressionLayout * superscriptLayout) const { + return new SumLayout(argumentLayout, subscriptLayout, superscriptLayout, false); } template diff --git a/poincare/src/symbol.cpp b/poincare/src/symbol.cpp index 89ccd48d1..abcb886f2 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -1,18 +1,23 @@ #include + #include #include #include -#include +#include #include #include -#include -#include "layout/baseline_relative_layout.h" -#include "layout/string_layout.h" + +#include +#include "layout/char_layout.h" +#include "layout/horizontal_layout.h" +#include "layout/vertical_offset_layout.h" + #include +#include + extern "C" { #include } -#include namespace Poincare { @@ -54,7 +59,6 @@ const char * Symbol::textForSpecialSymbols(char name) const { } } - Symbol::SpecialSymbols Symbol::matrixSymbol(char index) { switch (index - '0') { case 0: @@ -190,25 +194,49 @@ ExpressionLayout * Symbol::privateCreateLayout(PrintFloat::Mode floatDisplayMode assert(floatDisplayMode != PrintFloat::Mode::Default); assert(complexFormat != ComplexFormat::Default); if (m_name == SpecialSymbols::Ans) { - return new StringLayout("ans", 3); + return LayoutEngine::createStringLayout("ans", 3); } if (m_name == SpecialSymbols::un) { - return new BaselineRelativeLayout(new StringLayout("u", 1), new StringLayout("n",1, KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + return new HorizontalLayout( + new CharLayout('u'), + new VerticalOffsetLayout( + new CharLayout('n', KDText::FontSize::Small), + VerticalOffsetLayout::Type::Subscript, + false), + false); } if (m_name == SpecialSymbols::un1) { - return new BaselineRelativeLayout(new StringLayout("u", 1), new StringLayout("n+1",3, KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + return new HorizontalLayout( + new CharLayout('u'), + new VerticalOffsetLayout( + LayoutEngine::createStringLayout("n+1", 3, KDText::FontSize::Small), + VerticalOffsetLayout::Type::Subscript, + false), + false); } if (m_name == SpecialSymbols::vn) { - return new BaselineRelativeLayout(new StringLayout("v", 1), new StringLayout("n",1, KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + return new HorizontalLayout( + new CharLayout('v'), + new VerticalOffsetLayout( + new CharLayout('n', KDText::FontSize::Small), + VerticalOffsetLayout::Type::Subscript, + false), + false); } if (m_name == SpecialSymbols::vn1) { - return new BaselineRelativeLayout(new StringLayout("v", 1), new StringLayout("n+1",3, KDText::FontSize::Small), BaselineRelativeLayout::Type::Subscript); + return new HorizontalLayout( + new CharLayout('v'), + new VerticalOffsetLayout( + LayoutEngine::createStringLayout("n+1", 3, KDText::FontSize::Small), + VerticalOffsetLayout::Type::Subscript, + false), + false); } if (isMatrixSymbol()) { const char mi[] = { 'M', (char)(m_name-(char)SpecialSymbols::M0+'0') }; - return new StringLayout(mi, sizeof(mi)); + return LayoutEngine::createStringLayout(mi, sizeof(mi)); } - return new StringLayout(&m_name, 1); + return LayoutEngine::createStringLayout(&m_name, 1); } int Symbol::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { diff --git a/poincare/src/undefined.cpp b/poincare/src/undefined.cpp index 7270b3dae..c5600b15c 100644 --- a/poincare/src/undefined.cpp +++ b/poincare/src/undefined.cpp @@ -1,8 +1,10 @@ #include +#include + extern "C" { #include +#include } -#include "layout/string_layout.h" namespace Poincare { @@ -25,7 +27,7 @@ template Complex * Undefined::templatedApproximate(Context& conte ExpressionLayout * Undefined::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { char buffer[16]; int numberOfChars = PrintFloat::convertFloatToText(NAN, buffer, 16, 1, floatDisplayMode); - return new StringLayout(buffer, numberOfChars); + return LayoutEngine::createStringLayout(buffer, numberOfChars); } int Undefined::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { diff --git a/poincare/test/addition.cpp b/poincare/test/addition.cpp index 85216991c..84581b3bc 100644 --- a/poincare/test/addition.cpp +++ b/poincare/test/addition.cpp @@ -34,6 +34,8 @@ QUIZ_CASE(poincare_addition_evaluate) { QUIZ_CASE(poincare_addition_simplify) { assert_parsed_expression_simplify_to("2+1", "3"); + assert_parsed_expression_simplify_to("-2+6", "4"); + assert_parsed_expression_simplify_to("-2-6", "-8"); assert_parsed_expression_simplify_to("2+A", "2+A"); assert_parsed_expression_simplify_to("1+2+3+4+5+6+7", "28"); assert_parsed_expression_simplify_to("1+2+3+4+5+A+6+7", "28+A"); diff --git a/poincare/test/binomial_coefficient_layout.cpp b/poincare/test/binomial_coefficient_layout.cpp new file mode 100644 index 000000000..c7960c551 --- /dev/null +++ b/poincare/test/binomial_coefficient_layout.cpp @@ -0,0 +1,10 @@ +#include +#include +#include +#include "helper.h" + +using namespace Poincare; + +QUIZ_CASE(poincare_binomial_coefficient_layout_serialize) { + assert_parsed_expression_layout_serialize_to_self("binomial(7,6)"); +} diff --git a/poincare/test/fraction_layout.cpp b/poincare/test/fraction_layout.cpp new file mode 100644 index 000000000..a2e060917 --- /dev/null +++ b/poincare/test/fraction_layout.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include "helper.h" + +using namespace Poincare; + +QUIZ_CASE(poincare_fraction_layout_create) { + + /* 12 + * 12|34+5 -> "Divide" -> --- + 5 + * |34 + * */ + HorizontalLayout * layout = static_cast(LayoutEngine::createStringLayout("1234+5", 6)); + ExpressionLayoutCursor cursor(layout->editableChild(2), ExpressionLayoutCursor::Position::Left); + cursor.addFractionLayoutAndCollapseSiblings(); + assert_expression_layout_serialize_to(layout, "12/34+5"); + assert(cursor.isEquivalentTo(ExpressionLayoutCursor(layout->editableChild(0)->editableChild(1), ExpressionLayoutCursor::Position::Left))); + delete layout; +} + +QUIZ_CASE(poincare_fraction_layout_delete) { + + /* 12 + * --- -> "BackSpace" -> 12|34 + * |34 + * */ + HorizontalLayout * layout1 = new HorizontalLayout( + new FractionLayout( + LayoutEngine::createStringLayout("12", 2), + LayoutEngine::createStringLayout("34", 2), + false), + false); + ExpressionLayoutCursor cursor1(layout1->editableChild(0)->editableChild(1), ExpressionLayoutCursor::Position::Left); + cursor1.performBackspace(); + assert_expression_layout_serialize_to(layout1, "1234"); + assert(cursor1.isEquivalentTo(ExpressionLayoutCursor(layout1->editableChild(1), ExpressionLayoutCursor::Position::Right))); + delete layout1; + + /* • + * 1 + --- -> "BackSpace" -> 1+|3 + * |3 + * */ + HorizontalLayout * layout2 = new HorizontalLayout( + new CharLayout('1'), + new CharLayout('+'), + new FractionLayout( + new EmptyLayout(), + new CharLayout('3'), + false), + false); + ExpressionLayoutCursor cursor2(layout2->editableChild(2)->editableChild(1), ExpressionLayoutCursor::Position::Left); + cursor2.performBackspace(); + assert_expression_layout_serialize_to(layout2, "1+3"); + assert(cursor2.isEquivalentTo(ExpressionLayoutCursor(layout2->editableChild(1), ExpressionLayoutCursor::Position::Right))); + delete layout2; +} + +QUIZ_CASE(poincare_fraction_layout_serialize) { + FractionLayout * layout = new FractionLayout( + new CharLayout('1'), + LayoutEngine::createStringLayout("2+3", 3), + false); + assert_expression_layout_serialize_to(layout, "1/(2+3)"); + delete layout; +} diff --git a/poincare/test/helper.cpp b/poincare/test/helper.cpp index 801ecd684..c1e658f7c 100644 --- a/poincare/test/helper.cpp +++ b/poincare/test/helper.cpp @@ -106,5 +106,34 @@ void assert_parsed_expression_simplify_to(const char * expression, const char * delete e; } +void assert_parsed_expression_layout_serialize_to_self(const char * expressionLayout) { + Expression * e = parse_expression(expressionLayout); +#if POINCARE_TESTS_PRINT_EXPRESSIONS + cout << "---- Serialize: " << expressionLayout << "----" << endl; +#endif + ExpressionLayout * el = e->createLayout(); + int bufferSize = 255; + char buffer[bufferSize]; + el->writeTextInBuffer(buffer, bufferSize); +#if POINCARE_TESTS_PRINT_EXPRESSIONS + cout << "---- serialized to: " << buffer << " ----\n" << endl; +#endif + assert(strcmp(expressionLayout, buffer) == 0); + delete e; + delete el; +} + +void assert_expression_layout_serialize_to(Poincare::ExpressionLayout * layout, const char * serialization) { + int bufferSize = 255; + char buffer[bufferSize]; + layout->writeTextInBuffer(buffer, bufferSize); +#if POINCARE_TESTS_PRINT_EXPRESSIONS + cout << "---- Serialize: " << serialization << "----" << endl; + cout << "---- serialized to: " << buffer << " ----" << endl; + cout << "----- compared to: " << serialization << " ----\n" << endl; +#endif + assert(strcmp(serialization, buffer) == 0); +} + template void assert_parsed_expression_evaluates_to(char const*, Poincare::Complex*, int, int, Poincare::Expression::AngleUnit); template void assert_parsed_expression_evaluates_to(char const*, Poincare::Complex*, int, int, Poincare::Expression::AngleUnit); diff --git a/poincare/test/helper.h b/poincare/test/helper.h index 460e7bd03..f182418dd 100644 --- a/poincare/test/helper.h +++ b/poincare/test/helper.h @@ -1,5 +1,7 @@ #include +// Expressions + constexpr Poincare::Expression::AngleUnit Degree = Poincare::Expression::AngleUnit::Degree; constexpr Poincare::Expression::AngleUnit Radian = Poincare::Expression::AngleUnit::Radian; @@ -15,3 +17,7 @@ void assert_parsed_expression_evaluates_to(const char * expression, Poincare::Co assert_parsed_expression_evaluates_to(expression, results, 0, 0, angleUnit); } void assert_parsed_expression_simplify_to(const char * expression, const char * simplifiedExpression, Poincare::Expression::AngleUnit angleUnit = Poincare::Expression::AngleUnit::Radian); + +// Layouts +void assert_parsed_expression_layout_serialize_to_self(const char * expressionLayout); +void assert_expression_layout_serialize_to(Poincare::ExpressionLayout * layout, const char * serialization); diff --git a/poincare/test/nth_root_layout.cpp b/poincare/test/nth_root_layout.cpp new file mode 100644 index 000000000..ea0debc7e --- /dev/null +++ b/poincare/test/nth_root_layout.cpp @@ -0,0 +1,10 @@ +#include +#include +#include +#include "helper.h" + +using namespace Poincare; + +QUIZ_CASE(poincare_nth_root_layout_serialize) { + assert_parsed_expression_layout_serialize_to_self("root(7,3)"); +} diff --git a/poincare/test/parentheses_layout.cpp b/poincare/test/parentheses_layout.cpp new file mode 100644 index 000000000..0b175a54d --- /dev/null +++ b/poincare/test/parentheses_layout.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include "helper.h" + +using namespace Poincare; + +QUIZ_CASE(poincare_parenthesis_layout_size) { + /* 3 + * (2+(---)6)1 + * 4 + * Assert that the first and last parentheses have the same size. + */ + HorizontalLayout * layout = new HorizontalLayout(); + LeftParenthesisLayout leftPar = new LeftParenthesisLayout(); + RightParenthesisLayout rightPar = new RightParenthesisLayout(); + layout->addChildAtIndex(leftPar, 0); + layout->addChildAtIndex(new CharLayout('2'), 1); + layout->addChildAtIndex(new CharLayout('+'), 2); + layout->addChildAtIndex(new LeftParenthesisLayout(), 3); + layout->addChildAtIndex(new FractionLayout( + new CharLayout('3'), + new CharLayout('4')), + 4); + layout->addChildAtIndex(new RightParenthesisLayout(), 3); + layout->addChildAtIndex(new CharLayout('6'), 5); + layout->addChildAtIndex(rightPar, 7); + layout->addChildAtIndex(new CharLayout('1'), 8); + assert(leftPar->size().height() == rightPar->size().height()); + delete layout; +} diff --git a/poincare/test/vertical_offset_layout.cpp b/poincare/test/vertical_offset_layout.cpp new file mode 100644 index 000000000..bea971293 --- /dev/null +++ b/poincare/test/vertical_offset_layout.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include +#include "helper.h" + +using namespace Poincare; + +QUIZ_CASE(poincare_vertical_offset_layout_serialize) { + assert_parsed_expression_layout_serialize_to_self("2^3"); + + HorizontalLayout * layout = new HorizontalLayout( + new CharLayout('2'), + new VerticalOffsetLayout( + LayoutEngine::createStringLayout("4+5", 3), + VerticalOffsetLayout::Type::Superscript, + false), + false); + assert_expression_layout_serialize_to(layout, "2^(4+5)"); + delete layout; + +}