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; 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 Graphics::textwrapheight(String str, int width)
{ {
int x=0, height=FONT_H, cw; 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); static pixel *render_packed_rgb(void *image, int width, int height, int cmp_size);
//Font/text metrics //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 CharWidth(String::value_type c);
static int textnwidth(String s, int n); static int textnwidth(String s, int n);
static void textnpos(String s, int n, int w, int *cx, int *cy); 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_; } CommandHighlighter(ConsoleView * v_) { v = v_; }
void TextChangedCallback(ui::Textbox * sender) override 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), ""); 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: case SDLK_KP_ENTER:
c->EvaluateCommand(commandField->GetText()); c->EvaluateCommand(commandField->GetText());
commandField->SetText(""); commandField->SetText("");
commandField->SetDisplayText(""); // commandField->SetDisplayText("");
break; break;
case SDLK_DOWN: case SDLK_DOWN:
c->NextCommand(); c->NextCommand();
@ -106,7 +106,7 @@ void ConsoleView::NotifyPreviousCommandsChanged(ConsoleModel * sender)
void ConsoleView::NotifyCurrentCommandChanged(ConsoleModel * sender) void ConsoleView::NotifyCurrentCommandChanged(ConsoleModel * sender)
{ {
commandField->SetText(sender->GetCurrentCommand().Command); 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): Label::Label(Point position, Point size, String labelText):
Component(position, size), Component(position, size),
text(labelText),
textColour(255, 255, 255), textColour(255, 255, 255),
selectionIndex0(-1), selectionIndexL(textWrapper.IndexBegin()),
selectionIndex1(-1), selectionIndexH(textWrapper.IndexBegin()),
selectionXL(-1),
selectionXH(-1),
multiline(false), multiline(false),
selecting(false), selecting(false),
autoHeight(size.Y==-1?true:false) autoHeight(size.Y==-1?true:false)
{ {
SetText(labelText);
menu = new ContextMenu(this); menu = new ContextMenu(this);
menu->AddItem(ContextMenuItem("Copy", 0, true)); menu->AddItem(ContextMenuItem("Copy", 0, true));
} }
Label::~Label() Label::~Label()
{ {
} }
void Label::SetMultiline(bool status) void Label::SetMultiline(bool status)
{ {
multiline = status; multiline = status;
if(status) updateTextWrapper();
{ updateSelection();
updateMultiline(); TextPosition(textWrapper.WrappedText());
updateSelection();
TextPosition(textLines);
}
else
{
TextPosition(text);
}
} }
void Label::SetText(String text) void Label::SetText(String newText)
{ {
this->text = text; this->text = newText;
if(multiline) updateTextWrapper();
{ updateSelection();
updateMultiline(); TextPosition(textWrapper.WrappedText());
updateSelection();
TextPosition(textLines);
}
else
{
TextPosition(text);
}
} }
void Label::AutoHeight() void Label::AutoHeight()
{ {
bool oldAH = autoHeight; bool oldAH = autoHeight;
autoHeight = true; autoHeight = true;
updateMultiline(); updateTextWrapper();
autoHeight = oldAH; autoHeight = oldAH;
} }
void Label::updateMultiline() void Label::updateTextWrapper()
{ {
int lines = 1; int lines = textWrapper.Update(text, multiline, Size.X - Appearance.Margin.Left - Appearance.Margin.Right);
if (text.length()>0) if (autoHeight)
{ {
String::value_type *rawText = new String::value_type[text.length()+1]; Size.Y = lines * 12 + 3;
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;
}
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 = "";
} }
} }
@ -193,17 +82,17 @@ void Label::OnMouseClick(int x, int y, unsigned button)
{ {
if(button == SDL_BUTTON_RIGHT) if(button == SDL_BUTTON_RIGHT)
{ {
if(menu) if (menu)
{
menu->Show(GetScreenPos() + ui::Point(x, y)); menu->Show(GetScreenPos() + ui::Point(x, y));
}
} }
else else
{ {
selecting = true; selecting = true;
if(multiline) selectionIndex0 = textWrapper.Point2Index(x - textPosition.X, y - textPosition.Y);
selectionIndex0 = Graphics::CharIndexAtPosition(textLines, x-textPosition.X, y-textPosition.Y); selectionIndexL = selectionIndex0;
else selectionIndexH = selectionIndex0;
selectionIndex0 = Graphics::CharIndexAtPosition(text, x-textPosition.X, y-textPosition.Y);
selectionIndex1 = selectionIndex0;
updateSelection(); updateSelection();
} }
@ -211,18 +100,10 @@ void Label::OnMouseClick(int x, int y, unsigned button)
void Label::copySelection() void Label::copySelection()
{ {
String currentText = text; if (HasSelection())
String copyText; {
ClipboardPush(format::CleanString(text.Between(selectionIndexL.raw_index, selectionIndexH.raw_index), false, true, false).ToUtf8());
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());
} }
void Label::OnMouseUp(int x, int y, unsigned button) 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) void Label::OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt)
{ {
if (repeat) if (repeat)
{
return; return;
}
if (ctrl && scan == SDL_SCANCODE_C) if (ctrl && scan == SDL_SCANCODE_C)
{ {
copySelection(); 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) void Label::OnMouseMoved(int localx, int localy, int dx, int dy)
{ {
if(selecting) if (selecting)
{ {
if(multiline) selectionIndex1 = textWrapper.Point2Index(localx - textPosition.X, localy - textPosition.Y);
selectionIndex1 = Graphics::CharIndexAtPosition(textLines, localx-textPosition.X, localy-textPosition.Y); if (selectionIndex1.raw_index < selectionIndex0.raw_index)
{
selectionIndexL = selectionIndex1;
selectionIndexH = selectionIndex0;
}
else else
selectionIndex1 = Graphics::CharIndexAtPosition(text, localx-textPosition.X, localy-textPosition.Y); {
selectionIndexL = selectionIndex0;
selectionIndexH = selectionIndex1;
}
updateSelection(); updateSelection();
} }
} }
void Label::Tick(float dt) void Label::Tick(float dt)
{ {
if(!this->IsFocused() && (selecting || (selectionIndex0 != -1 && selectionIndex1 != -1))) if (!this->IsFocused() && (HasSelection() || selecting))
{ {
ClearSelection(); ClearSelection();
} }
@ -267,174 +157,123 @@ void Label::Tick(float dt)
int Label::getLowerSelectionBound() int Label::getLowerSelectionBound()
{ {
return (selectionIndex0 > selectionIndex1) ? selectionIndex1 : selectionIndex0; return selectionIndexL.raw_index;
} }
int Label::getHigherSelectionBound() int Label::getHigherSelectionBound()
{ {
return (selectionIndex0 > selectionIndex1) ? selectionIndex0 : selectionIndex1; return selectionIndexH.raw_index;
} }
bool Label::HasSelection() bool Label::HasSelection()
{ {
if(selectionIndex0 != -1 && selectionIndex1 != -1 && selectionIndex0 != selectionIndex1) return selectionIndexH.raw_index > selectionIndexL.raw_index;
return true;
return false;
} }
void Label::ClearSelection() void Label::ClearSelection()
{ {
selecting = false; selecting = false;
selectionIndex0 = -1; selectionIndexL = textWrapper.IndexBegin();
selectionIndex1 = -1; selectionIndexH = textWrapper.IndexBegin();
updateSelection(); updateSelection();
} }
void Label::selectAll() void Label::selectAll()
{ {
selectionIndex0 = 0; selectionIndexL = textWrapper.IndexBegin();
selectionIndex1 = text.length(); selectionIndexH = textWrapper.IndexEnd();
updateSelection(); updateSelection();
} }
void Label::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; displayTextWithSelection = textWrapper.WrappedText();
if (selectionIndex0 > (int)text.length()) selectionIndex0 = text.length(); if (HasSelection())
if (selectionIndex1 < 0) selectionIndex1 = 0;
if (selectionIndex1 > (int)text.length()) selectionIndex1 = text.length();
if(selectionIndex0 == -1 || selectionIndex1 == -1)
{ {
selectionXH = -1; displayTextWithSelection.Insert(selectionIndexL.wrapped_index, "\x01");
selectionXL = -1; displayTextWithSelection.Insert(selectionIndexH.wrapped_index + 1, "\x01");
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");
}
} }
} }
void Label::SetDisplayText(String newText) // void Label::SetDisplayText(String newText)
{ // {
ClearSelection(); // displayText = newText;
displayText = tDisplayText = newText; // ClearSelection();
} // updateTextWrapper();
// updateSelection();
// TextPosition(textWrapper.WrappedText());
// }
void Label::Draw(const Point& screenPos) void Label::Draw(const Point& screenPos)
{ {
if(!drawn) if (!drawn)
{ {
if(multiline) TextPosition(textWrapper.WrappedText());
{ updateTextWrapper();
TextPosition(textLines); updateSelection();
updateMultiline();
updateSelection();
}
else
TextPosition(text);
drawn = true; 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 else
{ {
if(multiline) g->fillrect(
cDisplayText = textLines; screenPos.X + textPosition.X + selectionXL,
else screenPos.Y + textPosition.Y + selectionYL - 1,
cDisplayText = text; textSize.X - selectionXL,
} 10,
} 255, 255, 255, 255
);
if(multiline) for (int i = 1; i < selectionLineH - selectionLineL; ++i)
{
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); g->fillrect(
for(int i = 1; i < selectionLineH-selectionLineL; i++) screenPos.X + textPosition.X,
{ screenPos.Y + textPosition.Y + selectionYL - 1 + i * 12,
g->fillrect(screenPos.X+textPosition.X, (screenPos.Y+textPosition.Y-1)+selectionYL+(i*12), textSize.X, 10, 255, 255, 255, 255); textSize.X,
} 10,
g->fillrect(screenPos.X+textPosition.X, (screenPos.Y+textPosition.Y-1)+selectionYH, selectionXH, 10, 255, 255, 255, 255); 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); g->fillrect(
} screenPos.X + textPosition.X,
else screenPos.Y + textPosition.Y + selectionYH - 1,
{ selectionXH,
g->drawtext(screenPos.X+textPosition.X, screenPos.Y+textPosition.Y, cDisplayText, textColour.Red, textColour.Green, textColour.Blue, 255); 10,
} 255, 255, 255, 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 "Component.h"
#include "Colour.h" #include "Colour.h"
#include "TextWrapper.h"
namespace ui namespace ui
{ {
@ -12,32 +13,27 @@ namespace ui
{ {
protected: protected:
String textFragments; String textFragments;
String textLines; String displayTextWithSelection;
String displayText;
String tDisplayText;
String text; String text;
Colour textColour; Colour textColour;
int selectionIndex0; TextWrapper::Index selectionIndex0;
int selectionIndex1; TextWrapper::Index selectionIndex1;
TextWrapper::Index selectionIndexL;
int selectionXL; TextWrapper::Index selectionIndexH;
int selectionXH;
int selectionYL;
int selectionYH;
int selectionLineL;
int selectionLineH;
bool multiline; bool multiline;
bool selecting; bool selecting;
bool autoHeight; bool autoHeight;
void updateMultiline(); void updateTextWrapper();
void updateSelection(); void updateSelection();
int getLowerSelectionBound(); int getLowerSelectionBound();
int getHigherSelectionBound(); int getHigherSelectionBound();
TextWrapper textWrapper;
void copySelection(); void copySelection();
public: public:
//Label(Window* parent_state, String labelText); //Label(Window* parent_state, String labelText);
@ -48,7 +44,6 @@ namespace ui
void SetMultiline(bool status); void SetMultiline(bool status);
virtual void SetText(String text); virtual void SetText(String text);
virtual void SetDisplayText(String newText);
virtual String GetText(); virtual String GetText();
bool HasSelection(); bool HasSelection();

View File

@ -9,6 +9,7 @@
#include "gui/interface/Component.h" #include "gui/interface/Component.h"
#include "graphics/Graphics.h" #include "graphics/Graphics.h"
#include "graphics/FontReader.h"
#include "Colour.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); 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) void RichLabel::OnMouseClick(int x, int y, unsigned button)
{ {
int cursorPosition = Graphics::CharIndexAtPosition(displayText, x-textPosition.X, y-textPosition.Y); int cursorPosition = EndMySuffering(displayText, x-textPosition.X, y-textPosition.Y);
for(std::vector<RichTextRegion>::iterator iter = regions.begin(), end = regions.end(); iter != end; ++iter) 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': case 'a':
Platform::OpenURI((*iter).actionData.ToUtf8()); Platform::OpenURI(region.actionData.ToUtf8());
break; break;
} }
} }

View File

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