Fix text wrapping (fixes #166)

This breaks the syntax highlighting in ConsoleView.
I haven't yet thought of a way to fix that properly,
ideas anyone?
This commit is contained in:
Tamás Bálint Misius 2019-07-19 02:53:54 +02:00
parent c5c4d92c32
commit 4bb85578a8
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
10 changed files with 506 additions and 394 deletions

View File

@ -644,71 +644,6 @@ int Graphics::textwidthx(String str, int w)
return n;
}
int Graphics::PositionAtCharIndex(String str, int charIndex, int & positionX, int & positionY)
{
int x = 0, y = 0, lines = 1;
String::value_type const *s = str.c_str();
for (; *s; s++)
{
if (!charIndex)
break;
if(*s == '\n') {
lines++;
x = 0;
y += FONT_H;
charIndex--;
continue;
} else if(*s =='\b') {
if(!s[1]) break;
s++;
charIndex-=2;
continue;
} else if(*s == '\x0F') {
if(!s[1] || !s[2] || !s[3]) break;
s+=3;
charIndex-=4;
continue;
}
x += FontReader(*s).GetWidth();
charIndex--;
}
positionX = x;
positionY = y;
return lines;
}
int Graphics::CharIndexAtPosition(String str, int positionX, int positionY)
{
int x=0, y=-2,charIndex=0,cw;
String::value_type const *s = str.c_str();
for (; *s; s++)
{
if(*s == '\n') {
x = 0;
y += FONT_H;
charIndex++;
continue;
} else if(*s == '\b') {
if(!s[1]) break;
s++;
charIndex+=2;
continue;
} else if (*s == '\x0F') {
if(!s[1] || !s[2] || !s[3]) break;
s+=3;
charIndex+=4;
continue;
}
cw = FontReader(*s).GetWidth();
if ((x+(cw/2) >= positionX && y+FONT_H >= positionY) || y > positionY)
break;
x += cw;
charIndex++;
}
return charIndex;
}
int Graphics::textwrapheight(String str, int width)
{
int x=0, height=FONT_H, cw;

View File

@ -113,8 +113,6 @@ public:
static pixel *render_packed_rgb(void *image, int width, int height, int cmp_size);
//Font/text metrics
static int CharIndexAtPosition(String s, int positionX, int positionY);
static int PositionAtCharIndex(String s, int charIndex, int & positionX, int & positionY);
static int CharWidth(String::value_type c);
static int textnwidth(String s, int n);
static void textnpos(String s, int n, int w, int *cx, int *cy);

View File

@ -26,7 +26,7 @@ ConsoleView::ConsoleView():
CommandHighlighter(ConsoleView * v_) { v = v_; }
void TextChangedCallback(ui::Textbox * sender) override
{
sender->SetDisplayText(v->c->FormatCommand(sender->GetText()));
// sender->SetDisplayText(v->c->FormatCommand(sender->GetText()));
}
};
commandField = new ui::Textbox(ui::Point(0, Size.Y-16), ui::Point(Size.X, 16), "");
@ -52,7 +52,7 @@ void ConsoleView::DoKeyPress(int key, int scan, bool repeat, bool shift, bool ct
case SDLK_KP_ENTER:
c->EvaluateCommand(commandField->GetText());
commandField->SetText("");
commandField->SetDisplayText("");
// commandField->SetDisplayText("");
break;
case SDLK_DOWN:
c->NextCommand();
@ -106,7 +106,7 @@ void ConsoleView::NotifyPreviousCommandsChanged(ConsoleModel * sender)
void ConsoleView::NotifyCurrentCommandChanged(ConsoleModel * sender)
{
commandField->SetText(sender->GetCurrentCommand().Command);
commandField->SetDisplayText(c->FormatCommand(commandField->GetText()));
// commandField->SetDisplayText(c->FormatCommand(commandField->GetText()));
}

View File

@ -13,164 +13,53 @@ using namespace ui;
Label::Label(Point position, Point size, String labelText):
Component(position, size),
text(labelText),
textColour(255, 255, 255),
selectionIndex0(-1),
selectionIndex1(-1),
selectionXL(-1),
selectionXH(-1),
selectionIndexL(textWrapper.IndexBegin()),
selectionIndexH(textWrapper.IndexBegin()),
multiline(false),
selecting(false),
autoHeight(size.Y==-1?true:false)
{
SetText(labelText);
menu = new ContextMenu(this);
menu->AddItem(ContextMenuItem("Copy", 0, true));
}
Label::~Label()
{
}
void Label::SetMultiline(bool status)
{
multiline = status;
if(status)
{
updateMultiline();
updateTextWrapper();
updateSelection();
TextPosition(textLines);
}
else
{
TextPosition(text);
}
TextPosition(textWrapper.WrappedText());
}
void Label::SetText(String text)
void Label::SetText(String newText)
{
this->text = text;
if(multiline)
{
updateMultiline();
this->text = newText;
updateTextWrapper();
updateSelection();
TextPosition(textLines);
}
else
{
TextPosition(text);
}
TextPosition(textWrapper.WrappedText());
}
void Label::AutoHeight()
{
bool oldAH = autoHeight;
autoHeight = true;
updateMultiline();
updateTextWrapper();
autoHeight = oldAH;
}
void Label::updateMultiline()
void Label::updateTextWrapper()
{
int lines = 1;
if (text.length()>0)
{
String::value_type *rawText = new String::value_type[text.length()+1];
std::copy(text.begin(), text.end(), rawText);
rawText[text.length()] = 0;
String::value_type c, pc = 0;
int charIndex = 0;
int wordWidth = 0;
int lineWidth = 0;
String::value_type *wordStart = NULL;
while ((c = rawText[charIndex++]))
{
switch(c)
{
case ' ':
lineWidth += Graphics::CharWidth(c);
lineWidth += wordWidth;
wordWidth = 0;
break;
case '\n':
lineWidth = wordWidth = 0;
lines++;
break;
default:
wordWidth += Graphics::CharWidth(c);
break;
}
if (pc == ' ')
{
wordStart = &rawText[charIndex-2];
}
if ((c != ' ' || pc == ' ') && lineWidth + wordWidth >= Size.X-(Appearance.Margin.Left+Appearance.Margin.Right))
{
if (wordStart && *wordStart)
{
*wordStart = '\n';
if (lineWidth != 0)
lineWidth = wordWidth;
}
else if (!wordStart)
{
rawText[charIndex-1] = '\n';
lineWidth = 0;
}
wordWidth = 0;
wordStart = 0;
lines++;
}
pc = c;
}
int lines = textWrapper.Update(text, multiline, Size.X - Appearance.Margin.Left - Appearance.Margin.Right);
if (autoHeight)
{
Size.Y = lines*12+3;
}
textLines = rawText;
delete[] rawText;
/*int currentWidth = 0;
char * lastSpace = NULL;
char * currentWord = rawText;
char * nextSpace;
while(true)
{
nextSpace = strchr(currentWord+1, ' ');
if(nextSpace)
nextSpace[0] = 0;
int width = Graphics::textwidth(currentWord);
if(width+currentWidth >= Size.X-(Appearance.Margin.Left+Appearance.Margin.Right))
{
currentWidth = width;
if(currentWord!=rawText)
{
currentWord[0] = '\n';
lines++;
}
}
else
currentWidth += width;
if(nextSpace)
nextSpace[0] = ' ';
if(!currentWord[0] || !currentWord[1] || !(currentWord = strchr(currentWord+1, ' ')))
break;
}
if(autoHeight)
{
Size.Y = lines*12;
}
textLines = std::string(rawText);
delete[] rawText;*/
}
else
{
if (autoHeight)
{
Size.Y = 15;
}
textLines = "";
Size.Y = lines * 12 + 3;
}
}
@ -193,17 +82,17 @@ void Label::OnMouseClick(int x, int y, unsigned button)
{
if(button == SDL_BUTTON_RIGHT)
{
if(menu)
if (menu)
{
menu->Show(GetScreenPos() + ui::Point(x, y));
}
}
else
{
selecting = true;
if(multiline)
selectionIndex0 = Graphics::CharIndexAtPosition(textLines, x-textPosition.X, y-textPosition.Y);
else
selectionIndex0 = Graphics::CharIndexAtPosition(text, x-textPosition.X, y-textPosition.Y);
selectionIndex1 = selectionIndex0;
selectionIndex0 = textWrapper.Point2Index(x - textPosition.X, y - textPosition.Y);
selectionIndexL = selectionIndex0;
selectionIndexH = selectionIndex0;
updateSelection();
}
@ -211,18 +100,10 @@ void Label::OnMouseClick(int x, int y, unsigned button)
void Label::copySelection()
{
String currentText = text;
String copyText;
if (selectionIndex1 > selectionIndex0)
copyText = currentText.Between(selectionIndex0, selectionIndex1).c_str();
else if(selectionIndex0 > selectionIndex1)
copyText = currentText.Between(selectionIndex1, selectionIndex0).c_str();
else if (!currentText.length())
return;
else
copyText = currentText.c_str();
ClipboardPush(format::CleanString(copyText, false, true, false).ToUtf8());
if (HasSelection())
{
ClipboardPush(format::CleanString(text.Between(selectionIndexL.raw_index, selectionIndexH.raw_index), false, true, false).ToUtf8());
}
}
void Label::OnMouseUp(int x, int y, unsigned button)
@ -233,7 +114,9 @@ void Label::OnMouseUp(int x, int y, unsigned button)
void Label::OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt)
{
if (repeat)
{
return;
}
if (ctrl && scan == SDL_SCANCODE_C)
{
copySelection();
@ -247,19 +130,26 @@ void Label::OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bo
void Label::OnMouseMoved(int localx, int localy, int dx, int dy)
{
if(selecting)
if (selecting)
{
if(multiline)
selectionIndex1 = Graphics::CharIndexAtPosition(textLines, localx-textPosition.X, localy-textPosition.Y);
selectionIndex1 = textWrapper.Point2Index(localx - textPosition.X, localy - textPosition.Y);
if (selectionIndex1.raw_index < selectionIndex0.raw_index)
{
selectionIndexL = selectionIndex1;
selectionIndexH = selectionIndex0;
}
else
selectionIndex1 = Graphics::CharIndexAtPosition(text, localx-textPosition.X, localy-textPosition.Y);
{
selectionIndexL = selectionIndex0;
selectionIndexH = selectionIndex1;
}
updateSelection();
}
}
void Label::Tick(float dt)
{
if(!this->IsFocused() && (selecting || (selectionIndex0 != -1 && selectionIndex1 != -1)))
if (!this->IsFocused() && (HasSelection() || selecting))
{
ClearSelection();
}
@ -267,174 +157,123 @@ void Label::Tick(float dt)
int Label::getLowerSelectionBound()
{
return (selectionIndex0 > selectionIndex1) ? selectionIndex1 : selectionIndex0;
return selectionIndexL.raw_index;
}
int Label::getHigherSelectionBound()
{
return (selectionIndex0 > selectionIndex1) ? selectionIndex0 : selectionIndex1;
return selectionIndexH.raw_index;
}
bool Label::HasSelection()
{
if(selectionIndex0 != -1 && selectionIndex1 != -1 && selectionIndex0 != selectionIndex1)
return true;
return false;
return selectionIndexH.raw_index > selectionIndexL.raw_index;
}
void Label::ClearSelection()
{
selecting = false;
selectionIndex0 = -1;
selectionIndex1 = -1;
selectionIndexL = textWrapper.IndexBegin();
selectionIndexH = textWrapper.IndexBegin();
updateSelection();
}
void Label::selectAll()
{
selectionIndex0 = 0;
selectionIndex1 = text.length();
selectionIndexL = textWrapper.IndexBegin();
selectionIndexH = textWrapper.IndexEnd();
updateSelection();
}
void Label::updateSelection()
{
String currentText;
if (selectionIndexL.raw_index < 0) selectionIndexL = textWrapper.IndexBegin();
if (selectionIndexL.raw_index > (int)text.length()) selectionIndexL = textWrapper.IndexEnd();
if (selectionIndexH.raw_index < 0) selectionIndexH = textWrapper.IndexBegin();
if (selectionIndexH.raw_index > (int)text.length()) selectionIndexH = textWrapper.IndexEnd();
if (selectionIndex0 < 0) selectionIndex0 = 0;
if (selectionIndex0 > (int)text.length()) selectionIndex0 = text.length();
if (selectionIndex1 < 0) selectionIndex1 = 0;
if (selectionIndex1 > (int)text.length()) selectionIndex1 = text.length();
if(selectionIndex0 == -1 || selectionIndex1 == -1)
displayTextWithSelection = textWrapper.WrappedText();
if (HasSelection())
{
selectionXH = -1;
selectionXL = -1;
textFragments = currentText;
return;
}
if(multiline)
currentText = textLines;
else
currentText = text;
if(selectionIndex1 > selectionIndex0) {
selectionLineH = Graphics::PositionAtCharIndex(currentText, selectionIndex1, selectionXH, selectionYH);
selectionLineL = Graphics::PositionAtCharIndex(currentText, selectionIndex0, selectionXL, selectionYL);
textFragments = currentText;
//textFragments.insert(selectionIndex1, "\x0E");
//textFragments.insert(selectionIndex0, "\x0F\x01\x01\x01");
textFragments.Insert(selectionIndex1, "\x01");
textFragments.Insert(selectionIndex0, "\x01");
} else if(selectionIndex0 > selectionIndex1) {
selectionLineH = Graphics::PositionAtCharIndex(currentText, selectionIndex0, selectionXH, selectionYH);
selectionLineL = Graphics::PositionAtCharIndex(currentText, selectionIndex1, selectionXL, selectionYL);
textFragments = currentText;
//textFragments.insert(selectionIndex0, "\x0E");
//textFragments.insert(selectionIndex1, "\x0F\x01\x01\x01");
textFragments.Insert(selectionIndex0, "\x01");
textFragments.Insert(selectionIndex1, "\x01");
} else {
selectionXH = -1;
selectionXL = -1;
textFragments = currentText;
}
if(displayText.length())
{
displayText = tDisplayText;
if(selectionIndex1 > selectionIndex0) {
int tSelectionIndex1 = Graphics::CharIndexAtPosition(displayText, selectionXH, selectionYH);
int tSelectionIndex0 = Graphics::CharIndexAtPosition(displayText, selectionXL, selectionYL);
displayText.Insert(tSelectionIndex1, "\x01");
displayText.Insert(tSelectionIndex0, "\x01");
} else if(selectionIndex0 > selectionIndex1) {
int tSelectionIndex0 = Graphics::CharIndexAtPosition(displayText, selectionXH, selectionYH);
int tSelectionIndex1 = Graphics::CharIndexAtPosition(displayText, selectionXL, selectionYL);
displayText.Insert(tSelectionIndex0, "\x01");
displayText.Insert(tSelectionIndex1, "\x01");
}
displayTextWithSelection.Insert(selectionIndexL.wrapped_index, "\x01");
displayTextWithSelection.Insert(selectionIndexH.wrapped_index + 1, "\x01");
}
}
void Label::SetDisplayText(String newText)
{
ClearSelection();
displayText = tDisplayText = newText;
}
// void Label::SetDisplayText(String newText)
// {
// displayText = newText;
// ClearSelection();
// updateTextWrapper();
// updateSelection();
// TextPosition(textWrapper.WrappedText());
// }
void Label::Draw(const Point& screenPos)
{
if(!drawn)
if (!drawn)
{
if(multiline)
{
TextPosition(textLines);
updateMultiline();
TextPosition(textWrapper.WrappedText());
updateTextWrapper();
updateSelection();
}
else
TextPosition(text);
drawn = true;
}
Graphics * g = GetGraphics();
Graphics *g = GetGraphics();
String cDisplayText = displayText;
int selectionXL;
int selectionYL;
int selectionLineL = textWrapper.Index2Point(selectionIndexL, selectionXL, selectionYL);
if(!cDisplayText.length())
int selectionXH;
int selectionYH;
int selectionLineH = textWrapper.Index2Point(selectionIndexH, selectionXH, selectionYH);
if (HasSelection())
{
if(selectionXL != -1 && selectionXH != -1)
if (selectionLineH == selectionLineL)
{
cDisplayText = textFragments;
g->fillrect(
screenPos.X + textPosition.X + selectionXL,
screenPos.Y + textPosition.Y + selectionYL - 1,
selectionXH - selectionXL,
10,
255, 255, 255, 255
);
}
else
{
if(multiline)
cDisplayText = textLines;
else
cDisplayText = text;
g->fillrect(
screenPos.X + textPosition.X + selectionXL,
screenPos.Y + textPosition.Y + selectionYL - 1,
textSize.X - selectionXL,
10,
255, 255, 255, 255
);
for (int i = 1; i < selectionLineH - selectionLineL; ++i)
{
g->fillrect(
screenPos.X + textPosition.X,
screenPos.Y + textPosition.Y + selectionYL - 1 + i * 12,
textSize.X,
10,
255, 255, 255, 255
);
}
g->fillrect(
screenPos.X + textPosition.X,
screenPos.Y + textPosition.Y + selectionYH - 1,
selectionXH,
10,
255, 255, 255, 255
);
}
}
if(multiline)
{
if(selectionXL != -1 && selectionXH != -1)
{
if(selectionLineH - selectionLineL > 0)
{
g->fillrect(screenPos.X+textPosition.X+selectionXL, (screenPos.Y+textPosition.Y-1)+selectionYL, textSize.X-(selectionXL), 10, 255, 255, 255, 255);
for(int i = 1; i < selectionLineH-selectionLineL; i++)
{
g->fillrect(screenPos.X+textPosition.X, (screenPos.Y+textPosition.Y-1)+selectionYL+(i*12), textSize.X, 10, 255, 255, 255, 255);
}
g->fillrect(screenPos.X+textPosition.X, (screenPos.Y+textPosition.Y-1)+selectionYH, selectionXH, 10, 255, 255, 255, 255);
} else {
g->fillrect(screenPos.X+textPosition.X+selectionXL, screenPos.Y+selectionYL+textPosition.Y-1, selectionXH-(selectionXL), 10, 255, 255, 255, 255);
}
g->drawtext(screenPos.X+textPosition.X, screenPos.Y+textPosition.Y, cDisplayText, textColour.Red, textColour.Green, textColour.Blue, 255);
}
else
{
g->drawtext(screenPos.X+textPosition.X, screenPos.Y+textPosition.Y, cDisplayText, textColour.Red, textColour.Green, textColour.Blue, 255);
}
} else {
if(selectionXL != -1 && selectionXH != -1)
{
g->fillrect(screenPos.X+textPosition.X+selectionXL, screenPos.Y+textPosition.Y-1, selectionXH-(selectionXL), 10, 255, 255, 255, 255);
g->drawtext(screenPos.X+textPosition.X, screenPos.Y+textPosition.Y, cDisplayText, textColour.Red, textColour.Green, textColour.Blue, 255);
}
else
{
g->drawtext(screenPos.X+textPosition.X, screenPos.Y+textPosition.Y, cDisplayText, textColour.Red, textColour.Green, textColour.Blue, 255);
}
}
g->drawtext(
screenPos.X + textPosition.X,
screenPos.Y + textPosition.Y,
displayTextWithSelection,
textColour.Red, textColour.Green, textColour.Blue, 255
);
}

View File

@ -5,6 +5,7 @@
#include "Component.h"
#include "Colour.h"
#include "TextWrapper.h"
namespace ui
{
@ -12,32 +13,27 @@ namespace ui
{
protected:
String textFragments;
String textLines;
String displayText;
String tDisplayText;
String displayTextWithSelection;
String text;
Colour textColour;
int selectionIndex0;
int selectionIndex1;
int selectionXL;
int selectionXH;
int selectionYL;
int selectionYH;
int selectionLineL;
int selectionLineH;
TextWrapper::Index selectionIndex0;
TextWrapper::Index selectionIndex1;
TextWrapper::Index selectionIndexL;
TextWrapper::Index selectionIndexH;
bool multiline;
bool selecting;
bool autoHeight;
void updateMultiline();
void updateTextWrapper();
void updateSelection();
int getLowerSelectionBound();
int getHigherSelectionBound();
TextWrapper textWrapper;
void copySelection();
public:
//Label(Window* parent_state, String labelText);
@ -48,7 +44,6 @@ namespace ui
void SetMultiline(bool status);
virtual void SetText(String text);
virtual void SetDisplayText(String newText);
virtual String GetText();
bool HasSelection();

View File

@ -9,6 +9,7 @@
#include "gui/interface/Component.h"
#include "graphics/Graphics.h"
#include "graphics/FontReader.h"
#include "Colour.h"
@ -185,17 +186,49 @@ void RichLabel::Draw(const Point& screenPos)
g->drawtext(screenPos.X+textPosition.X, screenPos.Y+textPosition.Y, displayText, textColour.Red, textColour.Green, textColour.Blue, 255);
}
// don't ever use this for anything ever again eww
int EndMySuffering(String const &displayText, int positionX, int positionY)
{
int x=0, y=-2,charIndex=0,cw;
auto s = displayText.begin();
for (; s != displayText.end(); ++s)
{
if(*s == '\n') {
x = 0;
y += FONT_H;
charIndex++;
continue;
} else if(*s == '\b') {
if((displayText.end() - s) < 2) break;
s++;
charIndex+=2;
continue;
} else if (*s == '\x0F') {
if((displayText.end() - s) < 4) break;
s+=3;
charIndex+=4;
continue;
}
cw = FontReader(*s).GetWidth();
if ((x+(cw/2) >= positionX && y+FONT_H >= positionY) || y > positionY)
break;
x += cw;
charIndex++;
}
return charIndex;
}
void RichLabel::OnMouseClick(int x, int y, unsigned button)
{
int cursorPosition = Graphics::CharIndexAtPosition(displayText, x-textPosition.X, y-textPosition.Y);
for(std::vector<RichTextRegion>::iterator iter = regions.begin(), end = regions.end(); iter != end; ++iter)
int cursorPosition = EndMySuffering(displayText, x-textPosition.X, y-textPosition.Y);
for (auto const &region : regions)
{
if((*iter).start <= cursorPosition && (*iter).finish >= cursorPosition)
if (region.start <= cursorPosition && region.finish >= cursorPosition)
{
switch((*iter).action)
switch (region.action)
{
case 'a':
Platform::OpenURI((*iter).actionData.ToUtf8());
Platform::OpenURI(region.actionData.ToUtf8());
break;
}
}

View File

@ -26,6 +26,7 @@ namespace ui
void Draw(const Point& screenPos) override;
void OnMouseClick(int x, int y, unsigned button) override;
protected:
String textSource;
String displayText;

View File

@ -0,0 +1,260 @@
#include "TextWrapper.h"
#include "graphics/Graphics.h"
#include "graphics/FontReader.h"
#include <algorithm>
#include <vector>
#include <iterator>
namespace ui
{
int TextWrapper::Update(String const &text, bool do_wrapping, int max_width)
{
raw_text_size = (int)text.size();
struct wrap_record
{
String::value_type character;
int width;
std::iterator_traits<String::iterator>::difference_type position;
bool wraps;
};
int line_width = 0;
std::vector<wrap_record> records;
int word_begins_at = -1; // this is a pointer into records; we're not currently in a word
int word_width;
int lines = 1;
for (auto it = text.begin(); it != text.end(); ++it)
{
auto char_width = Graphics::CharWidth(*it);
int sequence_length = 0;
switch (*it) // set sequence_length if *it starts a sequence that should be forwarded as-is
{
case '\b': sequence_length = 2; break;
case '\x0f': sequence_length = 4; break;
}
switch (*it)
{
// add more supported spaces here
case ' ':
if (do_wrapping && line_width + char_width > max_width)
{
records.push_back(wrap_record{
'\n', // character; makes the line wrap when rendered
0, // width; fools the clickmap generator into not seeing this newline
0, // position; the clickmap generator is fooled, this can be anything
true // signal the end of the line to the clickmap generator
});
line_width = 0;
lines += 1;
}
else
{
// this is in an else branch to make spaces immediately following
// newline characters inserted by the wrapper disappear
records.push_back(wrap_record{
*it,
char_width,
it - text.begin(),
false
});
line_width += char_width;
}
word_begins_at = -1; // reset word state
break;
// add more supported linebreaks here
case '\n':
records.push_back(wrap_record{
*it, // character; makes the line wrap when rendered
max_width - line_width, // width; make it span all the way to the end
it - text.begin(), // position; so the clickmap generator knows where *it is
true // signal the end of the line to the clickmap generator
});
lines += 1;
line_width = 0;
word_begins_at = -1; // reset word state
break;
default:
if (sequence_length) // *it starts a sequence such as \b? or \x0f???
{
if (text.end() - it < sequence_length)
{
it = text.end() - 1;
continue; // text is broken, we might as well skip the whole thing
}
for (auto skip = it + sequence_length; it != skip; ++it)
{
records.push_back(wrap_record{
*it, // character; forward the sequence to the output
0, // width; fools the clickmap generator into not seeing this sequence
0, // position; the clickmap generator is fooled, this can be anything
false // signal nothing to the clickmap generator
});
}
--it;
}
else
{
if (word_begins_at == -1)
{
word_begins_at = records.size();
word_width = 0;
}
if (do_wrapping && word_width + char_width > max_width)
{
records.push_back(wrap_record{
'\n', // character; makes the line wrap when rendered
0, // width; fools the clickmap generator into not seeing this newline
0, // position; the clickmap generator is fooled, this can be anything
true // signal the end of the line to the clickmap generator
});
lines += 1;
word_begins_at = records.size();
word_width = 0;
line_width = 0;
}
if (do_wrapping && line_width + char_width > max_width)
{
// if we get in here, we skipped the previous block (since line_width
// would have been set to 0 (unless of course (char_width > max_width) which
// is dumb)). since (word_width + char_width) <= (line_width + char_width) always
// holds and we are in this block, we can be sure that word_width < line_width,
// so breaking the line by the preceding space is sure to decrease line_width.
records.insert(records.begin() + word_begins_at, wrap_record{
'\n', // character; makes the line wrap when rendered
0, // width; fools the clickmap generator into not seeing this newline
0, // position; the clickmap generator is fooled, this can be anything
true // signal the end of the line to the clickmap generator
});
lines += 1;
word_begins_at += 1;
line_width = word_width;
}
records.push_back(wrap_record{
*it, // character; make the line wrap with *it
char_width, // width; make it span all the way to the end
it - text.begin(), // position; so the clickmap generator knows where *it is
false // signal nothing to the clickmap generator
});
word_width += char_width;
line_width += char_width;
}
break;
}
}
regions.clear();
wrapped_text.clear();
int x = 0;
int l = 0;
int counter = 0;
for (auto const &record : records)
{
regions.push_back(clickmap_region{ x, l * FONT_H, record.width, l + 1, Index{ (int)record.position, counter } });
++counter;
x += record.width;
if (record.wraps)
{
x = 0;
l += 1;
}
wrapped_text.append(1, record.character);
}
wrapped_lines = lines;
return lines;
}
TextWrapper::Index TextWrapper::Point2Index(int x, int y) const
{
if (y < 0)
{
return IndexBegin();
}
if (regions.size())
{
auto curr = regions.begin();
auto end = regions.end();
auto find_next_nonempty = [end](decltype(end) it) {
++it;
while (it != end && !it->width)
{
++it;
}
return it;
};
auto next = find_next_nonempty(curr);
while (next != end)
{
if (curr->pos_y + FONT_H > y)
{
if (curr->pos_x + curr->width / 2 > x)
{
// if x is to the left of the vertical bisector of the current region,
// return this one; really we should have returned 'the next one' in
// the previous iteration
return curr->index;
}
if (curr->pos_x + curr->width / 2 <= x && next->pos_x + next->width / 2 > x)
{
// if x is to the right of the vertical bisector of the current region
// but to the left of the next one's, return the next one
return next->index;
}
if (curr->pos_x + curr->width / 2 <= x && next->pos_y > curr->pos_y)
{
// nominate the next region if x is to the right of the vertical bisector of
// the current region and the next one is on a new line
return next->index;
}
}
curr = next;
next = find_next_nonempty(next);
}
}
return IndexEnd();
}
int TextWrapper::Index2Point(Index index, int &x, int &y) const
{
if (index.wrapped_index < 0 || index.wrapped_index > (int)regions.size() || !regions.size())
{
return -1;
}
if (index.wrapped_index == (int)regions.size())
{
x = regions[index.wrapped_index - 1].pos_x + regions[index.wrapped_index - 1].width;
y = regions[index.wrapped_index - 1].pos_y;
return regions[index.wrapped_index - 1].pos_line;
}
x = regions[index.wrapped_index].pos_x;
y = regions[index.wrapped_index].pos_y;
return regions[index.wrapped_index].pos_line;
}
TextWrapper::Index TextWrapper::Raw2Index(int raw_index) const
{
if (raw_index < 0)
{
return IndexBegin();
}
for (auto const &region : regions)
{
if (region.index.raw_index >= raw_index)
{
return region.index;
}
}
return IndexEnd();
}
}

View File

@ -0,0 +1,51 @@
#pragma once
#include "common/String.h"
#include "Point.h"
#include <vector>
namespace ui
{
class TextWrapper
{
public:
struct Index
{
int raw_index;
int wrapped_index;
};
private:
int raw_text_size;
String wrapped_text;
struct clickmap_region
{
int pos_x, pos_y, width, pos_line;
Index index;
};
int wrapped_lines;
std::vector<clickmap_region> regions;
public:
int Update(String const &text, bool do_wrapping, int max_width);
Index Raw2Index(int raw_index) const;
Index Point2Index(int x, int y) const;
int Index2Point(Index index, int &x, int &y) const;
String const &WrappedText() const
{
return wrapped_text;
}
Index IndexBegin() const
{
return Index{ 0, 0 };
}
Index IndexEnd() const
{
return Index{ raw_text_size, (int)wrapped_text.size() };
}
};
}

View File

@ -77,7 +77,7 @@ void Textbox::SetText(String newText)
if(cursor)
{
Graphics::PositionAtCharIndex(multiline?textLines:text, cursor, cursorPositionX, cursorPositionY);
textWrapper.Index2Point(textWrapper.Raw2Index(cursor), cursorPositionX, cursorPositionY);
}
else
{
@ -128,7 +128,7 @@ void Textbox::OnContextMenuAction(int item)
void Textbox::resetCursorPosition()
{
Graphics::PositionAtCharIndex(multiline?textLines:text, cursor, cursorPositionX, cursorPositionY);
textWrapper.Index2Point(textWrapper.Raw2Index(cursor), cursorPositionX, cursorPositionY);
}
void Textbox::TabFocus()
@ -169,14 +169,13 @@ void Textbox::cutSelection()
text = backingText;
}
if(multiline)
updateMultiline();
updateTextWrapper();
updateSelection();
TextPosition(text);
if(cursor)
{
Graphics::PositionAtCharIndex(multiline?textLines:text, cursor, cursorPositionX, cursorPositionY);
textWrapper.Index2Point(textWrapper.Raw2Index(cursor), cursorPositionX, cursorPositionY);
}
else
{
@ -210,12 +209,19 @@ void Textbox::pasteIntoSelection()
if (!multiline && Graphics::textwidth(backingText + newText) > regionWidth)
{
int pLimit = regionWidth - Graphics::textwidth(backingText);
int cIndex = Graphics::CharIndexAtPosition(newText, pLimit, 0);
if (cIndex > 0)
newText = newText.Substr(0, cIndex);
else
newText = "";
int pWidth = 0;
auto it = newText.begin();
while (it != newText.end())
{
auto w = Graphics::CharWidth(*it);
if (pWidth + w > pLimit)
{
break;
}
pWidth += w;
++it;
}
newText = String(newText.begin(), it);
}
backingText.Insert(cursor, newText);
@ -233,17 +239,13 @@ void Textbox::pasteIntoSelection()
text = backingText;
}
if(multiline)
updateMultiline();
updateTextWrapper();
updateSelection();
if(multiline)
TextPosition(textLines);
else
TextPosition(text);
TextPosition(textWrapper.WrappedText());
if(cursor)
{
Graphics::PositionAtCharIndex(multiline?textLines:text, cursor, cursorPositionX, cursorPositionY);
textWrapper.Index2Point(textWrapper.Raw2Index(cursor), cursorPositionX, cursorPositionY);
}
else
{
@ -455,17 +457,13 @@ void Textbox::AfterTextChange(bool changed)
}
}
if (multiline)
updateMultiline();
updateTextWrapper();
updateSelection();
if (multiline)
TextPosition(textLines);
else
TextPosition(text);
TextPosition(textWrapper.WrappedText());
if(cursor)
{
Graphics::PositionAtCharIndex(multiline?textLines:text, cursor, cursorPositionX, cursorPositionY);
textWrapper.Index2Point(textWrapper.Raw2Index(cursor), cursorPositionX, cursorPositionY);
}
else
{
@ -515,10 +513,11 @@ void Textbox::OnMouseClick(int x, int y, unsigned button)
if (button != SDL_BUTTON_RIGHT)
{
mouseDown = true;
cursor = Graphics::CharIndexAtPosition(multiline?textLines:text, x-textPosition.X, y-textPosition.Y);
auto index = textWrapper.Point2Index(x-textPosition.X, y-textPosition.Y);
cursor = index.raw_index;
if(cursor)
{
Graphics::PositionAtCharIndex(multiline?textLines:text, cursor, cursorPositionX, cursorPositionY);
textWrapper.Index2Point(index, cursorPositionX, cursorPositionY);
}
else
{
@ -538,10 +537,11 @@ void Textbox::OnMouseMoved(int localx, int localy, int dx, int dy)
{
if(mouseDown)
{
cursor = Graphics::CharIndexAtPosition(multiline?textLines:text, localx-textPosition.X, localy-textPosition.Y);
auto index = textWrapper.Point2Index(localx-textPosition.X, localy-textPosition.Y);
cursor = index.raw_index;
if(cursor)
{
Graphics::PositionAtCharIndex(multiline?textLines:text, cursor, cursorPositionX, cursorPositionY);
textWrapper.Index2Point(index, cursorPositionX, cursorPositionY);
}
else
{