diff --git a/gemrb/core/GUI/TextSystem/TextContainer.cpp b/gemrb/core/GUI/TextSystem/TextContainer.cpp index 8a8cc46652..e268f23712 100644 --- a/gemrb/core/GUI/TextSystem/TextContainer.cpp +++ b/gemrb/core/GUI/TextSystem/TextContainer.cpp @@ -48,7 +48,9 @@ Regions Content::LayoutForPointInRegion(Point p, const Region& rgn) const TextSpan::TextSpan(const String& string, const Font* fnt, Holder pal, const Size* frame) : Content((frame) ? *frame : Size()), text(string), font(fnt), palette(pal) -{} +{ + Alignment = IE_FONT_ALIGN_LEFT; +} inline const Font* TextSpan::LayoutFont() const { @@ -244,7 +246,9 @@ void TextSpan::DrawContentsInRegions(const Regions& rgns, const Point& offset) c assert(charsPrinted < text.length()); core->GetVideoDriver()->DrawRect(drawRect, ColorRed, true); #endif - charsPrinted += printFont->Print(drawRect, text.substr(charsPrinted), printPalette.get(), IE_FONT_ALIGN_LEFT); + // FIXME: layout assumes left alignment, so alignment is mostly broken + // we only use it for TextEdit tho which is single line and therefore works as long as the text ends in a newline + charsPrinted += printFont->Print(drawRect, text.substr(charsPrinted), printPalette.get(), Alignment); #if (DEBUG_TEXT) core->GetVideoDriver()->DrawRect(drawRect, ColorWhite, false); #endif @@ -293,11 +297,13 @@ void ContentContainer::SetMargin(ieByte top, ieByte right, ieByte bottom, ieByte SetMargin(Margin(top, right, bottom, left)); } -void ContentContainer::DrawSelf(Region drawFrame, const Region& /*clip*/) +void ContentContainer::DrawSelf(Region drawFrame, const Region& clip) { Video* video = core->GetVideoDriver(); #if DEBUG_TEXT video->DrawRect(clip, ColorGreen, true); +#else + (void)clip; #endif // layout shouldn't be empty unless there is no content anyway... @@ -314,19 +320,17 @@ void ContentContainer::DrawSelf(Region drawFrame, const Region& /*clip*/) ContentLayout::const_iterator it = layout.begin(); for (; it != layout.end(); ++it) { const Layout& l = *it; + // TODO: pass the clip rect so we can skip non-intersecting regions - l.content->DrawContentsInRegions(l.regions, dp); - - if (&l == cursorPos.layout) { - Sprite2D* cursor = core->GetCursorSprite(); - const Region* rgn = cursorPos.rgn; - video->BlitSprite(cursor, cursorPos.regionPos.x + rgn->x + dp.x, - cursorPos.regionPos.y + rgn->y + dp.y + cursor->YPos); - cursor->release(); - } + DrawContents(l, dp); } } +void ContentContainer::DrawContents(const Layout& layout, const Point& point) +{ + layout.content->DrawContentsInRegions(layout.regions, point); +} + void ContentContainer::AppendContent(Content* content) { if (contents.empty()) @@ -393,6 +397,8 @@ Content* ContentContainer::RemoveContent(const Content* span, bool doLayout) if (doLayout) { LayoutContentsFrom(it); } + + ContentRemoved(content); return content; } return NULL; @@ -404,11 +410,22 @@ ContentContainer::EraseContent(ContentList::const_iterator it) Content* content = *it; content->parent = NULL; layout.erase(std::find(layout.begin(), layout.end(), content)); + layoutPoint = Point(); // reset cached layoutPoint + ContentRemoved(content); delete content; return contents.erase(it); } +ContentContainer::ContentList::const_iterator +ContentContainer::EraseContent(ContentList::const_iterator beg, ContentList::const_iterator end) +{ + for (; beg != end;) { + beg = EraseContent(beg); + } + return end; +} + Content* ContentContainer::ContentAtPoint(const Point& p) const { const Layout* layout = LayoutAtPoint(p); @@ -604,6 +621,8 @@ TextContainer::TextContainer(const Region& frame, Font* fnt, Holder pal : ContentContainer(frame), font(fnt) { SetPalette(pal); + alignment = IE_FONT_ALIGN_LEFT; + textLen = 0; } void TextContainer::AppendText(const String& text) @@ -614,10 +633,19 @@ void TextContainer::AppendText(const String& text) void TextContainer::AppendText(const String& text, Font* fnt, Holder pal) { if (text.length()) { - AppendContent(new TextSpan(text, fnt, pal)); + TextSpan* span = new TextSpan(text, fnt, pal); + span->Alignment = alignment; + AppendContent(span); + textLen += text.length(); } } +void TextContainer::ContentRemoved(const Content* content) +{ + const TextSpan* ts = static_cast(content); + textLen -= ts->Text().length(); +} + void TextContainer::SetPalette(Holder pal) { palette = (pal) ? pal : font->GetPalette(); @@ -649,6 +677,69 @@ String TextContainer::TextFrom(ContentList::const_iterator it) const return text; } +void TextContainer::DrawSelf(Region drawFrame, const Region& clip) +{ + printPos = 0; + ContentContainer::DrawSelf(drawFrame, clip); +} + +void TextContainer::DrawContents(const Layout& layout, const Point& dp) +{ + ContentContainer::DrawContents(layout, dp); + + const TextSpan* ts = (const TextSpan*)layout.content; + const String& text = ts->Text(); + size_t textLen = ts->Text().length(); + + if (printPos < cursorPos && printPos + textLen >= cursorPos) { + const Font* printFont = ts->LayoutFont(); + Font::StringSizeMetrics metrics = {Size(0,0), 0, true}; + + // diff is the length of the TextSpan we want however, it may fall inside of a word + size_t diff = cursorPos - printPos; + size_t start = 0; + size_t stop = text.find_last_of(WHITESPACE_STRING, diff); + if (stop == String::npos) + stop = 0; + + Point p; + Regions::const_iterator rit = layout.regions.begin(); + for (; rit != layout.regions.end(); ++rit) { + const Region& rect = *rit; + p = rect.Origin(); + metrics.size = rect.Dimensions(); + + const String& substr = text.substr(start, stop); + printFont->StringSize(substr, &metrics); + + if (metrics.numChars == stop) { + if (text[start+stop] == '\n') { + // FIXME: technically ought to use the next rect rather then assume we can jump down a line + p.y += printFont->LineHeight; + ++start; + } else if (metrics.numChars > 0) { + p.x += metrics.size.w; + } + // found it + if (stop < diff) { + // inside a word + const String& substr = text.substr(start + stop, diff - start - metrics.numChars); + p.x += printFont->StringSizeWidth(substr, 0); + } + break; + } + start += metrics.numChars; + stop -= metrics.numChars; + } + + Video* video = core->GetVideoDriver(); + Sprite2D* cursor = core->GetCursorSprite(); + video->BlitSprite(cursor, p.x + dp.x, p.y + dp.y + cursor->YPos); + cursor->release(); + } + printPos += textLen; +} + void TextContainer::MoveCursorToPoint(const Point& p) { const Layout* layout = LayoutAtPoint(p); @@ -659,8 +750,6 @@ void TextContainer::MoveCursorToPoint(const Point& p) const Font* printFont = ts->LayoutFont(); Font::StringSizeMetrics metrics = {Size(0,0), 0, true}; size_t numChars = 0; - cursorPos.layout = layout; - cursorPos.regionPos = Point(0,0); const Regions& regions = layout->regions; Regions::const_iterator rit = regions.begin(); @@ -673,14 +762,12 @@ void TextContainer::MoveCursorToPoint(const Point& p) if (lines) { metrics.size.w = rect.w; metrics.size.h = lines * printFont->LineHeight; - cursorPos.regionPos.y = metrics.size.h; printFont->StringSize(text.substr(numChars), &metrics); numChars += metrics.numChars; } size_t len = 0; - cursorPos.regionPos.x = printFont->StringSizeWidth(text.substr(numChars), p.x, &len); - cursorPos.charIndex = numChars + len; - cursorPos.rgn = ▭ + printFont->StringSizeWidth(text.substr(numChars), p.x, &len); + cursorPos = numChars + len; break; } else { // not in this one so we need to consume some text @@ -691,41 +778,118 @@ void TextContainer::MoveCursorToPoint(const Point& p) } } + core->GetVideoDriver()->ShowSoftKeyboard(); MarkDirty(); + } else { + // FIXME: this isnt _always_ the end (it works out that way for left alignment tho) + CursorEnd(); } } -void TextContainer::MouseDown(const MouseEvent& me, unsigned short /*Mod*/) +bool TextContainer::OnMouseDown(const MouseEvent& me, unsigned short /*Mod*/) { - // TODO: we need a flag for being editable. - Point p = ConvertPointFromScreen(me.Pos()); MoveCursorToPoint(p); + return true; +} + +bool TextContainer::OnKeyPress(const KeyboardEvent& key, unsigned short /*Mod*/) +{ + switch (key.keycode) { + case GEM_HOME: + CursorHome(); + return true; + case GEM_END: + CursorEnd(); + return true; + case GEM_LEFT: + AdvanceCursor(-1); + return true; + case GEM_RIGHT: + AdvanceCursor(1); + return true; + case GEM_DELETE: + AdvanceCursor(1); + DeleteText(1); + return true; + case GEM_BACKSP: + DeleteText(1); + return true; + case GEM_RETURN: + InsertText(String(1, '\n')); + return true; + } + if (key.character) { + InsertText(String(1, key.character)); + return true; + } + return false; } -bool TextContainer::KeyPress(const KeyboardEvent& key, unsigned short /*Mod*/) +// move cursor to beginning of text +void TextContainer::CursorHome() { - if (key.character) { - // TODO: handle delete and arrow keys + // top right of first region in first layout area + cursorPos = 0; + MarkDirty(); +} + +// move cursor to end of text +void TextContainer::CursorEnd() +{ + // bottom left of last region in last layout area + cursorPos = textLen; + MarkDirty(); +} + +void TextContainer::AdvanceCursor(int delta) +{ + cursorPos += delta; + if (int(cursorPos) < 0) { + CursorHome(); + } else if (cursorPos >= textLen) { + CursorEnd(); + } else { + MarkDirty(); + } +} - const TextSpan* content = static_cast(cursorPos.layout->content); - Point newPoint = cursorPos.rgn->Origin() + cursorPos.regionPos; - const Font* printFont = content->LayoutFont(); +TextContainer::ContentIndex TextContainer::FindContentForChar(size_t idx) +{ + size_t charCount = 0; + ContentList::iterator it = contents.begin(); + while (it != contents.end()) { + TextSpan* ts = static_cast(*it); + size_t textLen = ts->Text().length(); + if (charCount + textLen >= idx) { + break; + } + charCount += textLen; + ++it; + } + return std::make_pair(charCount, it); +} - ContentList::const_iterator it = std::find(contents.begin(), contents.end(), content); - String newtext = TextFrom(it); - newtext.insert(cursorPos.charIndex, 1, key.character); +void TextContainer::InsertText(const String& text) +{ + ContentIndex idx = FindContentForChar(cursorPos); + String newtext = TextFrom(idx.second); + newtext.insert(cursorPos - idx.first, text); - EraseContent(it); - AppendText(newtext); + EraseContent(idx.second, contents.end()); + AppendText(newtext); + AdvanceCursor(1); +} - size_t charw = printFont->StringSizeWidth(String(1, key.character), 0); - newPoint.x += charw*2; - MoveCursorToPoint(newPoint); +void TextContainer::DeleteText(size_t len) +{ + ContentIndex idx = FindContentForChar(cursorPos); + String newtext = TextFrom(idx.second); + newtext.erase(cursorPos - idx.first, len); - return true; - } - return false; + EraseContent(idx.second, contents.end()); + AppendText(newtext); + AdvanceCursor(-int(len)); } } diff --git a/gemrb/core/GUI/TextSystem/TextContainer.h b/gemrb/core/GUI/TextSystem/TextContainer.h index 6874973ecf..38ed06646f 100644 --- a/gemrb/core/GUI/TextSystem/TextContainer.h +++ b/gemrb/core/GUI/TextSystem/TextContainer.h @@ -73,6 +73,8 @@ friend class TextContainer; const String& Text() const { return text; }; + unsigned char Alignment; + protected: virtual void DrawContentsInRegions(const Regions&, const Point&) const; virtual Regions LayoutForPointInRegion(Point p, const Region&) const; @@ -153,13 +155,6 @@ class ContentContainer : public View } }; - struct { - const Layout* layout; // the layout struct the cursor is inside - const Region* rgn; // the Region in layout->Regions - Point regionPos; // the drawing point relative to the Region - size_t charIndex; // the index of the character string contained in Layout - } cursorPos; - typedef std::deque ContentLayout; ContentLayout layout; Point layoutPoint; @@ -196,15 +191,18 @@ class ContentContainer : public View void LayoutContentsFrom(ContentList::const_iterator); void LayoutContentsFrom(const Content*); Content* RemoveContent(const Content* content, bool doLayout); - ContentList::const_iterator EraseContent(ContentList::const_iterator); + ContentList::const_iterator EraseContent(ContentList::const_iterator it); + ContentList::const_iterator EraseContent(ContentList::const_iterator beg, ContentList::const_iterator end); const Layout& LayoutForContent(const Content*) const; const Layout* LayoutAtPoint(const Point& p) const; + void DrawSelf(Region drawFrame, const Region& clip); + virtual void DrawContents(const Layout& layout, const Point& point); + private: void SizeChanged(const Size& oldSize); - - void DrawSelf(Region drawFrame, const Region& clip); + virtual void ContentRemoved(const Content* /*content*/) {}; }; // TextContainers can hold any content, but they represent a string of text that is divided into TextSpans @@ -213,11 +211,33 @@ class TextContainer : public ContentContainer { // default font/palette for adding plain text Font* font; Holder palette; + unsigned char alignment; + + size_t textLen; + size_t cursorPos, printPos; private: String TextFrom(ContentList::const_iterator) const; + void ContentRemoved(const Content* content); + void MoveCursorToPoint(const Point& p); + void CursorHome(); + void CursorEnd(); + void AdvanceCursor(int); + + // relative to cursor pos + void InsertText(const String& text); + void DeleteText(size_t len); + + bool OnMouseDown(const MouseEvent& /*me*/, unsigned short /*Mod*/); + bool OnKeyPress(const KeyboardEvent& /*Key*/, unsigned short /*Mod*/); + + void DrawSelf(Region drawFrame, const Region& clip); + virtual void DrawContents(const Layout& layout, const Point& point); + + typedef std::pair ContentIndex; + ContentIndex FindContentForChar(size_t idx); public: TextContainer(const Region& frame, Font* font, Holder); @@ -229,11 +249,9 @@ class TextContainer : public ContentContainer { void SetPalette(Holder pal); Holder TextPalette() const { return palette; } + void SetFont(Font* fnt) { font = fnt; } const Font* TextFont() const { return font; } - - void MouseDown(const MouseEvent& /*me*/, unsigned short /*Mod*/); - bool KeyPress(const KeyboardEvent& /*Key*/, unsigned short /*Mod*/); - + void SetAlignment(unsigned char align) { alignment = align; } }; }