diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index e16e2f74d..9a6e52c46 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -13,6 +13,7 @@ public: KDColor textColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); void setDelegate(TextAreaDelegate * delegate); + bool handleEvent(Ion::Events::Event event) override; private: class Text { @@ -42,12 +43,12 @@ private: class Position { public: - Position(size_t column, size_t line) : m_column(column), m_line(line) {} - size_t column() const { return m_column; } - size_t line() const { return m_line; } + Position(int column, int line) : m_column(column), m_line(line) {} + int column() const { return m_column; } + int line() const { return m_line; } private: - size_t m_column; - size_t m_line; + int m_column; + int m_line; }; LineIterator begin() const { return LineIterator(m_buffer); }; @@ -55,12 +56,11 @@ private: Position span() const; - void insert(char c, Position p); - void remove(Position p); - private: + Position positionAtIndex(size_t index); size_t indexAtPosition(Position p); - void insert(char c, size_t index); - void remove(size_t index); + + void insertChar(char c, size_t index); + void removeChar(size_t index); private: char * m_buffer; size_t m_bufferSize; @@ -72,7 +72,18 @@ private: KDColor textColor, KDColor backgroundColor); void drawRect(KDContext * ctx, KDRect rect) const override; KDSize minimalSizeForOptimalDisplay() const override; + void insertText(const char * text); + void moveCursorIndex(int deltaX); + void moveCursorGeo(int deltaX, int deltaY); + void removeChar(); + KDRect cursorRect(); private: + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + void layoutSubviews() override; + KDRect characterFrameAtIndex(size_t index); + TextCursorView m_cursorView; + size_t m_cursorIndex; Text m_text; KDText::FontSize m_fontSize; KDColor m_textColor; diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index 9e0bd014d..aca0111a4 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -5,6 +5,11 @@ #include #include + +static inline size_t min(size_t a, size_t b) { + return (a>b ? b : a); +} + TextArea::Text::Text(char * buffer, size_t bufferSize) : m_buffer(buffer), m_bufferSize(bufferSize) @@ -35,21 +40,40 @@ TextArea::Text::LineIterator & TextArea::Text::LineIterator::operator++() { } size_t TextArea::Text::indexAtPosition(Position p) { + if (p.line() < 0) { + return 0; + } size_t y = 0; + const char * endOfLastLine = nullptr; for (Line l : *this) { if (p.line() == y) { - size_t x = (p.column() > l.length() ? l.length() : p.column()); + size_t x = min(p.column(), l.length()); return l.text() - m_buffer + x; } + endOfLastLine = l.text() + l.length(); + y++; + } + assert(endOfLastLine != nullptr && endOfLastLine >= m_buffer); + return endOfLastLine - m_buffer; +} + +TextArea::Text::Position TextArea::Text::positionAtIndex(size_t index) { + assert(index < m_bufferSize); + const char * target = m_buffer + index; + size_t y = 0; + for (Line l : *this) { + if (l.text() <= target && l.text() + l.length() >= target) { + size_t x = target - l.text(); + return Position(x, y); + } y++; } assert(false); - return 0; + return Position(0, 0); } -void TextArea::Text::insert(char c, size_t index) { +void TextArea::Text::insertChar(char c, size_t index) { assert(index < m_bufferSize); - char * cursor = m_buffer + index; char previous = c; for (size_t i=index; ib ? b : a); -} void TextArea::ContentView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(rect, m_backgroundColor); @@ -142,6 +164,61 @@ void TextArea::ContentView::drawRect(KDContext * ctx, KDRect rect) const { } } +int TextArea::ContentView::numberOfSubviews() const { + return 1; +} + +View * TextArea::ContentView::subviewAtIndex(int index) { + return &m_cursorView; +} + +void TextArea::ContentView::layoutSubviews() { + m_cursorView.setFrame(cursorRect()); +} + +void TextArea::TextArea::ContentView::insertText(const char * text) { + while (*text != 0) { + m_text.insertChar(*text++, m_cursorIndex++); + } + layoutSubviews(); // Reposition the cursor + markRectAsDirty(bounds()); // FIXME: Vastly suboptimal +} + +void TextArea::TextArea::ContentView::removeChar() { + if (m_cursorIndex > 0) { + m_text.removeChar(--m_cursorIndex); + } + layoutSubviews(); // Reposition the cursor + markRectAsDirty(bounds()); // FIXME: Vastly suboptimal +} + +KDRect TextArea::TextArea::ContentView::cursorRect() { + return characterFrameAtIndex(m_cursorIndex); +} + +KDRect TextArea::TextArea::ContentView::characterFrameAtIndex(size_t index) { + KDSize charSize = KDText::stringSize(" ", m_fontSize); + Text::Position p = m_text.positionAtIndex(index); + return KDRect( + p.column() * charSize.width(), + p.line() * charSize.height(), + charSize.width(), + charSize.height() + ); +} + +void TextArea::TextArea::ContentView::moveCursorGeo(int deltaX, int deltaY) { + Text::Position p = m_text.positionAtIndex(m_cursorIndex); + m_cursorIndex = m_text.indexAtPosition(Text::Position(p.column() + deltaX, p.line() + deltaY)); + layoutSubviews(); +} + +void TextArea::TextArea::ContentView::moveCursorIndex(int deltaX) { + // FIXME: bound checks! + m_cursorIndex += deltaX; + layoutSubviews(); +} + /* TextArea */ TextArea::TextArea(Responder * parentResponder, char * textBuffer, @@ -153,6 +230,26 @@ TextArea::TextArea(Responder * parentResponder, char * textBuffer, { } +bool TextArea::TextArea::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::Left) { + m_contentView.moveCursorIndex(-1); + } else if (event == Ion::Events::Right) { + m_contentView.moveCursorIndex(1); + } else if (event == Ion::Events::Up) { + m_contentView.moveCursorGeo(0, -1); + } else if (event == Ion::Events::Down) { + m_contentView.moveCursorGeo(0, 1); + } else if (event == Ion::Events::Backspace) { + m_contentView.removeChar(); + } else if (event.hasText()) { + m_contentView.insertText(event.text()); + } else { + return false; + } + scrollToContentRect(m_contentView.cursorRect()); + return true; +} + View * TextArea::view() { return &m_contentView; }