diff --git a/apps/calculation/Makefile b/apps/calculation/Makefile index 77906d671..2393d9748 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\ + editable_expression_view.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..a9b82ee29 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), + EditableExpressionViewDelegateApp(container, snapshot, &m_editExpressionController), m_historyController(&m_editExpressionController, snapshot->calculationStore()), m_editExpressionController(&m_modalViewController, &m_historyController, snapshot->calculationStore()) { @@ -85,6 +85,36 @@ bool App::textInputIsCorrect(const char * text) { return true; } +bool App::scrollableExpressionViewWithCursorDidReceiveEvent(::ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) { + if ((event == Ion::Events::Var || event == Ion::Events::XNT) && EditableExpressionViewDelegateApp::scrollableExpressionViewWithCursorDidReceiveEvent(scrollableExpressionViewWithCursor, event)) { + return true; + } + /* Here, we check that the expression entered by the user can be printed with + * less than k_printedExpressionLength characters. Otherwise, we prevent the + * user from adding this expression to the calculation store. */ + if (scrollableExpressionViewWithCursor->isEditing() && scrollableExpressionViewWithCursor->scrollableExpressionViewWithCursorShouldFinishEditing(event)) { + int bufferLength = TextField::maxBufferSize(); + char bufferForParsing[bufferLength]; + Poincare::ExpressionLayout * expressionLayout = scrollableExpressionViewWithCursor->expressionViewWithCursor()->expressionView()->expressionLayout(); + expressionLayout->writeTextInBuffer(bufferForParsing, bufferLength); + Expression * exp = Expression::parse(bufferForParsing); + if (exp == nullptr) { + scrollableExpressionViewWithCursor->app()->displayWarning(I18n::Message::SyntaxError); + return true; + } + char buffer[Calculation::k_printedExpressionSize]; + int length = exp->writeTextInBuffer(buffer, sizeof(buffer)); + delete exp; + /* if the buffer is totally full, it is VERY likely that writeTextInBuffer + * escaped before printing utterly the expression. */ + if (length >= Calculation::k_printedExpressionSize-1) { + displayWarning(I18n::Message::SyntaxError); + return true; + } + } + return false; +} + const char * App::XNT() { return "x"; } diff --git a/apps/calculation/app.h b/apps/calculation/app.h index e8dd96f7b..ecc8ab991 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::EditableExpressionViewDelegateApp { 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 scrollableExpressionViewWithCursorDidReceiveEvent(::ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, 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..37cd3c772 100644 --- a/apps/calculation/edit_expression_controller.cpp +++ b/apps/calculation/edit_expression_controller.cpp @@ -1,49 +1,41 @@ #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, ScrollableExpressionViewWithCursorDelegate * scrollableExpressionViewWithCursorDelegate) : View(), m_mainView(subview), - m_textField(parentResponder, m_textBody, TextField::maxBufferSize(), textFieldDelegate) + m_editableExpressionView(parentResponder, textFieldDelegate, scrollableExpressionViewWithCursorDelegate) { - m_textBody[0] = 0; -} - -int EditExpressionController::ContentView::numberOfSubviews() const { - return 2; } 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_editableExpressionView; } void EditExpressionController::ContentView::layoutSubviews() { - KDRect mainViewFrame(0, 0, bounds().width(), bounds().height() - k_textFieldHeight-k_separatorThickness); + KDCoordinate inputViewFrameHeight = m_editableExpressionView.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_editableExpressionView.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) : @@ -55,20 +47,18 @@ EditExpressionController::EditExpressionController(Responder * parentResponder, } const char * EditExpressionController::textBody() { - return ((ContentView *)view())->textField()->text(); + return ((ContentView *)view())->editableExpressionView()->text(); } void EditExpressionController::insertTextBody(const char * text) { - TextField * tf = ((ContentView *)view())->textField(); - tf->setEditing(true, false); - tf->handleEventWithText(text); + ((ContentView *)view())->editableExpressionView()->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())->editableExpressionView()->setEditing(false, false); app()->setFirstResponder(m_historyController); } return true; @@ -79,58 +69,115 @@ 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())->editableExpressionView()->setEditing(true, false); + app()->setFirstResponder(((ContentView *)view())->editableExpressionView()); } bool EditExpressionController::textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) { + assert(textField == ((ContentView *)view())->editableExpressionView()->textField()); 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; + assert(textField == ((ContentView *)view())->editableExpressionView()->textField()); + 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) { + assert(textField == ((ContentView *)view())->editableExpressionView()->textField()); + return inputViewDidAbortEditing(textField->text()); +} + +bool EditExpressionController::scrollableExpressionViewWithCursorDidReceiveEvent(::ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) { + assert(scrollableExpressionViewWithCursor == ((ContentView *)view())->editableExpressionView()->scrollableExpressionViewWithCursor()); + if (scrollableExpressionViewWithCursor->isEditing() && scrollableExpressionViewWithCursor->scrollableExpressionViewWithCursorShouldFinishEditing(event) && !expressionLayout()->hasText() && m_calculationStore->numberOfCalculations() > 0) { + return inputViewDidReceiveEvent(event); + } + return editableExpressionViewDelegateApp()->scrollableExpressionViewWithCursorDidReceiveEvent(scrollableExpressionViewWithCursor, event); +} + +bool EditExpressionController::scrollableExpressionViewWithCursorDidFinishEditing(::ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, const char * text, Ion::Events::Event event) { + assert(scrollableExpressionViewWithCursor == ((ContentView *)view())->editableExpressionView()->scrollableExpressionViewWithCursor()); + return inputViewDidFinishEditing(text, event); +} + +bool EditExpressionController::scrollableExpressionViewWithCursorDidAbortEditing(::ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { + assert(scrollableExpressionViewWithCursor == ((ContentView *)view())->editableExpressionView()->scrollableExpressionViewWithCursor()); + return inputViewDidAbortEditing(nullptr); +} + +void EditExpressionController::scrollableExpressionViewWithCursorDidChangeSize(::ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { + assert(scrollableExpressionViewWithCursor == ((ContentView *)view())->editableExpressionView()->scrollableExpressionViewWithCursor()); + reloadView(); } TextFieldDelegateApp * EditExpressionController::textFieldDelegateApp() { return (App *)app(); } +EditableExpressionViewDelegateApp * EditExpressionController::editableExpressionViewDelegateApp() { + 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 expressionLayout(); 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())->editableExpressionView()->setEditing(true); + ((ContentView *)view())->editableExpressionView()->setText(""); + return true; +} + +bool EditExpressionController::inputViewDidAbortEditing(const char * text) { + if (text != nullptr) { + ((ContentView *)view())->editableExpressionView()->setEditing(true, true); + ((ContentView *)view())->editableExpressionView()->setText(text); + } + return false; +} + void EditExpressionController::viewDidDisappear() { DynamicViewController::viewDidDisappear(); m_historyController->viewDidDisappear(); } +Poincare::ExpressionLayout * EditExpressionController::expressionLayout() { + return ((ContentView *)view())->editableExpressionView()->scrollableExpressionViewWithCursor()->expressionViewWithCursor()->expressionView()->expressionLayout(); +} + } diff --git a/apps/calculation/edit_expression_controller.h b/apps/calculation/edit_expression_controller.h index 50f4e27ff..7275e411f 100644 --- a/apps/calculation/edit_expression_controller.h +++ b/apps/calculation/edit_expression_controller.h @@ -2,16 +2,17 @@ #define CALCULATION_EDIT_EXPRESSION_CONTROLLER_H #include +#include "editable_expression_view.h" #include "../shared/text_field_delegate.h" +#include "../shared/scrollable_expression_view_with_cursor_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::ScrollableExpressionViewWithCursorDelegate { public: EditExpressionController(Responder * parentResponder, HistoryController * historyController, CalculationStore * calculationStore); void didBecomeFirstResponder() override; @@ -19,31 +20,43 @@ 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; + + /* ScrollableExpressionViewWithCursorDelegate */ + bool scrollableExpressionViewWithCursorDidReceiveEvent(::ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) override; + bool scrollableExpressionViewWithCursorDidFinishEditing(::ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, const char * text, Ion::Events::Event event) override; + bool scrollableExpressionViewWithCursorDidAbortEditing(::ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) override; + void scrollableExpressionViewWithCursorDidChangeSize(::ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) 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, ScrollableExpressionViewWithCursorDelegate * scrollableExpressionViewWithCursorDelegate); + void reload(); + TableView * mainView() { return m_mainView; } + EditableExpressionView * editableExpressionView() { return &m_editableExpressionView; } + /* 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; TableView * m_mainView; - TextField m_textField; - char m_textBody[TextField::maxBufferSize()]; + EditableExpressionView m_editableExpressionView; }; 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; char m_cacheBuffer[TextField::maxBufferSize()]; + Shared::EditableExpressionViewDelegateApp * editableExpressionViewDelegateApp() override; + Poincare::ExpressionLayout * expressionLayout(); HistoryController * m_historyController; CalculationStore * m_calculationStore; }; diff --git a/apps/calculation/editable_expression_view.cpp b/apps/calculation/editable_expression_view.cpp new file mode 100644 index 000000000..ac9b2000d --- /dev/null +++ b/apps/calculation/editable_expression_view.cpp @@ -0,0 +1,31 @@ +#include "editable_expression_view.h" + +namespace Calculation { + +EditableExpressionView::EditableExpressionView(Responder * parentResponder, TextFieldDelegate * textFieldDelegate, ScrollableExpressionViewWithCursorDelegate * scrollableExpressionViewWithCursorDelegate) : + ::EditableExpressionView(parentResponder, textFieldDelegate, scrollableExpressionViewWithCursorDelegate) +{ + setEditing(true); +} + +bool EditableExpressionView::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(::EditableExpressionView::handleEvent(event)); +} + +} diff --git a/apps/calculation/editable_expression_view.h b/apps/calculation/editable_expression_view.h new file mode 100644 index 000000000..52a1faad3 --- /dev/null +++ b/apps/calculation/editable_expression_view.h @@ -0,0 +1,17 @@ +#ifndef CALCULATION_EDITABLE_EXPRESSION_VIEW_H +#define CALCULATION_EDITABLE_EXPRESSION_VIEW_H + +#include + +namespace Calculation { + +class EditableExpressionView : public ::EditableExpressionView { +public: + EditableExpressionView(Responder * parentResponder, TextFieldDelegate * textFieldDelegate, ScrollableExpressionViewWithCursorDelegate * scrollableExpressionViewWithCursorDelegate); +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 a1f6881ee..ceb77bbfa 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..4fd7572df 100644 --- a/apps/calculation/scrollable_expression_view.cpp +++ b/apps/calculation/scrollable_expression_view.cpp @@ -10,8 +10,8 @@ ScrollableExpressionView::ScrollableExpressionView(Responder * parentResponder) { } -void ScrollableExpressionView::setExpression(ExpressionLayout * expressionLayout) { - m_expressionView.setExpression(expressionLayout); +void ScrollableExpressionView::setExpressionLayout(ExpressionLayout * expressionLayout) { + m_expressionView.setExpressionLayout(expressionLayout); layoutSubviews(); } diff --git a/apps/calculation/scrollable_expression_view.h b/apps/calculation/scrollable_expression_view.h index a613600f1..fb156fbc9 100644 --- a/apps/calculation/scrollable_expression_view.h +++ b/apps/calculation/scrollable_expression_view.h @@ -8,7 +8,7 @@ namespace Calculation { class ScrollableExpressionView : public ScrollableView, public ScrollViewDataSource { public: ScrollableExpressionView(Responder * parentResponder); - void setExpression(Poincare::ExpressionLayout * expressionLayout); + void setExpressionLayout(Poincare::ExpressionLayout * expressionLayout); void setBackgroundColor(KDColor backgroundColor); KDSize minimalSizeForOptimalDisplay() const override; private: 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/console_controller.cpp b/apps/code/console_controller.cpp index 69fc8ea0f..33d7e73f5 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -277,7 +277,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 2b7a78107..11338bc88 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -55,7 +55,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); } } } @@ -343,8 +343,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..070470576 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -291,8 +291,9 @@ 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); + int strippedEditedTextMaxLength = strlen(editedText)+1; + char strippedEditedText[strippedEditedTextMaxLength]; + Shared::ToolboxHelpers::TextToInsertForCommandMessage(node->insertedText(), strippedEditedText, strippedEditedTextMaxLength); TextInput * textInput = static_cast(sender()); textInput->handleEventWithText(strippedEditedText, true); app()->dismissModalViewController(); diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index d4804031a..7f426ad2b 100644 --- a/apps/code/variable_box_controller.cpp +++ b/apps/code/variable_box_controller.cpp @@ -113,8 +113,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/math_toolbox.cpp b/apps/math_toolbox.cpp index 6ec75cb8a..e8f126b18 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,71 @@ 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())), + m_action(actionForTextInput) { } -TextField * MathToolbox::sender() { - return (TextField *)Toolbox::sender(); +void MathToolbox::setSenderAndAction(Responder * sender, Action action) { + setSender(sender); + m_action = action; +} + +void MathToolbox::actionForScrollableExpressionViewWithCursor(void * sender, const char * text, bool removeArguments) { + ScrollableExpressionViewWithCursor * expressionLayoutEditorSender = static_cast(sender); + Expression * resultExpression = nullptr; + if (removeArguments) { + // Replace the arguments with Empty chars. + int textToInsertMaxLength = strlen(text); + char textToInsert[textToInsertMaxLength]; + Shared::ToolboxHelpers::TextToParseIntoLayoutForCommandText(text, textToInsert, textToInsertMaxLength); + // Create the layout + resultExpression = Expression::parse(textToInsert); + } else { + resultExpression = Expression::parse(text); + } + if (resultExpression == nullptr) { + return; + } + ExpressionLayout * resultLayout = resultExpression->createLayout(); + // Find the pointed layout. + ExpressionLayout * pointedLayout = nullptr; + 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; + } + } + } else if (resultLayout->numberOfChildren() > 0) { + // Else, if the layout has children, pick the first one. + pointedLayout = resultLayout->editableChild(0); + } + // Insert the layout. If pointedLayout is nullptr, the cursor will be on the + // right of the inserted layout. + expressionLayoutEditorSender->insertLayoutAtCursor(resultLayout, pointedLayout); +} + +void MathToolbox::actionForTextInput(void * sender, const char * text, bool removeArguments) { + TextInput * textInputSender = static_cast(sender); + if (removeArguments) { + int maxTextToInsertLength = strlen(text); + char textToInsert[maxTextToInsertLength]; + // Translate the message and remove the arguments. + Shared::ToolboxHelpers::TextToInsertForCommandText(text, textToInsert, maxTextToInsertLength); + textInputSender->handleEventWithText(textToInsert); + } else { + textInputSender->handleEventWithText(text); + } } 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(); + m_action(sender(), I18n::translate(messageTree->insertedText()), true); app()->dismissModalViewController(); return true; } diff --git a/apps/math_toolbox.h b/apps/math_toolbox.h index 0579969de..e4f29bda2 100644 --- a/apps/math_toolbox.h +++ b/apps/math_toolbox.h @@ -7,15 +7,19 @@ class MathToolbox : public Toolbox { public: + typedef void (*Action)(void * sender, const char * text, bool removeArguments); MathToolbox(); + void setSenderAndAction(Responder * sender, Action action); + static void actionForScrollableExpressionViewWithCursor(void * sender, const char * text, bool removeArguments = true); + static void actionForTextInput(void * sender, const char * text, bool removeArguments = true); protected: - TextField * sender() override; bool selectLeaf(ToolboxMessageTree * selectedMessageTree) override; const ToolboxMessageTree * rootModel() override; MessageTableCellWithMessage * leafCellAtIndex(int index) override; MessageTableCellWithChevron* nodeCellAtIndex(int index) override; int maxNumberOfDisplayedRows() override; constexpr static int k_maxNumberOfDisplayedRows = 6; // = 240/40 + Action m_action; private: MessageTableCellWithMessage m_leafCells[k_maxNumberOfDisplayedRows]; MessageTableCellWithChevron m_nodeCells[k_maxNumberOfDisplayedRows]; diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index 92b123c1c..da11aa3f4 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..3a4a7f185 100644 --- a/apps/sequence/list/list_controller.cpp +++ b/apps/sequence/list/list_controller.cpp @@ -24,13 +24,14 @@ 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); + setToolboxExtraCells(); + m_sequenceToolbox.setSenderAndAction(textInput, MathToolbox::actionForTextInput); + return &m_sequenceToolbox; +} + +Toolbox * ListController::toolboxForScrollableExpressionViewWithCursor(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { + setToolboxExtraCells(); + m_sequenceToolbox.setSenderAndAction(scrollableExpressionViewWithCursor, MathToolbox::actionForScrollableExpressionViewWithCursor); return &m_sequenceToolbox; } @@ -38,6 +39,10 @@ TextFieldDelegateApp * ListController::textFieldDelegateApp() { return (App *)app(); } +EditableExpressionViewDelegateApp * ListController::editableExpressionViewDelegateApp() { + return (App *)app(); +} + int ListController::numberOfRows() { int numberOfRows = 0; for (int i = 0; i < m_sequenceStore->numberOfFunctions(); i++) { @@ -169,13 +174,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 +190,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; @@ -304,4 +309,14 @@ void ListController::unloadView(View * view) { Shared::ListController::unloadView(view); } +void ListController::setToolboxExtraCells() { + 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); +} + } diff --git a/apps/sequence/list/list_controller.h b/apps/sequence/list/list_controller.h index 3e8e9075d..7b4c5c853 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/scrollable_expression_view_with_cursor_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::ScrollableExpressionViewWithCursorDelegate { public: ListController(Responder * parentResponder, SequenceStore * sequenceStore, ButtonRowController * header, ButtonRowController * footer); const char * title() override; @@ -22,9 +23,11 @@ public: virtual KDCoordinate rowHeight(int j) override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; Toolbox * toolboxForTextInput(TextInput * textInput) override; + Toolbox * toolboxForScrollableExpressionViewWithCursor(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) override; void selectPreviousNewSequenceCell(); private: Shared::TextFieldDelegateApp * textFieldDelegateApp() override; + Shared::EditableExpressionViewDelegateApp * editableExpressionViewDelegateApp() override; void editExpression(Sequence * sequence, int sequenceDefinitionIndex, Ion::Events::Event event); ListParameterController * parameterController() override; int maxNumberOfRows() override; @@ -41,6 +44,7 @@ private: void reinitExpression(Shared::Function * function) override; View * loadView() override; void unloadView(View * view) override; + void setToolboxExtraCells(); static constexpr KDCoordinate k_emptySubRowHeight = 30; constexpr static int k_maxNumberOfRows = 3*MaxNumberOfSequences; SequenceStore * m_sequenceStore; 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..08d67f4fd 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,32 +89,51 @@ 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; - sender()->handleEventWithText(buffer); + int bufferSize = 10; + char buffer[bufferSize]; + m_addedCellLayout[selectedRow]->writeTextInBuffer(buffer, bufferSize); + if (m_action == MathToolbox::actionForTextInput) { + /* DIRTY. The symbols are layouted using a Subscript VerticalOffsetLayout, + * which serializes into "_{}", but we want parentheses for text fields. We + * thus need to remove any underscores, and changes brackets into + * parentheses. */ + for (int i = 0; i < bufferSize; i++) { + if (buffer[i] == '{') { + buffer[i] = '('; + } + if (buffer[i] == '}') { + buffer[i] = ')'; + } + if (buffer[i] == '_') { + memmove(&buffer[i], &buffer[i+1], bufferSize - (i+1) + 1); + bufferSize--; + i--; + } + } + } + m_action(sender(), buffer, false); app()->dismissModalViewController(); return true; } diff --git a/apps/sequence/list/type_parameter_controller.cpp b/apps/sequence/list/type_parameter_controller.cpp index c760bc37a..3960cef74 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; @@ -124,9 +126,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..4e6150ca8 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::Large), + 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; @@ -245,12 +259,10 @@ 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); - } + 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 +272,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..c7a9992ab 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 = "Editiermodus" +EditionLinear = "1D " +Edition2D = "2D " ComplexFormat = "Komplex" ExamMode = "Testmodus" ActivateExamMode = "Start Testmodus" diff --git a/apps/settings/base.en.i18n b/apps/settings/base.en.i18n index 6ed3a0c64..c2188e480 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 = "Edition mode" +EditionLinear = "1D " +Edition2D = "2D " 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..a7bdaf690 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 = "Modo edicion" +EditionLinear = "1D " +Edition2D = "2D " 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..7fffe0d16 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 = "Mode d'edition" +EditionLinear = "1D " +Edition2D = "2D " 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..788d63e85 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 = "Modo de edicao" +EditionLinear = "1D " +Edition2D = "2D " 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 c6993de72..6bf5b26d1 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) : @@ -151,14 +155,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 @@ -169,36 +173,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()); @@ -213,6 +214,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 f1108437a..0eea81887 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 @@ -22,13 +21,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); @@ -73,7 +69,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; @@ -260,6 +256,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); } @@ -272,6 +271,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..0b4a7235a 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..b7f4c0574 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\ + editable_expression_view_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\ + scrollable_expression_view_with_cursor_delegate.o\ store_controller.o\ store_parameter_controller.o\ sum_graph_controller.o\ diff --git a/apps/shared/editable_expression_view_delegate_app.cpp b/apps/shared/editable_expression_view_delegate_app.cpp new file mode 100644 index 000000000..3ce1f53cb --- /dev/null +++ b/apps/shared/editable_expression_view_delegate_app.cpp @@ -0,0 +1,56 @@ +#include "editable_expression_view_delegate_app.h" +#include "../i18n.h" +#include "../apps_container.h" + +using namespace Poincare; + +namespace Shared { + +EditableExpressionViewDelegateApp::EditableExpressionViewDelegateApp(Container * container, Snapshot * snapshot, ViewController * rootViewController) : + TextFieldDelegateApp(container, snapshot, rootViewController), + ScrollableExpressionViewWithCursorDelegate() +{ +} + +bool EditableExpressionViewDelegateApp::scrollableExpressionViewWithCursorShouldFinishEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) { + return event == Ion::Events::OK || event == Ion::Events::EXE; +} + +bool EditableExpressionViewDelegateApp::scrollableExpressionViewWithCursorDidReceiveEvent(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) { + if (scrollableExpressionViewWithCursor->isEditing() && scrollableExpressionViewWithCursor->scrollableExpressionViewWithCursorShouldFinishEditing(event)) { + if (!scrollableExpressionViewWithCursor->expressionViewWithCursor()->expressionView()->expressionLayout()->hasText()) { + scrollableExpressionViewWithCursor->app()->displayWarning(I18n::Message::SyntaxError); + return true; + } + int bufferSize = 256; + char buffer[bufferSize]; + scrollableExpressionViewWithCursor->expressionViewWithCursor()->expressionView()->expressionLayout()->writeTextInBuffer(buffer, bufferSize); + Expression * exp = Expression::parse(buffer); + if (exp != nullptr) { + delete exp; + } + if (exp == nullptr) { + scrollableExpressionViewWithCursor->app()->displayWarning(I18n::Message::SyntaxError); + return true; + } + } + if (event == Ion::Events::Var) { + if (!scrollableExpressionViewWithCursor->isEditing()) { + scrollableExpressionViewWithCursor->setEditing(true); + } + AppsContainer * appsContainer = (AppsContainer *)scrollableExpressionViewWithCursor->app()->container(); + VariableBoxController * variableBoxController = appsContainer->variableBoxController(); + variableBoxController->setScrollableExpressionViewWithCursorSender(scrollableExpressionViewWithCursor); + scrollableExpressionViewWithCursor->app()->displayModalViewController(variableBoxController, 0.f, 0.f, Metric::PopUpTopMargin, Metric::PopUpLeftMargin, 0, Metric::PopUpRightMargin); + return true; + } + return false; +} + +Toolbox * EditableExpressionViewDelegateApp::toolboxForScrollableExpressionViewWithCursor(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { + Toolbox * toolbox = container()->mathToolbox(); + static_cast(toolbox)->setSenderAndAction(scrollableExpressionViewWithCursor, MathToolbox::actionForScrollableExpressionViewWithCursor); + return toolbox; +} + +} diff --git a/apps/shared/editable_expression_view_delegate_app.h b/apps/shared/editable_expression_view_delegate_app.h new file mode 100644 index 000000000..626b3fac7 --- /dev/null +++ b/apps/shared/editable_expression_view_delegate_app.h @@ -0,0 +1,21 @@ +#ifndef SHARED_EDITABLE_EXPRESSION_VIEW_DELEGATE_APP_H +#define SHARED_EDITABLE_EXPRESSION_VIEW_DELEGATE_APP_H + +#include "text_field_delegate_app.h" +#include + +namespace Shared { + +class EditableExpressionViewDelegateApp : public TextFieldDelegateApp, public ScrollableExpressionViewWithCursorDelegate { +public: + virtual ~EditableExpressionViewDelegateApp() = default; + bool scrollableExpressionViewWithCursorShouldFinishEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) override; + virtual bool scrollableExpressionViewWithCursorDidReceiveEvent(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) override; + Toolbox * toolboxForScrollableExpressionViewWithCursor(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) override; +protected: + EditableExpressionViewDelegateApp(Container * container, Snapshot * snapshot, ViewController * rootViewController); +}; + +} + +#endif diff --git a/apps/shared/function_app.cpp b/apps/shared/function_app.cpp index 982e8e854..60a122020 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) + EditableExpressionViewDelegateApp(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..362ec2af7 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 "editable_expression_view_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 EditableExpressionViewDelegateApp { 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/scrollable_expression_view_with_cursor_delegate.cpp b/apps/shared/scrollable_expression_view_with_cursor_delegate.cpp new file mode 100644 index 000000000..84a29933c --- /dev/null +++ b/apps/shared/scrollable_expression_view_with_cursor_delegate.cpp @@ -0,0 +1,31 @@ +#include "scrollable_expression_view_with_cursor_delegate.h" + +using namespace Poincare; + +namespace Shared { + +bool ScrollableExpressionViewWithCursorDelegate::scrollableExpressionViewWithCursorShouldFinishEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) { + return editableExpressionViewDelegateApp()->scrollableExpressionViewWithCursorShouldFinishEditing(scrollableExpressionViewWithCursor, event); +} + +bool ScrollableExpressionViewWithCursorDelegate::scrollableExpressionViewWithCursorDidReceiveEvent(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) { + return editableExpressionViewDelegateApp()->scrollableExpressionViewWithCursorDidReceiveEvent(scrollableExpressionViewWithCursor, event); +} + +bool ScrollableExpressionViewWithCursorDelegate::scrollableExpressionViewWithCursorDidFinishEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, const char * text, Ion::Events::Event event) { + return editableExpressionViewDelegateApp()->scrollableExpressionViewWithCursorDidFinishEditing(scrollableExpressionViewWithCursor, text, event); +} + +bool ScrollableExpressionViewWithCursorDelegate::scrollableExpressionViewWithCursorDidAbortEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { + return editableExpressionViewDelegateApp()->scrollableExpressionViewWithCursorDidAbortEditing(scrollableExpressionViewWithCursor); +} + +void ScrollableExpressionViewWithCursorDelegate::scrollableExpressionViewWithCursorDidChangeSize(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { + return editableExpressionViewDelegateApp()->scrollableExpressionViewWithCursorDidChangeSize(scrollableExpressionViewWithCursor); +} + +Toolbox * ScrollableExpressionViewWithCursorDelegate::toolboxForScrollableExpressionViewWithCursor(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { + return editableExpressionViewDelegateApp()->toolboxForScrollableExpressionViewWithCursor(scrollableExpressionViewWithCursor); +} + +} diff --git a/apps/shared/scrollable_expression_view_with_cursor_delegate.h b/apps/shared/scrollable_expression_view_with_cursor_delegate.h new file mode 100644 index 000000000..093cecf71 --- /dev/null +++ b/apps/shared/scrollable_expression_view_with_cursor_delegate.h @@ -0,0 +1,23 @@ +#ifndef SHARED_SCROLLABLE_EXPRESSION_VIEW_WITH_CURSOR_DELEGATE_H +#define SHARED_SCROLLABLE_EXPRESSION_VIEW_WITH_CURSOR_DELEGATE_H + +#include +#include "editable_expression_view_delegate_app.h" + +namespace Shared { + +class ScrollableExpressionViewWithCursorDelegate : public ::ScrollableExpressionViewWithCursorDelegate { +public: + bool scrollableExpressionViewWithCursorShouldFinishEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) override; + bool scrollableExpressionViewWithCursorDidReceiveEvent(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) override; + bool scrollableExpressionViewWithCursorDidFinishEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, const char * text, Ion::Events::Event event) override; + bool scrollableExpressionViewWithCursorDidAbortEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) override; + void scrollableExpressionViewWithCursorDidChangeSize(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) override; + Toolbox * toolboxForScrollableExpressionViewWithCursor(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) override; +private: + virtual EditableExpressionViewDelegateApp * editableExpressionViewDelegateApp() = 0; +}; + +} + +#endif diff --git a/apps/shared/sum_graph_controller.cpp b/apps/shared/sum_graph_controller.cpp index 0f162a333..5244263a7 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), + nullptr, + 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..3e92edd1f 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->setTextFieldSender(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(); + static_cast(toolbox)->setSenderAndAction(textInput, MathToolbox::actionForTextInput); + return toolbox; } } diff --git a/apps/shared/toolbox_helpers.cpp b/apps/shared/toolbox_helpers.cpp index 333a38171..33c7b4461 100644 --- a/apps/shared/toolbox_helpers.cpp +++ b/apps/shared/toolbox_helpers.cpp @@ -1,36 +1,52 @@ #include "toolbox_helpers.h" +#include +#include #include +#include namespace Shared { namespace ToolboxHelpers { -int CursorIndexInCommand(const char * text) { +int CursorIndexInCommandText(const char * text) { for (size_t i = 0; i < strlen(text); i++) { if (text[i] == '(' || text[i] == '\'') { return i + 1; } + if (text[i] == ']') { + return i; + } } return strlen(text); } -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) { + TextToInsertForCommandText(I18n::translate(message), buffer, bufferSize); } -void TextToInsertForCommandText(const char * command, char * buffer) { +void TextToInsertForCommandText(const char * command, char * buffer, int bufferSize) { int currentNewTextIndex = 0; + int numberOfOpenParentheses = 0; int numberOfOpenBrackets = 0; bool insideQuote = 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); buffer[currentNewTextIndex++] = command[i]; } if (command[i] == '(') { + numberOfOpenParentheses++; + } + if (command[i] == '[') { numberOfOpenBrackets++; } if (command[i] == '\'') { @@ -40,5 +56,24 @@ void TextToInsertForCommandText(const char * command, char * buffer) { buffer[currentNewTextIndex] = 0; } +void TextToParseIntoLayoutForCommandMessage(I18n::Message message, char * buffer, int bufferSize) { + TextToParseIntoLayoutForCommandText(I18n::translate(message), buffer, bufferSize); +} + +void TextToParseIntoLayoutForCommandText(const char * command, char * buffer, int bufferSize) { + TextToInsertForCommandText(command, buffer, bufferSize); + size_t bufferLength = strlen(buffer); + for (size_t i = 0; i < bufferLength; i++) { + if (buffer[i] == '(' || buffer[i] == ',' || (i < bufferLength - 1 && buffer[i] == '[' && buffer[i+1] == ']')) { + // Shift the buffer to make room for the new char. Use memmove to avoid + // overwritting. + memmove(&buffer[i+2], &buffer[i+1], bufferLength - (i+1) + 1); + bufferLength++; + i++; + buffer[i] = Ion::Charset::Empty; + } + } +} + } } diff --git a/apps/shared/toolbox_helpers.h b/apps/shared/toolbox_helpers.h index e28e8e4cc..0065993a2 100644 --- a/apps/shared/toolbox_helpers.h +++ b/apps/shared/toolbox_helpers.h @@ -6,17 +6,20 @@ 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); +void TextToInsertForCommandText(const char * command, char * buffer, int bufferSize); /* Removes the arguments from a command: - * - Removes text between parentheses, except commas */ + * - Removes text between parentheses or brackets, except commas */ +void TextToParseIntoLayoutForCommandMessage(I18n::Message message, char * buffer, int bufferSize); +void TextToParseIntoLayoutForCommandText(const char * command, char * buffer, int bufferSize); +/* Removes the arguments from a command and replaces them with empty chars. */ } } 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 69be56c49..1413861e1 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), @@ -18,14 +18,13 @@ VariableBoxController::ContentViewController::ContentViewController(Responder * { } -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); @@ -65,7 +64,7 @@ bool VariableBoxController::ContentViewController::handleEvent(Ion::Events::Even char label[3]; putLabelAtIndexInBuffer(selectedRow(), label); const char * editedText = label; - m_textFieldCaller->handleEventWithText(editedText); + m_insertTextAction(m_sender, editedText); #if MATRIX_VARIABLES m_selectableTableView.deselectTable(); m_currentPage = Page::RootMenu; @@ -196,34 +195,31 @@ 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::setTextFieldSender(TextField * textField) { + m_sender = textField; + m_insertTextAction = &insertTextInTextInput; } -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::setScrollableExpressionViewWithCursorSender(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { + m_sender = scrollableExpressionViewWithCursor; + m_insertTextAction = &insertTextInScrollableExpressionViewWithCursor; +} + +void VariableBoxController::ContentViewController::reloadData() { + m_selectableTableView.reloadData(); +} + +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) { @@ -264,25 +260,47 @@ 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; +} + +void VariableBoxController::ContentViewController::insertTextInTextInput(void * sender, const char * textToInsert) { + TextInput * textInput = static_cast(sender); + textInput->handleEventWithText(textToInsert); +} + +void VariableBoxController::ContentViewController::insertTextInScrollableExpressionViewWithCursor(void * sender, const char * textToInsert) { + ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor = static_cast(sender); + if (!scrollableExpressionViewWithCursor->isEditing()) { + scrollableExpressionViewWithCursor->setEditing(true); + } + scrollableExpressionViewWithCursor->insertLayoutFromTextAtCursor(textToInsert); } VariableBoxController::VariableBoxController(GlobalContext * context) : @@ -295,8 +313,12 @@ void VariableBoxController::didBecomeFirstResponder() { app()->setFirstResponder(&m_contentViewController); } -void VariableBoxController::setTextFieldCaller(TextField * textField) { - m_contentViewController.setTextFieldCaller(textField); +void VariableBoxController::setTextFieldSender(TextField * textField) { + m_contentViewController.setTextFieldSender(textField); +} + +void VariableBoxController::setScrollableExpressionViewWithCursorSender(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { + m_contentViewController.setScrollableExpressionViewWithCursorSender(scrollableExpressionViewWithCursor); } void VariableBoxController::viewWillAppear() { diff --git a/apps/variable_box_controller.h b/apps/variable_box_controller.h index 17d40bd71..ca643a440 100644 --- a/apps/variable_box_controller.h +++ b/apps/variable_box_controller.h @@ -10,9 +10,11 @@ class VariableBoxController : public StackViewController { public: + typedef void (*Action)(void * sender, const char * text); VariableBoxController(Poincare::GlobalContext * context); void didBecomeFirstResponder() override; - void setTextFieldCaller(TextField * textField); + void setTextFieldSender(TextField * textField); + void setScrollableExpressionViewWithCursorSender(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor); void viewWillAppear() override; void viewDidDisappear() override; private: @@ -31,7 +33,8 @@ private: KDCoordinate cumulatedHeightFromIndex(int j) override; int indexFromCumulatedHeight(KDCoordinate offsetY) override; int typeAtLocation(int i, int j) override; - void setTextFieldCaller(TextField * textField); + void setTextFieldSender(TextField * textField); + void setScrollableExpressionViewWithCursorSender(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor); void reloadData(); void resetPage(); void viewDidDisappear() override; @@ -56,9 +59,11 @@ private: I18n::Message nodeLabelAtIndex(int index); const Poincare::Expression * expressionForIndex(int index); Poincare::ExpressionLayout * expressionLayoutForIndex(int index); - + static void insertTextInTextInput(void * sender, const char * textToInsert); + static void insertTextInScrollableExpressionViewWithCursor(void * sender, const char * textToInsert); Poincare::GlobalContext * m_context; - TextField * m_textFieldCaller; + Responder * m_sender; + Action m_insertTextAction; 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 c43affda1..e6e743a16 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 6723110b5..7eeebb132 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -12,6 +12,7 @@ objs += $(addprefix escher/src/,\ dynamic_view_controller.o\ editable_text_cell.o\ ellipsis_view.o\ + editable_expression_view.o\ even_odd_cell.o\ even_odd_cell_with_ellipsis.o\ even_odd_buffer_text_cell.o\ @@ -21,6 +22,7 @@ objs += $(addprefix escher/src/,\ expression_table_cell.o\ expression_table_cell_with_pointer.o\ expression_view.o\ + expression_view_with_cursor.o\ highlight_cell.o\ gauge_view.o\ image_view.o\ @@ -48,6 +50,7 @@ objs += $(addprefix escher/src/,\ scroll_view_data_source.o\ scroll_view_indicator.o\ scrollable_view.o\ + scrollable_expression_view_with_cursor.o\ selectable_table_view.o\ selectable_table_view_data_source.o\ selectable_table_view_delegate.o\ @@ -73,7 +76,6 @@ objs += $(addprefix escher/src/,\ tiled_view.o\ timer.o\ toolbox.o\ - toolbox_message_tree.o\ view.o\ view_controller.o\ warning_controller.o\ diff --git a/escher/include/escher.h b/escher/include/escher.h index 9a31e8d8c..8c07d9ea9 100644 --- a/escher/include/escher.h +++ b/escher/include/escher.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +53,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/escher/include/escher/editable_expression_view.h b/escher/include/escher/editable_expression_view.h new file mode 100644 index 000000000..44d5f867e --- /dev/null +++ b/escher/include/escher/editable_expression_view.h @@ -0,0 +1,48 @@ +#ifndef ESCHER_EDITABLE_EXPRESSION_VIEW_H +#define ESCHER_EDITABLE_EXPRESSION_VIEW_H + +#include +#include +#include +#include + +class EditableExpressionView : public Responder, public View { +public: + EditableExpressionView(Responder * parentResponder, TextFieldDelegate * textFieldDelegate, ScrollableExpressionViewWithCursorDelegate * scrollableExpressionViewWithCursorDelegate); + + 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(); + TextField * textField() { return &m_textField; } + ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor() { return &m_scrollableExpressionViewWithCursor; } + bool editionIsInTextField() const; + bool isEmpty() const; + bool heightIsMaximal() const; + + /* 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; + + static constexpr int k_bufferLength = TextField::maxBufferSize(); +private: + static constexpr KDCoordinate k_textFieldHeight = 37; + static constexpr KDCoordinate k_leftMargin = 5; + static constexpr KDCoordinate k_verticalExpressionViewMargin = 5; + constexpr static int k_separatorThickness = 1; + KDCoordinate inputViewHeight() const; + KDCoordinate maximalHeight() const; + TextField m_textField; + ScrollableExpressionViewWithCursor m_scrollableExpressionViewWithCursor; + char m_textBody[k_bufferLength]; +}; + +#endif 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_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/expression_view_with_cursor.h b/escher/include/escher/expression_view_with_cursor.h new file mode 100644 index 000000000..3128d3aca --- /dev/null +++ b/escher/include/escher/expression_view_with_cursor.h @@ -0,0 +1,36 @@ +#ifndef ESCHER_EXPRESSION_VIEW_WITH_CURSOR_H +#define ESCHER_EXPRESSION_VIEW_WITH_CURSOR_H + +#include +#include +#include +#include +#include + +class ExpressionViewWithCursor : public View { +public: + ExpressionViewWithCursor(Poincare::ExpressionLayout * expressionLayout); + bool isEditing() const { return m_isEditing; } + void setEditing(bool isEditing); + void cursorPositionChanged(); + KDRect cursorRect(); + Poincare::ExpressionLayoutCursor * cursor() { return &m_cursor; } + ExpressionView * expressionView() { return &m_expressionView; } + /* 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; +}; + +#endif diff --git a/escher/include/escher/input_view_controller.h b/escher/include/escher/input_view_controller.h index c4f2a2aa9..7c39d9b94 100644 --- a/escher/include/escher/input_view_controller.h +++ b/escher/include/escher/input_view_controller.h @@ -1,53 +1,56 @@ #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, ScrollableExpressionViewWithCursorDelegate { public: - InputViewController(Responder * parentResponder, ViewController * child, TextFieldDelegate * textFieldDelegate); + InputViewController(Responder * parentResponder, ViewController * child, TextFieldDelegate * textFieldDelegate, ScrollableExpressionViewWithCursorDelegate * scrollableExpressionViewWithCursorDelegate); 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; + + /* ScrollableExpressionViewWithCursorDelegate */ + bool scrollableExpressionViewWithCursorShouldFinishEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) override; + bool scrollableExpressionViewWithCursorDidReceiveEvent(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) override; + bool scrollableExpressionViewWithCursorDidFinishEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, const char * text, Ion::Events::Event event) override; + bool scrollableExpressionViewWithCursorDidAbortEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) override; + void scrollableExpressionViewWithCursorDidChangeSize(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) override; + Toolbox * toolboxForScrollableExpressionViewWithCursor(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) override; + private: - class TextFieldController : public ViewController { + class EditableExpressionViewController : public ViewController { public: - TextFieldController(Responder * parentResponder, TextFieldDelegate * textFieldDelegate); + EditableExpressionViewController(Responder * parentResponder, TextFieldDelegate * textFieldDelegate, ScrollableExpressionViewWithCursorDelegate * scrollableExpressionViewWithCursorDelegate); void didBecomeFirstResponder() override; - View * view() override; - TextField * textField(); + View * view() override { return &m_editableExpressionView; } + EditableExpressionView * editableExpressionView() { return &m_editableExpressionView; } 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; + EditableExpressionView m_editableExpressionView; }; - TextFieldController m_textFieldController; + bool inputViewDidFinishEditing(); + bool inputViewDidAbortEditing(); + EditableExpressionViewController m_editableExpressionViewController; Invocation m_successAction; Invocation m_failureAction; TextFieldDelegate * m_textFieldDelegate; + ScrollableExpressionViewWithCursorDelegate * m_scrollableExpressionViewWithCursorDelegate; + 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..3a326a12d 100644 --- a/escher/include/escher/modal_view_controller.h +++ b/escher/include/escher/modal_view_controller.h @@ -17,6 +17,8 @@ public: bool isDisplayingModal(); void viewWillAppear() override; void viewDidDisappear() override; +protected: + void reloadView(); private: class ContentView : public View { public: @@ -29,6 +31,7 @@ private: KDCoordinate topMargin, KDCoordinate leftMargin, KDCoordinate bottomMargin, KDCoordinate rightMargin); void dismissModalView(); bool isDisplayingModal() const; + void reload(); private: KDRect frame() const; View * m_regularView; diff --git a/escher/include/escher/scrollable_expression_view_with_cursor.h b/escher/include/escher/scrollable_expression_view_with_cursor.h new file mode 100644 index 000000000..7983e0ab7 --- /dev/null +++ b/escher/include/escher/scrollable_expression_view_with_cursor.h @@ -0,0 +1,40 @@ +#ifndef ESCHER_SCROLLABLE_EXPRESSION_VIEW_WITH_CURSOR_H +#define ESCHER_SCROLLABLE_EXPRESSION_VIEW_WITH_CURSOR_H + +#include +#include +#include +#include + +class ScrollableExpressionViewWithCursor : public ScrollableView, public ScrollViewDataSource { +public: + ScrollableExpressionViewWithCursor(Responder * parentResponder, Poincare::ExpressionLayout * expressionLayout, ScrollableExpressionViewWithCursorDelegate * delegate = nullptr); + void setDelegate(ScrollableExpressionViewWithCursorDelegate * delegate) { m_delegate = delegate; } + ExpressionViewWithCursor * expressionViewWithCursor() { return &m_expressionViewWithCursor; } + bool isEditing() const; + void setEditing(bool isEditing); + void clearLayout(); + void scrollToCursor(); + void reload(); + + /* Responder */ + Toolbox * toolbox() override; + bool handleEvent(Ion::Events::Event event) override; + + bool scrollableExpressionViewWithCursorShouldFinishEditing(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); + ExpressionViewWithCursor m_expressionViewWithCursor; +private: + ScrollableExpressionViewWithCursorDelegate * m_delegate; +}; + +#endif diff --git a/escher/include/escher/scrollable_expression_view_with_cursor_delegate.h b/escher/include/escher/scrollable_expression_view_with_cursor_delegate.h new file mode 100644 index 000000000..c2bf5fe96 --- /dev/null +++ b/escher/include/escher/scrollable_expression_view_with_cursor_delegate.h @@ -0,0 +1,19 @@ +#ifndef ESCHER_SCROLLABLE_EXPRESSION_VIEW_WITH_CURSOR_DELEGATE_H +#define ESCHER_SCROLLABLE_EXPRESSION_VIEW_WITH_CURSOR_DELEGATE_H + +#include +#include + +class ScrollableExpressionViewWithCursor; + +class ScrollableExpressionViewWithCursorDelegate { +public: + virtual bool scrollableExpressionViewWithCursorShouldFinishEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) = 0; + virtual bool scrollableExpressionViewWithCursorDidReceiveEvent(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) = 0; + virtual bool scrollableExpressionViewWithCursorDidFinishEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, const char * text, Ion::Events::Event event) { return false; } + virtual bool scrollableExpressionViewWithCursorDidAbortEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { return false; } + virtual void scrollableExpressionViewWithCursorDidChangeSize(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) {} + virtual Toolbox * toolboxForScrollableExpressionViewWithCursor(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) = 0; +}; + +#endif 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/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/include/escher/view.h b/escher/include/escher/view.h index c1f6bdde8..80d2d3714 100644 --- a/escher/include/escher/view.h +++ b/escher/include/escher/view.h @@ -41,6 +41,7 @@ public: KDPoint pointFromPointInView(View * view, KDPoint point); KDRect bounds() const; + KDRect frame() const; View * subview(int index); virtual KDSize minimalSizeForOptimalDisplay() const; diff --git a/escher/src/editable_expression_view.cpp b/escher/src/editable_expression_view.cpp new file mode 100644 index 000000000..00c98bd13 --- /dev/null +++ b/escher/src/editable_expression_view.cpp @@ -0,0 +1,129 @@ +#include +#include +#include + +EditableExpressionView::EditableExpressionView(Responder * parentResponder, TextFieldDelegate * textFieldDelegate, ScrollableExpressionViewWithCursorDelegate * scrollableExpressionViewWithCursorDelegate) : + Responder(parentResponder), + View(), + m_textField(parentResponder, m_textBody, m_textBody, k_bufferLength, textFieldDelegate, false), + m_scrollableExpressionViewWithCursor(parentResponder, new Poincare::HorizontalLayout(), scrollableExpressionViewWithCursorDelegate) +{ + m_textBody[0] = 0; +} + +void EditableExpressionView::setEditing(bool isEditing, bool reinitDraftBuffer) { + if (editionIsInTextField()) { + m_textField.setEditing(isEditing, reinitDraftBuffer); + } else { + if (reinitDraftBuffer) { + m_scrollableExpressionViewWithCursor.clearLayout(); + } + m_scrollableExpressionViewWithCursor.setEditing(isEditing); + } +} + +bool EditableExpressionView::isEditing() const { + return editionIsInTextField() ? m_textField.isEditing() : m_scrollableExpressionViewWithCursor.isEditing(); +} + +const char * EditableExpressionView::text() { + if (!editionIsInTextField()) { + m_scrollableExpressionViewWithCursor.expressionViewWithCursor()->expressionView()->expressionLayout()->writeTextInBuffer(m_textBody, k_bufferLength); + } + return m_textBody; +} + +void EditableExpressionView::setText(const char * text) { + if (editionIsInTextField()) { + m_textField.setText(text); + return; + } + m_scrollableExpressionViewWithCursor.clearLayout(); + if (strlen(text) > 0) { + m_scrollableExpressionViewWithCursor.insertLayoutFromTextAtCursor(text); + } +} + +void EditableExpressionView::insertText(const char * text) { + if (editionIsInTextField()) { + m_textField.handleEventWithText(text); + } else { + m_scrollableExpressionViewWithCursor.setEditing(true); + m_scrollableExpressionViewWithCursor.insertLayoutFromTextAtCursor(text); + } +} + +View * EditableExpressionView::subviewAtIndex(int index) { + assert(index == 0); + if (editionIsInTextField()) { + return &m_textField; + } + return &m_scrollableExpressionViewWithCursor; +} + +void EditableExpressionView::layoutSubviews() { + KDRect inputViewFrame(k_leftMargin, k_separatorThickness, bounds().width() - k_leftMargin, bounds().height() - k_separatorThickness); + if (editionIsInTextField()) { + m_textField.setFrame(inputViewFrame); + m_scrollableExpressionViewWithCursor.setFrame(KDRectZero); + return; + } + m_scrollableExpressionViewWithCursor.setFrame(inputViewFrame); + m_textField.setFrame(KDRectZero); +} + +void EditableExpressionView::reload() { + layoutSubviews(); + markRectAsDirty(bounds()); +} + +void EditableExpressionView::drawRect(KDContext * ctx, KDRect rect) const { + // Draw the separator + ctx->fillRect(KDRect(0, 0, bounds().width(), k_separatorThickness), Palette::GreyMiddle); + // Color the left margin + ctx->fillRect(KDRect(0, k_separatorThickness, k_leftMargin, bounds().height() - k_separatorThickness), m_textField.backgroundColor()); + if (!editionIsInTextField()) { + // Color the upper margin + ctx->fillRect(KDRect(0, k_separatorThickness, bounds().width(), k_verticalExpressionViewMargin), m_textField.backgroundColor()); + } +} + +bool EditableExpressionView::handleEvent(Ion::Events::Event event) { + return editionIsInTextField() ? m_textField.handleEvent(event) : m_scrollableExpressionViewWithCursor.handleEvent(event); +} + +KDSize EditableExpressionView::minimalSizeForOptimalDisplay() const { + return KDSize(0, inputViewHeight()); +} + +bool EditableExpressionView::editionIsInTextField() const { + return Poincare::Preferences::sharedPreferences()->editionMode() == Poincare::Preferences::EditionMode::Edition1D; +} + +bool EditableExpressionView::isEmpty() const { + if (editionIsInTextField()) { + return m_textField.draftTextLength() == 0; + } + Poincare::ExpressionLayout * layout = const_cast(&m_scrollableExpressionViewWithCursor)->expressionViewWithCursor()->expressionView()->expressionLayout(); + return !layout->hasText(); +} + +bool EditableExpressionView::heightIsMaximal() const { + return inputViewHeight() == k_separatorThickness + k_verticalExpressionViewMargin + maximalHeight(); +} + +KDCoordinate EditableExpressionView::inputViewHeight() const { + if (editionIsInTextField()) { + return k_separatorThickness + k_textFieldHeight; + } + return k_separatorThickness + + k_verticalExpressionViewMargin + + min(maximalHeight(), + max(k_textFieldHeight, + m_scrollableExpressionViewWithCursor.minimalSizeForOptimalDisplay().height() + + k_verticalExpressionViewMargin)); +} + +KDCoordinate EditableExpressionView::maximalHeight() const { + return 0.6*Ion::Display::Height; +} 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_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..dfd23e3b3 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()); } @@ -47,13 +47,18 @@ KDSize ExpressionView::minimalSizeForOptimalDisplay() const { return m_expressionLayout->size(); } +KDPoint ExpressionView::drawingOrigin() const { + KDSize expressionSize = m_expressionLayout->size(); + return KDPoint(m_horizontalAlignment*(m_frame.width() - expressionSize.width()), 0.5f*(m_frame.height() - expressionSize.height())); +} + +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/expression_view_with_cursor.cpp b/escher/src/expression_view_with_cursor.cpp new file mode 100644 index 000000000..f53fa5aa8 --- /dev/null +++ b/escher/src/expression_view_with_cursor.cpp @@ -0,0 +1,64 @@ +#include +#include +#include + +using namespace Poincare; + +ExpressionViewWithCursor::ExpressionViewWithCursor(ExpressionLayout * expressionLayout) : + m_cursor(), + m_expressionView(), + m_cursorView(), + m_isEditing(false) +{ + m_cursor.setPointedExpressionLayout(expressionLayout); + m_cursor.setPosition(ExpressionLayoutCursor::Position::Right); + m_expressionView.setExpressionLayout(expressionLayout); +} + +void ExpressionViewWithCursor::setEditing(bool isEditing) { + m_isEditing = isEditing; + markRectAsDirty(bounds()); + layoutSubviews(); +} + +void ExpressionViewWithCursor::cursorPositionChanged() { + layoutCursorSubview(); +} + +KDRect ExpressionViewWithCursor::cursorRect() { + return m_cursorView.frame(); +} + +KDSize ExpressionViewWithCursor::minimalSizeForOptimalDisplay() const { + KDSize expressionViewSize = m_expressionView.minimalSizeForOptimalDisplay(); + KDSize cursorSize = isEditing() ? m_cursorView.minimalSizeForOptimalDisplay() : KDSizeZero; + KDCoordinate resultWidth = expressionViewSize.width() + cursorSize.width(); + KDCoordinate resultHeight = expressionViewSize.height() + cursorSize.height()/2; + return KDSize(resultWidth, resultHeight); +} + +View * ExpressionViewWithCursor::subviewAtIndex(int index) { + assert(index >= 0 && index < 2); + View * m_views[] = {&m_expressionView, &m_cursorView}; + return m_views[index]; +} + +void ExpressionViewWithCursor::layoutSubviews() { + m_expressionView.setFrame(bounds()); + layoutCursorSubview(); +} + +void ExpressionViewWithCursor::layoutCursorSubview() { + if (!m_isEditing) { + m_cursorView.setFrame(KDRectZero); + return; + } + KDPoint expressionViewOrigin = m_expressionView.absoluteDrawingOrigin(); + KDPoint cursoredExpressionViewOrigin = m_cursor.pointedExpressionLayout()->absoluteOrigin(); + KDCoordinate cursorX = expressionViewOrigin.x() + cursoredExpressionViewOrigin.x(); + if (m_cursor.position() == ExpressionLayoutCursor::Position::Right) { + cursorX += m_cursor.pointedExpressionLayout()->size().width(); + } + KDPoint cursorTopLeftPosition(cursorX, expressionViewOrigin.y() + cursoredExpressionViewOrigin.y() + m_cursor.pointedExpressionLayout()->baseline()-m_cursor.cursorHeight()/2); + m_cursorView.setFrame(KDRect(cursorTopLeftPosition, 1, m_cursor.cursorHeight())); +} diff --git a/escher/src/input_view_controller.cpp b/escher/src/input_view_controller.cpp index df68abf35..2eec8b207 100644 --- a/escher/src/input_view_controller.cpp +++ b/escher/src/input_view_controller.cpp @@ -1,88 +1,46 @@ #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::EditableExpressionViewController::EditableExpressionViewController(Responder * parentResponder, TextFieldDelegate * textFieldDelegate, ScrollableExpressionViewWithCursorDelegate * scrollableExpressionViewWithCursorDelegate) : ViewController(parentResponder), - m_view(this, textFieldDelegate) + m_editableExpressionView(this, textFieldDelegate, scrollableExpressionViewWithCursorDelegate) { } -View * InputViewController::TextFieldController::view() { - return &m_view; +void InputViewController::EditableExpressionViewController::didBecomeFirstResponder() { + app()->setFirstResponder(&m_editableExpressionView); } -void InputViewController::TextFieldController::didBecomeFirstResponder() { - app()->setFirstResponder(&m_view); -} - -TextField * InputViewController::TextFieldController::textField() { - return m_view.textField(); -} - -InputViewController::InputViewController(Responder * parentResponder, ViewController * child, TextFieldDelegate * textFieldDelegate) : +InputViewController::InputViewController(Responder * parentResponder, ViewController * child, TextFieldDelegate * textFieldDelegate, ScrollableExpressionViewWithCursorDelegate * scrollableExpressionViewWithCursorDelegate) : ModalViewController(parentResponder, child), - m_textFieldController(this, this), + m_editableExpressionViewController(this, this, this), m_successAction(Invocation(nullptr, nullptr)), m_failureAction(Invocation(nullptr, nullptr)), - m_textFieldDelegate(textFieldDelegate) + m_textFieldDelegate(textFieldDelegate), + m_scrollableExpressionViewWithCursorDelegate(scrollableExpressionViewWithCursorDelegate), + m_inputViewHeightIsMaximal(false) { } const char * InputViewController::textBody() { - return m_textFieldController.textField()->text(); + return m_editableExpressionViewController.editableExpressionView()->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_editableExpressionViewController, 1.0f, 1.0f); + m_editableExpressionViewController.editableExpressionView()->handleEvent(event); if (initialText != nullptr) { - m_textFieldController.textField()->handleEventWithText(initialText); + m_editableExpressionViewController.editableExpressionView()->setText(initialText); } } -void InputViewController::abortTextFieldEditionAndDismiss() { - m_textFieldController.textField()->setEditing(false); +void InputViewController::abortEditionAndDismiss() { + m_editableExpressionViewController.editableExpressionView()->setEditing(false); dismissModalViewController(); } @@ -91,15 +49,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 +63,45 @@ bool InputViewController::textFieldDidReceiveEvent(TextField * textField, Ion::E Toolbox * InputViewController::toolboxForTextInput(TextInput * input) { return m_textFieldDelegate->toolboxForTextInput(input); } + +bool InputViewController::scrollableExpressionViewWithCursorShouldFinishEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) { + return event == Ion::Events::OK || event == Ion::Events::EXE; +} + +bool InputViewController::scrollableExpressionViewWithCursorDidReceiveEvent(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, Ion::Events::Event event) { + return m_scrollableExpressionViewWithCursorDelegate->scrollableExpressionViewWithCursorDidReceiveEvent(scrollableExpressionViewWithCursor, event); +} + +bool InputViewController::scrollableExpressionViewWithCursorDidFinishEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor, const char * text, Ion::Events::Event event) { + return inputViewDidFinishEditing(); +} + +bool InputViewController::scrollableExpressionViewWithCursorDidAbortEditing(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { + return inputViewDidAbortEditing(); +} + +void InputViewController::scrollableExpressionViewWithCursorDidChangeSize(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { + // Reload the view only if the EditableExpressionView height actually changes, + // i.e. not if the height is already maximal and stays maximal. + bool newInputViewHeightIsMaximal = m_editableExpressionViewController.editableExpressionView()->heightIsMaximal(); + if (!m_inputViewHeightIsMaximal || !newInputViewHeightIsMaximal) { + m_inputViewHeightIsMaximal = newInputViewHeightIsMaximal; + reloadView(); + } +} + +Toolbox * InputViewController::toolboxForScrollableExpressionViewWithCursor(ScrollableExpressionViewWithCursor * scrollableExpressionViewWithCursor) { + return m_scrollableExpressionViewWithCursorDelegate->toolboxForScrollableExpressionViewWithCursor(scrollableExpressionViewWithCursor); +} + +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..1f7aaed18 100644 --- a/escher/src/modal_view_controller.cpp +++ b/escher/src/modal_view_controller.cpp @@ -95,6 +95,11 @@ bool ModalViewController::ContentView::isDisplayingModal() const { return m_isDisplayingModal; } +void ModalViewController::ContentView::reload() { + markRectAsDirty(frame()); + layoutSubviews(); +} + ModalViewController::ModalViewController(Responder * parentResponder, ViewController * child) : ViewController(parentResponder), m_contentView(), @@ -162,3 +167,7 @@ void ModalViewController::viewDidDisappear() { } m_regularViewController->viewDidDisappear(); } + +void ModalViewController::reloadView() { + m_contentView.reload(); +} diff --git a/escher/src/scrollable_expression_view_with_cursor.cpp b/escher/src/scrollable_expression_view_with_cursor.cpp new file mode 100644 index 000000000..c80e887a8 --- /dev/null +++ b/escher/src/scrollable_expression_view_with_cursor.cpp @@ -0,0 +1,238 @@ +#include +#include +#include +#include +#include + +ScrollableExpressionViewWithCursor::ScrollableExpressionViewWithCursor(Responder * parentResponder, Poincare::ExpressionLayout * expressionLayout, ScrollableExpressionViewWithCursorDelegate * delegate) : + ScrollableView(parentResponder, &m_expressionViewWithCursor, this), + m_expressionViewWithCursor(expressionLayout), + m_delegate(delegate) +{ +} + +bool ScrollableExpressionViewWithCursor::isEditing() const { + return m_expressionViewWithCursor.isEditing(); +} + +void ScrollableExpressionViewWithCursor::setEditing(bool isEditing) { + m_expressionViewWithCursor.setEditing(isEditing); +} + +void ScrollableExpressionViewWithCursor::clearLayout() { + delete m_expressionViewWithCursor.expressionView()->expressionLayout(); + Poincare::ExpressionLayout * newLayout = new Poincare::HorizontalLayout(); + m_expressionViewWithCursor.expressionView()->setExpressionLayout(newLayout); + m_expressionViewWithCursor.cursor()->setPointedExpressionLayout(newLayout); +} + +void ScrollableExpressionViewWithCursor::scrollToCursor() { + scrollToContentRect(m_expressionViewWithCursor.cursorRect(), true); +} + +Toolbox * ScrollableExpressionViewWithCursor::toolbox() { + if (m_delegate) { + return m_delegate->toolboxForScrollableExpressionViewWithCursor(this); + } + return nullptr; +} + +bool ScrollableExpressionViewWithCursor::handleEvent(Ion::Events::Event event) { + KDSize previousSize = minimalSizeForOptimalDisplay(); + bool shouldRecomputeLayout = false; + if (privateHandleMoveEvent(event, &shouldRecomputeLayout)) { + if (!shouldRecomputeLayout) { + m_expressionViewWithCursor.cursorPositionChanged(); + scrollToCursor(); + return true; + } + reload(); + KDSize newSize = minimalSizeForOptimalDisplay(); + if (m_delegate && previousSize.height() != newSize.height()) { + m_delegate->scrollableExpressionViewWithCursorDidChangeSize(this); + reload(); + } + return true; + } + if (privateHandleEvent(event)) { + reload(); + KDSize newSize = minimalSizeForOptimalDisplay(); + if (m_delegate && previousSize.height() != newSize.height()) { + m_delegate->scrollableExpressionViewWithCursorDidChangeSize(this); + reload(); + } + return true; + } + return false; +} + +bool ScrollableExpressionViewWithCursor::scrollableExpressionViewWithCursorShouldFinishEditing(Ion::Events::Event event) { + return m_delegate->scrollableExpressionViewWithCursorShouldFinishEditing(this, event); +} + +KDSize ScrollableExpressionViewWithCursor::minimalSizeForOptimalDisplay() const { + return m_expressionViewWithCursor.minimalSizeForOptimalDisplay(); +} + +bool ScrollableExpressionViewWithCursor::privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout) { + if (event == Ion::Events::Left) { + return m_expressionViewWithCursor.cursor()->moveLeft(shouldRecomputeLayout); + } + if (event == Ion::Events::Right) { + return m_expressionViewWithCursor.cursor()->moveRight(shouldRecomputeLayout); + } + if (event == Ion::Events::Up) { + return m_expressionViewWithCursor.cursor()->moveUp(shouldRecomputeLayout); + } + if (event == Ion::Events::Down) { + return m_expressionViewWithCursor.cursor()->moveDown(shouldRecomputeLayout); + } + if (event == Ion::Events::ShiftLeft) { + m_expressionViewWithCursor.cursor()->setPointedExpressionLayout(m_expressionViewWithCursor.expressionView()->expressionLayout()); + m_expressionViewWithCursor.cursor()->setPosition(Poincare::ExpressionLayoutCursor::Position::Left); + return true; + } + if (event == Ion::Events::ShiftRight) { + m_expressionViewWithCursor.cursor()->setPointedExpressionLayout(m_expressionViewWithCursor.expressionView()->expressionLayout()); + m_expressionViewWithCursor.cursor()->setPosition(Poincare::ExpressionLayoutCursor::Position::Right); + return true; + } + return false; +} + +bool ScrollableExpressionViewWithCursor::privateHandleEvent(Ion::Events::Event event) { + if (m_delegate && m_delegate->scrollableExpressionViewWithCursorDidReceiveEvent(this, event)) { + return true; + } + if (Responder::handleEvent(event)) { + /* The only event Responder handles is 'Toolbox' displaying. In that case, + * the ScrollableExpressionViewWithCursor is forced into editing mode. */ + if (!isEditing()) { + setEditing(true); + } + return true; + } + if (isEditing() && scrollableExpressionViewWithCursorShouldFinishEditing(event)) { + setEditing(false); + int bufferSize = TextField::maxBufferSize(); + char buffer[bufferSize]; + m_expressionViewWithCursor.expressionView()->expressionLayout()->writeTextInBuffer(buffer, bufferSize); + if (m_delegate->scrollableExpressionViewWithCursorDidFinishEditing(this, buffer, event)) { + delete m_expressionViewWithCursor.expressionView()->expressionLayout(); + Poincare::ExpressionLayout * newLayout = new Poincare::HorizontalLayout(); + m_expressionViewWithCursor.expressionView()->setExpressionLayout(newLayout); + m_expressionViewWithCursor.cursor()->setPointedExpressionLayout(newLayout); + } + return true; + } + if ((event == Ion::Events::OK || event == Ion::Events::EXE) && !isEditing()) { + setEditing(true); + m_expressionViewWithCursor.cursor()->setPointedExpressionLayout(m_expressionViewWithCursor.expressionView()->expressionLayout()); + m_expressionViewWithCursor.cursor()->setPosition(Poincare::ExpressionLayoutCursor::Position::Right); + return true; + } + if (event == Ion::Events::Back && isEditing()) { + setEditing(false); + m_delegate->scrollableExpressionViewWithCursorDidAbortEditing(this); + return true; + } + if (event == Ion::Events::Division) { + m_expressionViewWithCursor.cursor()->addFractionLayoutAndCollapseBrothers(); + return true; + } + if (event == Ion::Events::XNT) { + m_expressionViewWithCursor.cursor()->addXNTCharLayout(); + return true; + } + if (event == Ion::Events::Exp) { + m_expressionViewWithCursor.cursor()->addEmptyExponentialLayout(); + return true; + } + if (event == Ion::Events::Power) { + m_expressionViewWithCursor.cursor()->addEmptyPowerLayout(); + return true; + } + if (event == Ion::Events::Sqrt) { + m_expressionViewWithCursor.cursor()->addEmptySquareRootLayout(); + return true; + } + if (event == Ion::Events::Square) { + m_expressionViewWithCursor.cursor()->addEmptySquarePowerLayout(); + return true; + } + if (event.hasText()) { + const char * textToInsert = event.text(); + if (textToInsert[1] == 0) { + if (textToInsert[0] == Ion::Charset::MultiplicationSign) { + const char middleDotString[] = {Ion::Charset::MiddleDot, 0}; + m_expressionViewWithCursor.cursor()->insertText(middleDotString); + return true; + } + if (textToInsert[0] == '[' || textToInsert[0] == ']') { + m_expressionViewWithCursor.cursor()->addEmptyMatrixLayout(); + return true; + } + } + m_expressionViewWithCursor.cursor()->insertText(textToInsert); + return true; + } + if (event == Ion::Events::Backspace) { + m_expressionViewWithCursor.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 ScrollableExpressionViewWithCursor::insertLayoutAtCursor(Poincare::ExpressionLayout * layout, Poincare::ExpressionLayout * pointedLayout) { + if (layout == nullptr) { + return; + } + KDSize previousSize = minimalSizeForOptimalDisplay(); + if (layout->isMatrix() && pointedLayout && pointedLayout->hasAncestor(layout)) { + static_cast(layout)->addGreySquares(); + } + if (pointedLayout != nullptr) { + m_expressionViewWithCursor.cursor()->addLayout(layout); + m_expressionViewWithCursor.cursor()->setPointedExpressionLayout(pointedLayout); + m_expressionViewWithCursor.cursor()->setPosition(Poincare::ExpressionLayoutCursor::Position::Right); + } else { + m_expressionViewWithCursor.cursor()->addLayoutAndMoveCursor(layout); + } + reload(); + KDSize newSize = minimalSizeForOptimalDisplay(); + if (m_delegate && previousSize.height() != newSize.height()) { + m_delegate->scrollableExpressionViewWithCursorDidChangeSize(this); + } +} + +void ScrollableExpressionViewWithCursor::insertLayoutFromTextAtCursor(const char * text) { + Poincare::Expression * expression = Poincare::Expression::parse(text); + if (expression != nullptr) { + Poincare::ExpressionLayout * layout = expression->createLayout(); + delete expression; + insertLayoutAtCursor(layout, nullptr); + reload(); + return; + } + m_expressionViewWithCursor.cursor()->insertText(text); + reload(); +} + +void ScrollableExpressionViewWithCursor::reload() { + m_expressionViewWithCursor.expressionView()->expressionLayout()->invalidAllSizesPositionsAndBaselines(); + m_expressionViewWithCursor.cursorPositionChanged(); + layoutSubviews(); + scrollToCursor(); + markRectAsDirty(bounds()); +} diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index c483fadba..e37cc4576 100644 --- a/escher/src/text_field.cpp +++ b/escher/src/text_field.cpp @@ -249,7 +249,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()) { 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..a2092490a 100644 --- a/escher/src/view.cpp +++ b/escher/src/view.cpp @@ -158,6 +158,10 @@ KDRect View::bounds() const { return m_frame.movedTo(KDPointZero); } +KDRect View::frame() const { + return KDRect(m_frame); +} + KDPoint View::absoluteOrigin() const { if (m_superview == nullptr) { assert(this == (View *)window()); 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/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..d5c93ae82 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 { @@ -61,6 +63,8 @@ public: KDRect intersectedWith(const KDRect & other) const; KDRect unionedWith(const KDRect & other) const; // Returns the smallest rectangle containing r1 and 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..9cf0df654 100644 --- a/kandinsky/src/rect.cpp +++ b/kandinsky/src/rect.cpp @@ -105,6 +105,14 @@ 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/poincare/Makefile b/poincare/Makefile index 1a47014ce..1ace1408d 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,21 +88,35 @@ 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_left_layout.o\ + bracket_left_right_layout.o\ + bracket_right_layout.o\ + ceiling_layout.o\ + char_layout.o\ condensed_sum_layout.o\ conjugate_layout.o\ + dynamic_layout_hierarchy.o\ + empty_visible_layout.o\ expression_layout.o\ + floor_layout.o\ fraction_layout.o\ grid_layout.o\ horizontal_layout.o\ integral_layout.o\ + matrix_layout.o\ nth_root_layout.o\ - parenthesis_layout.o\ + parenthesis_left_layout.o\ + parenthesis_left_right_layout.o\ + parenthesis_right_layout.o\ product_layout.o\ sequence_layout.o\ - string_layout.o\ + static_layout_hierarchy.o\ sum_layout.o\ + vertical_offset_layout.o\ ) tests += $(addprefix poincare/test/,\ 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..43dbb606e --- /dev/null +++ b/poincare/include/poincare/dynamic_layout_hierarchy.h @@ -0,0 +1,41 @@ +#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; + virtual void mergeChildrenAtIndex(DynamicLayoutHierarchy * eL, int index, bool removeEmptyChildren); // WITHOUT delete. + +protected: + const ExpressionLayout ** m_children; + int m_numberOfChildren; +}; + +} + +#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..15203c7cf 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -76,9 +76,10 @@ class Expression { friend class Trigonometry; friend class ApproximationEngine; friend class SimplificationEngine; - friend class LayoutEngine; + friend class LayoutEngine; //TODO MERGE SAISIEJOLIE: Remove? 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..ec5bc978c 100644 --- a/poincare/include/poincare/expression_layout.h +++ b/poincare/include/poincare/expression_layout.h @@ -2,30 +2,149 @@ #define POINCARE_EXPRESSION_LAYOUT_H #include +#include namespace Poincare { +class ExpressionLayoutCursor; + 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; + 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) const; + + /* Dynamic Layout */ + + // Add + virtual bool addChildAtIndex(ExpressionLayout * child, int index) { return false; } + void addBrother(ExpressionLayoutCursor * cursor, ExpressionLayout * brother); + void addBrotherAndMoveCursor(ExpressionLayoutCursor * cursor, ExpressionLayout * brother); + + // 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 backspaceAtCursor(ExpressionLayoutCursor * cursor); + + /* Tree navigation */ + virtual bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) = 0; + virtual bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) = 0; + virtual bool moveUp( + ExpressionLayoutCursor * cursor, + bool * shouldRecomputeLayout = nullptr, + ExpressionLayout * previousLayout = nullptr, + ExpressionLayout * previousPreviousLayout = nullptr); + virtual bool moveDown( + ExpressionLayoutCursor * cursor, + bool * shouldRecomputeLayout = nullptr, + ExpressionLayout * previousLayout = nullptr, + ExpressionLayout * previousPreviousLayout = nullptr); + virtual bool moveUpInside(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout); + virtual bool moveDownInside(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout); + + /* Expression Engine */ + virtual int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const = 0; + + /* Other */ + bool addGreySquaresToAllMatrixAncestors(); + bool hasText() const; + virtual bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { return true; } + /* isCollapsable is used when adding a brother 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 mustHaveLeftBrother() const { return false; } + 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 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 moveCursorInsideAtDirection ( + VerticalDirection direction, + ExpressionLayoutCursor * cursor, + bool * shouldRecomputeLayout, + ExpressionLayout ** childResult, + void * resultPosition, + int * resultScore); + virtual void privateAddBrother(ExpressionLayoutCursor * cursor, ExpressionLayout * brother, bool moveCursor); + 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 brother 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); + bool moveInside(VerticalDirection direction, ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout); + ExpressionLayout * replaceWithJuxtapositionOf(ExpressionLayout * leftChild, ExpressionLayout * rightChild, bool deleteAfterReplace); 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..2d6791e9a --- /dev/null +++ b/poincare/include/poincare/expression_layout_cursor.h @@ -0,0 +1,63 @@ +#ifndef POINCARE_EXPRESSION_LAYOUT_CURSOR_H +#define POINCARE_EXPRESSION_LAYOUT_CURSOR_H + +#include +#include + +namespace Poincare { + +class ExpressionLayoutCursor { +public: + enum class Position { + Left, + Right + }; + + ExpressionLayoutCursor() : + m_pointedExpressionLayout(nullptr), + m_position(Position::Right) + {}; + + /* 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() const { return k_cursorHeight; } + + /* Comparison */ + bool positionIsEquivalentTo(ExpressionLayout * expressionLayout, Position position); + + /* Position */ + KDPoint middleLeftPoint(); + KDPoint middleLeftPointOfCursor(ExpressionLayout * expressionLayout, Position position); + + /* Move */ + bool moveLeft(bool * shouldRecomputeLayout); + bool moveRight(bool * shouldRecomputeLayout); + bool moveUp(bool * shouldRecomputeLayout); + bool moveDown(bool * shouldRecomputeLayout); + + /* Edition */ + void addLayout(ExpressionLayout * layout); + void addLayoutAndMoveCursor(ExpressionLayout * layout); + void addEmptyExponentialLayout(); + void addFractionLayoutAndCollapseBrothers(); + void addEmptyMatrixLayout(int numberOfRows = 1, int numberOfColumns = 1); + void addEmptyPowerLayout(); + void addEmptySquareRootLayout(); + void addEmptySquarePowerLayout(); + void addXNTCharLayout(); + void insertText(const char * text); + void performBackspace(); + +private: + constexpr static KDCoordinate k_cursorHeight = 18; + bool baseForNewPowerLayout(); + ExpressionLayout * m_pointedExpressionLayout; + Position m_position; +}; + +} + +#endif diff --git a/poincare/include/poincare/integer.h b/poincare/include/poincare/integer.h index 3889d28c2..659e446e6 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..818f38e82 100644 --- a/poincare/include/poincare/layout_engine.h +++ b/poincare/include/poincare/layout_engine.h @@ -6,12 +6,59 @@ 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 */ + typedef bool (*ChildNeedsParenthesis)(const char * operatorName); + static int writeInfixExpressionLayoutTextInBuffer( + const ExpressionLayout * expressionLayout, + char * buffer, + int bufferSize, + int numberOfDigits, + const char * operatorName, + int firstChildIndex = 0, + int lastChildIndex = -1, + ChildNeedsParenthesis childNeedsParenthesis = [](const char * operatorName) { + return (operatorName[1] == 0 && (operatorName[0] == divideChar)); }); + 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, ChildNeedsParenthesis childNeedsParenthesis); + 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..818351031 --- /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 efe7820b6..dd63c9c63 100644 --- a/poincare/src/decimal.cpp +++ b/poincare/src/decimal.cpp @@ -9,8 +9,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) { @@ -220,7 +218,7 @@ bool Decimal::needParenthesisWithParent(const Expression * e) const { ExpressionLayout * Decimal::privateCreateLayout(PrintFloat::Mode floatDisplayMode, ComplexFormat complexFormat) const { char buffer[255]; int numberOfChars = writeTextInBuffer(buffer, 255); - 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..455e26b59 --- /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 EmptyVisibleLayout(); +} + +template Complex * EmptyExpression::templatedApproximate(Context& context, AngleUnit angleUnit) const { + return new Complex(Complex::Float(NAN)); +} + +} diff --git a/poincare/src/expression_layout_cursor.cpp b/poincare/src/expression_layout_cursor.cpp new file mode 100644 index 000000000..90b0aaabb --- /dev/null +++ b/poincare/src/expression_layout_cursor.cpp @@ -0,0 +1,239 @@ +#include +#include +#include //TODO: finer include? +#include +#include + +namespace Poincare { + +bool ExpressionLayoutCursor::positionIsEquivalentTo(ExpressionLayout * expressionLayout, Position position) { + assert(expressionLayout != nullptr); + return middleLeftPoint() == middleLeftPointOfCursor(expressionLayout, position); +} + +KDPoint ExpressionLayoutCursor::middleLeftPoint() { + return middleLeftPointOfCursor(m_pointedExpressionLayout, m_position); +} + +KDPoint ExpressionLayoutCursor::middleLeftPointOfCursor(ExpressionLayout * expressionLayout, Position position) { + KDPoint layoutOrigin = expressionLayout->absoluteOrigin(); + KDCoordinate y = layoutOrigin.y() + expressionLayout->baseline() - k_cursorHeight/2; + if (position == Position::Left) { + return KDPoint(layoutOrigin.x(), y); + } + assert(position == Position::Right); + return KDPoint(layoutOrigin.x() + expressionLayout->size().width(), y); +} + +bool ExpressionLayoutCursor::moveLeft(bool * shouldRecomputeLayout) { + return m_pointedExpressionLayout->moveLeft(this, shouldRecomputeLayout); +} + +bool ExpressionLayoutCursor::moveRight(bool * shouldRecomputeLayout) { + return m_pointedExpressionLayout->moveRight(this, shouldRecomputeLayout); +} + +bool ExpressionLayoutCursor::moveUp(bool * shouldRecomputeLayout) { + return m_pointedExpressionLayout->moveUp(this, shouldRecomputeLayout); +} + +bool ExpressionLayoutCursor::moveDown(bool * shouldRecomputeLayout) { + return m_pointedExpressionLayout->moveDown(this, shouldRecomputeLayout); +} + +void ExpressionLayoutCursor::addLayout(ExpressionLayout * layout) { + pointedExpressionLayout()->addBrother(this, layout); +} + +void ExpressionLayoutCursor::addLayoutAndMoveCursor(ExpressionLayout * layout) { + pointedExpressionLayout()->addBrotherAndMoveCursor(this, layout); +} + +void ExpressionLayoutCursor::addEmptyExponentialLayout() { + CharLayout * child1 = new CharLayout(Ion::Charset::Exponential); + VerticalOffsetLayout * offsetLayout = new VerticalOffsetLayout(new EmptyVisibleLayout(), VerticalOffsetLayout::Type::Superscript, false); + HorizontalLayout * newChild = new HorizontalLayout(child1, offsetLayout, false); + pointedExpressionLayout()->addBrother(this, newChild); + setPointedExpressionLayout(offsetLayout->editableChild(0)); + setPosition(ExpressionLayoutCursor::Position::Right); +} + +void ExpressionLayoutCursor::addFractionLayoutAndCollapseBrothers() { + // Add a new FractionLayout + HorizontalLayout * child1 = new HorizontalLayout(new EmptyVisibleLayout(), false); + HorizontalLayout * child2 = new HorizontalLayout(new EmptyVisibleLayout(), false); + FractionLayout * newChild = new FractionLayout(child1, child2, false); + pointedExpressionLayout()->addBrother(this, newChild); + + if (!newChild->parent()->isHorizontal()) { + setPointedExpressionLayout(child2->editableChild(0)); + setPosition(Position::Left); + return; + } + + int fractionIndexInParent = newChild->parent()->indexOfChild(newChild); + int numberOfBrothers = newChild->parent()->numberOfChildren(); + + // Collapse the brothers on the right + int numberOfOpenParenthesis = 0; + bool canCollapseOnRight = true; + if (fractionIndexInParent < numberOfBrothers - 1) { + canCollapseOnRight = !(newChild->editableParent()->editableChild(fractionIndexInParent+1)->mustHaveLeftBrother()); + } + ExpressionLayout * rightBrother = nullptr; + while (canCollapseOnRight && fractionIndexInParent < numberOfBrothers - 1) { + rightBrother = newChild->editableParent()->editableChild(fractionIndexInParent+1); + if (rightBrother->isCollapsable(&numberOfOpenParenthesis, false)) { + newChild->editableParent()->removeChildAtIndex(fractionIndexInParent+1, false); + child2->addOrMergeChildAtIndex(rightBrother, child2->numberOfChildren(), false); + numberOfBrothers--; + } else { + break; + } + } + // Collapse the brothers on the left + numberOfOpenParenthesis = 0; + while (fractionIndexInParent > 0) { + ExpressionLayout * leftBrother = newChild->editableParent()->editableChild(fractionIndexInParent-1); + if (leftBrother->isCollapsable(&numberOfOpenParenthesis, true)) { + newChild->editableParent()->removeChildAtIndex(fractionIndexInParent-1, false); + child1->addOrMergeChildAtIndex(leftBrother, 0, true); + fractionIndexInParent--; + } else { + break; + } + } + // Set the cursor position + setPointedExpressionLayout(child2->editableChild(0)); + setPosition(Position::Left); +} + +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 EmptyVisibleLayout(EmptyVisibleLayout::Color::Yellow); + } else { + children[i*(numberOfColumns+1)+j] = new EmptyVisibleLayout(EmptyVisibleLayout::Color::Grey); + } + } + } + ExpressionLayout * matrixLayout = new MatrixLayout(const_cast(const_cast(children)), numberOfRows+1, numberOfColumns+1, false); + m_pointedExpressionLayout->addBrother(this, matrixLayout); + setPointedExpressionLayout(matrixLayout->editableChild(0)); + setPosition(ExpressionLayoutCursor::Position::Right); +} + +void ExpressionLayoutCursor::addEmptyPowerLayout() { + VerticalOffsetLayout * offsetLayout = new VerticalOffsetLayout(new EmptyVisibleLayout(), VerticalOffsetLayout::Type::Superscript, false); + // If there is already a base + if (baseForNewPowerLayout()) { + m_pointedExpressionLayout->addBrother(this, offsetLayout); + setPointedExpressionLayout(offsetLayout->editableChild(0)); + setPosition(ExpressionLayoutCursor::Position::Left); + return; + } + // Else, add an empty base + EmptyVisibleLayout * child1 = new EmptyVisibleLayout(); + HorizontalLayout * newChild = new HorizontalLayout(child1, offsetLayout, false); + m_pointedExpressionLayout->addBrother(this, newChild); + setPointedExpressionLayout(child1); + setPosition(ExpressionLayoutCursor::Position::Right); +} + +void ExpressionLayoutCursor::addEmptySquareRootLayout() { + EmptyVisibleLayout * child1 = new EmptyVisibleLayout(); + NthRootLayout * newChild = new NthRootLayout(child1, false); + m_pointedExpressionLayout->addBrother(this, newChild); + setPointedExpressionLayout(child1); + setPosition(ExpressionLayoutCursor::Position::Right); +} + +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->addBrother(this, offsetLayout); + setPointedExpressionLayout(offsetLayout); + setPosition(ExpressionLayoutCursor::Position::Right); + return; + } + // Else, add an empty base + EmptyVisibleLayout * child1 = new EmptyVisibleLayout(); + HorizontalLayout * newChild = new HorizontalLayout(child1, offsetLayout, false); + m_pointedExpressionLayout->addBrother(this, newChild); + setPointedExpressionLayout(child1); + setPosition(ExpressionLayoutCursor::Position::Right); +} + +void ExpressionLayoutCursor::addXNTCharLayout() { + CharLayout * newChild = new CharLayout(m_pointedExpressionLayout->XNTChar()); + m_pointedExpressionLayout->addBrother(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] == '(') { + newChild = new ParenthesisLeftLayout(); + if (pointedChild == nullptr) { + pointedChild = newChild; + } + } else if (text[i] == ')') { + newChild = new ParenthesisRightLayout(); + } else if (text[i] == '[') { + newChild = new BracketLeftLayout(); + } else if (text[i] == ']') { + newChild = new BracketRightLayout(); + } else { + newChild = new CharLayout(text[i]); + } + m_pointedExpressionLayout->addBrother(this, newChild); + m_pointedExpressionLayout = newChild; + m_position = Position::Right; + } + if (pointedChild != nullptr) { + m_pointedExpressionLayout = pointedChild; + } +} + +void ExpressionLayoutCursor::performBackspace() { + m_pointedExpressionLayout->backspaceAtCursor(this); +} + +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. + int numberOfOpenParenthesis = 0; + if (m_position == Position::Right + && m_pointedExpressionLayout->isCollapsable(&numberOfOpenParenthesis, true)) + { + return true; + } + if (m_pointedExpressionLayout->parent() != nullptr) { + int indexInParent = m_pointedExpressionLayout->parent()->indexOfChild(m_pointedExpressionLayout); + if (m_position == Position::Left + && m_pointedExpressionLayout->parent()->isHorizontal() + && indexInParent > 0 + && !m_pointedExpressionLayout->editableParent()->editableChild(indexInParent-1)->isEmpty() + && !m_pointedExpressionLayout->editableParent()->editableChild(indexInParent-1)->isCollapsable(&numberOfOpenParenthesis, true)) + { + return true; + } + } + return false; +} + +} + 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..c14f90b57 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 @@ -90,9 +91,9 @@ void poincare_expression_yyerror(Poincare::Expression ** expressionOutput, char * one has the highest precedence. */ %nonassoc STO +%right UNARY_MINUS %left PLUS %left MINUS -%right UNARY_MINUS %left MULTIPLY %left DIVIDE %left IMPLICIT_MULTIPLY @@ -102,17 +103,28 @@ void poincare_expression_yyerror(Poincare::Expression ** expressionOutput, char %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 div; +%type mul; +%type min; +%type unmin; %type exp; %type number; %type symb; @@ -125,7 +137,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 unmin min mul div pow factor bang term number EMPTY %destructor { delete $$; } lstData /* MATRICES_ARE_DEFINED */ %destructor { delete $$; } mtxData @@ -137,64 +149,97 @@ 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); } + ; + +div : pow { $$ = $1; } + | div DIVIDE pow { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Division(terms, false); } + | div DIVIDE MINUS pow { Poincare::Expression * terms1[1] = {$4}; Poincare::Expression * terms[2] = {$1,new Poincare::Opposite(terms1, false)}; $$ = new Poincare::Division(terms, false); } + ; + +mul : div { $$ = $1; } + | mul MULTIPLY div { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Multiplication(terms, 2, false); } + | mul MULTIPLY MINUS div { Poincare::Expression * terms1[1] = {$4}; Poincare::Expression * terms[2] = {$1,new Poincare::Opposite(terms1, false)}; $$ = new Poincare::Multiplication(terms, 2, false); } + ; + +min : mul { $$ = $1; } + | mul MINUS min { Poincare::Expression * terms[2] = {$1,$3}; $$ = new Poincare::Subtraction(terms, false); } + | mul MINUS MINUS min { Poincare::Expression * terms1[1] = {$4}; Poincare::Expression * terms[2] = {$1,new Poincare::Opposite(terms1, false)}; $$ = new Poincare::Subtraction(terms, false); } + ; + +unmin : min { $$ = $1; } + | MINUS min %prec UNARY_MINUS { Poincare::Expression * terms[1] = {$2}; $$ = new Poincare::Opposite(terms, false); } + ; + +exp : unmin { $$ = $1; } + | exp PLUS unmin { 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 573ac81f2..932bb8de2 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 @@ -84,10 +84,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 5046e7f79..8d0580e71 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 { @@ -590,7 +590,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..eddb3ad55 100644 --- a/poincare/src/layout/absolute_value_layout.h +++ b/poincare/src/layout/absolute_value_layout.h @@ -2,21 +2,22 @@ #define POINCARE_ABSOLUTE_VALUE_LAYOUT_H #include "bracket_layout.h" +#include +#include namespace Poincare { class AbsoluteValueLayout : public BracketLayout { 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 BracketLayout::BracketLayout; + 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; } + 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..93867456a --- /dev/null +++ b/poincare/src/layout/binomial_coefficient_layout.cpp @@ -0,0 +1,139 @@ +#include "binomial_coefficient_layout.h" +#include "empty_visible_layout.h" +#include "grid_layout.h" +#include "horizontal_layout.h" +#include "parenthesis_left_layout.h" +#include "parenthesis_left_right_layout.h" +#include "parenthesis_right_layout.h" +#include +#include +extern "C" { +#include +#include +} + +namespace Poincare { + +ExpressionLayout * BinomialCoefficientLayout::clone() const { + return new BinomialCoefficientLayout(const_cast(this)->nLayout(), const_cast(this)->kLayout(), true); +} + +bool BinomialCoefficientLayout::moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + // Case: Left of the children. + // Go Left. + if (cursor->position() == ExpressionLayoutCursor::Position::Left + && (cursor->pointedExpressionLayout() == nLayout() + || cursor->pointedExpressionLayout() == kLayout())) + { + cursor->setPointedExpressionLayout(this); + return true; + } + + assert(cursor->pointedExpressionLayout() == this); + // Case: Right. + // Go to the kLayout. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + assert(kLayout() != nullptr); + cursor->setPointedExpressionLayout(kLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + + // Case: Left. + // Ask the parent. + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool BinomialCoefficientLayout::moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + // Case: Right of the children. + // Go Right. + if (cursor->position() == ExpressionLayoutCursor::Position::Right + && (cursor->pointedExpressionLayout() == nLayout() + || cursor->pointedExpressionLayout() == kLayout())) + { + cursor->setPointedExpressionLayout(this); + return true; + } + + assert(cursor->pointedExpressionLayout() == this); + // Case: Left. + // Go Left of the nLayout. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + assert(nLayout() != nullptr); + cursor->setPointedExpressionLayout(nLayout()); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. + // Ask the parent. + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +bool BinomialCoefficientLayout::moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // Case: kLayout. + // Move to nLayout. + if (previousLayout == kLayout()) { + return nLayout()->moveUpInside(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::moveUp(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +bool BinomialCoefficientLayout::moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // Case: nLayout. + // Move to kLayout. + if (previousLayout == nLayout()) { + return kLayout()->moveDownInside(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::moveUp(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +void BinomialCoefficientLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + // Render the parentheses. + ParenthesisLeftLayout * dummyLeftParenthesis = new ParenthesisLeftLayout(); + ParenthesisRightLayout * dummyRightParenthesis = new ParenthesisRightLayout(); + 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*ParenthesisLeftRightLayout::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) { + ParenthesisLeftLayout * dummyLeftParenthesis = new ParenthesisLeftLayout(); + ParenthesisRightLayout * dummyRightParenthesis = new ParenthesisRightLayout(); + 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)); +} + +ExpressionLayout * BinomialCoefficientLayout::nLayout() { + return editableChild(0); +} + +ExpressionLayout * BinomialCoefficientLayout::kLayout() { + return editableChild(1); +} + +} diff --git a/poincare/src/layout/binomial_coefficient_layout.h b/poincare/src/layout/binomial_coefficient_layout.h new file mode 100644 index 000000000..5ba22a114 --- /dev/null +++ b/poincare/src/layout/binomial_coefficient_layout.h @@ -0,0 +1,33 @@ +#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; + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout = nullptr, ExpressionLayout * previousLayout = nullptr, ExpressionLayout * previousPreviousLayout = nullptr) override; + bool moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout = nullptr, ExpressionLayout * previousLayout = nullptr, ExpressionLayout * previousPreviousLayout = nullptr) override; + 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 * kLayout(); + ExpressionLayout * nLayout(); +}; + +} + +#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..5544e628c 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,135 @@ extern "C" { namespace Poincare { -BracketLayout::BracketLayout(ExpressionLayout * operandLayout) : - ExpressionLayout(), - m_operandLayout(operandLayout) -{ - m_operandLayout->setParent(this); - m_baseline = m_operandLayout->baseline(); +ExpressionLayout * BracketLayout::clone() const { + BracketLayout * layout = new BracketLayout(const_cast(this)->operandLayout(), true); + return layout; } -BracketLayout::~BracketLayout() { - delete m_operandLayout; +void BracketLayout::backspaceAtCursor(ExpressionLayoutCursor * cursor) { + if (cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + // Case: Right. + // Delete the layout, keep the operand. + replaceWithAndMoveCursor(operandLayout(), true, cursor); + return; + } + ExpressionLayout::backspaceAtCursor(cursor); +} + +bool BracketLayout::moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + // Case: Left of the operand. + // Go Left of the brackets. + if (operandLayout() + && cursor->pointedExpressionLayout() == operandLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Left) + { + cursor->setPointedExpressionLayout(this); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Right of the brackets. + // Go Right of the operand. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + assert(operandLayout() != nullptr); + cursor->setPointedExpressionLayout(operandLayout()); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + // Case: Left of the brackets. + // Ask the parent. + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool BracketLayout::moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + // Case: Right of the operand. + // Go Right of the brackets. + if (operandLayout() + && cursor->pointedExpressionLayout() == operandLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + cursor->setPointedExpressionLayout(this); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Left of the brackets. + // Go Left of the operand. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + assert(operandLayout() != nullptr); + cursor->setPointedExpressionLayout(operandLayout()); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + // Case: Right of the brackets. + // Ask the parent. + cursor->setPointedExpressionLayout(this); + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +int BracketLayout::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; +} + +ExpressionLayout * BracketLayout::operandLayout() { + return editableChild(0); } 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); + const KDCoordinate k_widthMargin = widthMargin(); + const KDCoordinate k_externWidthMargin = externWidthMargin(); + KDSize operandSize = operandLayout()->size(); + ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y(), k_lineThickness, operandLayout()->size().height()), expressionColor); + ctx->fillRect(KDRect(p.x()+k_externWidthMargin+operandSize.width()+2*k_widthMargin+k_lineThickness, p.y(), k_lineThickness, 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); + ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y(), k_bracketWidth, k_lineThickness), expressionColor); + ctx->fillRect(KDRect(p.x()+k_externWidthMargin+2*k_lineThickness+operandSize.width()+2*k_widthMargin-k_bracketWidth, p.y(), k_bracketWidth, k_lineThickness), expressionColor); } 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); + ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y()+operandSize.height()-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()+operandSize.height()-k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); } } KDSize BracketLayout::computeSize() { - KDSize operandSize = m_operandLayout->size(); - return KDSize(operandSize.width() + 2*widthMargin() + 2*k_lineThickness, operandSize.height()); + 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()); } -ExpressionLayout * BracketLayout::child(uint16_t index) { - if (index == 0) { - return m_operandLayout; - } - return nullptr; +void BracketLayout::computeBaseline() { + m_baseline = operandLayout()->baseline(); + m_baselined = true; } KDPoint BracketLayout::positionOfChild(ExpressionLayout * child) { - return KDPoint(widthMargin()+k_lineThickness, 0); + const KDCoordinate k_widthMargin = widthMargin(); + const KDCoordinate k_externWidthMargin = externWidthMargin(); + return KDPoint(k_widthMargin+k_externWidthMargin+k_lineThickness, 0); } } diff --git a/poincare/src/layout/bracket_layout.h b/poincare/src/layout/bracket_layout.h index 4fca30e92..6ddb363b7 100644 --- a/poincare/src/layout/bracket_layout.h +++ b/poincare/src/layout/bracket_layout.h @@ -1,31 +1,32 @@ #ifndef POINCARE_BRACKET_LAYOUT_H #define POINCARE_BRACKET_LAYOUT_H -#include -#include +#include +#include namespace Poincare { -class BracketLayout : public ExpressionLayout { +class BracketLayout : public StaticLayoutHierarchy<1> { 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; + using StaticLayoutHierarchy::StaticLayoutHierarchy; + ExpressionLayout * clone() const override; + void backspaceAtCursor(ExpressionLayoutCursor * cursor) override; + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; protected: + ExpressionLayout * operandLayout(); + KDCoordinate externWidthMargin() const { return 2; } 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; KDPoint positionOfChild(ExpressionLayout * child) override; private: constexpr static KDCoordinate k_bracketWidth = 5; constexpr static KDCoordinate k_lineThickness = 1; - ExpressionLayout * m_operandLayout; }; } diff --git a/poincare/src/layout/bracket_left_layout.cpp b/poincare/src/layout/bracket_left_layout.cpp new file mode 100644 index 000000000..324b7ae15 --- /dev/null +++ b/poincare/src/layout/bracket_left_layout.cpp @@ -0,0 +1,64 @@ +#include "bracket_left_layout.h" +#include +extern "C" { +#include +} + +namespace Poincare { + +ExpressionLayout * BracketLeftLayout::clone() const { + BracketLeftLayout * layout = new BracketLeftLayout(); + return layout; +} + +void BracketLeftLayout::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); +} + +void BracketLeftLayout::computeOperandHeight() { + assert(m_parent != nullptr); + m_operandHeight = Metric::MinimalBracketAndParenthesisHeight; + int currentNumberOfOpenBrackets = 1; + int numberOfBrothers = m_parent->numberOfChildren(); + for (int i = m_parent->indexOfChild(this) + 1; i < numberOfBrothers; i++) { + ExpressionLayout * brother = m_parent->editableChild(i); + if (brother->isRightBracket()) { + currentNumberOfOpenBrackets--; + if (currentNumberOfOpenBrackets == 0) { + return; + } + } else if (brother->isLeftBracket()) { + currentNumberOfOpenBrackets++; + } + KDCoordinate brotherHeight = brother->size().height(); + if (brotherHeight > m_operandHeight) { + m_operandHeight = brotherHeight; + } + } +} + +void BracketLeftLayout::computeBaseline() { + assert(m_parent != nullptr); + m_baseline = operandHeight()/2; + int currentNumberOfOpenBrackets = 1; + int numberOfBrothers = m_parent->numberOfChildren(); + for (int i = m_parent->indexOfChild(this) + 1; i < numberOfBrothers; i++) { + ExpressionLayout * brother = m_parent->editableChild(i); + if (brother->isRightBracket()) { + currentNumberOfOpenBrackets--; + if (currentNumberOfOpenBrackets == 0) { + break; + } + } else if (brother->isLeftBracket()) { + currentNumberOfOpenBrackets++; + } + if (brother->baseline() > m_baseline) { + m_baseline = brother->baseline(); + } + } + m_baselined = true; +} + +} diff --git a/poincare/src/layout/bracket_left_layout.h b/poincare/src/layout/bracket_left_layout.h new file mode 100644 index 000000000..9db160521 --- /dev/null +++ b/poincare/src/layout/bracket_left_layout.h @@ -0,0 +1,26 @@ +#ifndef POINCARE_BRACKET_LEFT_LAYOUT_H +#define POINCARE_BRACKET_LEFT_LAYOUT_H + +#include +#include + +namespace Poincare { + +class BracketLeftLayout : public BracketLeftRightLayout { + friend class MatrixLayout; +public: + using BracketLeftRightLayout::BracketLeftRightLayout; + 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; + void computeOperandHeight() override; + void computeBaseline() override; +}; + +} + +#endif diff --git a/poincare/src/layout/bracket_left_right_layout.cpp b/poincare/src/layout/bracket_left_right_layout.cpp new file mode 100644 index 000000000..540a4ce89 --- /dev/null +++ b/poincare/src/layout/bracket_left_right_layout.cpp @@ -0,0 +1,73 @@ +#include "bracket_left_right_layout.h" +#include +extern "C" { +#include +#include +} + +namespace Poincare { + +BracketLeftRightLayout::BracketLeftRightLayout() : + StaticLayoutHierarchy<0>(), + m_operandHeightComputed(false) +{ +} + +void BracketLeftRightLayout::invalidAllSizesPositionsAndBaselines() { + m_operandHeightComputed = false; + ExpressionLayout::invalidAllSizesPositionsAndBaselines(); +} + +bool BracketLeftRightLayout::moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + assert(cursor->pointedExpressionLayout() == this); + // Case: Right. + // Go Left. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + // Case: Left. + // Ask the parent. + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool BracketLeftRightLayout::moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + assert(cursor->pointedExpressionLayout() == this); + // Case: Left. + // Go Right. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. + // Ask the parent. + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + + +KDSize BracketLeftRightLayout::computeSize() { + return KDSize(k_externWidthMargin + k_lineThickness + k_widthMargin, operandHeight() + k_lineThickness); +} + +KDCoordinate BracketLeftRightLayout::operandHeight() { + if (!m_operandHeightComputed) { + computeOperandHeight(); + m_operandHeightComputed = true; + } + return m_operandHeight; +} + +KDPoint BracketLeftRightLayout::positionOfChild(ExpressionLayout * child) { + assert(false); + return KDPointZero; +} + +} diff --git a/poincare/src/layout/bracket_left_right_layout.h b/poincare/src/layout/bracket_left_right_layout.h new file mode 100644 index 000000000..9791e18c4 --- /dev/null +++ b/poincare/src/layout/bracket_left_right_layout.h @@ -0,0 +1,28 @@ +#ifndef POINCARE_BRACKET_LEFT_RIGHT_LAYOUT_H +#define POINCARE_BRACKET_LEFT_RIGHT_LAYOUT_H + +#include + +namespace Poincare { + +class BracketLeftRightLayout : public StaticLayoutHierarchy<0> { +public: + BracketLeftRightLayout(); + void invalidAllSizesPositionsAndBaselines() override; + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; +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; + KDCoordinate operandHeight(); + virtual void computeOperandHeight() = 0; + KDPoint positionOfChild(ExpressionLayout * child) override; + bool m_operandHeightComputed; + uint16_t m_operandHeight; +}; +} + +#endif diff --git a/poincare/src/layout/bracket_right_layout.cpp b/poincare/src/layout/bracket_right_layout.cpp new file mode 100644 index 000000000..a1f5ce663 --- /dev/null +++ b/poincare/src/layout/bracket_right_layout.cpp @@ -0,0 +1,62 @@ +#include "bracket_right_layout.h" +#include +extern "C" { +#include +} + +namespace Poincare { + +ExpressionLayout * BracketRightLayout::clone() const { + BracketRightLayout * layout = new BracketRightLayout(); + return layout; +} + +void BracketRightLayout::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); +} + +void BracketRightLayout::computeOperandHeight() { + assert(m_parent != nullptr); + m_operandHeight = Metric::MinimalBracketAndParenthesisHeight; + int currentNumberOfOpenBrackets = 1; + for (int i = m_parent->indexOfChild(this) - 1; i >= 0; i--) { + ExpressionLayout * brother = m_parent->editableChild(i); + if (brother->isLeftBracket()) { + currentNumberOfOpenBrackets--; + if (currentNumberOfOpenBrackets == 0) { + return; + } + } else if (brother->isRightBracket()) { + currentNumberOfOpenBrackets++; + } + KDCoordinate brotherHeight = brother->size().height(); + if (brotherHeight > m_operandHeight) { + m_operandHeight = brotherHeight; + } + } +} + +void BracketRightLayout::computeBaseline() { + assert(m_parent != nullptr); + m_baseline = operandHeight()/2; + int currentNumberOfOpenBrackets = 1; + for (int i = m_parent->indexOfChild(this) - 1; i >= 0; i--) { + ExpressionLayout * brother = m_parent->editableChild(i); + if (brother->isLeftBracket()) { + currentNumberOfOpenBrackets--; + if (currentNumberOfOpenBrackets == 0) { + break; + } + } else if (brother->isRightBracket()) { + currentNumberOfOpenBrackets++; + } + if (brother->baseline() > m_baseline) { + m_baseline = brother->baseline(); + } + } + m_baselined = true; +} + +} diff --git a/poincare/src/layout/bracket_right_layout.h b/poincare/src/layout/bracket_right_layout.h new file mode 100644 index 000000000..1b303c0d2 --- /dev/null +++ b/poincare/src/layout/bracket_right_layout.h @@ -0,0 +1,26 @@ +#ifndef POINCARE_BRACKET_RIGHT_LAYOUT_H +#define POINCARE_BRACKET_RIGHT_LAYOUT_H + +#include +#include + +namespace Poincare { + +class BracketRightLayout : public BracketLeftRightLayout { + friend class MatrixLayout; +public: + using BracketLeftRightLayout::BracketLeftRightLayout; + 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; + void computeOperandHeight() override; + void computeBaseline() override; +}; + +} + +#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..1d60673d4 100644 --- a/poincare/src/layout/ceiling_layout.h +++ b/poincare/src/layout/ceiling_layout.h @@ -2,17 +2,17 @@ #define POINCARE_CEILING_LAYOUT_H #include "bracket_layout.h" +#include namespace Poincare { class CeilingLayout : public BracketLayout { 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 BracketLayout::BracketLayout; + 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..2a86c61f2 --- /dev/null +++ b/poincare/src/layout/char_layout.cpp @@ -0,0 +1,87 @@ +#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; +} + +bool CharLayout::moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + assert(cursor->pointedExpressionLayout() == this); + // Case: Right. + // Go Left. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + // Case: Left. + // Ask the parent. + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool CharLayout::moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + assert(cursor->pointedExpressionLayout() == this); + // Case: Left. + // Go Right. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + // Case: Right. + // Ask the parent. + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +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); +} + +KDPoint CharLayout::positionOfChild(ExpressionLayout * child) { + assert(false); + return KDPointZero; +} + +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..90b7de8bb --- /dev/null +++ b/poincare/src/layout/char_layout.h @@ -0,0 +1,34 @@ +#ifndef POINCARE_CHAR_LAYOUT_H +#define POINCARE_CHAR_LAYOUT_H + +#include +#include +#include + +namespace Poincare { + +class CharLayout : public StaticLayoutHierarchy<0> { +public: + CharLayout(char c, KDText::FontSize fontSize = KDText::FontSize::Large); + ExpressionLayout * clone() const override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writeOneCharInBuffer(buffer, bufferSize, m_char); + } + + char character() { return m_char; } + KDText::FontSize fontSize() const { return m_fontSize; } + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + 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; + 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..33058438e 100644 --- a/poincare/src/layout/condensed_sum_layout.cpp +++ b/poincare/src/layout/condensed_sum_layout.cpp @@ -1,30 +1,127 @@ #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); +ExpressionLayout * CondensedSumLayout::clone() const { + CondensedSumLayout * layout = new CondensedSumLayout(const_cast(this)->baseLayout(), const_cast(this)->subscriptLayout(), const_cast(this)->superscriptLayout(), true); + return layout; } -CondensedSumLayout::~CondensedSumLayout() { - delete m_baseLayout; - delete m_subscriptLayout; - if (m_superscriptLayout) { - delete m_superscriptLayout; +bool CondensedSumLayout::moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + // Case: Left of the bounds. + // Go Left of the sum. + if (((subscriptLayout() && cursor->pointedExpressionLayout() == subscriptLayout()) + || (superscriptLayout() && cursor->pointedExpressionLayout() == superscriptLayout())) + && cursor->position() == ExpressionLayoutCursor::Position::Left) + { + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; } + // Case: Left of the base. + // Go Right of the lower bound. + if (baseLayout() + && cursor->pointedExpressionLayout() == baseLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Left) + { + cursor->setPointedExpressionLayout(subscriptLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Right. + // Go to the base and move Left. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + assert(baseLayout()); + cursor->setPointedExpressionLayout(baseLayout()); + return baseLayout()->moveLeft(cursor, shouldRecomputeLayout); + } + // Case: Left. + // Ask the parent. + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool CondensedSumLayout::moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + // Case: Right of the bounds. + // Go Left of the operand. + if (((subscriptLayout() && cursor->pointedExpressionLayout() == subscriptLayout()) + || (superscriptLayout() && cursor->pointedExpressionLayout() == superscriptLayout())) + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + assert(baseLayout() != nullptr); + cursor->setPointedExpressionLayout(baseLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + // Case: Right of the base. + // Ask the parent. + if (baseLayout() + && cursor->pointedExpressionLayout() == baseLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Left. + // Go to the upper bound. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + assert(superscriptLayout()); + cursor->setPointedExpressionLayout(superscriptLayout()); + return true; + } + // Case: Right. + // Ask the parent. + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +bool CondensedSumLayout::moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // If the cursor is inside the subscript layout, move it to the superscript. + if (subscriptLayout() && previousLayout == subscriptLayout()) { + assert(superscriptLayout() != nullptr); + return superscriptLayout()->moveUpInside(cursor, shouldRecomputeLayout); + } + // If the cursor is Left of the base layout, move it to the superscript. + if (baseLayout() + && previousLayout == baseLayout() + && cursor->positionIsEquivalentTo(baseLayout(), ExpressionLayoutCursor::Position::Left)) + { + assert(superscriptLayout() != nullptr); + return superscriptLayout()->moveUpInside(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::moveUp(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +bool CondensedSumLayout::moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // If the cursor is inside the superscript layout, move it to the subscript. + if (superscriptLayout() && previousLayout == superscriptLayout()) { + assert(subscriptLayout() != nullptr); + return subscriptLayout()->moveUpInside(cursor, shouldRecomputeLayout); + } + // If the cursor is Left of the base layout, move it to the subscript. + if (baseLayout() + && previousLayout == baseLayout() + && cursor->positionIsEquivalentTo(baseLayout(), ExpressionLayoutCursor::Position::Left)) + { + assert(subscriptLayout() != nullptr); + return subscriptLayout()->moveUpInside(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::moveDown(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); } void CondensedSumLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { @@ -32,42 +129,47 @@ void CondensedSumLayout::render(KDContext * ctx, KDPoint p, KDColor expressionCo } 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); } +ExpressionLayout * CondensedSumLayout::baseLayout() { + return editableChild(0); +} + +ExpressionLayout * CondensedSumLayout::subscriptLayout() { + return editableChild(1); +} + +ExpressionLayout * CondensedSumLayout::superscriptLayout() { + return editableChild(2); +} + } diff --git a/poincare/src/layout/condensed_sum_layout.h b/poincare/src/layout/condensed_sum_layout.h index dc52f4e5f..aa76d39d9 100644 --- a/poincare/src/layout/condensed_sum_layout.h +++ b/poincare/src/layout/condensed_sum_layout.h @@ -1,28 +1,31 @@ #ifndef POINCARE_CONDENSED_SUM_LAYOUT_H #define POINCARE_CONDENSED_SUM_LAYOUT_H -#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; + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + bool moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override { + return LayoutEngine::writePrefixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, "sum"); + } 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: - ExpressionLayout * m_baseLayout; - ExpressionLayout * m_subscriptLayout; - ExpressionLayout * m_superscriptLayout; + ExpressionLayout * baseLayout(); + ExpressionLayout * subscriptLayout(); + ExpressionLayout * superscriptLayout(); }; } diff --git a/poincare/src/layout/conjugate_layout.cpp b/poincare/src/layout/conjugate_layout.cpp index ca0aa4cef..e9c3fda59 100644 --- a/poincare/src/layout/conjugate_layout.cpp +++ b/poincare/src/layout/conjugate_layout.cpp @@ -1,4 +1,7 @@ #include "conjugate_layout.h" +#include "empty_visible_layout.h" +#include +#include extern "C" { #include #include @@ -6,36 +9,116 @@ 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::backspaceAtCursor(ExpressionLayoutCursor * cursor) { + if (cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + // Case: Right. + // Move to the operand and delete. + cursor->setPointedExpressionLayout(operandLayout()); + cursor->performBackspace(); + return; + } + ExpressionLayout::backspaceAtCursor(cursor); +} + +bool ConjugateLayout::moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + // Case: Left of the operand. + // Move Left. + if (operandLayout() + && cursor->pointedExpressionLayout() == operandLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Left) + { + cursor->setPointedExpressionLayout(this); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Right. + // Go to the operand. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + assert(operandLayout() != nullptr); + cursor->setPointedExpressionLayout(operandLayout()); + return true; + } + // Case: Left. + // Ask the parent. + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool ConjugateLayout::moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + // Case: Right of the operand. + // Move Right. + if (operandLayout() + && cursor->pointedExpressionLayout() == operandLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + cursor->setPointedExpressionLayout(this); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Left. + // Go to the operand. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + assert(operandLayout() != nullptr); + cursor->setPointedExpressionLayout(operandLayout()); + return true; + } + // Case: Right. + // Ask the parent. + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +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() == child(index)) || (cursor->pointedExpressionLayout()->hasAncestor(child(index)))); + replaceChildAndMoveCursor(child(index), new EmptyVisibleLayout(), deleteAfterRemoval, cursor); } 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); +} + +ExpressionLayout * ConjugateLayout::operandLayout() { + return editableChild(0); } } diff --git a/poincare/src/layout/conjugate_layout.h b/poincare/src/layout/conjugate_layout.h index 29ebdfaa3..52b7f321e 100644 --- a/poincare/src/layout/conjugate_layout.h +++ b/poincare/src/layout/conjugate_layout.h @@ -1,28 +1,32 @@ #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; + void backspaceAtCursor(ExpressionLayoutCursor * cursor) override; + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + void replaceChildAndMoveCursor(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) override; + void removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor) override; + 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(); }; } diff --git a/poincare/src/layout/dynamic_layout_hierarchy.cpp b/poincare/src/layout/dynamic_layout_hierarchy.cpp new file mode 100644 index 000000000..3ed6dd802 --- /dev/null +++ b/poincare/src/layout/dynamic_layout_hierarchy.cpp @@ -0,0 +1,145 @@ +#include +#include "empty_visible_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; iclone(); + } else { + m_children[i] = children[i]; + } + const_cast(m_children[i])->setParent(this); + } +} + +DynamicLayoutHierarchy::~DynamicLayoutHierarchy() { + if (m_children != nullptr) { + for (int i = 0; i < m_numberOfChildren; i++) { + if (m_children[i] != nullptr) { + delete m_children[i]; + } + } + } + delete[] m_children; +} + +void DynamicLayoutHierarchy::mergeChildrenAtIndex(DynamicLayoutHierarchy * eL, int index, bool removeEmptyChildren) { + int indexOfEL = indexOfChild(eL); + if (indexOfEL >= 0) { + removeChildAtIndex(indexOfEL, false); + } + addChildrenAtIndex(eL->children(), eL->numberOfChildren(), index, removeEmptyChildren); + eL->detachChildren(); + 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 <= m_numberOfChildren); + for (int i=0; iisEmpty() + || (i < numberOfOperands-1 && operands[i+1]->mustHaveLeftBrother())) + { + const_cast(operands[i])->setParent(this); + newOperands[currentIndex++] = operands[i]; + } + } + for (int i=indexForInsertion; i= 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; + m_sized = false; + m_positioned = false; + m_baselined = false; + return true; +} + +void DynamicLayoutHierarchy::removeChildAtIndex(int index, bool deleteAfterRemoval) { + 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() == child(index)) || (cursor->pointedExpressionLayout()->hasAncestor(child(index)))); + 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)); +} + +} diff --git a/poincare/src/layout/empty_visible_layout.cpp b/poincare/src/layout/empty_visible_layout.cpp new file mode 100644 index 000000000..770ef69a5 --- /dev/null +++ b/poincare/src/layout/empty_visible_layout.cpp @@ -0,0 +1,107 @@ +#include "empty_visible_layout.h" +#include "matrix_layout.h" +#include +#include +#include + +namespace Poincare { + +EmptyVisibleLayout::EmptyVisibleLayout(Color color) : + StaticLayoutHierarchy(), + m_color(color) +{ +} + +ExpressionLayout * EmptyVisibleLayout::clone() const { + EmptyVisibleLayout * layout = new EmptyVisibleLayout(); + return layout; +} + +void EmptyVisibleLayout::backspaceAtCursor(ExpressionLayoutCursor * cursor) { + assert(cursor->pointedExpressionLayout() == this); + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + if (m_parent) { + return m_parent->backspaceAtCursor(cursor); + } +} + +bool EmptyVisibleLayout::moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + assert(cursor->pointedExpressionLayout() == this); + // Case: Right. + // Go Left. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + // Case: Left. + // Ask the parent. + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool EmptyVisibleLayout::moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + assert(cursor->pointedExpressionLayout() == this); + // Case: Left. + // Go Right. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + // Case: Right. + // Ask the parent. + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +int EmptyVisibleLayout::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { + if (bufferSize == 0) { + return -1; + } + buffer[0] = 0; + return 0; +} + +void EmptyVisibleLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + KDColor fillColor = m_color == Color::Yellow ? Palette::YellowDark : Palette::GreyBright; + ctx->fillRect(KDRect(p.x()+k_marginWidth, p.y()+k_marginHeight, k_width, k_height), fillColor); + ctx->fillRect(KDRect(p.x()+k_marginWidth, p.y()+k_marginHeight, k_width, k_height), fillColor); +} + +KDSize EmptyVisibleLayout::computeSize() { + return KDSize(k_width + 2*k_marginWidth, k_height + 2*k_marginHeight); +} + +void EmptyVisibleLayout::computeBaseline() { + m_baseline = k_marginHeight + k_height/2; + m_baselined = true; +} + +void EmptyVisibleLayout::privateAddBrother(ExpressionLayoutCursor * cursor, ExpressionLayout * brother, bool moveCursor) { + Color currentColor = m_color; + int indexInParent = m_parent->indexOfChild(this); + ExpressionLayout * parent = m_parent; + if (brother->mustHaveLeftBrother()) { + m_color = Color::Yellow; + ExpressionLayout::privateAddBrother(cursor, brother, moveCursor); + } else { + if (moveCursor) { + replaceWithAndMoveCursor(brother, true, cursor); + } else { + replaceWith(brother, true); + } + } + if (currentColor == Color::Grey) { + // The parent is a MatrixLayout. + static_cast(parent)->newRowOrColumnAtIndex(indexInParent); + } +} + +} diff --git a/poincare/src/layout/empty_visible_layout.h b/poincare/src/layout/empty_visible_layout.h new file mode 100644 index 000000000..c9d0aa123 --- /dev/null +++ b/poincare/src/layout/empty_visible_layout.h @@ -0,0 +1,44 @@ +#ifndef POINCARE_EMPTY_VISIBLE_LAYOUT_H +#define POINCARE_EMPTY_VISIBLE_LAYOUT_H + +#include +#include + +namespace Poincare { + +class EmptyVisibleLayout : public StaticLayoutHierarchy<0> { +public: + enum class Color { + Yellow, + Grey + }; + EmptyVisibleLayout(Color color = Color::Yellow); + ExpressionLayout * clone() const override; + void backspaceAtCursor(ExpressionLayoutCursor * cursor) override; + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; + bool isEmpty() const override { return true; } + Color color() const { return m_color; } + void setColor(Color color) { m_color = color; } +protected: + virtual void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + virtual KDSize computeSize() override; + void computeBaseline() override; + KDPoint positionOfChild(ExpressionLayout * child) override { + assert(false); + return KDPointZero; + } + void privateAddBrother(ExpressionLayoutCursor * cursor, ExpressionLayout * brother, bool moveCursor) override; +private: + constexpr static KDCoordinate k_width = 7; + constexpr static KDCoordinate k_height = 13; + constexpr static KDCoordinate k_marginWidth = 1; + constexpr static KDCoordinate k_marginHeight = 2; + constexpr static KDCoordinate k_lineThickness = 1; + Color m_color; +}; + +} + +#endif diff --git a/poincare/src/layout/expression_layout.cpp b/poincare/src/layout/expression_layout.cpp index 560eed6f8..8d9feb31c 100644 --- a/poincare/src/layout/expression_layout.cpp +++ b/poincare/src/layout/expression_layout.cpp @@ -1,19 +1,30 @@ +#include +#include "empty_visible_layout.h" +#include "horizontal_layout.h" +#include "matrix_layout.h" +#include #include #include -#include "string_layout.h" +#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 +36,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 +56,355 @@ 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; +} + +int ExpressionLayout::indexOfChild(const ExpressionLayout * child) const { + if (child == nullptr) { + return -1; + } + for (int i = 0; i < numberOfChildren(); i++) { + if (children()[i] == child) { + return i; + } + } + return -1; +} + +bool ExpressionLayout::hasAncestor(const ExpressionLayout * e) const { + assert(m_parent != this); + if (m_parent == e) { + return true; + } + if (m_parent == nullptr) { + return false; + } + return m_parent->hasAncestor(e); +} + +void ExpressionLayout::addBrother(ExpressionLayoutCursor * cursor, ExpressionLayout * brother) { + privateAddBrother(cursor, brother, false); +} + +void ExpressionLayout::addBrotherAndMoveCursor(ExpressionLayoutCursor * cursor, ExpressionLayout * brother) { + privateAddBrother(cursor, brother, 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; + } + } + m_sized = false; + m_positioned = false; + m_baselined = false; +} + +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(indexOfChild(e) >= 0); + 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 EmptyVisibleLayout(), deleteAfterRemoval); +} + +void ExpressionLayout::removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor) { + assert(index >= 0 && index < numberOfChildren()); + assert((cursor->pointedExpressionLayout() == child(index)) || (cursor->pointedExpressionLayout()->hasAncestor(child(index)))); + 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()->addBrother(cursor, newChild); + return true; +} + +void ExpressionLayout::backspaceAtCursor(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; + cursor->moveLeft(&shouldRecomputeLayout); + 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->backspaceAtCursor(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 'x'; + } + return m_parent->XNTChar(); +} + +bool ExpressionLayout::moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + if (m_parent) { + return m_parent->moveUp(cursor, shouldRecomputeLayout, this, previousLayout); + } + return false; +} + +bool ExpressionLayout::moveUpInside(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + return moveInside(VerticalDirection::Up, cursor, shouldRecomputeLayout); +} + +bool ExpressionLayout::moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + if (m_parent) { + return m_parent->moveDown(cursor, shouldRecomputeLayout, this, previousLayout); + } + return false; +} + +bool ExpressionLayout::moveDownInside(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + return moveInside(VerticalDirection::Down, cursor, shouldRecomputeLayout); +} + +bool ExpressionLayout::addGreySquaresToAllMatrixAncestors() { + bool addedSquares = false; + ExpressionLayout * currentAncestor = m_parent; + while (currentAncestor != nullptr) { + if (currentAncestor->isMatrix()) { + static_cast(currentAncestor)->addGreySquares(); + addedSquares = true; + } + currentAncestor = currentAncestor->editableParent(); + } + return addedSquares; +} + +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 mustHaveLeftBrother 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) && !mustHaveLeftBrother(); +} + +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; + m_sized = false; + m_positioned = false; + m_baselined = false; +} + +bool ExpressionLayout::moveInside(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; + + moveCursorInsideAtDirection(direction, cursor, shouldRecomputeLayout, childResultPtr, &resultPosition, &resultScore); + + // If there is a valid result + if (*childResultPtr == nullptr) { + return false; + } + cursor->setPointedExpressionLayout(*childResultPtr); + cursor->setPosition(resultPosition); + *shouldRecomputeLayout = (*childResultPtr)->addGreySquaresToAllMatrixAncestors(); + return true; +} + +void ExpressionLayout::moveCursorInsideAtDirection ( + 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 = cursor->middleLeftPointOfCursor(this, ExpressionLayoutCursor::Position::Left).squareDistanceTo(cursorMiddleLeft); + if (currentDistance <= *resultScore ){ + *childResult = this; + *castedResultPosition = ExpressionLayoutCursor::Position::Left; + *resultScore = currentDistance; + } + + // Check the distance to a Right cursor. + currentDistance = cursor->middleLeftPointOfCursor(this, ExpressionLayoutCursor::Position::Right).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)->moveCursorInsideAtDirection(direction, cursor, shouldRecomputeLayout, childResult, castedResultPosition, resultScore); + } + } +} + +void ExpressionLayout::privateAddBrother(ExpressionLayoutCursor * cursor, ExpressionLayout * brother, bool moveCursor) { + // The layout must have a parent, because HorizontalLayout overrides + // privateAddBrother and only an HorizontalLayout can be the root layout. + assert(m_parent); + if (m_parent->isHorizontal()) { + int brotherIndex = cursor->position() == ExpressionLayoutCursor::Position::Left ? m_parent->indexOfChild(this) : m_parent->indexOfChild(this) + 1; + if (moveCursor) { + if (brotherIndex < m_parent->numberOfChildren()) { + cursor->setPointedExpressionLayout(m_parent->editableChild(brotherIndex)); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + } else { + cursor->setPointedExpressionLayout(m_parent); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + } + } + static_cast(m_parent)->addOrMergeChildAtIndex(brother, brotherIndex, true); + return; + } + ExpressionLayout * juxtapositionLayout = nullptr; + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + juxtapositionLayout = replaceWithJuxtapositionOf(brother, this, false); + } else { + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + juxtapositionLayout = replaceWithJuxtapositionOf(this, brother, false); + } + if (moveCursor) { + cursor->setPointedExpressionLayout(juxtapositionLayout); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + } +} + +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; } } 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..8d8d65227 100644 --- a/poincare/src/layout/floor_layout.h +++ b/poincare/src/layout/floor_layout.h @@ -2,17 +2,17 @@ #define POINCARE_FLOOR_LAYOUT_H #include "bracket_layout.h" +#include namespace Poincare { class FloorLayout : public BracketLayout { 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 BracketLayout::BracketLayout; + 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..7702cad28 100644 --- a/poincare/src/layout/fraction_layout.cpp +++ b/poincare/src/layout/fraction_layout.cpp @@ -1,59 +1,232 @@ +#include "fraction_layout.h" +#include "empty_visible_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::backspaceAtCursor(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 EmptyVisibleLayout(), 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. + + // Prepare the cursor position. + ExpressionLayout * nextPointedLayout = numeratorLayout(); + ExpressionLayoutCursor::Position nextPosition = ExpressionLayoutCursor::Position::Right; + if (numeratorLayout()->isEmpty()) { + int indexInParent = m_parent->indexOfChild(this); + if (indexInParent > 0) { + nextPointedLayout = m_parent->editableChild(indexInParent - 1); + } else { + nextPointedLayout = m_parent; + nextPosition = ExpressionLayoutCursor::Position::Left; + } + } else if (numeratorLayout()->isHorizontal()) { + nextPointedLayout = numeratorLayout()->editableChild(numeratorLayout()->numberOfChildren() - 1); + } + + // Juxtapose. + ExpressionLayout * numerator = numeratorLayout(); + ExpressionLayout * denominator = denominatorLayout(); + detachChild(numerator); + detachChild(denominator); + HorizontalLayout * newLayout = new HorizontalLayout(); + newLayout->addOrMergeChildAtIndex(denominator, 0, true); + newLayout->addOrMergeChildAtIndex(numerator, 0, true); + // Add the denominator before the numerator to have the right order. + 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::backspaceAtCursor(cursor); +} + +bool FractionLayout::moveLeft(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) + { + cursor->setPointedExpressionLayout(this); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Right. + // Go to the denominator. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + assert(denominatorLayout() != nullptr); + cursor->setPointedExpressionLayout(denominatorLayout()); + return true; + } + // Case: Left. + // Ask the parent. + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool FractionLayout::moveRight(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) + { + cursor->setPointedExpressionLayout(this); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Left. + // Go to the numerator. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + assert(numeratorLayout() != nullptr); + cursor->setPointedExpressionLayout(numeratorLayout()); + return true; + } + // Case: Right. + // Ask the parent. + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +bool FractionLayout::moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // If the cursor is inside denominator, move it to the numerator. + if (denominatorLayout() && previousLayout == denominatorLayout()) { + assert(numeratorLayout() != nullptr); + return numeratorLayout()->moveUpInside(cursor, shouldRecomputeLayout); + } + // If the cursor is Left or Right, move it to the numerator. + if (cursor->pointedExpressionLayout() == this){ + assert(numeratorLayout() != nullptr); + return numeratorLayout()->moveUpInside(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::moveUp(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +bool FractionLayout::moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // If the cursor is inside numerator, move it to the denominator. + if (numeratorLayout() && previousLayout == numeratorLayout()) { + assert(denominatorLayout() != nullptr); + return denominatorLayout()->moveDownInside(cursor, shouldRecomputeLayout); + } + // If the cursor is Left or Right, move it to the denominator. + if (cursor->pointedExpressionLayout() == this){ + assert(denominatorLayout() != nullptr); + return denominatorLayout()->moveDownInside(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::moveDown(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +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;} + } + + // Write the content of the fraction + numberOfChar += LayoutEngine::writeInfixExpressionLayoutTextInBuffer(this, buffer+numberOfChar, bufferSize-numberOfChar, numberOfSignificantDigits, "/"); + 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; } 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, 1), 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 } return KDPoint(x, y); } +ExpressionLayout * FractionLayout::numeratorLayout() { + return editableChild(0); +} + +ExpressionLayout * FractionLayout::denominatorLayout() { + return editableChild(1); +} + } diff --git a/poincare/src/layout/fraction_layout.h b/poincare/src/layout/fraction_layout.h index 4a01a60ae..c71dd5ef9 100644 --- a/poincare/src/layout/fraction_layout.h +++ b/poincare/src/layout/fraction_layout.h @@ -1,31 +1,37 @@ #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; + void backspaceAtCursor(ExpressionLayoutCursor * cursor) override; + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + bool moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const 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 brother 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. */ 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; + ExpressionLayout * numeratorLayout(); + ExpressionLayout * denominatorLayout(); }; } diff --git a/poincare/src/layout/grid_layout.cpp b/poincare/src/layout/grid_layout.cpp index 92dae7a72..b5d9f9934 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_visible_layout.h" +#include +#include extern "C" { #include #include @@ -6,40 +9,128 @@ 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; +} + +bool GridLayout::moveLeft(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); + cursor->setPointedExpressionLayout(lastChild); + return true; } - 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 + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + // Case: Left of another child. + // Go Right of its brother on the left. + cursor->setPointedExpressionLayout(editableChild(childIndex-1)); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Left. + // Ask the parent. + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool GridLayout::moveRight(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); + cursor->setPointedExpressionLayout(firstChild); + return true; + } + // Case: The cursor points to a grid's child. + int childIndex = indexOfChild(cursor->pointedExpressionLayout()); + if (childIndex >- 1 && cursor->position() == ExpressionLayoutCursor::Position::Right) { + if (childIsRightOfGrid(childIndex)) { + // Case: Right of a child on the right of the grid. + // Go Right of the grid. + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + // Case: Right of another child. + // Go Left of its brother on the right. + cursor->setPointedExpressionLayout(editableChild(childIndex+1)); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Right. + // Ask the parent. + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +bool GridLayout::moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // If the cursor is child that is not on the top row, move it inside the upper + // neighbourg. + int childIndex = indexOfChild(previousLayout); + if (childIndex >- 1 && !childIsTopOfGrid(childIndex)) { + return editableChild(childIndex - m_numberOfColumns)->moveUpInside(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::moveUp(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +bool GridLayout::moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // If the cursor is child that is not on the bottom row, move it inside the + // lower neighbourg. + int childIndex = indexOfChild(previousLayout); + if (childIndex >- 1 && !childIsBottomOfGrid(childIndex)) { + return editableChild(childIndex + m_numberOfColumns)->moveDownInside(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::moveDown(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +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 +147,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 +169,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 +179,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 +199,76 @@ KDPoint GridLayout::positionOfChild(ExpressionLayout * child) { return KDPoint(x, y); } +void GridLayout::addEmptyRow(EmptyVisibleLayout::Color color) { + ExpressionLayout * newChildren[m_numberOfColumns]; + for (int i = 0; i < m_numberOfColumns; i++) { + newChildren[i] = new EmptyVisibleLayout(color); + } + addChildrenAtIndex(const_cast(const_cast(newChildren)), m_numberOfColumns, numberOfChildren(), false); + m_numberOfRows++; + invalidAllSizesPositionsAndBaselines(); +} + +void GridLayout::addEmptyColumn(EmptyVisibleLayout::Color color) { + m_numberOfColumns++; + for (int i = 0; i < m_numberOfRows; i++) { + addChildAtIndex(new EmptyVisibleLayout(color), i*m_numberOfColumns + m_numberOfColumns-1); + } + invalidAllSizesPositionsAndBaselines(); +} + +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--; + invalidAllSizesPositionsAndBaselines(); +} + +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--; + invalidAllSizesPositionsAndBaselines(); +} + +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..b0e981ae4 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_visible_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; + + /* Navigation */ + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + bool moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + + /* Dynamic layout */ + void removeChildAtIndex(int index, bool deleteAfterRemoval) override; + // This function replaces the child with an EmptyVisibleLayout. To delete the + // grid's children, do not call this function. + + /* Expression engine */ + 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(EmptyVisibleLayout::Color color); + void addEmptyColumn(EmptyVisibleLayout::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..7c454e760 100644 --- a/poincare/src/layout/horizontal_layout.cpp +++ b/poincare/src/layout/horizontal_layout.cpp @@ -1,31 +1,320 @@ +#include "horizontal_layout.h" +#include "empty_visible_layout.h" +#include 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; } -HorizontalLayout::~HorizontalLayout() { - for (int i=0; ipointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Left + && m_parent == nullptr) + { + // Case: Left and this is the main layout. + // Return. + return; } - delete[] m_children_layouts; + 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; + } + } + assert(cursor->pointedExpressionLayout() == 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; + } + ExpressionLayout::backspaceAtCursor(cursor); +} + +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 brother 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 brother 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); + return; + } + addChildAtIndex(eL, index); +} + +bool HorizontalLayout::moveLeft(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->moveLeft(cursor, shouldRecomputeLayout); + } + return false; + } + 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->moveLeft(cursor, shouldRecomputeLayout); + } + return false; + } + ExpressionLayout * lastChild = editableChild(numberOfChildren()-1); + assert(lastChild != nullptr); + cursor->setPointedExpressionLayout(lastChild); + return lastChild->moveLeft(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->moveLeft(cursor, shouldRecomputeLayout); + } + return false; + } + // Case: the child is not the leftmost. + // Go to its left brother and move Left. + cursor->setPointedExpressionLayout(editableChild(childIndex-1)); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return editableChild(childIndex-1)->moveLeft(cursor, shouldRecomputeLayout); +} + +bool HorizontalLayout::moveRight(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->moveRight(cursor, shouldRecomputeLayout); + } + return false; + } + 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->moveRight(cursor, shouldRecomputeLayout); + } + return false; + } + ExpressionLayout * firstChild = editableChild(0); + assert(firstChild != nullptr); + cursor->setPointedExpressionLayout(firstChild); + return firstChild->moveRight(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->moveRight(cursor, shouldRecomputeLayout); + } + return false; + } + // Case: the child is not the rightmost. + // Go to its right brother and move Right. + cursor->setPointedExpressionLayout(editableChild(childIndex+1)); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return editableChild(childIndex+1)->moveRight(cursor, shouldRecomputeLayout); +} + +bool HorizontalLayout::moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + return moveVertically(ExpressionLayout::VerticalDirection::Up, cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +bool HorizontalLayout::moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + return moveVertically(ExpressionLayout::VerticalDirection::Down, cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + + +void HorizontalLayout::addChildrenAtIndex(const ExpressionLayout * const * operands, int numberOfOperands, int indexForInsertion, bool removeEmptyChildren) { + int newIndex = removeEmptyChildBeforeInsertionAtIndex(indexForInsertion, operands[0]->mustHaveLeftBrother()); + DynamicLayoutHierarchy::addChildrenAtIndex(operands, numberOfOperands, newIndex, removeEmptyChildren); +} + +bool HorizontalLayout::addChildAtIndex(ExpressionLayout * operand, int index) { + int newIndex = removeEmptyChildBeforeInsertionAtIndex(index, operand->mustHaveLeftBrother()); + 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) { + bool shouldRemoveOnLeft = eL->numberOfChildren() > 0 ? eL->child(0)->mustHaveLeftBrother() : 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, ""); +} + +bool HorizontalLayout::isEmpty() const { + if (m_numberOfChildren == 1 && child(0)->isEmpty()) + { + return true; + } + return false; } void HorizontalLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { @@ -36,7 +325,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 +338,129 @@ 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::privateAddBrother(ExpressionLayoutCursor * cursor, ExpressionLayout * brother, bool moveCursor) { + // Add the "brother" as a child. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + // If the first child is empty, remove it before adding the layout. + if (numberOfChildren() > 0 && editableChild(0)->isEmpty()) { + removeChildAtIndex(0, true); + } + if (moveCursor) { + if (numberOfChildren() > 0) { + cursor->setPointedExpressionLayout(editableChild(0)); + } else { + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + } + } + addOrMergeChildAtIndex(brother, 0, 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()) { + removeChildAtIndex(childrenCount - 1, true); + } + addOrMergeChildAtIndex(brother, numberOfChildren(), false); + if (moveCursor) { + cursor->setPointedExpressionLayout(this); + } +} + +bool HorizontalLayout::moveVertically(ExpressionLayout::VerticalDirection direction, ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // Prevent looping fom child to parent + if (previousPreviousLayout == this) { + if (direction == ExpressionLayout::VerticalDirection::Up) { + return ExpressionLayout::moveUp(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); + } + assert(direction == ExpressionLayout::VerticalDirection::Down); + return ExpressionLayout::moveDown(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); + } + // If the cursor Left or Right of a child, try moving it up from its brother. + int previousLayoutIndex = indexOfChild(previousLayout); + if (previousLayoutIndex > -1) { + ExpressionLayout * brother = nullptr; + ExpressionLayoutCursor::Position newPosition = ExpressionLayoutCursor::Position::Right; + if (cursor->position() == ExpressionLayoutCursor::Position::Left && previousLayoutIndex > 0) { + brother = editableChild(previousLayoutIndex - 1); + newPosition = ExpressionLayoutCursor::Position::Right; + } + if (cursor->position() == ExpressionLayoutCursor::Position::Right && previousLayoutIndex < numberOfChildren() - 1) { + brother = editableChild(previousLayoutIndex + 1); + newPosition = ExpressionLayoutCursor::Position::Left; + } + if (brother && cursor->positionIsEquivalentTo(brother, newPosition)) { + ExpressionLayout * previousPointedLayout = cursor->pointedExpressionLayout(); + ExpressionLayoutCursor::Position previousPosition = cursor->position(); + cursor->setPointedExpressionLayout(brother); + cursor->setPosition(newPosition); + if (direction == ExpressionLayout::VerticalDirection::Up && brother->moveUp(cursor, shouldRecomputeLayout, this, previousLayout)) { + return true; + } + if (direction == ExpressionLayout::VerticalDirection::Down && brother->moveDown(cursor, shouldRecomputeLayout, this, previousLayout)) { + return true; + } + cursor->setPointedExpressionLayout(previousPointedLayout); + cursor->setPosition(previousPosition); + } + } + if (direction == ExpressionLayout::VerticalDirection::Up) { + return ExpressionLayout::moveUp(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); + } + assert(direction == ExpressionLayout::VerticalDirection::Down); + return ExpressionLayout::moveDown(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +void HorizontalLayout::privateRemoveChildAtIndex(int index, bool deleteAfterRemoval, bool forceRemove) { + // If the child to remove is at index 0 and its right brother must have a left + // brother (e.g. it is a VerticalOffsetLayout), replace the child with an + // EmptyVisibleLayout instead of removing it. + if ( !forceRemove && index == 0 && numberOfChildren() > 1 && child(1)->mustHaveLeftBrother()) { + addChildAtIndex(new EmptyVisibleLayout(), index + 1); + } + DynamicLayoutHierarchy::removeChildAtIndex(index, deleteAfterRemoval); +} + +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..e714e73a9 100644 --- a/poincare/src/layout/horizontal_layout.h +++ b/poincare/src/layout/horizontal_layout.h @@ -1,27 +1,56 @@ #ifndef POINCARE_HORIZONTAL_LAYOUT_H #define POINCARE_HORIZONTAL_LAYOUT_H -#include -#include +#include +#include namespace Poincare { -class HorizontalLayout : public ExpressionLayout { +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 backspaceAtCursor(ExpressionLayoutCursor * cursor) override; + + /* Hierarchy */ + 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); + + /* Navigation */ + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + bool moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) 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; + + /* Expression Engine */ + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; + + /* Other */ + bool isHorizontal() const override { return true; } + bool isEmpty() const override; + 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 privateAddBrother(ExpressionLayoutCursor * cursor, ExpressionLayout * brother, bool moveCursor) override; private: - int m_number_of_children; - ExpressionLayout ** m_children_layouts; + bool moveVertically(ExpressionLayout::VerticalDirection direction, ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout); + 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..2184a1cd3 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,195 @@ 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::backspaceAtCursor(ExpressionLayoutCursor * cursor) { + if (cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + // Case: Right. + // Delete the layout, keep the integrand. + replaceWithAndMoveCursor(integrandLayout(), true, cursor); + return; + } + if (cursor->pointedExpressionLayout() == integrandLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Left) + { + // Case: Left of the integrand. + // Delete the layout, keep the integrand. + replaceWithAndMoveCursor(integrandLayout(), true, cursor); + return; + } + ExpressionLayout::backspaceAtCursor(cursor); +} + +bool IntegralLayout::moveLeft(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) + { + cursor->setPointedExpressionLayout(this); + return true; + } + // Case: Left the integrand. + // Go Right of the lower bound. + if (integrandLayout() + && cursor->pointedExpressionLayout() == integrandLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Left) + { + assert(lowerBoundLayout() != nullptr); + cursor->setPointedExpressionLayout(lowerBoundLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Right of the integral. + // Go to the integrand. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + assert(integrandLayout() != nullptr); + cursor->setPointedExpressionLayout(integrandLayout()); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + // Case: Left of the brackets. + // Ask the parent. + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool IntegralLayout::moveRight(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); + cursor->setPointedExpressionLayout(integrandLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + // Case: Right the integrand. + // Go Right. + if (integrandLayout() + && cursor->pointedExpressionLayout() == integrandLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + cursor->setPointedExpressionLayout(this); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Left of the integral. + // Go to the upper bound. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + assert(upperBoundLayout() != nullptr); + cursor->setPointedExpressionLayout(upperBoundLayout()); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. + // Ask the parent. + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +bool IntegralLayout::moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // If the cursor is inside the lower bound, move it to the upper bound. + if (lowerBoundLayout() && previousLayout == lowerBoundLayout()) { + assert(upperBoundLayout() != nullptr); + return upperBoundLayout()->moveUpInside(cursor, shouldRecomputeLayout); + } + // If the cursor is Left of the integrand, move it to the upper bound. + if (integrandLayout() + && previousLayout == integrandLayout() + && cursor->positionIsEquivalentTo(integrandLayout(), ExpressionLayoutCursor::Position::Left)) + { + assert(upperBoundLayout() != nullptr); + return upperBoundLayout()->moveUpInside(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::moveUp(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +bool IntegralLayout::moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // If the cursor is inside the upper bound, move it to the lower bound. + if (upperBoundLayout() && previousLayout == upperBoundLayout()) { + assert(lowerBoundLayout() != nullptr); + return lowerBoundLayout()->moveDownInside(cursor, shouldRecomputeLayout); + } + // If the cursor is Left of the integrand, move it to the lower bound. + if (integrandLayout() + && previousLayout == integrandLayout() + && cursor->positionIsEquivalentTo(integrandLayout(), ExpressionLayoutCursor::Position::Left)) + { + assert(lowerBoundLayout() != nullptr); + return lowerBoundLayout()->moveDownInside(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::moveDown(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +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 +219,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 { @@ -94,4 +263,16 @@ KDPoint IntegralLayout::positionOfChild(ExpressionLayout * child) { return KDPoint(x,y); } +ExpressionLayout * IntegralLayout::upperBoundLayout() { + return editableChild(2); +} + +ExpressionLayout * IntegralLayout::lowerBoundLayout() { + return editableChild(1); +} + +ExpressionLayout * IntegralLayout::integrandLayout() { + return editableChild(0); +} + } diff --git a/poincare/src/layout/integral_layout.h b/poincare/src/layout/integral_layout.h index 2e7059acd..a5866df1b 100644 --- a/poincare/src/layout/integral_layout.h +++ b/poincare/src/layout/integral_layout.h @@ -1,25 +1,34 @@ #ifndef POINCARE_INTEGRAL_LAYOUT_H #define POINCARE_INTEGRAL_LAYOUT_H -#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 backspaceAtCursor(ExpressionLayoutCursor * cursor) override; + + /* Tree navigation */ + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + bool moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + + /* Expression Engine */ + 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; 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 +36,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 * lowerBoundLayout(); + ExpressionLayout * upperBoundLayout(); + ExpressionLayout * integrandLayout(); }; } diff --git a/poincare/src/layout/matrix_layout.cpp b/poincare/src/layout/matrix_layout.cpp new file mode 100644 index 000000000..209d3455f --- /dev/null +++ b/poincare/src/layout/matrix_layout.cpp @@ -0,0 +1,326 @@ +#include "matrix_layout.h" +#include "empty_visible_layout.h" +#include "horizontal_layout.h" +#include "bracket_left_layout.h" +#include "bracket_right_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; +} + +bool MatrixLayout::moveLeft(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; + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + // Case: Right. + // Add the grey squares to the matrix, then move to the bottom right non empty + // nor grey child. + if (cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + assert(!hasGreySquares()); + addGreySquares(); + *shouldRecomputeLayout = true; + ExpressionLayout * lastChild = editableChild((m_numberOfColumns-1)*(m_numberOfRows-1)); + assert(lastChild != nullptr); + cursor->setPointedExpressionLayout(lastChild); + return true; + } + return GridLayout::moveLeft(cursor, shouldRecomputeLayout); +} + +bool MatrixLayout::moveRight(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); + cursor->setPointedExpressionLayout(firstChild); + return true; + } + // Case: The cursor points to a grid's child. + int childIndex = indexOfChild(cursor->pointedExpressionLayout()); + if (childIndex >- 1 + && cursor->position() == ExpressionLayoutCursor::Position::Right + && childIsRightOfGrid(childIndex)) + { + // Case: Right of a child on the right of the grid. + // Remove the grey squares of the grid, then go right of the grid. + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + assert(hasGreySquares()); + removeGreySquares(); + *shouldRecomputeLayout = true; + return true; + } + return GridLayout::moveRight(cursor, shouldRecomputeLayout); +} + +bool MatrixLayout::moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + bool shouldRemoveGreySquares = false; + int childIndex = indexOfChild(previousLayout); + if (childIndex >- 1 && childIsTopOfGrid(childIndex)) { + // The cursor is leaving the matrix, so remove the grey squares. + shouldRemoveGreySquares = true; + } + bool returnValue = GridLayout::moveUp(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); + if (returnValue && shouldRemoveGreySquares) { + assert(hasGreySquares()); + removeGreySquares(); + *shouldRecomputeLayout = true; + } + return returnValue; +} + +bool MatrixLayout::moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + bool shouldRemoveGreySquares = false; + int childIndex = indexOfChild(previousLayout); + if (childIndex >- 1 && childIsBottomOfGrid(childIndex)) { + // The cursor is leaving the matrix, so remove the grey squares. + shouldRemoveGreySquares = true; + } + bool returnValue = GridLayout::moveDown(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); + if (returnValue && shouldRemoveGreySquares) { + assert(hasGreySquares()); + removeGreySquares(); + *shouldRecomputeLayout = true; + } + return returnValue; +} + +bool MatrixLayout::moveUpInside(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + bool result = GridLayout::moveUpInside(cursor, shouldRecomputeLayout); + if (result && cursor->pointedExpressionLayout() != this) { + // Add the grey squares if the cursor is pointing at a matrix descendant, + // not at the matrix itself. + assert(!hasGreySquares()); + addGreySquares(); + *shouldRecomputeLayout = true; + } + return result; +} + +bool MatrixLayout::moveDownInside(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + bool result = GridLayout::moveDownInside(cursor, shouldRecomputeLayout); + if (result && cursor->pointedExpressionLayout() != this) { + // Add the grey squares if the cursor is pointing at a matrix descendant, + // not at the matrix itself. + assert(!hasGreySquares()); + addGreySquares(); + *shouldRecomputeLayout = true; + } + return result; +} + +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() == child(index)) || (cursor->pointedExpressionLayout()->hasAncestor(child(index)))); + replaceChildAndMoveCursor(child(index), new EmptyVisibleLayout(), 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 bool before modifying the layout.:w + // + if (GridLayout::childIsRightOfGrid(index)) { + // Color the grey EmptyVisibleLayouts 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(EmptyVisibleLayout::Color::Yellow); + } + } + // Add a column of grey EmptyVisibleLayouts on the right. + addEmptyColumn(EmptyVisibleLayout::Color::Grey); + } + if (shouldAddNewRow) { + // Color the grey EmptyVisibleLayouts 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(EmptyVisibleLayout::Color::Yellow); + } + } + // Add a row of grey EmptyVisibleLayouts at the bottom. + addEmptyRow(EmptyVisibleLayout::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(EmptyVisibleLayout::Color::Grey); + } + } +} + +void MatrixLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + BracketLeftLayout * dummyLeftBracket = new BracketLeftLayout(); + BracketRightLayout * dummyRightBracket = new BracketRightLayout(); + ExpressionLayout * dummyGridLayout = new GridLayout(children(), m_numberOfRows, m_numberOfColumns, true); + HorizontalLayout dummyLayout(dummyLeftBracket, dummyGridLayout, dummyRightBracket, false); + KDPoint leftBracketPoint = dummyLayout.positionOfChild(dummyLeftBracket); + KDPoint rightBracketPoint = dummyLayout.positionOfChild(dummyRightBracket); + dummyLeftBracket->render(ctx, p.translatedBy(leftBracketPoint), expressionColor, backgroundColor); + dummyRightBracket->render(ctx, p.translatedBy(rightBracketPoint), expressionColor, backgroundColor); +} + +KDSize MatrixLayout::computeSize() { + BracketLeftLayout * dummyLeftBracket = new BracketLeftLayout(); + BracketRightLayout * dummyRightBracket = new BracketRightLayout(); +ExpressionLayout * dummyGridLayout = new GridLayout(children(), m_numberOfRows, m_numberOfColumns, true); + HorizontalLayout dummyLayout(dummyLeftBracket, dummyGridLayout, dummyRightBracket, false); + return dummyLayout.size(); +} + +KDPoint MatrixLayout::positionOfChild(ExpressionLayout * child) { + assert(indexOfChild(child) > -1); + BracketLeftLayout * dummyLeftBracket = new BracketLeftLayout(); + BracketRightLayout * dummyRightBracket = new BracketRightLayout(); +ExpressionLayout * dummyGridLayout = new GridLayout(children(), m_numberOfRows, m_numberOfColumns, true); + HorizontalLayout dummyLayout(dummyLeftBracket, dummyGridLayout, dummyRightBracket, 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(EmptyVisibleLayout::Color::Grey); + addEmptyColumn(EmptyVisibleLayout::Color::Grey); + } +} + +void MatrixLayout::removeGreySquares() { + if (hasGreySquares()) { + deleteRowAtIndex(m_numberOfRows - 1); + deleteColumnAtIndex(m_numberOfColumns - 1); + } +} + +bool MatrixLayout::hasGreySquares() const { + assert(m_numberOfRows*m_numberOfColumns - 1 >= 0); + const ExpressionLayout * lastChild = child(m_numberOfRows * m_numberOfColumns - 1); + if (lastChild->isEmpty() + && !lastChild->isHorizontal() + && (static_cast(lastChild))->color() == EmptyVisibleLayout::Color::Grey) + { + assert(isRowEmpty(m_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..f18cba8aa --- /dev/null +++ b/poincare/src/layout/matrix_layout.h @@ -0,0 +1,50 @@ +#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; + + /* Navigation */ + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout = nullptr, ExpressionLayout * previousLayout = nullptr, ExpressionLayout * previousPreviousLayout = nullptr) override; + bool moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout = nullptr, ExpressionLayout * previousLayout = nullptr, ExpressionLayout * previousPreviousLayout = nullptr) override; + bool moveUpInside(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveDownInside(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + + /* Dynamic layout */ + 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; + + /* Expression engine */ + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; + + /* Other */ + bool isMatrix() const override { return true; } + + /* Special matrix method */ + void newRowOrColumnAtIndex(int index); + void addGreySquares(); + +protected: + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + KDSize computeSize() override; + KDPoint positionOfChild(ExpressionLayout * child) override; +private: + void childWasReplacedAtIndex(int index); + bool isRowEmpty(int index) const; + bool isColumnEmpty(int index) const; + void removeGreySquares(); + bool hasGreySquares() const; +}; + +} + +#endif diff --git a/poincare/src/layout/nth_root_layout.cpp b/poincare/src/layout/nth_root_layout.cpp index c9b75fbae..80ce7f0cc 100644 --- a/poincare/src/layout/nth_root_layout.cpp +++ b/poincare/src/layout/nth_root_layout.cpp @@ -1,6 +1,8 @@ +#include "nth_root_layout.h" +#include +#include #include #include -#include "nth_root_layout.h" namespace Poincare { @@ -15,47 +17,222 @@ 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::backspaceAtCursor(ExpressionLayoutCursor * cursor) { + if (cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + // Case: Right. + // Delete the layout, keep the radicand. + replaceWithAndMoveCursor(radicandLayout(), true, cursor); + return; } + 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::backspaceAtCursor(cursor); +} + +bool NthRootLayout::moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + // Case: Left of the radicand. + // Go the index if there is one, else go Left of the root. + if (radicandLayout() + && cursor->pointedExpressionLayout() == radicandLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Left) + { + if (indexLayout()) { + cursor->setPointedExpressionLayout(indexLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + cursor->setPointedExpressionLayout(this); + return true; + } + // Case: Left of the index. + // Go Left of the root. + if (indexLayout() + && cursor->pointedExpressionLayout() == indexLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Left) + { + cursor->setPointedExpressionLayout(this); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Right. + // Go Right of the radicand. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + assert(radicandLayout() != nullptr); + cursor->setPointedExpressionLayout(radicandLayout()); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + // Case: Left. + // Ask the parent. + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool NthRootLayout::moveRight(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) + { + cursor->setPointedExpressionLayout(this); + return true; + } + // Case: Right of the index. + // Go Left of the integrand. + if (indexLayout() + && cursor->pointedExpressionLayout() == indexLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + assert(radicandLayout() != nullptr); + cursor->setPointedExpressionLayout(radicandLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + 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()) { + cursor->setPointedExpressionLayout(indexLayout()); + return true; + } + assert(radicandLayout() != nullptr); + cursor->setPointedExpressionLayout(radicandLayout()); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. + // Ask the parent. + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +bool NthRootLayout::moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // If the cursor is Left of the radicand, move it to the index. + if (indexLayout() + && radicandLayout() + && previousLayout == radicandLayout() + && cursor->positionIsEquivalentTo(radicandLayout(), ExpressionLayoutCursor::Position::Left)) + { + cursor->setPointedExpressionLayout(indexLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + // If the cursor is Left, move it to the index. + if (indexLayout() + && cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Left) + { + cursor->setPointedExpressionLayout(indexLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + return ExpressionLayout::moveUp(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +bool NthRootLayout::moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + if (indexLayout() && previousLayout == indexLayout()) { + // If the cursor is Right of the index, move it to the radicand. + if (cursor->positionIsEquivalentTo(indexLayout(), ExpressionLayoutCursor::Position::Right)) { + assert(radicandLayout() != nullptr); + cursor->setPointedExpressionLayout(radicandLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + // If the cursor is Left of the index, move it Left . + if (cursor->positionIsEquivalentTo(indexLayout(), ExpressionLayoutCursor::Position::Left)) { + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + } + return ExpressionLayout::moveDown(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +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 = indexLayout() != nullptr ? indexLayout()->size() : KDSize(k_leftRadixWidth,0); 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 +242,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 +253,49 @@ 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 = indexLayout() != nullptr ? indexLayout()->size() : KDSize(k_leftRadixWidth,0); 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 = indexLayout() != nullptr ? indexLayout()->size() : KDSize(k_leftRadixWidth,0); + 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); } +ExpressionLayout * NthRootLayout::radicandLayout() { + return editableChild(0); } +ExpressionLayout * NthRootLayout::indexLayout() { + if (numberOfChildren() > 1) { + return editableChild(1); + } + return nullptr; +} + +} diff --git a/poincare/src/layout/nth_root_layout.h b/poincare/src/layout/nth_root_layout.h index b75cebdb0..c46ccbe86 100644 --- a/poincare/src/layout/nth_root_layout.h +++ b/poincare/src/layout/nth_root_layout.h @@ -1,34 +1,44 @@ #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; + + /* Dynamic Layout*/ + void backspaceAtCursor(ExpressionLayoutCursor * cursor) override; + + /* Tree navigation */ + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + bool moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + + /* Expression Engine */ + 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; 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_radixHorizontalOverflow = 2; constexpr static KDCoordinate k_indexHeight = 5; constexpr static KDCoordinate k_heightMargin = 2; constexpr static KDCoordinate k_widthMargin = 1; constexpr static KDCoordinate k_radixLineThickness = 1; - ExpressionLayout * m_radicandLayout; - ExpressionLayout * m_indexLayout; + ExpressionLayout * radicandLayout(); + ExpressionLayout * indexLayout(); }; } 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 deleted file mode 100644 index 9f4772e58..000000000 --- a/poincare/src/layout/parenthesis_layout.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef POINCARE_PARENTHESIS_LAYOUT_H -#define POINCARE_PARENTHESIS_LAYOUT_H - -#include -#include - -namespace Poincare { - -class ParenthesisLayout : public ExpressionLayout { -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; - 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; -}; - -} - -#endif diff --git a/poincare/src/layout/parenthesis_left_layout.cpp b/poincare/src/layout/parenthesis_left_layout.cpp new file mode 100644 index 000000000..2ed96c93a --- /dev/null +++ b/poincare/src/layout/parenthesis_left_layout.cpp @@ -0,0 +1,153 @@ +#include "parenthesis_left_layout.h" +#include +extern "C" { +#include +#include +} + +namespace Poincare { + +const uint8_t topLeftCurve[ParenthesisLeftRightLayout::k_parenthesisCurveHeight][ParenthesisLeftRightLayout::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[ParenthesisLeftRightLayout::k_parenthesisCurveHeight][ParenthesisLeftRightLayout::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 * ParenthesisLeftLayout::clone() const { + ParenthesisLeftLayout * layout = new ParenthesisLeftLayout(); + return layout; +} + +bool ParenthesisLeftLayout::isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { + if (*numberOfOpenParenthesis == 0 && goingLeft) { + return false; + } + *numberOfOpenParenthesis = goingLeft ? *numberOfOpenParenthesis - 1 : *numberOfOpenParenthesis + 1; + return true; +} + +void ParenthesisLeftLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + KDRect frame(p.x()+ParenthesisLeftRightLayout::k_externWidthMargin, + p.y()+ParenthesisLeftRightLayout::k_externHeightMargin, + ParenthesisLeftRightLayout::k_parenthesisCurveWidth, + ParenthesisLeftRightLayout::k_parenthesisCurveHeight); + + ctx->blendRectWithMask(frame, expressionColor, (const uint8_t *)topLeftCurve, (KDColor *)(ParenthesisLeftRightLayout::s_parenthesisWorkingBuffer)); + + frame = KDRect(p.x()+ParenthesisLeftRightLayout::k_externWidthMargin, + p.y() + operandHeight() - ParenthesisLeftRightLayout::k_parenthesisCurveHeight - ParenthesisLeftRightLayout::k_externHeightMargin, + ParenthesisLeftRightLayout::k_parenthesisCurveWidth, + ParenthesisLeftRightLayout::k_parenthesisCurveHeight); + + ctx->blendRectWithMask(frame, expressionColor, (const uint8_t *)bottomLeftCurve, (KDColor *)(ParenthesisLeftRightLayout::s_parenthesisWorkingBuffer)); + + ctx->fillRect(KDRect(p.x()+ParenthesisLeftRightLayout::k_externWidthMargin, + p.y()+ParenthesisLeftRightLayout::k_parenthesisCurveHeight+ParenthesisLeftRightLayout::k_externHeightMargin, + ParenthesisLeftRightLayout::k_lineThickness, + operandHeight() - 2*(ParenthesisLeftRightLayout::k_parenthesisCurveHeight+ParenthesisLeftRightLayout::k_externHeightMargin)), + expressionColor); +} + +void ParenthesisLeftLayout::computeOperandHeight() { + assert(m_parent != nullptr); + m_operandHeight = Metric::MinimalBracketAndParenthesisHeight; + KDCoordinate max_under_baseline = 0; + KDCoordinate max_above_baseline = 0; + int indexInParent = m_parent->indexOfChild(this); + int currentNumberOfOpenParentheses = 1; + int numberOfBrothers = m_parent->numberOfChildren(); + if (indexInParent < numberOfBrothers - 1 + && m_parent->child(indexInParent + 1)->mustHaveLeftBrother()) + { + // If the parenthesis is the base of a superscript layout, it should have a + // default height, else it creates an infinite loop because the parenthesis + // needs the superscript height, which needs the parenthesis height. + return; + } + for (int i = indexInParent + 1; i < numberOfBrothers; i++) { + ExpressionLayout * brother = m_parent->editableChild(i); + if (brother->isRightParenthesis()) { + currentNumberOfOpenParentheses--; + if (currentNumberOfOpenParentheses == 0) { + if (max_under_baseline + max_above_baseline > m_operandHeight) { + m_operandHeight = max_under_baseline + max_above_baseline; + } + return; + } + } else if (brother->isLeftParenthesis()) { + currentNumberOfOpenParentheses++; + } + KDCoordinate brotherHeight = brother->size().height(); + KDCoordinate brotherBaseline = brother->baseline(); + if (brotherHeight - brotherBaseline > max_under_baseline) { + max_under_baseline = brotherHeight - brotherBaseline ; + } + if (brotherBaseline > max_above_baseline) { + max_above_baseline = brotherBaseline; + } + } + if (max_under_baseline + max_above_baseline > m_operandHeight) { + m_operandHeight = max_under_baseline + max_above_baseline; + } +} + +void ParenthesisLeftLayout::computeBaseline() { + assert(m_parent != nullptr); + int currentNumberOfOpenParentheses = 1; + int indexInParent = m_parent->indexOfChild(this); + int numberOfBrothers = m_parent->numberOfChildren(); + if (indexInParent == numberOfBrothers - 1) { + // The parenthesis is the rightmost child of its parent. + m_baseline = operandHeight()/2; + m_baselined = true; + return; + } + if (m_parent->child(indexInParent + 1)->mustHaveLeftBrother()) { + // If the parenthesis is the base of a superscript layout, it should have a + // default baseline, else it creates an infinite loop because the + // parenthesis needs the superscript height, which needs the parenthesis + // baseline. + m_baseline = operandHeight()/2; + m_baselined = true; + return; + } + + m_baseline = 0; + for (int i = indexInParent + 1; i < numberOfBrothers; i++) { + ExpressionLayout * brother = m_parent->editableChild(i); + if (brother->isRightParenthesis()) { + if (i == indexInParent + 1) { + // If the parenthesis is immediately closed, we set the baseline to half + // the parenthesis height. + m_baseline = operandHeight()/2; + break; + } + currentNumberOfOpenParentheses--; + if (currentNumberOfOpenParentheses == 0) { + break; + } + } else if (brother->isLeftParenthesis()) { + currentNumberOfOpenParentheses++; + } + if (brother->baseline() > m_baseline) { + m_baseline = brother->baseline(); + } + } + m_baselined = true; +} + +} diff --git a/poincare/src/layout/parenthesis_left_layout.h b/poincare/src/layout/parenthesis_left_layout.h new file mode 100644 index 000000000..99c070fe4 --- /dev/null +++ b/poincare/src/layout/parenthesis_left_layout.h @@ -0,0 +1,28 @@ +#ifndef POINCARE_PARENTHESIS_LEFT_LAYOUT_H +#define POINCARE_PARENTHESIS_LEFT_LAYOUT_H + +#include +#include + +namespace Poincare { + +class ParenthesisLeftLayout : public ParenthesisLeftRightLayout { + friend class BinomialCoefficientLayout; + friend class SequenceLayout; +public: + using ParenthesisLeftRightLayout::ParenthesisLeftRightLayout; + 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; + void computeOperandHeight() override; + void computeBaseline() override; +}; + +} + +#endif diff --git a/poincare/src/layout/parenthesis_left_right_layout.cpp b/poincare/src/layout/parenthesis_left_right_layout.cpp new file mode 100644 index 000000000..b7d8f819e --- /dev/null +++ b/poincare/src/layout/parenthesis_left_right_layout.cpp @@ -0,0 +1,72 @@ +#include "parenthesis_left_right_layout.h" +#include +extern "C" { +#include +#include +} + +namespace Poincare { + +ParenthesisLeftRightLayout::ParenthesisLeftRightLayout() : + StaticLayoutHierarchy<0>(), + m_operandHeightComputed(false) +{ +} + +void ParenthesisLeftRightLayout::invalidAllSizesPositionsAndBaselines() { + m_operandHeightComputed = false; + ExpressionLayout::invalidAllSizesPositionsAndBaselines(); +} + +bool ParenthesisLeftRightLayout::moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + assert(cursor->pointedExpressionLayout() == this); + // Case: Right. + // Go Left. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + // Case: Left. + // Ask the parent. + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool ParenthesisLeftRightLayout::moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + assert(cursor->pointedExpressionLayout() == this); + // Case: Left. + // Go Right. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. + // Ask the parent. + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +KDSize ParenthesisLeftRightLayout::computeSize() { + return KDSize(parenthesisWidth(), operandHeight()); +} + +KDCoordinate ParenthesisLeftRightLayout::operandHeight() { + if (!m_operandHeightComputed) { + computeOperandHeight(); + m_operandHeightComputed = true; + } + return m_operandHeight; +} + +KDPoint ParenthesisLeftRightLayout::positionOfChild(ExpressionLayout * child) { + assert(false); + return KDPointZero; +} + +} diff --git a/poincare/src/layout/parenthesis_left_right_layout.h b/poincare/src/layout/parenthesis_left_right_layout.h new file mode 100644 index 000000000..b40f2536d --- /dev/null +++ b/poincare/src/layout/parenthesis_left_right_layout.h @@ -0,0 +1,32 @@ +#ifndef POINCARE_PARENTHESIS_LEFT_RIGHT_LAYOUT_H +#define POINCARE_PARENTHESIS_LEFT_RIGHT_LAYOUT_H + +#include + +namespace Poincare { + +class ParenthesisLeftRightLayout : public StaticLayoutHierarchy<0> { +public: + ParenthesisLeftRightLayout(); + void invalidAllSizesPositionsAndBaselines() override; + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + constexpr static KDCoordinate parenthesisWidth() { return k_widthMargin + k_lineThickness + k_externWidthMargin; } + constexpr static KDCoordinate k_parenthesisCurveWidth = 5; + constexpr static KDCoordinate k_parenthesisCurveHeight = 7; + constexpr static KDCoordinate k_externWidthMargin = 1; + constexpr static KDCoordinate k_externHeightMargin = 2; + constexpr static KDCoordinate k_widthMargin = 5; + constexpr static KDCoordinate k_lineThickness = 1; +protected: + KDColor s_parenthesisWorkingBuffer[k_parenthesisCurveHeight*k_parenthesisCurveWidth]; + KDSize computeSize() override; + KDCoordinate operandHeight(); + virtual void computeOperandHeight() = 0; + KDPoint positionOfChild(ExpressionLayout * child) override; + bool m_operandHeightComputed; + uint16_t m_operandHeight; +}; +} + +#endif diff --git a/poincare/src/layout/parenthesis_right_layout.cpp b/poincare/src/layout/parenthesis_right_layout.cpp new file mode 100644 index 000000000..e0346d42b --- /dev/null +++ b/poincare/src/layout/parenthesis_right_layout.cpp @@ -0,0 +1,139 @@ +#include "parenthesis_right_layout.h" +#include +extern "C" { +#include +#include +} + +namespace Poincare { + +const uint8_t topRightCurve[ParenthesisLeftRightLayout::k_parenthesisCurveHeight][ParenthesisLeftRightLayout::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[ParenthesisLeftRightLayout::k_parenthesisCurveHeight][ParenthesisLeftRightLayout::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 * ParenthesisRightLayout::clone() const { + ParenthesisRightLayout * layout = new ParenthesisRightLayout(); + return layout; +} + +bool ParenthesisRightLayout::isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { + if (*numberOfOpenParenthesis == 0 && !goingLeft) { + return false; + } + *numberOfOpenParenthesis = goingLeft ? *numberOfOpenParenthesis + 1 : *numberOfOpenParenthesis - 1; + return true; +} + +void ParenthesisRightLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + KDRect frame = KDRect(p.x() + ParenthesisLeftRightLayout::k_widthMargin + ParenthesisLeftRightLayout::k_lineThickness - ParenthesisLeftRightLayout::k_parenthesisCurveWidth, + p.y() + ParenthesisLeftRightLayout::k_externHeightMargin, + ParenthesisLeftRightLayout::k_parenthesisCurveWidth, + ParenthesisLeftRightLayout::k_parenthesisCurveHeight); + + ctx->blendRectWithMask(frame, expressionColor, (const uint8_t *)topRightCurve, (KDColor *)(ParenthesisLeftRightLayout::s_parenthesisWorkingBuffer)); + + frame = KDRect(p.x() + ParenthesisLeftRightLayout::k_widthMargin + ParenthesisLeftRightLayout::k_lineThickness - ParenthesisLeftRightLayout::k_parenthesisCurveWidth, + p.y() + operandHeight() - ParenthesisLeftRightLayout::k_parenthesisCurveHeight - ParenthesisLeftRightLayout::k_externHeightMargin, + ParenthesisLeftRightLayout::k_parenthesisCurveWidth, + ParenthesisLeftRightLayout::k_parenthesisCurveHeight); + + ctx->blendRectWithMask(frame, expressionColor, (const uint8_t *)bottomRightCurve, (KDColor *)(ParenthesisLeftRightLayout::s_parenthesisWorkingBuffer)); + + ctx->fillRect(KDRect(p.x()+ParenthesisLeftRightLayout::k_widthMargin, + p.y()+ParenthesisLeftRightLayout::k_parenthesisCurveHeight+2, + ParenthesisLeftRightLayout::k_lineThickness, + operandHeight() - 2*(ParenthesisLeftRightLayout::k_parenthesisCurveHeight+ParenthesisLeftRightLayout::k_externHeightMargin)), + expressionColor); +} + +void ParenthesisRightLayout::computeOperandHeight() { + assert(m_parent != nullptr); + m_operandHeight = Metric::MinimalBracketAndParenthesisHeight; + KDCoordinate max_under_baseline = 0; + KDCoordinate max_above_baseline = 0; + int currentNumberOfOpenParentheses = 1; + for (int i = m_parent->indexOfChild(this) - 1; i >= 0; i--) { + ExpressionLayout * brother = m_parent->editableChild(i); + if (brother->isLeftParenthesis()) { + currentNumberOfOpenParentheses--; + if (currentNumberOfOpenParentheses == 0) { + if (max_under_baseline + max_above_baseline > m_operandHeight) { + m_operandHeight = max_under_baseline + max_above_baseline; + } + return; + } + } else if (brother->isRightParenthesis()) { + currentNumberOfOpenParentheses++; + } + KDCoordinate brotherHeight = brother->size().height(); + KDCoordinate brotherBaseline = brother->baseline(); + if (brotherHeight - brotherBaseline > max_under_baseline) { + max_under_baseline = brotherHeight - brotherBaseline ; + } + if (brotherBaseline > max_above_baseline) { + max_above_baseline = brotherBaseline; + } + } + if (max_under_baseline + max_above_baseline > m_operandHeight) { + m_operandHeight = max_under_baseline + max_above_baseline; + } +} + +void ParenthesisRightLayout::computeBaseline() { + assert(m_parent != nullptr); + int currentNumberOfOpenParentheses = 1; + int indexInParent = m_parent->indexOfChild(this); + if (indexInParent == 0) { + // The parenthesis is the leftmost child of its parent. + m_baseline = operandHeight()/2; + m_baselined = true; + return; + } + m_baseline = 0; + for (int i = indexInParent - 1; i >= 0; i--) { + ExpressionLayout * brother = m_parent->editableChild(i); + if (brother->isLeftParenthesis()) { + if (i == indexInParent - 1) { + // If the parenthesis is immediately closed, we set the baseline to half + // the parenthesis height. + m_baseline = operandHeight()/2; + break; + } + currentNumberOfOpenParentheses--; + if (currentNumberOfOpenParentheses == 0) { + break; + } + } else if (brother->isRightParenthesis()) { + currentNumberOfOpenParentheses++; + } + if (brother->baseline() > m_baseline) { + m_baseline = brother->baseline(); + } + } + m_baselined = true; +} + +} + + + + + + + diff --git a/poincare/src/layout/parenthesis_right_layout.h b/poincare/src/layout/parenthesis_right_layout.h new file mode 100644 index 000000000..edf361dfb --- /dev/null +++ b/poincare/src/layout/parenthesis_right_layout.h @@ -0,0 +1,28 @@ +#ifndef POINCARE_PARENTHESIS_RIGHT_LAYOUT_H +#define POINCARE_PARENTHESIS_RIGHT_LAYOUT_H + +#include +#include + +namespace Poincare { + +class ParenthesisRightLayout : public ParenthesisLeftRightLayout { + friend class BinomialCoefficientLayout; + friend class SequenceLayout; +public: + using ParenthesisLeftRightLayout::ParenthesisLeftRightLayout; + 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; + void computeOperandHeight() override; + void computeBaseline() override; +}; + +} + +#endif 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/sequence_layout.cpp b/poincare/src/layout/sequence_layout.cpp index 52ed5dc95..dd1066051 100644 --- a/poincare/src/layout/sequence_layout.cpp +++ b/poincare/src/layout/sequence_layout.cpp @@ -1,68 +1,254 @@ #include "sequence_layout.h" -#include +#include "char_layout.h" +#include "horizontal_layout.h" +#include "parenthesis_left_layout.h" +#include "parenthesis_right_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::backspaceAtCursor(ExpressionLayoutCursor * cursor) { + if (cursor->pointedExpressionLayout() == this + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + // Case: Right. + // Delete the layout, keep the operand. + replaceWithAndMoveCursor(argumentLayout(), true, cursor); + return; + } + ExpressionLayout::backspaceAtCursor(cursor); } -SequenceLayout::~SequenceLayout() { - delete m_lowerBoundLayout; - delete m_upperBoundLayout; - delete m_argumentLayout; +bool SequenceLayout::moveLeft(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()))) + { + cursor->setPointedExpressionLayout(this); + return true; + } + // Case: Left of the argument. + // Go Right of the lower bound. + if (cursor->position() == ExpressionLayoutCursor::Position::Left + && argumentLayout() + && cursor->pointedExpressionLayout() == argumentLayout()) + { + assert(lowerBoundLayout() != nullptr); + cursor->setPointedExpressionLayout(lowerBoundLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Right. + // Go to the argument and move Left. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + assert(argumentLayout() != nullptr); + cursor->setPointedExpressionLayout(argumentLayout()); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + // Case: Left. + // Ask the parent. + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool SequenceLayout::moveRight(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); + cursor->setPointedExpressionLayout(argumentLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + // Case: Right of the argument. + // Ask the parent. + if (cursor->position() == ExpressionLayoutCursor::Position::Right + && argumentLayout() + && cursor->pointedExpressionLayout() == argumentLayout()) + { + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + assert(cursor->pointedExpressionLayout() == this); + // Case: Left. + // Go to the upper bound. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + assert(upperBoundLayout() != nullptr); + cursor->setPointedExpressionLayout(upperBoundLayout()); + return true; + } + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. + // Ask the parent. + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +bool SequenceLayout::moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // If the cursor is inside the lower bound, move it to the upper bound. + if (lowerBoundLayout() && previousLayout == lowerBoundLayout()) { + assert(upperBoundLayout() != nullptr); + return upperBoundLayout()->moveUpInside(cursor, shouldRecomputeLayout); + } + // If the cursor is Left of the argument, move it to the upper bound. + if (argumentLayout() + && cursor->positionIsEquivalentTo(argumentLayout(), ExpressionLayoutCursor::Position::Left)) + { + assert(upperBoundLayout() != nullptr); + return upperBoundLayout()->moveUpInside(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::moveUp(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} +bool SequenceLayout::moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // If the cursor is inside the upper bound, move it to the lower bound. + if (upperBoundLayout() && previousLayout == upperBoundLayout()) { + assert(lowerBoundLayout() != nullptr); + return lowerBoundLayout()->moveDownInside(cursor, shouldRecomputeLayout); + } + // If the cursor is Left of the argument, move it to the lower bound. + if (argumentLayout() + && cursor->positionIsEquivalentTo(argumentLayout(), ExpressionLayoutCursor::Position::Left)) + { + assert(lowerBoundLayout() != nullptr); + return lowerBoundLayout()->moveDownInside(cursor, shouldRecomputeLayout); + } + return ExpressionLayout::moveDown(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +char SequenceLayout::XNTChar() const { + return 'n'; +} + +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; +} + +ExpressionLayout * SequenceLayout::upperBoundLayout() { + return editableChild(2); +} + +ExpressionLayout * SequenceLayout::lowerBoundLayout() { + return editableChild(1); +} + +ExpressionLayout * SequenceLayout::argumentLayout() { + return editableChild(0); } 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(); + ParenthesisLeftLayout * dummyLeftParenthesis = new ParenthesisLeftLayout(); + ParenthesisRightLayout * dummyRightParenthesis = new ParenthesisRightLayout(); + 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(); + ParenthesisLeftLayout * dummyLeftParenthesis = new ParenthesisLeftLayout(); + 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. + ParenthesisLeftLayout * dummyLeftParenthesis = new ParenthesisLeftLayout(); + ParenthesisRightLayout * dummyRightParenthesis = new ParenthesisRightLayout(); + 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..164ead79d 100644 --- a/poincare/src/layout/sequence_layout.h +++ b/poincare/src/layout/sequence_layout.h @@ -1,31 +1,33 @@ #ifndef POINCARE_SEQUENCE_LAYOUT_H #define POINCARE_SEQUENCE_LAYOUT_H -#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; + void backspaceAtCursor(ExpressionLayoutCursor * cursor) override; + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + bool moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + char XNTChar() const override; 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; + ExpressionLayout * lowerBoundLayout(); + ExpressionLayout * upperBoundLayout(); + ExpressionLayout * argumentLayout(); + KDSize computeSize() override; + KDPoint positionOfChild(ExpressionLayout * eL) override; + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; +private: + void computeBaseline() override; }; } diff --git a/poincare/src/layout/static_layout_hierarchy.cpp b/poincare/src/layout/static_layout_hierarchy.cpp new file mode 100644 index 000000000..224902945 --- /dev/null +++ b/poincare/src/layout/static_layout_hierarchy.cpp @@ -0,0 +1,70 @@ +#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++) { + if (cloneOperands && operands[i] != nullptr) { + 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..0448502f2 --- /dev/null +++ b/poincare/src/layout/vertical_offset_layout.cpp @@ -0,0 +1,274 @@ +#include "vertical_offset_layout.h" +#include "empty_visible_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::backspaceAtCursor(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::backspaceAtCursor(cursor); +} + +bool VerticalOffsetLayout::moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + // Case: Left of the indice. + // Go Left. + if (indiceLayout() + && cursor->pointedExpressionLayout() == indiceLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Left) + { + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + + assert(cursor->pointedExpressionLayout() == this); + // Case: Right. + // Go to the indice. + if (cursor->position() == ExpressionLayoutCursor::Position::Right) { + assert(indiceLayout() != nullptr); + cursor->setPointedExpressionLayout(indiceLayout()); + return true; + } + // Case: Left. + // Ask the parent. + assert(cursor->position() == ExpressionLayoutCursor::Position::Left); + if (m_parent) { + return m_parent->moveLeft(cursor, shouldRecomputeLayout); + } + return false; +} + +bool VerticalOffsetLayout::moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) { + // Case: Right of the indice. + // Go Right. + if (indiceLayout() + && cursor->pointedExpressionLayout() == indiceLayout() + && cursor->position() == ExpressionLayoutCursor::Position::Right) + { + cursor->setPointedExpressionLayout(this); + return true; + } + + assert(cursor->pointedExpressionLayout() == this); + // Case: Left. + // Go to the indice. + if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + assert(indiceLayout() != nullptr); + cursor->setPointedExpressionLayout(indiceLayout()); + return true; + } + // Case: Right. + // Ask the parent. + assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + if (m_parent) { + return m_parent->moveRight(cursor, shouldRecomputeLayout); + } + return false; +} + +bool VerticalOffsetLayout::moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // Case: Superscript. + if (m_type == VerticalOffsetLayout::Type::Superscript) { + // Case: Right. + // Move to the indice. + if (cursor->positionIsEquivalentTo(this, ExpressionLayoutCursor::Position::Right)) { + assert(indiceLayout() != nullptr); + cursor->setPointedExpressionLayout(indiceLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + // Case: Left. + // Move to the indice. + if (cursor->positionIsEquivalentTo(this, ExpressionLayoutCursor::Position::Left)) { + assert(indiceLayout() != nullptr); + cursor->setPointedExpressionLayout(indiceLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + } + // Case: Subscript. + // Case: Left or Right of the indice. + // Put the cursor at the same position, pointing this. + if (m_type == VerticalOffsetLayout::Type::Subscript + && indiceLayout() != nullptr + && (cursor->positionIsEquivalentTo(indiceLayout(), ExpressionLayoutCursor::Position::Left) + || cursor->positionIsEquivalentTo(indiceLayout(), ExpressionLayoutCursor::Position::Right))) + { + cursor->setPointedExpressionLayout(this); + return true; + } + return ExpressionLayout::moveUp(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +bool VerticalOffsetLayout::moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) { + // Case: Subscript. + if (m_type == VerticalOffsetLayout::Type::Subscript) { + // Case: Right. + // Move to the indice. + if (cursor->positionIsEquivalentTo(this, ExpressionLayoutCursor::Position::Right)) { + assert(indiceLayout() != nullptr); + cursor->setPointedExpressionLayout(indiceLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Right); + return true; + } + // Case: Left. + // Move to the indice. + if (cursor->positionIsEquivalentTo(this, ExpressionLayoutCursor::Position::Left)) { + assert(indiceLayout() != nullptr); + cursor->setPointedExpressionLayout(indiceLayout()); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return true; + } + } + // Case: Superscript. + // Case: 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()) + { + cursor->setPointedExpressionLayout(this); + return true; + } + return ExpressionLayout::moveDown(cursor, shouldRecomputeLayout, previousLayout, previousPreviousLayout); +} + +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)" + int numberOfChar = LayoutEngine::writePrefixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, "^"); + + // 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::indiceLayout() { + return editableChild(0); +} + +ExpressionLayout * VerticalOffsetLayout::baseLayout() { + int indexInParent = parent()->indexOfChild(this); + assert(indexInParent > 0); + return editableParent()->editableChild(indexInParent - 1); +} + +void VerticalOffsetLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + // There is nothing to draw for a subscript/superscript, only the position of the child matters +} + +KDSize VerticalOffsetLayout::computeSize() { + KDSize indiceSize = indiceLayout()->size(); + KDCoordinate width = indiceSize.width(); + KDCoordinate height = 0; + if (m_type == Type::Subscript) { + height = positionOfChild(indiceLayout()).y()+ indiceLayout()->size().height(); + } else { + height = indiceLayout()->size().height() + baseLayout()->baseline() - k_indiceHeight; + } + return KDSize(width, height); +} + +void VerticalOffsetLayout::computeBaseline() { + if (m_type == Type::Subscript) { + m_baseline = 0; + } else { + m_baseline = size().height(); + } + 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() - base->baseline() - k_indiceHeight); +} + +} diff --git a/poincare/src/layout/vertical_offset_layout.h b/poincare/src/layout/vertical_offset_layout.h new file mode 100644 index 000000000..d63ca2e9c --- /dev/null +++ b/poincare/src/layout/vertical_offset_layout.h @@ -0,0 +1,37 @@ +#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; + void backspaceAtCursor(ExpressionLayoutCursor * cursor) override; + bool moveLeft(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveRight(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout) override; + bool moveUp(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + bool moveDown(ExpressionLayoutCursor * cursor, bool * shouldRecomputeLayout, ExpressionLayout * previousLayout, ExpressionLayout * previousPreviousLayout) override; + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const override; + bool mustHaveLeftBrother() const override { return true; } +protected: + ExpressionLayout * indiceLayout(); + ExpressionLayout * baseLayout(); + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + KDSize computeSize() override; + void computeBaseline() override; + KDPoint positionOfChild(ExpressionLayout * child) override; + Type m_type; +private: + constexpr static KDCoordinate k_indiceHeight = 5; +}; + +} + +#endif diff --git a/poincare/src/layout_engine.cpp b/poincare/src/layout_engine.cpp index 3c4dc5a01..9b2e43ddb 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/parenthesis_left_layout.h" +#include "layout/parenthesis_right_layout.h" +#include "layout/vertical_offset_layout.h" extern "C" { #include } @@ -13,72 +15,129 @@ 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. + HorizontalLayout * args = new HorizontalLayout(); 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->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); + args->addChildAtIndex(new CharLayout(','), args->numberOfChildren()); + args->addOrMergeChildAtIndex(expression->operand(i)->createLayout(floatDisplayMode, complexFormat), args->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 ParenthesisLeftLayout(), 0); + if (layout != nullptr) { + result->addOrMergeChildAtIndex(cloneLayout ? layout->clone() : layout, 1, true); + } + result->addChildAtIndex(new ParenthesisRightLayout(), 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, [](const char * operatorName) { return true; }); +} + +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, ChildNeedsParenthesis childNeedsParenthesis) { + return writeInfixExpressionOrExpressionLayoutTextInBuffer(nullptr, expressionLayout, buffer, bufferSize, numberOfDigits, operatorName, firstChildIndex, lastChildIndex, childNeedsParenthesis); +} + +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, ChildNeedsParenthesis childNeedsParenthesis) { + 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)) { - buffer[numberOfChar++] = '('; - if (numberOfChar >= bufferSize-1) { return bufferSize-1; } + int numberOfOperands = (expression != nullptr) ? expression->numberOfOperands() : expressionLayout->numberOfChildren(); + assert(numberOfOperands > 0); //TODO MERGE SAISIEJOLIE 1 dans HEAD + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; } - numberOfChar += expression->operand(0)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits); - if (expression->operand(0)->needParenthesisWithParent(expression)) { + + if ((expression != nullptr && expression->operand(firstChildIndex)->needParenthesisWithParent(expression)) + || (expression == nullptr && childNeedsParenthesis(operatorName))) + { + buffer[numberOfChar++] = '('; + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; + } + } + + numberOfChar += (expression != nullptr) ? expression->operand(firstChildIndex)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits) : expressionLayout->child(firstChildIndex)->writeTextInBuffer(buffer+numberOfChar, bufferSize-numberOfChar, numberOfDigits); + if ((expression != nullptr && expression->operand(firstChildIndex)->needParenthesisWithParent(expression)) + || (expression == nullptr && childNeedsParenthesis(operatorName))) + { buffer[numberOfChar++] = ')'; if (numberOfChar >= bufferSize-1) { return bufferSize-1; } } - for (int i=1; i= bufferSize-1) { return bufferSize-1; } numberOfChar += strlcpy(buffer+numberOfChar, operatorName, bufferSize-numberOfChar); if (numberOfChar >= bufferSize-1) { return bufferSize-1; } - if (expression->operand(i)->needParenthesisWithParent(expression)) { + if ((expression != nullptr && expression->operand(i)->needParenthesisWithParent(expression)) + || (expression == nullptr && childNeedsParenthesis(operatorName))) + { 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 ((expression != nullptr && expression->operand(i)->needParenthesisWithParent(expression)) + || (expression == nullptr && childNeedsParenthesis(operatorName))) + { buffer[numberOfChar++] = ')'; if (numberOfChar >= bufferSize-1) { return bufferSize-1; } } @@ -87,24 +146,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 +182,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..3d307a086 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); + return result; + } + 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 b8ca902ff..412ead979 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,13 @@ 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); + return new HorizontalLayout( + m_operands[0]->createLayout(floatDisplayMode, complexFormat), + new VerticalOffsetLayout( + indiceOperand->createLayout(floatDisplayMode, complexFormat), + VerticalOffsetLayout::Type::Superscript, + false), + false); } int Power::simplificationOrderSameType(const Expression * e, bool canBeInterrupted) const { diff --git a/poincare/src/preferences.cpp b/poincare/src/preferences.cpp index 17294df3e..2dbc12bce 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::Edition2D), 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 {