mirror of https://github.com/axmolengine/axmol.git
1072 lines
27 KiB
C++
1072 lines
27 KiB
C++
//
|
|
// Copyright (c) 2014-2020 @HALX99 - All Rights Reserved
|
|
//
|
|
#ifndef _UITEXTFIELD_CPP_H_
|
|
#define _UITEXTFIELD_CPP_H_
|
|
#include "UITextFieldEx.h"
|
|
|
|
#include "base/CCDirector.h"
|
|
// #include "CCGLView.h"
|
|
//#include "NXMacroDefs.h"
|
|
//#include "purelib/utils/iconvw.h"
|
|
|
|
/// cocos2d singleton objects
|
|
#define CCDIRECTOR cocos2d::Director::getInstance()
|
|
#define CCRUNONGL CCDIRECTOR->getScheduler()->performFunctionInCocosThread
|
|
#define CCEVENTMGR CCDIRECTOR->getEventDispatcher()
|
|
#define CCSCHTASKS CCDIRECTOR->getScheduler()
|
|
#define CCACTIONMGR CCDIRECTOR->getActionManager()
|
|
#define CCFILEUTILS cocos2d::FileUtils::getInstance()
|
|
#define CCAUDIO cocos2d::SimpleAudioEngine::getInstance()
|
|
#define CCAPP cocos2d::CCApplication::getInstance()
|
|
|
|
NS_CC_BEGIN
|
|
|
|
#ifdef _WIN32
|
|
# define nxbeep(t) MessageBeep(t)
|
|
#else
|
|
# define nxbeep(t)
|
|
#endif
|
|
|
|
static Label* createLabel(const std::string& text,
|
|
const std::string& font,
|
|
float fontSize,
|
|
const Vec2& dimensions = Vec2::ZERO,
|
|
TextHAlignment hAlignment = TextHAlignment::LEFT,
|
|
TextVAlignment vAlignment = TextVAlignment::TOP)
|
|
{
|
|
if (FileUtils::getInstance()->isFileExist(font))
|
|
{
|
|
return Label::createWithTTF(text, font, fontSize, dimensions, hAlignment, vAlignment);
|
|
}
|
|
else
|
|
{
|
|
return Label::createWithSystemFont(text, font, fontSize, dimensions, hAlignment, vAlignment);
|
|
}
|
|
}
|
|
|
|
static bool engine_inj_checkVisibility(Node* theNode)
|
|
{
|
|
// CC_ASSERT(theNode != NULL);
|
|
bool visible = false;
|
|
for (Node* ptr = theNode; (ptr != nullptr && (visible = ptr->isVisible())); ptr = ptr->getParent())
|
|
;
|
|
return visible;
|
|
}
|
|
|
|
static bool engine_inj_containsTouchPoint(cocos2d::Node* target, cocos2d::Touch* touch)
|
|
{
|
|
assert(target != nullptr);
|
|
|
|
cocos2d::Point pt = target->convertTouchToNodeSpace(touch);
|
|
|
|
const Vec2& size = target->getContentSize();
|
|
|
|
cocos2d::Rect rc(0, 0, size.width, size.height);
|
|
|
|
bool contains = (rc.containsPoint(pt));
|
|
|
|
// CCLOG("check %#x coordinate:(%f, %f), contains:%d", target, pt.x, pt.y, contains);
|
|
return contains;
|
|
}
|
|
|
|
static bool engine_inj_containsPoint(cocos2d::Node* target, const cocos2d::Vec2& worldPoint)
|
|
{
|
|
cocos2d::Point pt = target->convertToNodeSpace(worldPoint);
|
|
|
|
const Vec2& size = target->getContentSize();
|
|
|
|
cocos2d::Rect rc(0, 0, size.width, size.height);
|
|
|
|
bool contains = (rc.containsPoint(pt));
|
|
|
|
// CCLOG("check %#x coordinate:(%f, %f), contains:%d", target, pt.x, pt.y, contains);
|
|
return contains;
|
|
}
|
|
|
|
static uint32_t engine_inj_c4b2dw(const Color4B& value)
|
|
{
|
|
auto rvalue = (uint32_t)value.a << 24 | (uint32_t)value.b << 16 | (uint32_t)value.g << 8 | (uint32_t)value.r;
|
|
return rvalue;
|
|
}
|
|
|
|
static Sprite* engine_inj_create_lump(const Color4B& color, int height, int width)
|
|
{
|
|
unsigned int* pixels((unsigned int*)malloc(height * width * sizeof(unsigned int)));
|
|
|
|
// Fill Pixels
|
|
uint32_t* ptr = pixels;
|
|
const Color4B fillColor = Color4B::WHITE;
|
|
for (int i = 0; i < height * width; ++i)
|
|
{
|
|
ptr[i] = engine_inj_c4b2dw(fillColor); // 0xffffffff;
|
|
}
|
|
|
|
// create cursor by pixels
|
|
Texture2D* texture = new Texture2D();
|
|
|
|
texture->initWithData(pixels, height * width * sizeof(unsigned int), backend::PixelFormat::RGBA8, width, height,
|
|
Vec2(width, height));
|
|
|
|
auto cursor = Sprite::createWithTexture(texture);
|
|
|
|
cursor->setColor(Color3B(color));
|
|
cursor->setOpacity(color.a);
|
|
|
|
texture->release();
|
|
|
|
free(pixels);
|
|
|
|
return cursor;
|
|
}
|
|
|
|
namespace ui
|
|
{
|
|
|
|
/// calculate the UTF-8 string's char count.
|
|
static int _calcCharCount(const char* text)
|
|
{
|
|
int n = 0;
|
|
char ch = 0;
|
|
while ((ch = *text) != 0x0)
|
|
{
|
|
CC_BREAK_IF(!ch);
|
|
|
|
if (0x80 != (0xC0 & ch))
|
|
{
|
|
++n;
|
|
}
|
|
++text;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/// calculate the UTF-8 string's char count.
|
|
static int _truncateUTF8String(const char* text, int limit, int& nb)
|
|
{
|
|
int n = 0;
|
|
char ch = 0;
|
|
nb = 0;
|
|
while ((ch = *text) != 0x0)
|
|
{
|
|
CC_BREAK_IF(!ch || n > limit);
|
|
|
|
if (0x80 != (0xC0 & ch))
|
|
{
|
|
++n;
|
|
}
|
|
++nb;
|
|
++text;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static void internalSetLableFont(Label* l, const std::string& fontName, float fontSize)
|
|
{
|
|
if (FileUtils::getInstance()->isFileExist(fontName))
|
|
{
|
|
TTFConfig config = l->getTTFConfig();
|
|
config.fontFilePath = fontName;
|
|
config.fontSize = fontSize;
|
|
l->setTTFConfig(config);
|
|
}
|
|
else
|
|
{
|
|
l->setSystemFontName(fontName);
|
|
l->requestSystemFontRefresh();
|
|
l->setSystemFontSize(fontSize);
|
|
}
|
|
}
|
|
|
|
static float internalCalcStringWidth(std::string_view s, const std::string& fontName, float fontSize)
|
|
{
|
|
auto label = createLabel(std::string{s}, fontName, fontSize);
|
|
return label->getContentSize().width;
|
|
}
|
|
|
|
static std::string internalUTF8MoveLeft(std::string_view utf8Text, int length /* default utf8Text.length() */)
|
|
{
|
|
if (!utf8Text.empty() && length > 0)
|
|
{
|
|
|
|
// get the delete byte number
|
|
int deleteLen = 1; // default, erase 1 byte
|
|
|
|
while (length >= deleteLen && 0x80 == (0xC0 & utf8Text.at(length - deleteLen)))
|
|
{
|
|
++deleteLen;
|
|
}
|
|
|
|
return std::string{utf8Text.data(), static_cast<size_t>(length - deleteLen)};
|
|
}
|
|
else
|
|
{
|
|
return std::string{utf8Text};
|
|
}
|
|
}
|
|
|
|
static std::string internalUTF8MoveRight(std::string_view utf8Text, int length /* default utf8Text.length() */)
|
|
{
|
|
if (!utf8Text.empty() && length >= 0)
|
|
{
|
|
|
|
// get the delete byte number
|
|
size_t addLen = 1; // default, erase 1 byte
|
|
|
|
while ((length + addLen) < utf8Text.size() && 0x80 == (0xC0 & utf8Text.at(length + addLen)))
|
|
{
|
|
++addLen;
|
|
}
|
|
|
|
return std::string{utf8Text.data(), static_cast<size_t>(length + addLen)};
|
|
}
|
|
else
|
|
{
|
|
return std::string{utf8Text};
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// constructor and destructor
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool TextFieldEx::s_keyboardVisible = false;
|
|
TextFieldEx::TextFieldEx()
|
|
: editable(true)
|
|
, renderLabel(nullptr)
|
|
, charCount(0)
|
|
, inputText("")
|
|
, placeHolder("")
|
|
, colorText(Color4B::WHITE)
|
|
, colorSpaceHolder(Color4B::GRAY)
|
|
, secureTextEntry(false)
|
|
, cursor(nullptr)
|
|
, enabled(true)
|
|
, touchListener(nullptr)
|
|
, kbdListener(nullptr)
|
|
, onTextModify(nullptr)
|
|
, onOpenIME(nullptr)
|
|
, onCloseIME(nullptr)
|
|
, charLimit(std::numeric_limits<int>::max())
|
|
, systemFontUsed(false)
|
|
, fontSize(24)
|
|
, insertPosUtf8(0)
|
|
, insertPos(0)
|
|
, cursorPos(0)
|
|
, touchCursorControlEnabled(true)
|
|
, cursorVisible(false)
|
|
, _continuousTouchDelayTimerID(nullptr)
|
|
, _continuousTouchDelayTime(0.6)
|
|
{}
|
|
|
|
TextFieldEx::~TextFieldEx()
|
|
{
|
|
if (this->kbdListener != nullptr)
|
|
CCEVENTMGR->removeEventListener(this->kbdListener);
|
|
if (this->touchListener != nullptr)
|
|
CCEVENTMGR->removeEventListener(this->touchListener);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// static constructor
|
|
//////////////////////////////////////////////////////////////////////////
|
|
TextFieldEx* TextFieldEx::create(const std::string& placeholder,
|
|
const std::string& fontName,
|
|
float fontSize,
|
|
float cursorWidth,
|
|
const Color4B& cursorColor)
|
|
{
|
|
TextFieldEx* ret = new TextFieldEx();
|
|
if (ret && ret->initWithPlaceHolder("", fontName, fontSize, cursorWidth, cursorColor))
|
|
{
|
|
ret->autorelease();
|
|
if (placeholder.size() > 0)
|
|
{
|
|
ret->setPlaceholderText(placeholder);
|
|
}
|
|
return ret;
|
|
}
|
|
CC_SAFE_DELETE(ret);
|
|
return nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// initialize
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool TextFieldEx::initWithPlaceHolder(const std::string& placeholder,
|
|
const std::string& fontName,
|
|
float fontSize,
|
|
float cursorWidth,
|
|
const Color4B& cursorColor)
|
|
{
|
|
this->placeHolder = placeholder;
|
|
|
|
this->renderLabel =
|
|
createLabel(placeholder, fontName, fontSize, Vec2::ZERO, TextHAlignment::CENTER, TextVAlignment::CENTER);
|
|
this->renderLabel->setAnchorPoint(Point::ANCHOR_MIDDLE_LEFT);
|
|
this->addChild(this->renderLabel);
|
|
|
|
CCRUNONGL([this] { renderLabel->setPosition(Point(0, this->getContentSize().height / 2)); });
|
|
|
|
__initCursor(fontSize, cursorWidth, cursorColor);
|
|
|
|
this->fontName = fontName;
|
|
this->fontSize = fontSize;
|
|
this->systemFontUsed = !FileUtils::getInstance()->isFileExist(fontName);
|
|
|
|
return true;
|
|
}
|
|
|
|
const std::string& TextFieldEx::getTextFontName() const
|
|
{
|
|
return this->fontName;
|
|
}
|
|
|
|
void TextFieldEx::setTextFontName(const std::string& fontName)
|
|
{
|
|
if (FileUtils::getInstance()->isFileExist(fontName))
|
|
{
|
|
TTFConfig config = renderLabel->getTTFConfig();
|
|
config.fontFilePath = fontName;
|
|
config.fontSize = this->fontSize;
|
|
renderLabel->setTTFConfig(config);
|
|
systemFontUsed = false;
|
|
_fontType = 1;
|
|
}
|
|
else
|
|
{
|
|
renderLabel->setSystemFontName(fontName);
|
|
if (!systemFontUsed)
|
|
{
|
|
renderLabel->requestSystemFontRefresh();
|
|
}
|
|
renderLabel->setSystemFontSize(this->fontSize);
|
|
systemFontUsed = true;
|
|
_fontType = 0;
|
|
}
|
|
this->fontName = fontName;
|
|
|
|
using namespace std::string_view_literals;
|
|
this->asteriskWidth = internalCalcStringWidth("*"sv, this->fontName, this->fontSize);
|
|
}
|
|
|
|
void TextFieldEx::setTextFontSize(float size)
|
|
{
|
|
if (this->systemFontUsed)
|
|
{
|
|
renderLabel->setSystemFontSize(size);
|
|
}
|
|
else
|
|
{
|
|
TTFConfig config = renderLabel->getTTFConfig();
|
|
config.fontSize = size;
|
|
renderLabel->setTTFConfig(config);
|
|
}
|
|
|
|
this->fontSize = size;
|
|
|
|
using namespace std::string_view_literals;
|
|
this->asteriskWidth = internalCalcStringWidth("*"sv, this->fontName, this->fontSize);
|
|
}
|
|
|
|
float TextFieldEx::getTextFontSize() const
|
|
{
|
|
return this->fontSize;
|
|
}
|
|
|
|
void TextFieldEx::enableIME(Node* control)
|
|
{
|
|
if (touchListener != nullptr)
|
|
{
|
|
return;
|
|
}
|
|
touchListener = EventListenerTouchOneByOne::create();
|
|
|
|
if (control == nullptr)
|
|
control = this;
|
|
|
|
touchListener->onTouchBegan = [=](Touch* touch, Event*) {
|
|
bool focus = (engine_inj_checkVisibility(this) && this->editable && this->enabled &&
|
|
engine_inj_containsTouchPoint(control, touch));
|
|
|
|
if (this->_continuousTouchDelayTimerID != nullptr)
|
|
{
|
|
stimer::kill(this->_continuousTouchDelayTimerID);
|
|
this->_continuousTouchDelayTimerID = nullptr;
|
|
}
|
|
|
|
if (focus && this->cursorVisible)
|
|
{
|
|
auto worldPoint = touch->getLocation();
|
|
if (this->_continuousTouchCallback)
|
|
{
|
|
this->_continuousTouchDelayTimerID = stimer::delay(
|
|
this->_continuousTouchDelayTime, [=]() { this->_continuousTouchCallback(worldPoint); });
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
touchListener->onTouchEnded = [control, this](Touch* touch, Event* e) {
|
|
if (this->_continuousTouchDelayTimerID != nullptr)
|
|
{
|
|
stimer::kill(this->_continuousTouchDelayTimerID);
|
|
this->_continuousTouchDelayTimerID = nullptr;
|
|
}
|
|
|
|
bool focus = (engine_inj_checkVisibility(this) && this->editable && this->enabled &&
|
|
engine_inj_containsTouchPoint(control, touch));
|
|
|
|
if (focus)
|
|
{
|
|
if (!s_keyboardVisible || !this->cursorVisible)
|
|
openIME();
|
|
if (this->touchCursorControlEnabled)
|
|
{
|
|
auto renderLabelPoint = renderLabel->convertToNodeSpace(touch->getLocation());
|
|
__moveCursorTo(renderLabelPoint.x);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
closeIME();
|
|
}
|
|
};
|
|
|
|
CCEVENTMGR->addEventListenerWithSceneGraphPriority(touchListener, this);
|
|
|
|
/// enable use keyboard <- -> to move cursor.
|
|
kbdListener = EventListenerKeyboard::create();
|
|
kbdListener->onKeyPressed = [this](EventKeyboard::KeyCode code, Event*) {
|
|
if (this->cursorVisible)
|
|
{
|
|
switch (code)
|
|
{
|
|
case EventKeyboard::KeyCode::KEY_LEFT_ARROW:
|
|
this->__moveCursor(-1);
|
|
break;
|
|
case EventKeyboard::KeyCode::KEY_RIGHT_ARROW:
|
|
this->__moveCursor(1);
|
|
break;
|
|
case EventKeyboard::KeyCode::KEY_DELETE:
|
|
case EventKeyboard::KeyCode::KEY_KP_DELETE:
|
|
this->handleDeleteKeyEvent();
|
|
break;
|
|
default:;
|
|
}
|
|
}
|
|
};
|
|
|
|
CCEVENTMGR->addEventListenerWithSceneGraphPriority(kbdListener, this);
|
|
}
|
|
|
|
void TextFieldEx::disableIME(void)
|
|
{
|
|
CCEVENTMGR->removeEventListener(kbdListener);
|
|
CCEVENTMGR->removeEventListener(touchListener);
|
|
|
|
kbdListener = nullptr;
|
|
touchListener = nullptr;
|
|
closeIME();
|
|
}
|
|
|
|
Label* TextFieldEx::getRenderLabel()
|
|
{
|
|
return this->renderLabel;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// IMEDelegate
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
bool TextFieldEx::attachWithIME()
|
|
{
|
|
bool ret = IMEDelegate::attachWithIME();
|
|
if (ret)
|
|
{
|
|
// open keyboard
|
|
GLView* pGlView = _director->getOpenGLView();
|
|
if (pGlView)
|
|
{
|
|
pGlView->setIMEKeyboardState(true);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool TextFieldEx::detachWithIME()
|
|
{
|
|
bool ret = IMEDelegate::detachWithIME();
|
|
if (ret)
|
|
{
|
|
// close keyboard
|
|
GLView* glView = _director->getOpenGLView();
|
|
if (glView)
|
|
{
|
|
glView->setIMEKeyboardState(false);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void TextFieldEx::keyboardDidShow(IMEKeyboardNotificationInfo& /*info*/)
|
|
{
|
|
s_keyboardVisible = true;
|
|
}
|
|
|
|
void TextFieldEx::keyboardDidHide(IMEKeyboardNotificationInfo& /*info*/)
|
|
{
|
|
s_keyboardVisible = false;
|
|
}
|
|
|
|
void TextFieldEx::openIME(void)
|
|
{
|
|
CCLOG("TextFieldEx:: openIME");
|
|
this->attachWithIME();
|
|
__updateCursorPosition();
|
|
__showCursor();
|
|
|
|
if (this->onOpenIME)
|
|
this->onOpenIME();
|
|
}
|
|
|
|
void TextFieldEx::closeIME(void)
|
|
{
|
|
CCLOG("TextFieldEx:: closeIME");
|
|
__hideCursor();
|
|
this->detachWithIME();
|
|
|
|
if (this->onCloseIME)
|
|
this->onCloseIME();
|
|
}
|
|
|
|
bool TextFieldEx::canAttachWithIME()
|
|
{
|
|
return true; //(_delegate) ? (! _delegate->onTextFieldAttachWithIME(this)) : true;
|
|
}
|
|
|
|
bool TextFieldEx::canDetachWithIME()
|
|
{
|
|
return true; //(_delegate) ? (! _delegate->onTextFieldDetachWithIME(this)) : true;
|
|
}
|
|
|
|
void TextFieldEx::insertText(const char* text, size_t len)
|
|
{
|
|
if (!this->editable || !this->enabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this->charLimit > 0 && this->charCount >= this->charLimit)
|
|
{ // regard zero as unlimited
|
|
nxbeep(0);
|
|
return;
|
|
}
|
|
|
|
int nb;
|
|
auto n = _truncateUTF8String(text, this->charLimit - this->charCount, nb);
|
|
|
|
std::string insert(text, nb);
|
|
|
|
// insert \n means input end
|
|
auto pos = insert.find('\n');
|
|
if (insert.npos != pos)
|
|
{
|
|
len = pos;
|
|
insert.erase(pos);
|
|
}
|
|
|
|
if (len > 0)
|
|
{
|
|
// if (_delegate && _delegate->onTextFieldInsertText(this, insert.c_str(), len))
|
|
//{
|
|
// // delegate doesn't want to insert text
|
|
// return;
|
|
// }
|
|
|
|
charCount += n; // _calcCharCount(insert.c_str());
|
|
std::string sText(inputText);
|
|
sText.insert(this->insertPos, insert); // original is: sText.append(insert);
|
|
|
|
// bool needUpdatePos
|
|
this->setString(sText);
|
|
while (n-- > 0)
|
|
__moveCursor(1);
|
|
|
|
// this->contentDirty = true;
|
|
// __updateCursorPosition();
|
|
|
|
if (this->onTextModify)
|
|
this->onTextModify();
|
|
}
|
|
|
|
if (insert.npos == pos)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// '\n' inserted, let delegate process first
|
|
/*if (_delegate && _delegate->onTextFieldInsertText(this, "\n", 1))
|
|
{
|
|
return;
|
|
}*/
|
|
|
|
// if delegate hasn't processed, detach from IME by default
|
|
this->closeIME();
|
|
}
|
|
|
|
void TextFieldEx::deleteBackward()
|
|
{
|
|
if (!this->editable || !this->enabled || 0 == this->charCount)
|
|
{
|
|
nxbeep(0);
|
|
return;
|
|
}
|
|
|
|
size_t len = inputText.length();
|
|
if (0 == len || insertPos == 0)
|
|
{
|
|
nxbeep(0);
|
|
// there is no string
|
|
// __updateCursorPosition();
|
|
return;
|
|
}
|
|
|
|
// get the delete byte number
|
|
size_t deleteLen = 1; // default, erase 1 byte
|
|
|
|
while (0x80 == (0xC0 & inputText.at(insertPos - deleteLen)))
|
|
{
|
|
++deleteLen;
|
|
}
|
|
|
|
// if (_delegate && _delegate->onTextFieldDeleteBackward(this, _inputText.c_str() + len - deleteLen,
|
|
// static_cast<int>(deleteLen)))
|
|
//{
|
|
// // delegate doesn't wan't to delete backwards
|
|
// return;
|
|
// }
|
|
|
|
// if all text deleted, show placeholder string
|
|
if (len <= deleteLen)
|
|
{
|
|
__moveCursor(-1);
|
|
|
|
this->inputText.clear();
|
|
this->charCount = 0;
|
|
this->renderLabel->setTextColor(colorSpaceHolder);
|
|
this->renderLabel->setString(placeHolder);
|
|
|
|
// __updateCursorPosition();
|
|
|
|
// this->contentDirty = true;
|
|
|
|
if (this->onTextModify)
|
|
this->onTextModify();
|
|
return;
|
|
}
|
|
|
|
// set new input text
|
|
std::string text = inputText; // (inputText.c_str(), len - deleteLen);
|
|
text.erase(insertPos - deleteLen, deleteLen);
|
|
|
|
__moveCursor(-1);
|
|
|
|
this->setString(text);
|
|
|
|
//__updateCursorPosition();
|
|
// __moveCursor(-1);
|
|
|
|
if (this->onTextModify)
|
|
this->onTextModify();
|
|
}
|
|
|
|
void TextFieldEx::handleDeleteKeyEvent()
|
|
{
|
|
if (!this->editable || !this->enabled || 0 == this->charCount)
|
|
{
|
|
nxbeep(0);
|
|
return;
|
|
}
|
|
|
|
size_t len = inputText.length();
|
|
if (0 == len || insertPosUtf8 == this->charCount)
|
|
{
|
|
nxbeep(0);
|
|
// there is no string
|
|
// __updateCursorPosition();
|
|
return;
|
|
}
|
|
|
|
// get the delete byte number
|
|
size_t deleteLen = 1; // default, erase 1 byte
|
|
|
|
while ((inputText.length() > insertPos + deleteLen) && 0x80 == (0xC0 & inputText.at(insertPos + deleteLen)))
|
|
{
|
|
++deleteLen;
|
|
}
|
|
|
|
// if (_delegate && _delegate->onTextFieldDeleteBackward(this, _inputText.c_str() + len - deleteLen,
|
|
// static_cast<int>(deleteLen)))
|
|
//{
|
|
// // delegate doesn't wan't to delete backwards
|
|
// return;
|
|
// }
|
|
|
|
// if all text deleted, show placeholder string
|
|
if (len <= deleteLen)
|
|
{
|
|
this->inputText.clear();
|
|
this->charCount = 0;
|
|
this->renderLabel->setTextColor(colorSpaceHolder);
|
|
this->renderLabel->setString(placeHolder);
|
|
|
|
__updateCursorPosition();
|
|
|
|
// this->contentDirty = true;
|
|
|
|
if (this->onTextModify)
|
|
this->onTextModify();
|
|
return;
|
|
}
|
|
|
|
// set new input text
|
|
std::string text = inputText; // (inputText.c_str(), len - deleteLen);
|
|
text.erase(insertPos, deleteLen);
|
|
|
|
// __moveCursor(-1);
|
|
|
|
this->setString(text);
|
|
|
|
if (this->onTextModify)
|
|
this->onTextModify();
|
|
}
|
|
|
|
const std::string& TextFieldEx::getContentText()
|
|
{
|
|
return inputText;
|
|
}
|
|
|
|
void TextFieldEx::setTextColor(const Color4B& color)
|
|
{
|
|
colorText = color;
|
|
if (!this->inputText.empty())
|
|
this->renderLabel->setTextColor(colorText);
|
|
}
|
|
|
|
const Color4B& TextFieldEx::getTextColor(void) const
|
|
{
|
|
return colorText;
|
|
}
|
|
|
|
void TextFieldEx::setCursorColor(const Color3B& color)
|
|
{
|
|
this->cursor->setColor(color);
|
|
}
|
|
|
|
const Color3B& TextFieldEx::getCursorColor(void) const
|
|
{
|
|
return this->cursor->getColor();
|
|
}
|
|
|
|
const Color4B& TextFieldEx::getPlaceholderColor() const
|
|
{
|
|
return colorSpaceHolder;
|
|
}
|
|
|
|
void TextFieldEx::setPlaceholderColor(const Color4B& color)
|
|
{
|
|
colorSpaceHolder = color;
|
|
if (this->inputText.empty())
|
|
this->renderLabel->setTextColor(color);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// properties
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// input text property
|
|
void TextFieldEx::setString(const std::string& text)
|
|
{
|
|
static char bulletString[] = {(char)0xe2, (char)0x80, (char)0xa2, (char)0x00};
|
|
|
|
this->inputText = text;
|
|
|
|
std::string secureText;
|
|
|
|
std::string* displayText = &this->inputText;
|
|
|
|
if (!this->inputText.empty())
|
|
{
|
|
if (secureTextEntry)
|
|
{
|
|
size_t length = inputText.length();
|
|
displayText = &secureText;
|
|
|
|
while (length > 0)
|
|
{
|
|
displayText->append(bulletString);
|
|
--length;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if there is no input text, display placeholder instead
|
|
if (this->inputText.empty())
|
|
{
|
|
renderLabel->setTextColor(colorSpaceHolder);
|
|
renderLabel->setString(placeHolder);
|
|
}
|
|
else
|
|
{
|
|
renderLabel->setTextColor(colorText);
|
|
renderLabel->setString(*displayText);
|
|
}
|
|
|
|
bool bInsertAtEnd = (insertPosUtf8 == charCount);
|
|
|
|
charCount = _calcCharCount(inputText.c_str());
|
|
|
|
if (bInsertAtEnd)
|
|
{
|
|
insertPosUtf8 = charCount;
|
|
insertPos = inputText.length();
|
|
cursorPos = displayText->length();
|
|
}
|
|
}
|
|
|
|
void TextFieldEx::updateContentSize(void)
|
|
{
|
|
this->setContentSize(renderLabel->getContentSize());
|
|
}
|
|
|
|
const std::string& TextFieldEx::getString() const
|
|
{
|
|
return inputText;
|
|
}
|
|
|
|
// place holder text property
|
|
void TextFieldEx::setPlaceholderText(const std::string& text)
|
|
{
|
|
placeHolder = text;
|
|
if (inputText.empty())
|
|
{
|
|
renderLabel->setTextColor(colorSpaceHolder);
|
|
renderLabel->setString(placeHolder);
|
|
}
|
|
}
|
|
|
|
const std::string& TextFieldEx::getPlaceholderText() const
|
|
{
|
|
return placeHolder;
|
|
}
|
|
|
|
// secureTextEntry
|
|
void TextFieldEx::setPasswordEnabled(bool value)
|
|
{
|
|
if (secureTextEntry != value)
|
|
{
|
|
secureTextEntry = value;
|
|
this->setString(this->getString());
|
|
__updateCursorPosition();
|
|
}
|
|
}
|
|
|
|
bool TextFieldEx::isPasswordEnabled() const
|
|
{
|
|
return secureTextEntry;
|
|
}
|
|
|
|
const Vec2& TextFieldEx::getContentSize() const
|
|
{
|
|
// const_cast<TextFieldEx*>(this)->setContentSize(renderLabel->getContentSize());
|
|
return Node::getContentSize();
|
|
}
|
|
|
|
void TextFieldEx::setEnabled(bool bEnabled)
|
|
{
|
|
if (this->enabled != bEnabled)
|
|
{
|
|
if (!bEnabled)
|
|
{
|
|
this->closeIME();
|
|
}
|
|
this->enabled = bEnabled;
|
|
}
|
|
}
|
|
|
|
bool TextFieldEx::isEnabled(void) const
|
|
{
|
|
return this->enabled;
|
|
}
|
|
|
|
int TextFieldEx::getFontType() const
|
|
{
|
|
return _fontType;
|
|
}
|
|
|
|
void TextFieldEx::__initCursor(int height, int width, const Color4B& color)
|
|
{
|
|
this->cursor = engine_inj_create_lump(Color4B(color), height, width);
|
|
|
|
this->addChild(this->cursor);
|
|
|
|
this->cursor->setPosition(Point(0, this->getContentSize().height / 2));
|
|
// nodes_layout::setNodeLB(this->cursor, cocos2d::Point::ZERO);
|
|
|
|
/*CCAction* blink = CCRepeatForever::create(
|
|
(CCActionInterval *)CCSequence::create(CCFadeOut::create(0.25f),
|
|
CCFadeIn::create(0.25f),
|
|
NULL));*/
|
|
|
|
__hideCursor();
|
|
|
|
__updateCursorPosition();
|
|
}
|
|
|
|
void TextFieldEx::__showCursor(void)
|
|
{
|
|
if (this->cursor)
|
|
{
|
|
this->cursorVisible = true;
|
|
this->cursor->setVisible(true);
|
|
this->cursor->runAction(RepeatForever::create(Blink::create(1, 1)));
|
|
}
|
|
}
|
|
|
|
void TextFieldEx::__hideCursor(void)
|
|
{
|
|
if (this->cursor)
|
|
{
|
|
this->cursor->setVisible(false);
|
|
this->cursorVisible = false;
|
|
this->cursor->stopAllActions();
|
|
}
|
|
}
|
|
|
|
void TextFieldEx::__updateCursorPosition(void)
|
|
{
|
|
if (this->cursor && this->insertPosUtf8 == this->charCount)
|
|
{
|
|
if (0 == this->getCharCount())
|
|
{
|
|
this->cursor->setPosition(Point(0, this->getContentSize().height / 2));
|
|
}
|
|
else
|
|
{
|
|
this->cursor->setPosition(Point(renderLabel->getContentSize().width, this->getContentSize().height / 2));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextFieldEx::__moveCursor(int direction)
|
|
{
|
|
/*bool checkSupport = this->renderLabel->getLetter(0) != nullptr;
|
|
if (!checkSupport)
|
|
{
|
|
MessageBeep(MB_ICONHAND);
|
|
return;
|
|
}*/
|
|
|
|
auto newOffset = this->insertPosUtf8 + direction;
|
|
|
|
if (newOffset > 0 && newOffset <= this->charCount)
|
|
{
|
|
|
|
std::string_view displayText;
|
|
if (!secureTextEntry)
|
|
displayText = this->getString();
|
|
else if (!this->inputText.empty())
|
|
displayText = renderLabel->getString();
|
|
|
|
if (direction < 0)
|
|
{
|
|
this->insertPos = internalUTF8MoveLeft(this->inputText, this->insertPos).size();
|
|
|
|
auto s = internalUTF8MoveLeft(displayText, this->cursorPos);
|
|
|
|
auto width = internalCalcStringWidth(s, this->fontName, this->fontSize);
|
|
this->cursor->setPosition(Point(width, this->getContentSize().height / 2));
|
|
this->cursorPos = s.length();
|
|
}
|
|
else
|
|
{
|
|
this->insertPos = internalUTF8MoveRight(this->inputText, this->insertPos).size();
|
|
|
|
auto s = internalUTF8MoveRight(displayText, this->cursorPos);
|
|
auto width = internalCalcStringWidth(s, this->fontName, this->fontSize);
|
|
this->cursor->setPosition(Point(width, this->getContentSize().height / 2));
|
|
this->cursorPos = s.length();
|
|
}
|
|
|
|
this->insertPosUtf8 = newOffset;
|
|
}
|
|
else if (newOffset == 0)
|
|
{
|
|
this->cursor->setPosition(Point(0, this->getContentSize().height / 2));
|
|
this->insertPosUtf8 = newOffset;
|
|
this->insertPos = 0;
|
|
this->cursorPos = 0;
|
|
}
|
|
else
|
|
{
|
|
// MessageBeep(0);
|
|
}
|
|
}
|
|
|
|
void TextFieldEx::__moveCursorTo(float x)
|
|
{ // test
|
|
// normalized x
|
|
float normalizedX = 0;
|
|
|
|
std::string_view displayText;
|
|
if (!secureTextEntry)
|
|
{
|
|
displayText = this->inputText;
|
|
}
|
|
else
|
|
{
|
|
if (!this->inputText.empty())
|
|
{
|
|
displayText = renderLabel->getString();
|
|
}
|
|
}
|
|
|
|
int length = displayText.length();
|
|
int n = this->charCount; // UTF8 char counter
|
|
|
|
int insertWhere = 0;
|
|
int insertWhereUtf8 = 0;
|
|
while (length > 0)
|
|
{
|
|
auto checkX = internalCalcStringWidth(displayText, this->fontName, this->fontSize);
|
|
if (x >= checkX)
|
|
{
|
|
insertWhere = length;
|
|
insertWhereUtf8 = n;
|
|
normalizedX = checkX;
|
|
break;
|
|
}
|
|
|
|
// clamp backward
|
|
size_t backwardLen = 1; // default, erase 1 byte
|
|
while (0x80 == (0xC0 & displayText.at(displayText.length() - backwardLen)))
|
|
{
|
|
++backwardLen;
|
|
}
|
|
|
|
--n;
|
|
displayText.remove_suffix(backwardLen);
|
|
|
|
length -= backwardLen;
|
|
}
|
|
|
|
this->insertPos = !this->secureTextEntry ? insertWhere : insertWhereUtf8;
|
|
this->cursorPos = insertWhere;
|
|
this->insertPosUtf8 = insertWhereUtf8;
|
|
this->cursor->setPosition(Point(normalizedX, this->getContentSize().height / 2));
|
|
}
|
|
}; // namespace ui
|
|
|
|
NS_CC_END
|
|
|
|
#endif
|