From 84dfd4f7f31ee30fca51cc1d84bc5233f833cc5e Mon Sep 17 00:00:00 2001 From: Eduardo Bart Date: Sun, 20 Jan 2013 11:38:02 -0200 Subject: [PATCH] UITextEdit selection, closes #55 --- data/styles/10-textedits.otui | 3 +- src/framework/graphics/particlemanager.cpp | 1 - src/framework/luafunctions.cpp | 21 +- src/framework/ui/uitextedit.cpp | 245 ++++++++++++++++++--- src/framework/ui/uitextedit.h | 38 +++- 5 files changed, 271 insertions(+), 37 deletions(-) diff --git a/data/styles/10-textedits.otui b/data/styles/10-textedits.otui index 4a1d4600..b2fea47e 100644 --- a/data/styles/10-textedits.otui +++ b/data/styles/10-textedits.otui @@ -6,7 +6,8 @@ TextEdit < UITextEdit padding: 3 image-source: /images/ui/textedit image-border: 1 - + selection-color: #111416 + selection-background-color: #999999 $disabled: color: #aaaaaa88 diff --git a/src/framework/graphics/particlemanager.cpp b/src/framework/graphics/particlemanager.cpp index 3629ae8b..e4ed4287 100644 --- a/src/framework/graphics/particlemanager.cpp +++ b/src/framework/graphics/particlemanager.cpp @@ -42,7 +42,6 @@ bool ParticleManager::importParticle(std::string file) ParticleTypePtr particleType = ParticleTypePtr(new ParticleType); particleType->load(node); m_particleTypes[particleType->getName()] = particleType; - dump << particleType->getName(); } } return true; diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index e73dbc55..11fab2fa 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -649,26 +649,45 @@ void Application::registerLuaFunctions() g_lua.registerClass(); g_lua.bindClassStaticFunction("create", []{ return UITextEditPtr(new UITextEdit); } ); g_lua.bindClassMemberFunction("setCursorPos", &UITextEdit::setCursorPos); + g_lua.bindClassMemberFunction("setSelection", &UITextEdit::setSelection); g_lua.bindClassMemberFunction("setCursorVisible", &UITextEdit::setCursorVisible); g_lua.bindClassMemberFunction("setTextHidden", &UITextEdit::setTextHidden); g_lua.bindClassMemberFunction("setValidCharacters", &UITextEdit::setValidCharacters); g_lua.bindClassMemberFunction("setShiftNavigation", &UITextEdit::setShiftNavigation); g_lua.bindClassMemberFunction("setMultiline", &UITextEdit::setMultiline); g_lua.bindClassMemberFunction("setEditable", &UITextEdit::setEditable); + g_lua.bindClassMemberFunction("setSelectable", &UITextEdit::setSelectable); + g_lua.bindClassMemberFunction("setSelectionColor", &UITextEdit::setSelectionColor); + g_lua.bindClassMemberFunction("setSelectionBackgroundColor", &UITextEdit::setSelectionBackgroundColor); g_lua.bindClassMemberFunction("setMaxLength", &UITextEdit::setMaxLength); g_lua.bindClassMemberFunction("setTextVirtualOffset", &UITextEdit::setTextVirtualOffset); g_lua.bindClassMemberFunction("getTextVirtualOffset", &UITextEdit::getTextVirtualOffset); g_lua.bindClassMemberFunction("getTextVirtualSize", &UITextEdit::getTextVirtualSize); g_lua.bindClassMemberFunction("getTextTotalSize", &UITextEdit::getTextTotalSize); - g_lua.bindClassMemberFunction("moveCursor", &UITextEdit::moveCursor); + g_lua.bindClassMemberFunction("moveCursorHorizontally", &UITextEdit::moveCursorHorizontally); + g_lua.bindClassMemberFunction("moveCursorVertically", &UITextEdit::moveCursorVertically); g_lua.bindClassMemberFunction("appendText", &UITextEdit::appendText); g_lua.bindClassMemberFunction("wrapText", &UITextEdit::wrapText); g_lua.bindClassMemberFunction("removeCharacter", &UITextEdit::removeCharacter); + g_lua.bindClassMemberFunction("blinkCursor", &UITextEdit::blinkCursor); + g_lua.bindClassMemberFunction("del", &UITextEdit::del); + g_lua.bindClassMemberFunction("paste", &UITextEdit::paste); + g_lua.bindClassMemberFunction("copy", &UITextEdit::copy); + g_lua.bindClassMemberFunction("cut", &UITextEdit::cut); + g_lua.bindClassMemberFunction("selectAll", &UITextEdit::selectAll); + g_lua.bindClassMemberFunction("clearSelection", &UITextEdit::clearSelection); g_lua.bindClassMemberFunction("getDisplayedText", &UITextEdit::getDisplayedText); + g_lua.bindClassMemberFunction("getSelection", &UITextEdit::getSelection); g_lua.bindClassMemberFunction("getTextPos", &UITextEdit::getTextPos); g_lua.bindClassMemberFunction("getCursorPos", &UITextEdit::getCursorPos); g_lua.bindClassMemberFunction("getMaxLength", &UITextEdit::getMaxLength); + g_lua.bindClassMemberFunction("getSelectionStart", &UITextEdit::getSelectionStart); + g_lua.bindClassMemberFunction("getSelectionEnd", &UITextEdit::getSelectionEnd); + g_lua.bindClassMemberFunction("getSelectionColor", &UITextEdit::getSelectionColor); + g_lua.bindClassMemberFunction("getSelectionBackgroundColor", &UITextEdit::getSelectionBackgroundColor); + g_lua.bindClassMemberFunction("hasSelection", &UITextEdit::hasSelection); g_lua.bindClassMemberFunction("isEditable", &UITextEdit::isEditable); + g_lua.bindClassMemberFunction("isSelectable", &UITextEdit::isSelectable); g_lua.bindClassMemberFunction("isCursorVisible", &UITextEdit::isCursorVisible); g_lua.bindClassMemberFunction("isTextHidden", &UITextEdit::isTextHidden); g_lua.bindClassMemberFunction("isShiftNavigation", &UITextEdit::isShiftNavigation); diff --git a/src/framework/ui/uitextedit.cpp b/src/framework/ui/uitextedit.cpp index e52a9a67..2f2d4058 100644 --- a/src/framework/ui/uitextedit.cpp +++ b/src/framework/ui/uitextedit.cpp @@ -40,6 +40,13 @@ UITextEdit::UITextEdit() m_cursorInRange = true; m_maxLength = 0; m_editable = true; + m_selectable = true; + m_selectionReference = 0; + m_selectionStart = 0; + m_selectionEnd = 0; + m_updatesEnabled = true; + m_selectionColor = Color::white; + m_selectionBackgroundColor = Color::black; blinkCursor(); } @@ -60,9 +67,27 @@ void UITextEdit::drawSelf(Fw::DrawPane drawPane) if(!texture) return; - g_painter->setColor(m_color); - for(int i=0;idrawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i]); + if(hasSelection()) { + g_painter->setColor(m_color); + for(int i=0;idrawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i]); + + for(int i=m_selectionStart;isetColor(m_selectionBackgroundColor); + g_painter->drawFilledRect(m_glyphsCoords[i]); + g_painter->setColor(m_selectionColor); + g_painter->drawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i]); + } + + g_painter->setColor(m_color); + for(int i=m_selectionEnd;idrawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i]); + } else { + g_painter->setColor(m_color); + for(int i=0;idrawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i]); + } + // render cursor if(isExplicitlyEnabled() && m_cursorVisible && m_cursorInRange && isActive() && m_cursorPos >= 0) { @@ -77,15 +102,26 @@ void UITextEdit::drawSelf(Fw::DrawPane drawPane) cursorRect = Rect(m_rect.left()+m_padding.left, m_rect.top()+m_padding.top, 1, m_font->getGlyphHeight()); else cursorRect = Rect(m_glyphsCoords[m_cursorPos-1].right(), m_glyphsCoords[m_cursorPos-1].top(), 1, m_font->getGlyphHeight()); + + if(hasSelection() && m_cursorPos >= m_selectionStart && m_cursorPos <= m_selectionEnd) + g_painter->setColor(m_selectionColor); + else + g_painter->setColor(m_color); + g_painter->drawFilledRect(cursorRect); } else if(elapsed >= 2*delay) { m_cursorTicks = g_clock.millis(); } } + + g_painter->resetColor(); } void UITextEdit::update(bool focusCursor) { + if(!m_updatesEnabled) + return; + std::string text = getDisplayedText(); int textLength = text.length(); @@ -111,7 +147,7 @@ void UITextEdit::update(bool focusCursor) // readjust start view area based on cursor position m_cursorInRange = false; if(focusCursor) { - if(m_cursorPos >= 0 && textLength > 0) { + if(m_cursorPos > 0 && textLength > 0) { assert(m_cursorPos <= textLength); Rect virtualRect(m_textVirtualOffset, m_rect.size() - Size(m_padding.left+m_padding.right, 0)); // previous rendered virtual rect int pos = m_cursorPos - 1; // element before cursor @@ -145,7 +181,7 @@ void UITextEdit::update(bool focusCursor) } m_cursorInRange = true; } else { - if(m_cursorPos >= 0 && textLength > 0) { + if(m_cursorPos > 0 && textLength > 0) { Rect virtualRect(m_textVirtualOffset, m_rect.size() - Size(2*m_padding.left+m_padding.right, 0) ); // previous rendered virtual rect int pos = m_cursorPos - 1; // element before cursor glyph = (uchar)text[pos]; // glyph of the element before cursor @@ -268,6 +304,8 @@ void UITextEdit::update(bool focusCursor) if(fireAreaUpdate) onTextAreaUpdate(m_textVirtualOffset, m_textVirtualSize, m_textTotalSize); + + g_app.repaint(); } void UITextEdit::setCursorPos(int pos) @@ -280,10 +318,23 @@ void UITextEdit::setCursorPos(int pos) else m_cursorPos = pos; update(true); - g_app.repaint(); } } +void UITextEdit::setSelection(int start, int end) +{ + if(start == m_selectionStart && end == m_selectionEnd) + return; + + if(start > end) + std::swap(start, end); + + m_selectionStart = std::min(std::max(start, 0), (int)m_text.length()); + m_selectionEnd = std::min(std::max(end, 0), (int)m_text.length()); + + onSelectionChange(getSelection(), m_selectionStart, m_selectionEnd); +} + void UITextEdit::setTextHidden(bool hidden) { m_textHidden = true; @@ -298,6 +349,9 @@ void UITextEdit::setTextVirtualOffset(const Point& offset) void UITextEdit::appendText(std::string text) { + if(hasSelection()) + del(); + if(m_cursorPos >= 0) { // replace characters that are now allowed if(!m_multiline) @@ -320,9 +374,7 @@ void UITextEdit::appendText(std::string text) std::string oldText = m_text; m_text.insert(m_cursorPos, text); m_cursorPos += text.length(); - blinkCursor(); - update(true); - UIWidget::onTextChange(m_text, oldText); + onTextChange(m_text, oldText); } } } @@ -332,6 +384,9 @@ void UITextEdit::appendCharacter(char c) if((c == '\n' && !m_multiline) || c == '\r') return; + if(hasSelection()) + del(); + if(m_cursorPos >= 0) { if(m_maxLength > 0 && m_text.length() + 1 > m_maxLength) return; @@ -344,9 +399,7 @@ void UITextEdit::appendCharacter(char c) std::string oldText = m_text; m_text.insert(m_cursorPos, tmp); m_cursorPos++; - blinkCursor(); - update(true); - UIWidget::onTextChange(m_text, oldText); + onTextChange(m_text, oldText); } } @@ -362,18 +415,59 @@ void UITextEdit::removeCharacter(bool right) else if(m_cursorPos > 0) m_text.erase(m_text.begin() + --m_cursorPos); } - blinkCursor(); - update(true); - UIWidget::onTextChange(m_text, oldText); + onTextChange(m_text, oldText); } } +void UITextEdit::blinkCursor() +{ + m_cursorTicks = g_clock.millis(); + g_app.repaint(); +} + +void UITextEdit::del(bool right) +{ + if(hasSelection()) { + std::string oldText = m_text; + m_text.erase(m_selectionStart, m_selectionEnd - m_selectionStart); + + setCursorPos(m_selectionStart); + clearSelection(); + onTextChange(m_text, oldText); + } else + removeCharacter(right); +} + +void UITextEdit::paste(const std::string& text) +{ + if(hasSelection()) + del(); + appendText(text); +} + +std::string UITextEdit::copy() +{ + std::string text; + if(hasSelection()) { + text = getSelection(); + g_window.setClipboardText(text); + } + return text; +} + +std::string UITextEdit::cut() +{ + std::string text = copy(); + del(); + return text; +} + void UITextEdit::wrapText() { setText(m_font->wrapText(m_text, getPaddingRect().width())); } -void UITextEdit::moveCursor(bool right) +void UITextEdit::moveCursorHorizontally(bool right) { if(right) { if((uint)m_cursorPos+1 <= m_text.length()) { @@ -389,6 +483,11 @@ void UITextEdit::moveCursor(bool right) update(true); } +void UITextEdit::moveCursorVertically(bool up) +{ + //TODO +} + int UITextEdit::getTextPos(Point pos) { int textLength = m_text.length(); @@ -419,6 +518,13 @@ std::string UITextEdit::getDisplayedText() return m_text; } +std::string UITextEdit::getSelection() +{ + if(!hasSelection()) + return std::string(); + return m_text.substr(m_selectionStart, m_selectionEnd - m_selectionStart); +} + void UITextEdit::onHoverChange(bool hovered) { if(hovered) @@ -431,6 +537,14 @@ void UITextEdit::onTextChange(const std::string& text, const std::string& oldTex { if(m_cursorPos > (int)text.length()) m_cursorPos = text.length(); + + // any text changes reset the selection + if(m_selectable) { + m_selectionEnd = 0; + m_selectionStart = 0; + onSelectionChange(std::string(), 0, 0); + } + blinkCursor(); update(true); UIWidget::onTextChange(text, oldText); @@ -460,6 +574,16 @@ void UITextEdit::onStyleApply(const std::string& styleName, const OTMLNodePtr& s setMaxLength(node->value()); else if(node->tag() == "editable") setEditable(node->value()); + else if(node->tag() == "selectable") + setSelectable(node->value()); + else if(node->tag() == "selection-color") + setSelectionColor(node->value()); + else if(node->tag() == "selection-background-color") + setSelectionBackgroundColor(node->value()); + else if(node->tag() == "selection") { + Point selectionRange = node->value(); + setSelection(selectionRange.x, selectionRange.y); + } else if(node->tag() == "cursor-visible") setCursorVisible(node->value()); } @@ -479,7 +603,8 @@ void UITextEdit::onFocusChange(bool focused, Fw::FocusReason reason) else blinkCursor(); update(true); - } + } else + clearSelection(); UIWidget::onFocusChange(focused, reason); } @@ -490,24 +615,29 @@ bool UITextEdit::onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeat if(keyboardModifiers == Fw::KeyboardNoModifier) { if(keyCode == Fw::KeyDelete && m_editable) { // erase right character - removeCharacter(true); + del(true); return true; } else if(keyCode == Fw::KeyBackspace && m_editable) { // erase left character { - removeCharacter(false); + del(false); return true; } else if(keyCode == Fw::KeyRight && !m_shiftNavigation) { // move cursor right - moveCursor(true); + clearSelection(); + moveCursorHorizontally(true); return true; } else if(keyCode == Fw::KeyLeft && !m_shiftNavigation) { // move cursor left - moveCursor(false); + clearSelection(); + moveCursorHorizontally(false); return true; } else if(keyCode == Fw::KeyHome) { // move cursor to first character + clearSelection(); setCursorPos(0); return true; } else if(keyCode == Fw::KeyEnd) { // move cursor to last character + clearSelection(); setCursorPos(m_text.length()); return true; } else if(keyCode == Fw::KeyTab && !m_shiftNavigation) { + clearSelection(); if(UIWidgetPtr parent = getParent()) parent->focusNextChild(Fw::KeyboardFocusReason); return true; @@ -515,13 +645,24 @@ bool UITextEdit::onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeat appendCharacter('\n'); return true; } else if(keyCode == Fw::KeyUp && !m_shiftNavigation && m_multiline) { + moveCursorVertically(true); return true; } else if(keyCode == Fw::KeyDown && !m_shiftNavigation && m_multiline) { + moveCursorVertically(false); return true; } } else if(keyboardModifiers == Fw::KeyboardCtrlModifier) { if(keyCode == Fw::KeyV && m_editable) { - appendText(g_window.getClipboardText()); + paste(g_window.getClipboardText()); + return true; + } else if(keyCode == Fw::KeyX && m_editable && m_selectable) { + cut(); + return true; + } else if(keyCode == Fw::KeyC && m_selectable) { + copy(); + return true; + } else if(keyCode == Fw::KeyA && m_selectable) { + selectAll(); return true; } } else if(keyboardModifiers == Fw::KeyboardShiftModifier) { @@ -529,11 +670,26 @@ bool UITextEdit::onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeat if(UIWidgetPtr parent = getParent()) parent->focusPreviousChild(Fw::KeyboardFocusReason); return true; - } else if(keyCode == Fw::KeyRight && m_shiftNavigation) { // move cursor right - moveCursor(true); - return true; - } else if(keyCode == Fw::KeyLeft && m_shiftNavigation) { // move cursor left - moveCursor(false); + } else if(keyCode == Fw::KeyRight || keyCode == Fw::KeyLeft || keyCode == Fw::KeyUp || keyCode == Fw::KeyDown) { + + int oldCursorPos = m_cursorPos; + + if(keyCode == Fw::KeyRight) // move cursor right + moveCursorHorizontally(true); + else if(keyCode == Fw::KeyLeft) // move cursor left + moveCursorHorizontally(false); + else if(keyCode == Fw::KeyUp) // move cursor right + moveCursorVertically(true); + else if(keyCode == Fw::KeyDown) // move cursor left + moveCursorVertically(false); + + if(m_shiftNavigation) + clearSelection(); + else { + if(!hasSelection()) + m_selectionReference = oldCursorPos; + setSelection(m_selectionReference, m_cursorPos); + } return true; } } @@ -554,10 +710,35 @@ bool UITextEdit::onMousePress(const Point& mousePos, Fw::MouseButton button) { if(button == Fw::MouseLeftButton) { int pos = getTextPos(mousePos); - if(pos >= 0) + if(pos >= 0) { setCursorPos(pos); + + if(m_selectable) { + m_selectionReference = pos; + setSelection(pos, pos); + } + } + return true; } - return true; + return UIWidget::onMousePress(mousePos, button); +} + +bool UITextEdit::onMouseRelease(const Point& mousePos, Fw::MouseButton button) +{ + return UIWidget::onMouseRelease(mousePos, button); +} + +bool UITextEdit::onMouseMove(const Point& mousePos, const Point& mouseMoved) +{ + if(m_selectable && isPressed()) { + int pos = getTextPos(mousePos); + if(pos >= 0) { + setSelection(m_selectionReference, pos); + setCursorPos(pos); + } + return true; + } + return UIWidget::onMouseMove(mousePos, mouseMoved); } void UITextEdit::onTextAreaUpdate(const Point& offset, const Size& visibleSize, const Size& totalSize) @@ -565,8 +746,8 @@ void UITextEdit::onTextAreaUpdate(const Point& offset, const Size& visibleSize, callLuaField("onTextAreaUpdate", offset, visibleSize, totalSize); } -void UITextEdit::blinkCursor() +void UITextEdit::onSelectionChange(const std::string& text, int start, int end) { - m_cursorTicks = g_clock.millis(); - g_app.repaint(); + update(true); + callLuaField("onSelectionChange", text, start, end); } diff --git a/src/framework/ui/uitextedit.h b/src/framework/ui/uitextedit.h index 0ee5a2f3..77a179e7 100644 --- a/src/framework/ui/uitextedit.h +++ b/src/framework/ui/uitextedit.h @@ -24,6 +24,7 @@ #define UITEXTEDIT_H #include "uiwidget.h" +#include // @bindclass class UITextEdit : public UIWidget @@ -38,6 +39,7 @@ private: public: void setCursorPos(int pos); + void setSelection(int start, int end); void setCursorVisible(bool enable) { m_cursorVisible = enable; } void setTextHidden(bool hidden); void setValidCharacters(const std::string validCharacters) { m_validCharacters = validCharacters; } @@ -46,25 +48,44 @@ public: void setMaxLength(uint maxLength) { m_maxLength = maxLength; } void setTextVirtualOffset(const Point& offset); void setEditable(bool editable) { m_editable = editable; } + void setSelectable(bool selectable) { m_selectable = selectable; } + void setSelectionColor(const Color& color) { m_selectionColor = color; } + void setSelectionBackgroundColor(const Color& color) { m_selectionBackgroundColor = color; } - void moveCursor(bool right); + void moveCursorHorizontally(bool right); + void moveCursorVertically(bool up); void appendText(std::string text); void appendCharacter(char c); void removeCharacter(bool right); + void blinkCursor(); + + void del(bool right = false); + void paste(const std::string& text); + std::string copy(); + std::string cut(); + void selectAll() { setSelection(0, m_text.length()); } + void clearSelection() { setSelection(0, 0); } void wrapText(); std::string getDisplayedText(); + std::string getSelection(); int getTextPos(Point pos); int getCursorPos() { return m_cursorPos; } Point getTextVirtualOffset() { return m_textVirtualOffset; } Size getTextVirtualSize() { return m_textVirtualSize; } Size getTextTotalSize() { return m_textTotalSize; } uint getMaxLength() { return m_maxLength; } + int getSelectionStart() { return m_selectionStart; } + int getSelectionEnd() { return m_selectionEnd; } + Color getSelectionColor() { return m_selectionColor; } + Color getSelectionBackgroundColor() { return m_selectionBackgroundColor; } + bool hasSelection() { return m_selectionEnd - m_selectionStart > 0; } bool isCursorVisible() { return m_cursorVisible; } bool isTextHidden() { return m_textHidden; } bool isShiftNavigation() { return m_shiftNavigation; } bool isMultiline() { return m_multiline; } bool isEditable() { return m_editable; } + bool isSelectable() { return m_selectable; } protected: virtual void onHoverChange(bool hovered); @@ -76,10 +97,14 @@ protected: virtual bool onKeyText(const std::string& keyText); virtual bool onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeatTicks); virtual bool onMousePress(const Point& mousePos, Fw::MouseButton button); + virtual bool onMouseRelease(const Point& mousePos, Fw::MouseButton button); + virtual bool onMouseMove(const Point& mousePos, const Point& mouseMoved); virtual void onTextAreaUpdate(const Point& vitualOffset, const Size& virtualSize, const Size& totalSize); + virtual void onSelectionChange(const std::string& text, int start, int end); private: - void blinkCursor(); + void disableUpdates() { m_updatesEnabled = false; } + void enableUpdates() { m_updatesEnabled = true; } Rect m_drawArea; int m_cursorPos; @@ -95,6 +120,15 @@ private: bool m_editable; std::string m_validCharacters; uint m_maxLength; + bool m_updatesEnabled; + + bool m_selectable; + int m_selectionReference; + int m_selectionStart; + int m_selectionEnd; + + Color m_selectionColor; + Color m_selectionBackgroundColor; std::vector m_glyphsCoords; std::vector m_glyphsTexCoords;