[escher] Create a class TextInput (from which derived TextField,

TextArea)
This commit is contained in:
Émilie Feral
2018-02-21 10:54:32 +01:00
committed by EmilieNumworks
parent c50e57029b
commit aade7cb2fe
24 changed files with 344 additions and 356 deletions

View File

@@ -313,7 +313,7 @@ bool ConsoleController::textFieldDidAbortEditing(TextField * textField, const ch
return true;
}
Toolbox * ConsoleController::toolboxForTextField(TextField * textField) {
Toolbox * ConsoleController::toolboxForTextInput(TextInput * textInput) {
Code::App * codeApp = static_cast<Code::App *>(app());
codeApp->pythonToolbox()->setAction(codeApp->toolboxActionForTextField());
return codeApp->pythonToolbox();

View File

@@ -61,7 +61,7 @@ public:
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;
Toolbox * toolboxForTextField(TextField * textField) override;
Toolbox * toolboxForTextInput(TextInput * textInput) override;
// MicroPython::ExecutionEnvironment
void displaySandbox() override;

View File

@@ -129,7 +129,7 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events:
return false;
}
Toolbox * EditorController::toolboxForTextArea(TextArea * textArea) {
Toolbox * EditorController::toolboxForTextInput(TextInput * textInput) {
Code::App * codeApp = static_cast<Code::App *>(app());
codeApp->pythonToolbox()->setAction(codeApp->toolboxActionForTextArea());
return codeApp->pythonToolbox();

View File

@@ -25,7 +25,7 @@ public:
/* TextAreaDelegate */
bool textAreaShouldFinishEditing(TextArea * textArea, Ion::Events::Event event) override;
bool textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) override;
Toolbox * toolboxForTextArea(TextArea * textArea) override;
Toolbox * toolboxForTextInput(TextInput * textInput) override;
private:
static constexpr int k_indentationSpacesNumber = 2;

View File

@@ -58,7 +58,7 @@ public:
bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override;
bool textFieldDidAbortEditing(TextField * textField, const char * text) override;
bool textFieldDidHandleEvent(TextField * textField, bool returnValue, bool textHasChanged) override;
Toolbox * toolboxForTextField(TextField * textField) override { return nullptr; }
Toolbox * toolboxForTextInput(TextInput * textInput) override { return nullptr; }
/* ButtonRowDelegate */
int numberOfButtons(ButtonRowController::Position position) const override { return 1; }

View File

@@ -23,7 +23,7 @@ const char * ListController::title() {
return I18n::translate(I18n::Message::SequenceTab);
}
Toolbox * ListController::toolboxForTextField(TextField * textField) {
Toolbox * ListController::toolboxForTextInput(TextInput * textInput) {
int recurrenceDepth = -1;
int sequenceDefinition = sequenceDefinitionForRow(selectedRow());
Sequence * sequence = m_sequenceStore->functionAtIndex(functionIndexForRow(selectedRow()));

View File

@@ -21,7 +21,7 @@ public:
int numberOfRows() override;
virtual KDCoordinate rowHeight(int j) override;
void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override;
Toolbox * toolboxForTextField(TextField * textField) override;
Toolbox * toolboxForTextInput(TextInput * textInput) override;
void selectPreviousNewSequenceCell();
private:
Shared::TextFieldDelegateApp * textFieldDelegateApp() override;

View File

@@ -12,8 +12,8 @@ bool TextFieldDelegate::textFieldDidReceiveEvent(::TextField * textField, Ion::E
return textFieldDelegateApp()->textFieldDidReceiveEvent(textField, event);
}
Toolbox * TextFieldDelegate::toolboxForTextField(::TextField * textField) {
return textFieldDelegateApp()->toolboxForTextField(textField);
Toolbox * TextFieldDelegate::toolboxForTextInput(TextInput * textInput) {
return textFieldDelegateApp()->toolboxForTextInput(textInput);
}
}

View File

@@ -11,7 +11,7 @@ class TextFieldDelegate : public ::TextFieldDelegate {
public:
bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override;
bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override;
Toolbox * toolboxForTextField(TextField * textField) override;
Toolbox * toolboxForTextInput(TextInput * textInput) override;
private:
virtual TextFieldDelegateApp * textFieldDelegateApp() = 0;
};

View File

@@ -107,7 +107,7 @@ bool TextFieldDelegateApp::textFieldDidReceiveEvent(TextField * textField, Ion::
return false;
}
Toolbox * TextFieldDelegateApp::toolboxForTextField(TextField * textField) {
Toolbox * TextFieldDelegateApp::toolboxForTextInput(TextInput * textInput) {
return container()->mathToolbox();
}

View File

@@ -17,7 +17,7 @@ public:
virtual const char * XNT();
bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override;
virtual bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override;
Toolbox * toolboxForTextField(TextField * textField) override;
Toolbox * toolboxForTextInput(TextInput * textInput) override;
protected:
TextFieldDelegateApp(Container * container, Snapshot * snapshot, ViewController * rootViewController);
private:

View File

@@ -67,6 +67,7 @@ objs += $(addprefix escher/src/,\
text_cursor_view.o\
text_area.o\
text_field.o\
text_input.o\
text_input_helpers.o\
text_view.o\
tiled_view.o\

View File

@@ -63,6 +63,8 @@
#include <escher/text_area_delegate.h>
#include <escher/text_field.h>
#include <escher/text_field_delegate.h>
#include <escher/text_input.h>
#include <escher/text_input_delegate.h>
#include <escher/text_input_helpers.h>
#include <escher/text_view.h>
#include <escher/tab_view_controller.h>

View File

@@ -16,7 +16,7 @@ public:
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;
Toolbox * toolboxForTextField(TextField * textFied) override;
Toolbox * toolboxForTextInput(TextInput * textInput) override;
private:
class TextFieldController : public ViewController {
public:

View File

@@ -1,36 +1,30 @@
#ifndef ESCHER_TEXT_AREA_H
#define ESCHER_TEXT_AREA_H
#include <escher/text_input.h>
#include <escher/text_area_delegate.h>
#include <assert.h>
#include <string.h>
#include <escher/scrollable_view.h>
#include <escher/text_cursor_view.h>
#include <escher/text_area_delegate.h>
class TextArea : public ScrollableView, public ScrollViewDataSource {
class TextArea : public TextInput {
public:
TextArea(Responder * parentResponder, char * textBuffer = nullptr, size_t textBufferSize = 0,
TextAreaDelegate * delegate = nullptr, KDText::FontSize fontSize = KDText::FontSize::Large,
TextArea(Responder * parentResponder, char * textBuffer = nullptr, size_t textBufferSize = 0, TextAreaDelegate * delegate = nullptr, KDText::FontSize fontSize = KDText::FontSize::Large,
KDColor textColor = KDColorBlack, KDColor backgroundColor = KDColorWhite);
void setDelegate(TextAreaDelegate * delegate) { m_delegate = delegate; }
Toolbox * toolbox() override;
bool handleEvent(Ion::Events::Event event) override;
bool handleEventWithText(const char * text, bool indentation = false);
bool handleEventWithText(const char * text, bool indentation = false) override;
void setText(char * textBuffer, size_t textBufferSize);
bool insertTextAtLocation(const char * textBuffer, int location) { return m_contentView.insertTextAtLocation(textBuffer, location); }
bool insertTextWithIndentation(const char * textBuffer, int location);
int indentationBeforeCursor() const;
bool removeChar();
const char * text() const { return m_contentView.text(); }
int cursorLocation() const { return m_contentView.cursorLocation(); }
bool setCursorLocation(int location);
private:
int indentationBeforeCursor() const;
bool insertTextWithIndentation(const char * textBuffer, int location);
TextInputDelegate * delegate() override {
return m_delegate;
}
class Text {
public:
Text(char * buffer, size_t bufferSize);
void setText(char * buffer, size_t bufferSize);
const char * text() const { return const_cast<const char *>(m_buffer); }
class Line {
public:
Line(const char * text);
@@ -67,7 +61,7 @@ private:
Position span() const;
Position positionAtIndex(size_t index);
Position positionAtIndex(size_t index) const;
size_t indexAtPosition(Position p);
void insertChar(char c, size_t index);
@@ -88,37 +82,28 @@ private:
size_t m_bufferSize;
};
class ContentView : public View {
class ContentView : public TextInput::ContentView {
public:
ContentView(char * textBuffer, size_t textBufferSize, KDText::FontSize size,
KDColor textColor, KDColor backgroundColor);
void drawRect(KDContext * ctx, KDRect rect) const override;
KDSize minimalSizeForOptimalDisplay() const override;
void setText(char * textBuffer, size_t textBufferSize);
const char * text() const;
const char * text() const override;
const Text * getText() const { return &m_text; }
int cursorLocation() const { return m_cursorIndex; }
bool insertTextAtLocation(const char * text, int location);
void setCursorLocation(int cursorLocation);
size_t editedTextLength() const override {
return m_text.textLength();
}
bool insertTextAtLocation(const char * text, int location) override;
void moveCursorGeo(int deltaX, int deltaY);
void removeChar();
bool removeEndOfLine();
void removeStartOfLine();
KDRect cursorRect();
bool removeChar() override;
bool removeEndOfLine() override;
bool removeStartOfLine();
private:
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
KDRect characterFrameAtIndex(size_t index);
KDRect dirtyRectFromCursorPosition(size_t index, bool lineBreak);
TextCursorView m_cursorView;
size_t m_cursorIndex;
KDRect characterFrameAtIndex(size_t index) const override;
Text m_text;
KDText::FontSize m_fontSize;
KDColor m_textColor;
KDColor m_backgroundColor;
};
const ContentView * nonEditableContentView() const override { return &m_contentView; }
ContentView m_contentView;
TextAreaDelegate * m_delegate;
};

View File

@@ -1,13 +1,14 @@
#ifndef ESCHER_TEXT_AREA_DELEGATE_H
#define ESCHER_TEXT_AREA_DELEGATE_H
#include <escher/text_input_delegate.h>
class TextArea;
class TextAreaDelegate {
class TextAreaDelegate : public TextInputDelegate {
public:
virtual bool textAreaShouldFinishEditing(TextArea * textArea, Ion::Events::Event event) = 0;
virtual bool textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) = 0;
virtual Toolbox * toolboxForTextArea(TextArea * textArea) = 0;
};
#endif

View File

@@ -1,74 +1,53 @@
#ifndef ESCHER_TEXT_FIELD_H
#define ESCHER_TEXT_FIELD_H
#include <escher/scrollable_view.h>
#include <escher/text_input.h>
#include <escher/text_field_delegate.h>
#include <escher/text_cursor_view.h>
#include <string.h>
class TextField : public ScrollableView, public ScrollViewDataSource {
class TextField : public TextInput {
public:
TextField(Responder * parentResponder, char * textBuffer, char * draftTextBuffer, size_t textBufferSize,
TextFieldDelegate * delegate = nullptr, bool hasTwoBuffers = true, KDText::FontSize size = KDText::FontSize::Large, float horizontalAlignment = 0.0f,
float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor = KDColorWhite);
void setDelegate(TextFieldDelegate * delegate);
void setDelegate(TextFieldDelegate * delegate) { m_delegate = delegate; }
void setDraftTextBuffer(char * draftTextBuffer);
Toolbox * toolbox() override;
bool isEditing() const;
const char * text() const;
int draftTextLength() const;
int cursorLocation() const;
void setCursorLocation(int location);
size_t draftTextLength() const;
void setText(const char * text);
void setBackgroundColor(KDColor backgroundColor);
KDColor backgroundColor() const;
void setTextColor(KDColor textColor);
void setAlignment(float horizontalAlignment, float verticalAlignment);
virtual void setEditing(bool isEditing, bool reinitDraftBuffer = true);
/* If the text to be appended is too long to be added without overflowing the
* buffer, nothing is done (not even adding few letters from the text to reach
* the maximum buffer capacity) and false is returned. */
bool insertTextAtLocation(const char * text, int location);
KDSize minimalSizeForOptimalDisplay() const override;
bool handleEventWithText(const char * text);
bool handleEventWithText(const char * text, bool indenting = false) override;
bool handleEvent(Ion::Events::Event event) override;
bool textFieldShouldFinishEditing(Ion::Events::Event event);
constexpr static int maxBufferSize() {
return ContentView::k_maxBufferSize;
}
void scrollToCursor();
void scrollToCursor() override;
bool textFieldShouldFinishEditing(Ion::Events::Event event) { return m_delegate->textFieldShouldFinishEditing(this, event); }
protected:
class ContentView : public View {
class ContentView : public TextInput::ContentView {
public:
ContentView(char * textBuffer, char * draftTextBuffer, size_t textBufferSize, KDText::FontSize size, float horizontalAlignment = 0.0f,
float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor = KDColorWhite);
ContentView(char * textBuffer, char * draftTextBuffer, size_t textBufferSize, KDText::FontSize size, float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor = KDColorWhite);
void setDraftTextBuffer(char * draftTextBuffer);
void drawRect(KDContext * ctx, KDRect rect) const override;
void reload();
bool isEditing() const { return m_isEditing; }
const char * text() const;
int draftTextLength() const;
int cursorLocation() const { return m_currentCursorLocation; }
const char * text() const override;
size_t editedTextLength() const override;
char * textBuffer() { return m_textBuffer; }
char * draftTextBuffer() { return m_draftTextBuffer; }
int bufferSize() { return k_maxBufferSize; }
void setText(const char * text);
void setBackgroundColor(KDColor backgroundColor);
KDColor backgroundColor() const { return m_backgroundColor; }
void setTextColor(KDColor textColor);
void setAlignment(float horizontalAlignment, float verticalAlignment);
void setEditing(bool isEditing, bool reinitDraftBuffer);
void reinitDraftTextBuffer();
void setCursorLocation(int location);
bool insertTextAtLocation(const char * text, int location);
/* If the text to be appended is too long to be added without overflowing the
* buffer, nothing is done (not even adding few letters from the text to reach
* the maximum buffer capacity) and false is returned. */
bool insertTextAtLocation(const char * text, int location) override;
KDSize minimalSizeForOptimalDisplay() const override;
KDCoordinate textHeight() const;
KDCoordinate textWidth() const;
KDCoordinate charWidth() const;
void deleteCharPrecedingCursor();
bool deleteEndOfLine();
KDRect cursorRect();
View * subviewAtIndex(int index) override { return &m_cursorView; }
bool removeChar() override;
bool removeEndOfLine() override;
/* In some app (ie Calculation), text fields record expression results whose
* lengths can reach 70 (ie
* [[1.234567e-123*e^(1.234567e-123*i), 1.234567e-123*e^(1.234567e-123*i)]]).
@@ -76,27 +55,23 @@ protected:
* over 70. */
constexpr static int k_maxBufferSize = 152;
private:
int numberOfSubviews() const override { return 1; }
void layoutSubviews() override;
KDPoint textOrigin() const;
TextCursorView m_cursorView;
KDRect characterFrameAtIndex(size_t index) const override;
bool m_isEditing;
char * m_textBuffer;
char * m_draftTextBuffer;
size_t m_currentDraftTextLength;
size_t m_currentCursorLocation;
size_t m_textBufferSize;
float m_horizontalAlignment;
float m_verticalAlignment;
KDColor m_textColor;
KDColor m_backgroundColor;
KDText::FontSize m_fontSize;
};
const ContentView * nonEditableContentView() const override { return &m_contentView; }
ContentView m_contentView;
private:
bool privateHandleEvent(Ion::Events::Event event);
void deleteCharPrecedingCursor();
bool deleteEndOfLine();
TextInputDelegate * delegate() override {
return m_delegate;
}
bool m_hasTwoBuffers;
TextFieldDelegate * m_delegate;
};

View File

@@ -1,16 +1,17 @@
#ifndef ESCHER_TEXT_FIELD_DELEGATE_H
#define ESCHER_TEXT_FIELD_DELEGATE_H
#include <escher/text_input_delegate.h>
class TextField;
class TextFieldDelegate {
class TextFieldDelegate : public TextInputDelegate {
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 textFieldDidHandleEvent(TextField * textField, bool returnValue, bool textHasChanged) { return returnValue; };
virtual Toolbox * toolboxForTextField(TextField * textField) = 0;
};
#endif

View File

@@ -0,0 +1,65 @@
#ifndef ESCHER_TEXT_INPUT_H
#define ESCHER_TEXT_INPUT_H
#include <assert.h>
#include <string.h>
#include <escher/scrollable_view.h>
#include <escher/text_cursor_view.h>
#include <escher/text_input_delegate.h>
class TextInput : public ScrollableView, public ScrollViewDataSource {
public:
TextInput(Responder * parentResponder, View * contentView);
Toolbox * toolbox() override;
const char * text() const { return nonEditableContentView()->text(); }
void setBackgroundColor(KDColor backgroundColor);
KDColor backgroundColor() const { return nonEditableContentView()->backgroundColor(); }
void setTextColor(KDColor textColor);
bool removeChar();
size_t cursorLocation() const { return nonEditableContentView()->cursorLocation(); }
bool setCursorLocation(int location);
virtual void scrollToCursor();
virtual bool handleEventWithText(const char * text, bool indenting = false) = 0;
protected:
class ContentView : public View {
public:
ContentView(KDText::FontSize size, KDColor textColor, KDColor backgroundColor);
void setBackgroundColor(KDColor backgroundColor);
KDColor backgroundColor() const { return m_backgroundColor; }
void setTextColor(KDColor textColor);
size_t cursorLocation() const { return m_cursorIndex; }
void setCursorLocation(int cursorLocation);
virtual const char * text() const = 0;
virtual bool insertTextAtLocation(const char * text, int location) = 0;
virtual bool removeChar() = 0;
virtual bool removeEndOfLine() = 0;
KDRect cursorRect();
protected:
virtual void layoutSubviews() override;
void reloadRectFromCursorPosition(size_t index, bool lineBreak = false);
virtual KDRect characterFrameAtIndex(size_t index) const = 0;
TextCursorView m_cursorView;
KDText::FontSize m_fontSize;
KDColor m_textColor;
KDColor m_backgroundColor;
size_t m_cursorIndex;
private:
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
virtual size_t editedTextLength() const = 0;
};
protected:
/* If the text to be appended is too long to be added without overflowing the
* buffer, nothing is done (not even adding few letters from the text to reach
* the maximum buffer capacity) and false is returned. */
bool insertTextAtLocation(const char * textBuffer, int location);
virtual bool removeEndOfLine();
ContentView * contentView() {
return const_cast<ContentView *>(nonEditableContentView());
}
virtual const ContentView * nonEditableContentView() const = 0;
private:
virtual TextInputDelegate * delegate() = 0;
};
#endif

View File

@@ -0,0 +1,11 @@
#ifndef ESCHER_TEXT_INPUT_DELEGATE_H
#define ESCHER_TEXT_INPUT_DELEGATE_H
class TextInput;
class TextInputDelegate {
public:
virtual Toolbox * toolboxForTextInput(TextInput * textInput) = 0;
};
#endif

View File

@@ -77,8 +77,7 @@ void InputViewController::edit(Responder * caller, Ion::Events::Event event, voi
displayModalViewController(&m_textFieldController, 1.0f, 1.0f);
m_textFieldController.textField()->handleEvent(event);
if (initialText != nullptr) {
m_textFieldController.textField()->insertTextAtLocation(initialText, 0);
m_textFieldController.textField()->setCursorLocation(strlen(initialText));
m_textFieldController.textField()->handleEventWithText(initialText);
}
}
@@ -107,6 +106,6 @@ bool InputViewController::textFieldDidReceiveEvent(TextField * textField, Ion::E
return m_textFieldDelegate->textFieldDidReceiveEvent(textField, event);
}
Toolbox * InputViewController::toolboxForTextField(TextField * textField) {
return m_textFieldDelegate->toolboxForTextField(textField);
Toolbox * InputViewController::toolboxForTextInput(TextInput * input) {
return m_textFieldDelegate->toolboxForTextInput(input);
}

View File

@@ -64,7 +64,7 @@ size_t TextArea::Text::indexAtPosition(Position p) {
return endOfLastLine - m_buffer;
}
TextArea::Text::Position TextArea::Text::positionAtIndex(size_t index) {
TextArea::Text::Position TextArea::Text::positionAtIndex(size_t index) const {
assert(m_buffer != nullptr);
assert(index < m_bufferSize);
const char * target = m_buffer + index;
@@ -151,12 +151,8 @@ TextArea::Text::Position TextArea::Text::span() const {
/* TextArea::ContentView */
TextArea::ContentView::ContentView(char * textBuffer, size_t textBufferSize, KDText::FontSize fontSize, KDColor textColor, KDColor backgroundColor) :
View(),
m_cursorIndex(0),
m_text(textBuffer, textBufferSize),
m_fontSize(fontSize),
m_textColor(textColor),
m_backgroundColor(backgroundColor)
TextInput::ContentView(fontSize, textColor, backgroundColor),
m_text(textBuffer, textBufferSize)
{
}
@@ -207,18 +203,6 @@ 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::setText(char * textBuffer, size_t textBufferSize) {
m_text.setText(textBuffer, textBufferSize);
m_cursorIndex = 0;
@@ -239,47 +223,47 @@ bool TextArea::TextArea::ContentView::insertTextAtLocation(const char * text, in
lineBreak |= *text == '\n';
m_text.insertChar(*text++, currentLocation++);
}
markRectAsDirty(dirtyRectFromCursorPosition(currentLocation-1, lineBreak));
reloadRectFromCursorPosition(currentLocation-1, lineBreak);
return true;
}
void TextArea::TextArea::ContentView::removeChar() {
bool lineBreak = false;
if (m_cursorIndex > 0) {
lineBreak = m_text.removeChar(--m_cursorIndex) == '\n';
bool TextArea::TextArea::ContentView::removeChar() {
if (cursorLocation() <= 0) {
return false;
}
bool lineBreak = false;
assert(m_cursorIndex > 0);
lineBreak = m_text.removeChar(--m_cursorIndex) == '\n';
layoutSubviews(); // Reposition the cursor
markRectAsDirty(dirtyRectFromCursorPosition(m_cursorIndex, lineBreak));
reloadRectFromCursorPosition(cursorLocation(), lineBreak);
return true;
}
bool TextArea::ContentView::removeEndOfLine() {
int removedLine = m_text.removeRemainingLine(m_cursorIndex, 1);
int removedLine = m_text.removeRemainingLine(cursorLocation(), 1);
if (removedLine > 0) {
layoutSubviews();
markRectAsDirty(dirtyRectFromCursorPosition(m_cursorIndex, false));
reloadRectFromCursorPosition(cursorLocation(), false);
return true;
}
return false;
}
void TextArea::ContentView::removeStartOfLine() {
if (m_cursorIndex <= 0) {
return;
bool TextArea::ContentView::removeStartOfLine() {
if (cursorLocation() <= 0) {
return false;
}
int removedLine = m_text.removeRemainingLine(m_cursorIndex-1, -1);
int removedLine = m_text.removeRemainingLine(cursorLocation()-1, -1);
if (removedLine > 0) {
assert(m_cursorIndex >= removedLine);
m_cursorIndex -= removedLine;
layoutSubviews();
markRectAsDirty(dirtyRectFromCursorPosition(m_cursorIndex, false));
setCursorLocation(cursorLocation()-removedLine);
reloadRectFromCursorPosition(cursorLocation(), false);
return true;
}
return false;
}
KDRect TextArea::TextArea::ContentView::cursorRect() {
return characterFrameAtIndex(m_cursorIndex);
}
KDRect TextArea::TextArea::ContentView::characterFrameAtIndex(size_t index) {
KDRect TextArea::TextArea::ContentView::characterFrameAtIndex(size_t index) const {
KDSize charSize = KDText::charSize(m_fontSize);
Text::Position p = m_text.positionAtIndex(index);
return KDRect(
@@ -292,24 +276,7 @@ KDRect TextArea::TextArea::ContentView::characterFrameAtIndex(size_t index) {
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::setCursorLocation(int location) {
int adjustedLocation = location < 0 ? 0 : location;
adjustedLocation = adjustedLocation > (signed int)m_text.textLength() ? (signed int)m_text.textLength() : adjustedLocation;
m_cursorIndex = adjustedLocation;
layoutSubviews();
}
KDRect TextArea::TextArea::ContentView::dirtyRectFromCursorPosition(size_t index, bool lineBreak) {
KDRect charRect = characterFrameAtIndex(index);
KDRect dirtyRect = KDRect(charRect.x(), charRect.y(), bounds().width() - charRect.x(), charRect.height());
if (lineBreak) {
dirtyRect = dirtyRect.unionedWith(KDRect(0, charRect.bottom()+1, bounds().width(), bounds().height()-charRect.bottom()-1));
}
return dirtyRect;
setCursorLocation(m_text.indexAtPosition(Text::Position(p.column() + deltaX, p.line() + deltaY)));
}
/* TextArea */
@@ -317,20 +284,13 @@ KDRect TextArea::TextArea::ContentView::dirtyRectFromCursorPosition(size_t index
TextArea::TextArea(Responder * parentResponder, char * textBuffer,
size_t textBufferSize, TextAreaDelegate * delegate,
KDText::FontSize fontSize, KDColor textColor, KDColor backgroundColor) :
ScrollableView(parentResponder, &m_contentView, this),
TextInput(parentResponder, &m_contentView),
m_contentView(textBuffer, textBufferSize, fontSize, textColor, backgroundColor),
m_delegate(delegate)
{
assert(textBufferSize < INT_MAX/2);
}
Toolbox * TextArea::toolbox() {
if (m_delegate != nullptr) {
return m_delegate->toolboxForTextArea(this);
}
return nullptr;
}
bool TextArea::handleEventWithText(const char * text, bool indentation) {
int nextCursorLocation = cursorLocation();
if ((indentation && insertTextWithIndentation(text, cursorLocation())) || insertTextAtLocation(text, cursorLocation())) {
@@ -342,6 +302,7 @@ bool TextArea::handleEventWithText(const char * text, bool indentation) {
bool TextArea::handleEvent(Ion::Events::Event event) {
if (m_delegate != nullptr && m_delegate->textAreaDidReceiveEvent(this, event)) {
return true;
} else if (Responder::handleEvent(event)) {
// The only event Responder handles is 'Toolbox' displaying.
return true;
@@ -372,14 +333,7 @@ bool TextArea::handleEvent(Ion::Events::Event event) {
} else {
return false;
}
/* Technically, we do not need to overscroll in text area. However,
* logically, we should layout the scroll view before calling
* scrollToContentRect in case the size of the scroll view has changed and
* then call scrollToContentRect which call another layout of the scroll view
* if the offset has evolved. In order to avoid requiring two layouts, we
* allow overscrolling in scrollToContentRect and the last layout of the
* scroll view corrects the size of the scroll view only once. */
scrollToContentRect(m_contentView.cursorRect(), true);
scrollToCursor();
return true;
}
@@ -431,15 +385,3 @@ int TextArea::indentationBeforeCursor() const {
}
return indentationSize;
}
bool TextArea::setCursorLocation(int location) {
m_contentView.setCursorLocation(location);
scrollToContentRect(m_contentView.cursorRect(), true);
return true;
}
bool TextArea::removeChar() {
m_contentView.removeChar();
scrollToContentRect(m_contentView.cursorRect(), true);
return true;
}

View File

@@ -6,18 +6,14 @@
/* TextField::ContentView */
TextField::ContentView::ContentView(char * textBuffer, char * draftTextBuffer, size_t textBufferSize, KDText::FontSize size, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor) :
View(),
TextInput::ContentView(size, textColor, backgroundColor),
m_isEditing(false),
m_textBuffer(textBuffer),
m_draftTextBuffer(draftTextBuffer),
m_currentDraftTextLength(0),
m_currentCursorLocation(0),
m_textBufferSize(textBufferSize),
m_horizontalAlignment(horizontalAlignment),
m_verticalAlignment(verticalAlignment),
m_textColor(textColor),
m_backgroundColor(backgroundColor),
m_fontSize(size)
m_verticalAlignment(verticalAlignment)
{
assert(m_textBufferSize <= k_maxBufferSize);
}
@@ -32,14 +28,7 @@ void TextField::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
bckCol = KDColorWhite;
}
ctx->fillRect(rect, bckCol);
ctx->drawString(text(), textOrigin(), m_fontSize, m_textColor, bckCol);
}
void TextField::ContentView::reload() {
KDSize textSize = KDText::stringSize(text(), m_fontSize);
KDSize textAndCursorSize = KDSize(textSize.width()+ m_cursorView.minimalSizeForOptimalDisplay().width(), textSize.height());
KDRect dirtyZone(textOrigin(), textAndCursorSize);
markRectAsDirty(dirtyZone);
ctx->drawString(text(), characterFrameAtIndex(0).origin(), m_fontSize, m_textColor, bckCol);
}
const char * TextField::ContentView::text() const {
@@ -49,14 +38,12 @@ const char * TextField::ContentView::text() const {
return const_cast<const char *>(m_textBuffer);
}
int TextField::ContentView::draftTextLength() const {
assert(isEditing());
assert(strlen(text()) == m_currentDraftTextLength);
size_t TextField::ContentView::editedTextLength() const {
return m_currentDraftTextLength;
}
void TextField::ContentView::setText(const char * text) {
reload();
reloadRectFromCursorPosition(0);
if (m_isEditing) {
strlcpy(m_draftTextBuffer, text, m_textBufferSize);
int textLength = strlen(text) >= m_textBufferSize ? m_textBufferSize-1 : strlen(text);
@@ -64,17 +51,7 @@ void TextField::ContentView::setText(const char * text) {
} else {
strlcpy(m_textBuffer, text, m_textBufferSize);
}
reload();
}
void TextField::ContentView::setBackgroundColor(KDColor backgroundColor) {
m_backgroundColor = backgroundColor;
markRectAsDirty(bounds());
}
void TextField::ContentView::setTextColor(KDColor textColor) {
m_textColor = textColor;
markRectAsDirty(bounds());
reloadRectFromCursorPosition(0);
}
void TextField::ContentView::setAlignment(float horizontalAlignment, float verticalAlignment) {
@@ -101,13 +78,6 @@ void TextField::ContentView::reinitDraftTextBuffer() {
m_currentDraftTextLength = 0;
}
void TextField::ContentView::setCursorLocation(int location) {
int adjustedLocation = location < 0 ? 0 : location;
adjustedLocation = adjustedLocation > (signed int)m_currentDraftTextLength ? (signed int)m_currentDraftTextLength : adjustedLocation;
m_currentCursorLocation = adjustedLocation;
layoutSubviews();
}
bool TextField::ContentView::insertTextAtLocation(const char * text, int location) {
int textSize = strlen(text);
if (m_currentDraftTextLength + textSize >= m_textBufferSize || textSize == 0) {
@@ -121,58 +91,47 @@ bool TextField::ContentView::insertTextAtLocation(const char * text, int locatio
m_draftTextBuffer[location+textSize-1] = text[textSize-1];
}
m_currentDraftTextLength += textSize;
reload();
reloadRectFromCursorPosition((m_horizontalAlignment == 0.0f ? location : 0));
return true;
}
KDSize TextField::ContentView::minimalSizeForOptimalDisplay() const {
KDSize charSize = KDText::charSize(m_fontSize);
if (m_isEditing) {
return KDSize(textWidth()+m_cursorView.minimalSizeForOptimalDisplay().width(), textHeight());
return KDSize(charSize.width()*strlen(text())+m_cursorView.minimalSizeForOptimalDisplay().width(), charSize.height());
}
return KDSize(textWidth(), textHeight());
return KDSize(charSize.width()*strlen(text()), charSize.height());
}
KDCoordinate TextField::ContentView::textHeight() const {
KDSize textSize = KDText::charSize(m_fontSize);
return textSize.height();
}
KDCoordinate TextField::ContentView::charWidth() const {
KDSize textSize = KDText::charSize(m_fontSize);
return textSize.width();
}
KDCoordinate TextField::ContentView::textWidth() const {
return strlen(text())*charWidth();
}
void TextField::ContentView::deleteCharPrecedingCursor() {
bool TextField::ContentView::removeChar() {
if (cursorLocation() <= 0) {
return;
return false;
}
reload();
m_currentDraftTextLength--;
setCursorLocation(m_currentCursorLocation-1);
for (int k = m_currentCursorLocation; k < (signed char)m_currentDraftTextLength; k ++) {
if (m_horizontalAlignment > 0.0f) {
reloadRectFromCursorPosition(0);
}
setCursorLocation(cursorLocation()-1);
if( m_horizontalAlignment == 0.0f) {
reloadRectFromCursorPosition(cursorLocation());
}
for (int k = cursorLocation(); k < (signed char)m_currentDraftTextLength; k ++) {
m_draftTextBuffer[k] = m_draftTextBuffer[k+1];
}
m_draftTextBuffer[m_currentDraftTextLength] = 0;
layoutSubviews();
}
bool TextField::ContentView::deleteEndOfLine() {
if (m_currentDraftTextLength == m_currentCursorLocation) {
return false;
}
reload();
m_currentDraftTextLength = m_currentCursorLocation;
m_draftTextBuffer[m_currentCursorLocation] = 0;
layoutSubviews();
return true;
}
KDRect TextField::ContentView::cursorRect() {
return KDRect(m_currentCursorLocation * charWidth(), 0, m_cursorView.minimalSizeForOptimalDisplay());
bool TextField::ContentView::removeEndOfLine() {
if (m_currentDraftTextLength == cursorLocation()) {
return false;
}
reloadRectFromCursorPosition((m_horizontalAlignment == 0.0f ? cursorLocation() : 0));
m_currentDraftTextLength = cursorLocation();
m_draftTextBuffer[cursorLocation()] = 0;
layoutSubviews();
return true;
}
void TextField::ContentView::layoutSubviews() {
@@ -180,62 +139,39 @@ void TextField::ContentView::layoutSubviews() {
m_cursorView.setFrame(KDRectZero);
return;
}
TextInput::ContentView::layoutSubviews();
}
KDRect TextField::ContentView::characterFrameAtIndex(size_t index) const {
KDSize charSize = KDText::charSize(m_fontSize);
KDSize textSize = KDText::stringSize(text(), m_fontSize);
KDCoordinate cursorWidth = m_cursorView.minimalSizeForOptimalDisplay().width();
KDRect frame(m_horizontalAlignment*(m_frame.width() - textSize.width()-cursorWidth)+ m_currentCursorLocation * charWidth(), m_verticalAlignment*(m_frame.height() - textSize.height()), cursorWidth, textSize.height());
m_cursorView.setFrame(frame);
return KDRect(m_horizontalAlignment*(m_frame.width() - textSize.width()-cursorWidth)+ index * charSize.width(), m_verticalAlignment*(m_frame.height() - charSize.height()), charSize);
}
/* TextField */
TextField::TextField(Responder * parentResponder, char * textBuffer, char * draftTextBuffer,
size_t textBufferSize, TextFieldDelegate * delegate, bool hasTwoBuffers, KDText::FontSize size,
float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor) :
ScrollableView(parentResponder, &m_contentView, this),
TextInput(parentResponder, &m_contentView),
m_contentView(textBuffer, draftTextBuffer, textBufferSize, size,horizontalAlignment, verticalAlignment, textColor, backgroundColor),
m_hasTwoBuffers(hasTwoBuffers),
m_delegate(delegate)
{
}
KDPoint TextField::ContentView::textOrigin() const {
KDSize textSize = KDText::stringSize(text(), m_fontSize);
return KDPoint(m_horizontalAlignment*(m_frame.width() - textSize.width()-m_cursorView.minimalSizeForOptimalDisplay().width()), m_verticalAlignment*(m_frame.height() - textSize.height()));
}
void TextField::setDelegate(TextFieldDelegate * delegate) {
m_delegate = delegate;
}
void TextField::setDraftTextBuffer(char * draftTextBuffer) {
m_contentView.setDraftTextBuffer(draftTextBuffer);
}
Toolbox * TextField::toolbox() {
if (m_delegate) {
return m_delegate->toolboxForTextField(this);
}
return nullptr;
}
bool TextField::isEditing() const {
return m_contentView.isEditing();
}
const char * TextField::text() const {
return m_contentView.text();
}
int TextField::draftTextLength() const {
size_t TextField::draftTextLength() const {
assert(isEditing());
return m_contentView.draftTextLength();
}
int TextField::cursorLocation() const{
return m_contentView.cursorLocation();
}
void TextField::setCursorLocation(int location) {
m_contentView.setCursorLocation(location);
scrollToCursor();
return m_contentView.editedTextLength();
}
void TextField::setText(const char * text) {
@@ -246,18 +182,6 @@ void TextField::setText(const char * text) {
}
}
void TextField::setBackgroundColor(KDColor backgroundColor) {
m_contentView.setBackgroundColor(backgroundColor);
}
KDColor TextField::backgroundColor() const {
return m_contentView.backgroundColor();
}
void TextField::setTextColor(KDColor textColor) {
m_contentView.setTextColor(textColor);
}
void TextField::setAlignment(float horizontalAlignment, float verticalAlignment) {
m_contentView.setAlignment(horizontalAlignment, verticalAlignment);
}
@@ -269,14 +193,6 @@ void TextField::setEditing(bool isEditing, bool reinitDrafBuffer) {
}
}
bool TextField::insertTextAtLocation(const char * text, int location) {
if (m_contentView.insertTextAtLocation(text, location)) {
scrollToCursor();
return true;
}
return false;
}
bool TextField::privateHandleEvent(Ion::Events::Event event) {
if (Responder::handleEvent(event)) {
/* The only event Responder handles is 'Toolbox' displaying. In that case,
@@ -287,20 +203,16 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) {
return true;
}
if (event == Ion::Events::Left && isEditing() && cursorLocation() > 0) {
setCursorLocation(cursorLocation()-1);
return true;
return setCursorLocation(cursorLocation()-1);
}
if (event == Ion::Events::ShiftLeft && isEditing()) {
setCursorLocation(0);
return true;
return setCursorLocation(0);
}
if (event == Ion::Events::Right && isEditing() && cursorLocation() < draftTextLength()) {
setCursorLocation(cursorLocation()+1);
return true;
return setCursorLocation(cursorLocation()+1);
}
if (event == Ion::Events::ShiftRight && isEditing()) {
setCursorLocation(draftTextLength());
return true;
return setCursorLocation(draftTextLength());
}
if (isEditing() && textFieldShouldFinishEditing(event)) {
char bufferText[ContentView::k_maxBufferSize];
@@ -314,7 +226,7 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) {
reloadScroll(true);
return true;
}
/* if the text was refused (textFieldDidFinishEditing returned false, we
/* if the text was refused (textInputDidFinishEditing returned false, we
* reset the textfield in the same state as before */
char bufferDraft[ContentView::k_maxBufferSize];
strlcpy(bufferDraft, m_contentView.textBuffer(), ContentView::k_maxBufferSize);
@@ -324,20 +236,8 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) {
setCursorLocation(cursorLoc);
return true;
}
if ((event == Ion::Events::OK || event == Ion::Events::EXE) && !isEditing()) {
setEditing(true);
/* If the text could not be inserted (buffer is full), we set the cursor
* at the end of the text. */
int nextCursorLocation = draftTextLength();
if (insertTextAtLocation(m_contentView.textBuffer(), cursorLocation())) {
nextCursorLocation = strlen(m_contentView.draftTextBuffer());
}
setCursorLocation(nextCursorLocation);
return true;
}
if (event == Ion::Events::Backspace && isEditing()) {
deleteCharPrecedingCursor();
return true;
return removeChar();
}
if (event == Ion::Events::Back && isEditing()) {
setEditing(false);
@@ -346,7 +246,7 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) {
return true;
}
if (event == Ion::Events::Clear && isEditing()) {
if (!deleteEndOfLine()) {
if (!removeEndOfLine()) {
setEditing(true, true);
}
return true;
@@ -363,27 +263,10 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) {
return false;
}
void TextField::deleteCharPrecedingCursor() {
m_contentView.deleteCharPrecedingCursor();
scrollToCursor();
}
bool TextField::deleteEndOfLine() {
if (m_contentView.deleteEndOfLine()) {
scrollToCursor();
return true;
}
return false;
}
KDSize TextField::minimalSizeForOptimalDisplay() const {
return m_contentView.minimalSizeForOptimalDisplay();
}
bool TextField::textFieldShouldFinishEditing(Ion::Events::Event event) {
return m_delegate->textFieldShouldFinishEditing(this, event);
}
bool TextField::handleEvent(Ion::Events::Event event) {
assert(m_delegate != nullptr);
if (m_delegate->textFieldDidReceiveEvent(this, event)) {
@@ -395,6 +278,9 @@ bool TextField::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::Paste) {
return handleEventWithText(Clipboard::sharedClipboard()->storedText());
}
if ((event == Ion::Events::OK || event == Ion::Events::EXE) && !isEditing()) {
return handleEventWithText(m_contentView.textBuffer());
}
size_t previousTextLength = strlen(text());
bool didHandleEvent = privateHandleEvent(event);
return m_delegate->textFieldDidHandleEvent(this, didHandleEvent, strlen(text()) != previousTextLength);
@@ -404,10 +290,10 @@ void TextField::scrollToCursor() {
if (!isEditing()) {
return;
}
scrollToContentRect(m_contentView.cursorRect(), true);
return TextInput::scrollToCursor();
}
bool TextField::handleEventWithText(const char * eventText) {
bool TextField::handleEventWithText(const char * eventText, bool indentation) {
size_t previousTextLength = strlen(text());
if (!isEditing()) {
setEditing(true);

120
escher/src/text_input.cpp Normal file
View File

@@ -0,0 +1,120 @@
#include <escher/text_input.h>
#include <assert.h>
/* TextInput::ContentView */
TextInput::ContentView::ContentView(KDText::FontSize size, KDColor textColor, KDColor backgroundColor) :
View(),
m_cursorView(),
m_fontSize(size),
m_textColor(textColor),
m_backgroundColor(backgroundColor),
m_cursorIndex(0)
{
}
void TextInput::ContentView::setBackgroundColor(KDColor backgroundColor) {
m_backgroundColor = backgroundColor;
markRectAsDirty(bounds());
}
void TextInput::ContentView::setTextColor(KDColor textColor) {
m_textColor = textColor;
markRectAsDirty(bounds());
}
void TextInput::ContentView::setCursorLocation(int location) {
int adjustedLocation = location < 0 ? 0 : location;
adjustedLocation = adjustedLocation > (signed int)editedTextLength() ? (signed int)editedTextLength() : adjustedLocation;
m_cursorIndex = adjustedLocation;
layoutSubviews();
}
KDRect TextInput::ContentView::cursorRect() {
return characterFrameAtIndex(m_cursorIndex);
}
int TextInput::ContentView::numberOfSubviews() const {
return 1;
}
View * TextInput::ContentView::subviewAtIndex(int index) {
return &m_cursorView;
}
void TextInput::ContentView::layoutSubviews() {
m_cursorView.setFrame(cursorRect());
}
void TextInput::ContentView::reloadRectFromCursorPosition(size_t index, bool lineBreak) {
KDRect charRect = characterFrameAtIndex(index);
KDRect dirtyRect = KDRect(charRect.x(), charRect.y(), bounds().width() - charRect.x(), charRect.height());
if (lineBreak) {
dirtyRect = dirtyRect.unionedWith(KDRect(0, charRect.bottom()+1, bounds().width(), bounds().height()-charRect.bottom()-1));
}
markRectAsDirty(dirtyRect);
}
/* TextInput */
TextInput::TextInput(Responder * parentResponder, View * contentView) :
ScrollableView(parentResponder, contentView, this)
{
}
Toolbox * TextInput::toolbox() {
if (delegate()) {
return delegate()->toolboxForTextInput(this);
}
return nullptr;
}
void TextInput::setBackgroundColor(KDColor backgroundColor) {
contentView()->setBackgroundColor(backgroundColor);
}
void TextInput::setTextColor(KDColor textColor) {
contentView()->setTextColor(textColor);
}
bool TextInput::removeChar() {
contentView()->removeChar();
scrollToCursor();
return true;
}
void TextInput::scrollToCursor() {
/* Technically, we do not need to overscroll in text input. However,
* logically, we should layout the scroll view before calling
* scrollToContentRect in case the size of the scroll view has changed and
* then call scrollToContentRect which call another layout of the scroll view
* if the offset has evolved. In order to avoid requiring two layouts, we
* allow overscrolling in scrollToContentRect and the last layout of the
* scroll view corrects the size of the scroll view only once. */
scrollToContentRect(contentView()->cursorRect(), true);
}
bool TextInput::setCursorLocation(int location) {
contentView()->setCursorLocation(location);
scrollToCursor();
return true;
}
bool TextInput::insertTextAtLocation(const char * text, int location) {
if (contentView()->insertTextAtLocation(text, location)) {
/* We layout the scrollable view before scrolling to cursor because the
* content size might have changed. */
layoutSubviews();
scrollToCursor();
return true;
}
return false;
}
bool TextInput::removeEndOfLine() {
if (contentView()->removeEndOfLine()) {
scrollToCursor();
return true;
}
return false;
}