mirror of
https://github.com/mudita/MuditaOS.git
synced 2026-04-23 00:19:31 -04:00
960 lines
25 KiB
C++
960 lines
25 KiB
C++
/*
|
|
* @file Text.cpp
|
|
* @author Robert Borzecki (robert.borzecki@mudita.com)
|
|
* @date 1 sie 2019
|
|
* @brief
|
|
* @copyright Copyright (C) 2019 mudita.com
|
|
* @details
|
|
*/
|
|
#include <iterator>
|
|
|
|
#include "vfs.hpp"
|
|
#include "log/log.hpp"
|
|
#include "utf8/UTF8.hpp"
|
|
#include "../core/Font.hpp"
|
|
#include "Text.hpp"
|
|
|
|
|
|
namespace gui {
|
|
|
|
Text::TextLine::TextLine( const UTF8& text, uint32_t startIndex, uint32_t endIndex, Text::LineEndType endType, uint32_t pixelLength ) :
|
|
text{text},
|
|
startIndex{ startIndex },
|
|
endIndex{endIndex},
|
|
endType{ endType },
|
|
pixelLength{ pixelLength }{
|
|
|
|
}
|
|
|
|
Text::Text() :
|
|
Rect(),
|
|
expandMode{ expandMode},
|
|
textType{ textType} {
|
|
|
|
setPenWidth( 1 );
|
|
setPenFocusWidth( 3 );
|
|
uint32_t fontID = FontManager::getInstance().getFontID("gt_pressura_regular_16");
|
|
font = FontManager::getInstance().getFont(fontID);
|
|
|
|
cursor = new Rect( this, 0,0,1,1);
|
|
cursor->setFilled( true );
|
|
cursor->setVisible( false );
|
|
|
|
//insert first empty text line
|
|
textLines.push_back( new TextLine( UTF8(""), 0, 0, LineEndType::EOT, 0 ) );
|
|
firstLine = textLines.begin();
|
|
lastLine = textLines.begin();
|
|
setBorderColor( gui::ColorFullBlack );
|
|
setEdges(RectangleEdgeFlags::GUI_RECT_ALL_EDGES);
|
|
updateCursor();
|
|
}
|
|
|
|
Text::Text( Item* parent, const uint32_t& x, const uint32_t& y, const uint32_t& w, const uint32_t& h,
|
|
const UTF8& text, ExpandMode expandMode, TextType textType) :
|
|
Rect( parent, x, y, w, h ),
|
|
expandMode{ expandMode},
|
|
textType{ textType} {
|
|
|
|
setPenWidth( 1 );
|
|
setPenFocusWidth( 3 );
|
|
uint32_t fontID = FontManager::getInstance().getFontID("gt_pressura_regular_16");
|
|
font = FontManager::getInstance().getFont(fontID);
|
|
|
|
cursor = new Rect( this, 0,0,1,1);
|
|
cursor->setFilled( true );
|
|
cursor->setVisible( false );
|
|
|
|
//insert first empty text line
|
|
textLines.push_back( new TextLine( UTF8(""), 0, 0, LineEndType::EOT, 0 ) );
|
|
firstLine = textLines.begin();
|
|
lastLine = textLines.begin();
|
|
setBorderColor( gui::ColorFullBlack );
|
|
setEdges(RectangleEdgeFlags::GUI_RECT_ALL_EDGES);
|
|
updateCursor();
|
|
}
|
|
|
|
Text::~Text() {
|
|
//if there are text lines erase them.
|
|
if( !textLines.empty() ) {
|
|
while( !textLines.empty() ) {
|
|
delete textLines.front();
|
|
textLines.pop_front();
|
|
}
|
|
}
|
|
textLines.clear();
|
|
}
|
|
|
|
void Text::setEditMode( EditMode mode ) {
|
|
editMode = mode;
|
|
if( mode == EditMode::BROWSE )
|
|
cursor->setVisible(false);
|
|
else {
|
|
if( focus )
|
|
cursor->setVisible(true);
|
|
}
|
|
}
|
|
|
|
void Text::setTextType( TextType type ) {
|
|
textType = type;
|
|
}
|
|
|
|
void Text::setUnderline( bool underline ) {
|
|
//do nothing, value of the flag doesn;t change
|
|
if( this->underline == underline )
|
|
return;
|
|
|
|
this->underline = underline;
|
|
LOG_INFO("lines count: %d", labelLines.size());
|
|
for( auto it = labelLines.begin(); it!=labelLines.end(); it++ ) {
|
|
|
|
gui::Label* label = *it;
|
|
if( this->underline )
|
|
label->setEdges(RectangleEdgeFlags::GUI_RECT_EDGE_BOTTOM);
|
|
else
|
|
label->setEdges(RectangleEdgeFlags::GUI_RECT_EDGE_NO_EDGES);
|
|
}
|
|
}
|
|
|
|
void Text::setNavigationBarrier( const NavigationBarrier& barrier, bool value ) {
|
|
if( value )
|
|
barriers |= static_cast<uint32_t>(barrier);
|
|
else
|
|
barriers &= ~(static_cast<uint32_t>(barrier));
|
|
}
|
|
|
|
void Text::setCursorWidth( uint32_t w ) {
|
|
cursorWidth = w;
|
|
}
|
|
|
|
void Text::setText( const UTF8& text ) {
|
|
clear();
|
|
cursorRow = 0;
|
|
cursorColumn = 0;
|
|
//erase default empty line
|
|
delete textLines.front();
|
|
textLines.pop_front();
|
|
textLines.clear();
|
|
//split and add new lines
|
|
splitTextToLines(text);
|
|
recalculateDrawParams();
|
|
updateCursor();
|
|
}
|
|
void Text::clear(){
|
|
//if there are text lines erase them.
|
|
if( !textLines.empty() ) {
|
|
while( !textLines.empty() ) {
|
|
delete textLines.front();
|
|
textLines.pop_front();
|
|
}
|
|
}
|
|
textLines.clear();
|
|
//insert first empty text line
|
|
textLines.push_back( new TextLine( UTF8(""), 0, 0, LineEndType::EOT, 0 ) );
|
|
firstLine = textLines.begin();
|
|
lastLine = textLines.begin();
|
|
}
|
|
|
|
UTF8 Text::getText() {
|
|
|
|
UTF8 output;
|
|
|
|
//iterate over all lines and add content from each line to output string.
|
|
auto it = textLines.begin();
|
|
while( it != textLines.end()) {
|
|
|
|
auto textLine = (*it);
|
|
output += (*it)->text;
|
|
if( (*it)->endType == LineEndType::BREAK ) {
|
|
output.insert("\n");
|
|
}
|
|
|
|
if( (*it)->endType == LineEndType::CONTINUE_SPACE ) {
|
|
output.insert(" ");
|
|
}
|
|
|
|
++it;
|
|
}
|
|
return output;
|
|
}
|
|
|
|
bool Text::saveText( UTF8 path ) {
|
|
|
|
auto file = vfs.fopen( path.c_str(), "wb" );
|
|
|
|
if( file == nullptr )
|
|
return false;
|
|
|
|
auto it = textLines.begin();
|
|
|
|
//iterate over all lines in text edit
|
|
while( it != textLines.end()) {
|
|
|
|
vfs.fwrite( (*it)->text.c_str(), (*it)->text.used()-1, 1, file );
|
|
if( (*it)->endType == LineEndType::BREAK ) {
|
|
vfs.fwrite( "\n", 1, 1, file );
|
|
}
|
|
|
|
if( (*it)->endType == LineEndType::CONTINUE_SPACE ) {
|
|
vfs.fwrite( " ", 1, 1, file );
|
|
}
|
|
|
|
++it;
|
|
}
|
|
//close file
|
|
vfs.fclose( file );
|
|
|
|
return true;
|
|
}
|
|
|
|
void Text::setFont( const UTF8& fontName) {
|
|
|
|
uint32_t fontID = FontManager::getInstance().getFontID( fontName.c_str() );
|
|
Font* newFont = FontManager::getInstance().getFont( fontID );
|
|
if( newFont != nullptr ) {
|
|
font = newFont;
|
|
// calculateDisplayText();
|
|
} else {
|
|
LOG_ERROR("Font not found");
|
|
}
|
|
recalculateDrawParams();
|
|
}
|
|
|
|
void Text::splitTextToLines( const UTF8& text) {
|
|
|
|
if( text.length() == 0 )
|
|
return;
|
|
|
|
//copy provided text to internal buffer
|
|
uint32_t index = 0;
|
|
uint32_t totalLength = text.length();
|
|
|
|
uint32_t availableSpace = getAvailableHPixelSpace();
|
|
|
|
while( index < totalLength ) {
|
|
|
|
UTF8 textCopy = text.substr(index,totalLength-index);
|
|
//find how many character fit in the widget's width
|
|
//this doesnt include any line breaking conditinos like enter or space because line is too long
|
|
uint32_t spaceConsumed = 0;
|
|
uint32_t charCount = font->getCharCountInSpace( textCopy, availableSpace, spaceConsumed );
|
|
UTF8 tmpText = textCopy.substr( 0, charCount );
|
|
|
|
//some default values
|
|
uint32_t startIndex = 0;
|
|
uint32_t endIndex = totalLength;
|
|
LineEndType lineEndType = LineEndType::EOT;
|
|
|
|
//check if this is not the end of the text
|
|
if( index + charCount == totalLength ) {
|
|
//try to find first enter.
|
|
uint32_t enterIndex = tmpText.find( "\n",0);
|
|
if( enterIndex != UTF8::npos ) {
|
|
endIndex = index+enterIndex;
|
|
index += enterIndex + 1;
|
|
lineEndType = LineEndType::BREAK;
|
|
textLines.push_back( new TextLine( tmpText.substr(0,enterIndex), startIndex, endIndex, lineEndType,
|
|
font->getPixelWidth(tmpText.substr(0,enterIndex))) );
|
|
// LOG_INFO("Text Input Line: [%s]", textLines.back()->text.c_str());
|
|
} //no enter found last line can be copied as a whole.
|
|
else {
|
|
startIndex = index;
|
|
endIndex = totalLength;
|
|
textLines.push_back( new TextLine( tmpText, startIndex, endIndex, lineEndType, spaceConsumed ) );
|
|
// LOG_INFO("Text Input Line: [%s]", textLines.back()->text.c_str());
|
|
index += charCount;
|
|
}
|
|
}
|
|
//if it wasn't the last line search for enter or space and break the line on it.
|
|
else {
|
|
|
|
startIndex = index;
|
|
|
|
//try to find first enter.
|
|
uint32_t enterIndex = tmpText.find( "\n",0);
|
|
if( enterIndex != UTF8::npos ) {
|
|
endIndex = index+enterIndex;
|
|
index += enterIndex + 1;
|
|
lineEndType = LineEndType::BREAK;
|
|
textLines.push_back( new TextLine( tmpText.substr(0,enterIndex), startIndex, endIndex, lineEndType, spaceConsumed ) );
|
|
// LOG_INFO("Text Input Line: [%s]", textLines.back()->text.c_str());
|
|
}
|
|
else {
|
|
//if there was no enter look for last space in the tmpText and break line on it
|
|
uint32_t spaceIndex = tmpText.findLast( " ",tmpText.length()-1);
|
|
|
|
//if there was no space take as many characters as possible and add CONTINUE ending
|
|
if( spaceIndex == UTF8::npos ) {
|
|
endIndex = index+charCount;
|
|
index += charCount;
|
|
lineEndType = LineEndType::CONTINUE;
|
|
textLines.push_back( new TextLine( tmpText, startIndex, endIndex, lineEndType, spaceConsumed ) );
|
|
// LOG_INFO("Text Input Line: [%s]", textLines.back()->text.c_str());
|
|
}
|
|
else {
|
|
lineEndType = LineEndType::CONTINUE_SPACE;
|
|
|
|
uint32_t spaceWidth = font->getPixelWidth(" ",0,1);
|
|
//if space is last character in string erase it and add appropriate CONTINUE_SPACE ending
|
|
if( spaceIndex == tmpText.length()-1 ) {
|
|
endIndex = index+charCount-1;
|
|
index += charCount;
|
|
textLines.push_back( new TextLine( tmpText.substr(0,tmpText.length()-1),
|
|
startIndex, endIndex, lineEndType, spaceConsumed - spaceWidth) );
|
|
// LOG_INFO("Text Input Line: [%s]", textLines.back()->text.c_str());
|
|
}
|
|
else {
|
|
endIndex = index+spaceIndex;
|
|
index += spaceIndex+1;
|
|
textLines.push_back( new TextLine( tmpText.substr(0,spaceIndex),
|
|
startIndex, endIndex, lineEndType, font->getPixelWidth(tmpText.substr(0,spaceIndex))) );
|
|
// LOG_INFO("Text Input Line: [%s]", textLines.back()->text.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( textType == TextType::SINGLE_LINE ) {
|
|
LOG_INFO("NUMBER OF LINES: %d", textLines.size());
|
|
auto textLine = textLines.front();
|
|
textLine->endType = LineEndType::EOT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for( auto it : textLines ) {
|
|
LOG_INFO("text: [%s] ending: %d", it->text.c_str(), static_cast<uint32_t>(it->endType));
|
|
}
|
|
|
|
firstLine = textLines.begin();
|
|
lastLine = textLines.begin();
|
|
}
|
|
|
|
bool Text::splitText( UTF8& source, UTF8& remaining, LineEndType& endType, uint32_t availableSpace ){
|
|
|
|
uint32_t spaceConsumed;
|
|
uint32_t charCount = font->getCharCountInSpace( source, availableSpace, spaceConsumed );
|
|
//this is sub-string that fits available space.
|
|
UTF8 searchStr = source.substr( 0, charCount );
|
|
|
|
//try to find first enter.
|
|
uint32_t enterIndex = searchStr.find( "\n",0);
|
|
if( enterIndex != UTF8::npos ) {
|
|
endType = LineEndType::BREAK;
|
|
remaining = source.substr( enterIndex, source.length() - 1 - enterIndex );
|
|
source.split( enterIndex );
|
|
// LOG_INFO("Split Text: source: [%s] remaining: [%s]", source.c_str(), remaining.c_str());
|
|
return true;
|
|
}
|
|
else {
|
|
//if there was no enter look for last space in the source and break line on it
|
|
uint32_t spaceIndex = searchStr.findLast( " ",searchStr.length()-1);
|
|
|
|
//if there was no space take as many characters as possible and add CONTINUE ending
|
|
if( spaceIndex == UTF8::npos ) {
|
|
remaining = source.split( charCount );
|
|
endType = LineEndType::CONTINUE;
|
|
// LOG_INFO("Split Text: source: [%s] remaining: [%s]", source.c_str(), remaining.c_str());
|
|
return true;
|
|
}
|
|
else {
|
|
endType = LineEndType::CONTINUE_SPACE;
|
|
|
|
remaining = source.substr( spaceIndex+1, source.length() - 1 - spaceIndex);
|
|
source.split( spaceIndex );
|
|
// LOG_INFO("Split Text: source: [%s] remaining: [%s]", source.c_str(), remaining.c_str());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Text::reworkLines( std::list<TextLine*>::iterator it ) {
|
|
|
|
//iterate until end of text lines or till line that fits available space has break line ending (enter).
|
|
while( it != textLines.end() ) {
|
|
|
|
//if current line has BREAK of EOT line ending check if current text fits available space
|
|
//finish procedure
|
|
uint32_t availableSpace = getAvailableHPixelSpace();
|
|
uint32_t consumedSpace;
|
|
|
|
if( ((*it)->endType == LineEndType::BREAK) || ((*it)->endType == LineEndType::EOT) ) {
|
|
consumedSpace = font->getPixelWidth( (*it)->getText() );
|
|
if( consumedSpace < availableSpace )
|
|
break;
|
|
}
|
|
|
|
//check if there is next line
|
|
auto itNext = it;
|
|
itNext++;
|
|
|
|
UTF8 mergedLinesText = (*it)->getTextWithEnding();
|
|
|
|
//if processed text line is not finished with break end type
|
|
if( ((*it)->endType != LineEndType::BREAK ) && (itNext != textLines.end()) ) {
|
|
|
|
//merge text from two lines
|
|
mergedLinesText += (*itNext)->getTextWithEnding();
|
|
//assign end type from next line to the current line
|
|
(*it)->endType = (*itNext)->endType;
|
|
|
|
//remove next line as the text was taken to the current line
|
|
delete (*itNext );
|
|
textLines.erase( itNext );
|
|
}
|
|
|
|
LineEndType endType;
|
|
UTF8 remainingText;
|
|
bool splitFlag = splitText( mergedLinesText, remainingText, endType, availableSpace );
|
|
|
|
//if there was a split update current and next item in the list
|
|
if( splitFlag ) {
|
|
|
|
(*it)->text = std::move(mergedLinesText);
|
|
(*it)->pixelLength = font->getPixelWidth( (*it)->getText() );
|
|
|
|
itNext = it;
|
|
itNext++;
|
|
textLines.insert( itNext, new TextLine( remainingText,
|
|
0, remainingText.length(), (*it)->endType, font->getPixelWidth( remainingText ) ) );
|
|
|
|
(*it)->endType = endType;
|
|
}
|
|
|
|
//proceed to next line
|
|
it++;
|
|
|
|
}
|
|
|
|
//TODO starting from first modified line up to last modified line update start and end index
|
|
|
|
}
|
|
|
|
|
|
std::list<DrawCommand*> Text::buildDrawList() {
|
|
return Rect::buildDrawList();
|
|
}
|
|
void Text::setPosition( const short& x, const short& y ) {
|
|
Rect::setPosition(x, y);
|
|
recalculateDrawParams();
|
|
updateCursor();
|
|
}
|
|
|
|
void Text::setSize( const short& w, const short& h ) {
|
|
Rect::setSize( w, h );
|
|
recalculateDrawParams();
|
|
updateCursor();
|
|
}
|
|
|
|
bool Text::onInput( const InputEvent& inputEvent ) {
|
|
|
|
//process only short release events
|
|
if( inputEvent.state != InputEvent::State::keyReleasedShort ) {
|
|
return false;
|
|
}
|
|
|
|
//check if this is navigation event
|
|
bool res = false;
|
|
if(( inputEvent.keyCode == KeyCode::KEY_LEFT ) ||
|
|
( inputEvent.keyCode == KeyCode::KEY_RIGHT ) ||
|
|
( inputEvent.keyCode == KeyCode::KEY_UP ) ||
|
|
( inputEvent.keyCode == KeyCode::KEY_DOWN )) {
|
|
if( editMode == EditMode::BROWSE )
|
|
res = handleBrowsing( inputEvent );
|
|
else
|
|
res = handleNavigation( inputEvent );
|
|
|
|
if( res )
|
|
updateCursor();
|
|
return res;
|
|
}
|
|
|
|
//it there is no key char it means that translator didn't handled the key and this key
|
|
//press is not for text input.
|
|
if( inputEvent.keyChar == 0 )
|
|
return false;
|
|
|
|
if( inputEvent.cycle ) {
|
|
handleBackspace();
|
|
res = handleChar( inputEvent );
|
|
if( res ) {
|
|
updateCursor();
|
|
contentCallback(*this);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
//if char is a new line char then create new line and move caret and return
|
|
if( inputEvent.keyChar == 0x0A) {
|
|
if( textType == TextType::MULTI_LINE ){
|
|
res = handleEnter();
|
|
contentCallback(*this);
|
|
}
|
|
}
|
|
//backspace handling
|
|
else if( inputEvent.keyChar == 0x08 ) {
|
|
res = handleBackspace();
|
|
contentCallback(*this);
|
|
}
|
|
else { //normal char -> add and check pixel width
|
|
res = handleChar( inputEvent );
|
|
contentCallback(*this);
|
|
}
|
|
if( res )
|
|
updateCursor();
|
|
return res;
|
|
}
|
|
|
|
bool Text::onActivated( void* data ) {
|
|
Rect::onActivated( data );
|
|
return false;
|
|
}
|
|
|
|
bool Text::onFocus( bool state ) {
|
|
bool ret = Rect::onFocus( state );
|
|
if( focus && editMode == EditMode::EDIT ) {
|
|
cursor->setVisible(true);
|
|
}
|
|
else
|
|
cursor->setVisible(false);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void Text::setRadius( int value ) {
|
|
Rect::setRadius( value );
|
|
//if margins are smaller than radius update the margins
|
|
if( margins.left < value )
|
|
margins.left = value;
|
|
if( margins.right < value )
|
|
margins.right = value;
|
|
updateCursor();
|
|
}
|
|
|
|
bool Text::onDimensionChanged( const BoundingBox& oldDim, const BoundingBox& newDim) {
|
|
Rect::onDimensionChanged(oldDim, newDim);
|
|
updateCursor();
|
|
return false;
|
|
}
|
|
|
|
bool Text::moveCursor( const NavigationDirection& direction ) {
|
|
|
|
auto it = getCursorTextLine();
|
|
|
|
if( direction == NavigationDirection::LEFT ) {
|
|
//if we are standing on the beginning for the line move to previous line
|
|
if( cursorColumn == 0 ) {
|
|
|
|
//if there is no previous line return false so window can switch focus to the item on the left.
|
|
if( it == textLines.begin()) {
|
|
return false;
|
|
}
|
|
|
|
//there is a previous line, check if cursor's row is greater than 0;
|
|
cursorColumn = (*std::prev(it,1))->text.length();
|
|
if( cursorRow > 0 ) {
|
|
--cursorRow;
|
|
}
|
|
else {
|
|
--firstLine;
|
|
recalculateDrawParams();
|
|
}
|
|
return true;
|
|
}
|
|
//cursor's column is greater than 0
|
|
else {
|
|
--cursorColumn;
|
|
return true;
|
|
}
|
|
}
|
|
else if( direction == NavigationDirection::RIGHT ) {
|
|
//if cursor is not at the end of current line simply move curosr
|
|
if( cursorColumn < (*it)->text.length()) {
|
|
++cursorColumn;
|
|
return true;
|
|
}
|
|
else {
|
|
auto itNext = std::next( it, 1 );
|
|
//if this is not the last line increment row and set column to 0
|
|
if( itNext != textLines.end() ) {
|
|
++cursorRow;
|
|
cursorColumn = 0;
|
|
|
|
//if increased row is out of visible are increment first line
|
|
if( cursorRow >= visibleRows ) {
|
|
firstLine++;
|
|
recalculateDrawParams();
|
|
cursorRow = visibleRows-1;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if( direction == NavigationDirection::DOWN ) {
|
|
|
|
//if this is a single line text widget there is no navigation down allowed
|
|
if( textType == TextType::SINGLE_LINE )
|
|
return false;
|
|
|
|
auto itNext = std::next( it, 1 );
|
|
|
|
//this is the last line, check for barrier
|
|
if( itNext == textLines.end( )) {
|
|
if( barriers & static_cast<uint32_t>(NavigationBarrier::BARRIER_DOWN))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
//increment line
|
|
++cursorRow;
|
|
|
|
//check if column position is still valid
|
|
if( cursorColumn > (*itNext)->text.length())
|
|
cursorColumn = (*itNext)->text.length();
|
|
|
|
if( cursorRow >= visibleRows ) {
|
|
firstLine++;
|
|
recalculateDrawParams();
|
|
cursorRow = visibleRows-1;
|
|
}
|
|
return true;
|
|
}
|
|
else if( direction == NavigationDirection::UP ) {
|
|
|
|
//if this is a single line text widget there is no navigation up allowed
|
|
if( textType == TextType::SINGLE_LINE )
|
|
return false;
|
|
|
|
//if cursor is standing on the first line return false to allow focus change to previous widget
|
|
if( it == textLines.begin()) {
|
|
return false;
|
|
}
|
|
|
|
auto itPrev = std::prev( it, 1 );
|
|
if( cursorRow == 0 ) {
|
|
--firstLine;
|
|
|
|
recalculateDrawParams();
|
|
return true;
|
|
}
|
|
else {
|
|
--cursorRow;
|
|
}
|
|
|
|
//check if previous line is shorter than last one if so move cursor to the end of previous line
|
|
if( cursorColumn > (*itPrev)->text.length()) {
|
|
cursorColumn = (*itPrev)->text.length();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Text::handleBrowsing( const InputEvent& inputEvent ) {
|
|
//if this is a single line text widget there is no browsing allowed
|
|
if( textType == TextType::SINGLE_LINE )
|
|
return false;
|
|
|
|
switch( inputEvent.keyCode )
|
|
{
|
|
case (KeyCode::KEY_UP):
|
|
{
|
|
//move cursor to first visible element
|
|
cursorRow = 0;
|
|
return moveCursor( NavigationDirection::UP );
|
|
} break;
|
|
case KeyCode::KEY_DOWN:
|
|
{
|
|
//move cursor to the last visible element
|
|
auto it = firstLine;
|
|
cursorRow = 0;
|
|
while( (it != textLines.end()) && (cursorRow < visibleRows-1) ) {
|
|
it++;
|
|
cursorRow++;
|
|
}
|
|
|
|
return moveCursor( NavigationDirection::DOWN );
|
|
} break;
|
|
default:
|
|
{
|
|
LOG_ERROR("Received unknown navigation key");
|
|
}
|
|
};
|
|
return false;
|
|
}
|
|
|
|
bool Text::handleNavigation( const InputEvent& inputEvent ) {
|
|
|
|
switch( inputEvent.keyCode )
|
|
{
|
|
case (KeyCode::KEY_UP):
|
|
{
|
|
return moveCursor( NavigationDirection::UP );
|
|
} break;
|
|
case KeyCode::KEY_DOWN:
|
|
{
|
|
return moveCursor( NavigationDirection::DOWN );
|
|
} break;
|
|
case KeyCode::KEY_LEFT:
|
|
{
|
|
return moveCursor( NavigationDirection::LEFT );
|
|
} break;
|
|
case KeyCode::KEY_RIGHT:
|
|
{
|
|
return moveCursor( NavigationDirection::RIGHT );
|
|
} break;
|
|
default:
|
|
{
|
|
LOG_ERROR("Received unknown navigation key");
|
|
}
|
|
};
|
|
return false;
|
|
}
|
|
|
|
bool Text::handleEnter() {
|
|
|
|
if( editMode == EditMode::BROWSE )
|
|
return false;
|
|
|
|
//get textline where cursor is located
|
|
auto it = firstLine;
|
|
std::advance(it, cursorRow );
|
|
|
|
//split current text in line using cursors position
|
|
UTF8 remainingText = (*it)->text.split( cursorColumn );
|
|
|
|
//store old type of line ending set new type of ending to the current line
|
|
LineEndType endType = (*it)->endType;
|
|
(*it)->endType = LineEndType::BREAK;
|
|
|
|
//create and add new line using remaining parts of text
|
|
auto itNext = it;
|
|
++itNext;
|
|
textLines.insert( itNext, new TextLine( remainingText, 0, remainingText.length(), endType, font->getPixelWidth( remainingText) ) );
|
|
cursorRow++;
|
|
|
|
if( cursorRow >= visibleRows ) {
|
|
cursorRow = visibleRows - 1;
|
|
firstLine++;
|
|
}
|
|
|
|
cursorColumn = 0;
|
|
|
|
reworkLines( it );
|
|
|
|
recalculateDrawParams();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Text::handleBackspace() {
|
|
|
|
if( editMode == EditMode::BROWSE )
|
|
return false;
|
|
|
|
//if cursor is in column 0 and there is no previous line return
|
|
if( (cursorRow == 0 ) && (cursorColumn == 0) &&
|
|
(firstLine == textLines.begin()) )
|
|
return true;
|
|
|
|
//if cursor is in position other than 0 remove previous character and run lines rework
|
|
auto it = getCursorTextLine();
|
|
if( cursorColumn > 0 ) {
|
|
TextLine* currentTextLine = (*it);
|
|
currentTextLine->text.removeChar( cursorColumn - 1 );
|
|
cursorColumn--;
|
|
}
|
|
//this is when cursor is located at the beginning of the line and there are previous lines
|
|
else {
|
|
|
|
if( it == textLines.begin() ) {
|
|
return true;
|
|
}
|
|
|
|
auto itPrev = std::prev(it,1);
|
|
|
|
//if ending is equal to LineEndType::CONTINUE delete last char from current string
|
|
if( (*itPrev)->endType == LineEndType::CONTINUE ) {
|
|
(*itPrev)->text.removeChar( (*itPrev)->text.length()-1);
|
|
}
|
|
if( (*itPrev)->endType == LineEndType::CONTINUE_SPACE ) {
|
|
(*itPrev)->endType = LineEndType::CONTINUE;
|
|
}
|
|
else if( (*itPrev)->endType == LineEndType::BREAK ) {
|
|
(*itPrev)->endType = LineEndType::CONTINUE;
|
|
|
|
}
|
|
|
|
cursorColumn = (*itPrev)->text.length();
|
|
|
|
if( cursorRow == 0 ) {
|
|
firstLine = itPrev;
|
|
}
|
|
else {
|
|
--cursorRow;
|
|
}
|
|
|
|
(*itPrev)->text += (*it)->text;
|
|
(*itPrev)->endType = (*it)->endType;
|
|
|
|
//delete current line
|
|
textLines.erase(it);
|
|
it = itPrev;
|
|
}
|
|
|
|
reworkLines( it );
|
|
recalculateDrawParams();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Text::handleChar( const InputEvent& inputEvent ) {
|
|
|
|
if( editMode == EditMode::BROWSE )
|
|
return false;
|
|
|
|
//get text line where cursor is standing
|
|
auto it = getCursorTextLine();
|
|
TextLine* currentTextLine = (*it);
|
|
|
|
//calculate width of the character that is going to be inserted
|
|
uint32_t charWidth = font->getCharPixelWidth( inputEvent.keyChar );
|
|
|
|
//insert character into string in currently selected line
|
|
if( currentTextLine->text.insertCode( inputEvent.keyChar, cursorColumn ) == false )
|
|
return false;
|
|
|
|
//if sum of the old string and width of the new character are greater than available space run lines rework procedure
|
|
uint32_t linesCount = textLines.size();
|
|
uint32_t availableSpace = getAvailableHPixelSpace();
|
|
uint32_t currentWidth = currentTextLine->pixelLength;
|
|
if( currentWidth + charWidth > availableSpace ) {
|
|
|
|
//this is the case when new character inserted into single line text
|
|
//is creating the line that doesn't fit available space.
|
|
if( textType == TextType::SINGLE_LINE ) {
|
|
|
|
currentTextLine->text.removeChar( cursorColumn, 1 );
|
|
return true;
|
|
}
|
|
|
|
++cursorColumn;
|
|
reworkLines( it );
|
|
|
|
//if cursor position is greater than number of characters in current line move cursor to next line.
|
|
if( cursorColumn > (*it)->text.length()) {
|
|
cursorColumn = 0;
|
|
++cursorRow;
|
|
}
|
|
|
|
if( cursorRow >= visibleRows ) {
|
|
firstRow++;
|
|
}
|
|
|
|
}
|
|
//no line splitting, update pixel width and proceed
|
|
else {
|
|
currentTextLine->pixelLength = font->getPixelWidth( currentTextLine->text );
|
|
++cursorColumn;
|
|
}
|
|
|
|
//if number of text lines have increased, text widget is multi-line and expandable change widgets space
|
|
// if( linesCount != textLines.size()) {
|
|
//
|
|
// }
|
|
|
|
recalculateDrawParams();
|
|
//calculate new position of the cursor
|
|
|
|
return true;
|
|
}
|
|
|
|
std::list<Text::TextLine*>::iterator Text::getCursorTextLine() {
|
|
auto it = firstLine;
|
|
//TODO add check for distance to advance
|
|
std::advance(it, cursorRow );
|
|
return it;
|
|
}
|
|
|
|
void Text::updateCursor() {
|
|
cursor->setSize( 2, font->info.line_height );
|
|
auto it = std::next( firstLine, cursorRow );
|
|
|
|
uint32_t posX = margins.left + font->getPixelWidth( (*it)->text, 0, cursorColumn );
|
|
uint32_t posY = margins.top + cursorRow*font->info.line_height;
|
|
cursor->setPosition( posX, posY );
|
|
}
|
|
|
|
void Text::recalculateDrawParams() {
|
|
|
|
//calculate number of lines for displaying text
|
|
int32_t h = widgetArea.h - margins.top - margins.bottom;
|
|
if( h < 0 )
|
|
h = 0;
|
|
|
|
//remove all old labels
|
|
for( uint32_t i=0; i<labelLines.size(); ++i ) {
|
|
removeWidget( labelLines[i]);
|
|
delete labelLines[i];
|
|
}
|
|
|
|
labelLines.clear();
|
|
|
|
//calculate how many rows can fit in available height.
|
|
uint32_t rowCount = h / font->info.line_height;
|
|
rowCount = (rowCount == 0)?1:rowCount;
|
|
|
|
if( textType == TextType::SINGLE_LINE ) {
|
|
rowCount = 1;
|
|
}
|
|
|
|
//if there is not enough space for single line start from 0 and ignore vertical margins
|
|
uint32_t startY = ( h < font->info.line_height )?0:margins.top;
|
|
|
|
//create labels to display text. There will be always at least one.
|
|
for( uint32_t i=0; i<rowCount; i++ ) {
|
|
gui::Label* label = new gui::Label( this, margins.left, startY, widgetArea.w - margins.left - margins.right, font->info.line_height );
|
|
label->setFilled( false );
|
|
label->setFont( font-> getName() );
|
|
label->setAlignement( gui::Alignment(gui::Alignment::ALIGN_HORIZONTAL_LEFT, gui::Alignment::ALIGN_VERTICAL_BOTTOM));
|
|
if( underline )
|
|
label->setEdges( RectangleEdgeFlags::GUI_RECT_EDGE_BOTTOM);
|
|
else
|
|
label->setEdges( RectangleEdgeFlags::GUI_RECT_EDGE_NO_EDGES );
|
|
labelLines.push_back( label );
|
|
startY += font->info.line_height;
|
|
}
|
|
visibleRows = rowCount;
|
|
|
|
//assign text to all lines
|
|
auto textIterator = firstLine;
|
|
for( uint32_t i=0; i<labelLines.size(); i++ ) {
|
|
if( textIterator == textLines.end())
|
|
break;
|
|
labelLines[i]->setText( (*textIterator)->text );
|
|
lastLine = textIterator;
|
|
textIterator++;
|
|
|
|
}
|
|
}
|
|
|
|
void Text::setMargins( const Margins& margins ) {
|
|
this->margins = margins;
|
|
recalculateDrawParams();
|
|
updateCursor();
|
|
}
|
|
|
|
Item* Text::getNavigationItem( NavigationDirection direction ) {
|
|
//if provided direction is forbidden than return nullptr
|
|
if( barriers & static_cast<uint32_t>(direction))
|
|
return nullptr;
|
|
|
|
//otherwise run default navigation method (Item)
|
|
return Rect::getNavigationItem( direction );
|
|
}
|
|
|
|
} /* namespace gui */
|