12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160 |
- /*
- Minetest
- Copyright (C) 2019 EvicenceBKidscode / Pierre-Yves Rollo <dev@pyrollo.com>
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
- #include "IGUIEnvironment.h"
- #include "IGUIElement.h"
- #include "guiScrollBar.h"
- #include "IGUIFont.h"
- #include <vector>
- #include <list>
- #include <unordered_map>
- using namespace irr::gui;
- #include "client/fontengine.h"
- #include <SColor.h>
- #include "client/tile.h"
- #include "IVideoDriver.h"
- #include "client/client.h"
- #include "client/renderingengine.h"
- #include "hud.h"
- #include "guiHyperText.h"
- #include "util/string.h"
- bool check_color(const std::string &str)
- {
- irr::video::SColor color;
- return parseColorString(str, color, false);
- }
- bool check_integer(const std::string &str)
- {
- if (str.empty())
- return false;
- char *endptr = nullptr;
- strtol(str.c_str(), &endptr, 10);
- return *endptr == '\0';
- }
- // -----------------------------------------------------------------------------
- // ParsedText - A text parser
- void ParsedText::Element::setStyle(StyleList &style)
- {
- this->underline = is_yes(style["underline"]);
- video::SColor color;
- if (parseColorString(style["color"], color, false))
- this->color = color;
- if (parseColorString(style["hovercolor"], color, false))
- this->hovercolor = color;
- unsigned int font_size = std::atoi(style["fontsize"].c_str());
- FontMode font_mode = FM_Standard;
- if (style["fontstyle"] == "mono")
- font_mode = FM_Mono;
- FontSpec spec(font_size, font_mode,
- is_yes(style["bold"]), is_yes(style["italic"]));
- // TODO: find a way to check font validity
- // Build a new fontengine ?
- this->font = g_fontengine->getFont(spec);
- if (!this->font)
- printf("No font found ! Size=%d, mode=%d, bold=%s, italic=%s\n",
- font_size, font_mode, style["bold"].c_str(),
- style["italic"].c_str());
- }
- void ParsedText::Paragraph::setStyle(StyleList &style)
- {
- if (style["halign"] == "center")
- this->halign = HALIGN_CENTER;
- else if (style["halign"] == "right")
- this->halign = HALIGN_RIGHT;
- else if (style["halign"] == "justify")
- this->halign = HALIGN_JUSTIFY;
- else
- this->halign = HALIGN_LEFT;
- }
- ParsedText::ParsedText(const wchar_t *text)
- {
- // Default style
- m_root_tag.name = "root";
- m_root_tag.style["fontsize"] = "16";
- m_root_tag.style["fontstyle"] = "normal";
- m_root_tag.style["bold"] = "false";
- m_root_tag.style["italic"] = "false";
- m_root_tag.style["underline"] = "false";
- m_root_tag.style["halign"] = "left";
- m_root_tag.style["color"] = "#EEEEEE";
- m_root_tag.style["hovercolor"] = "#FF0000";
- m_active_tags.push_front(&m_root_tag);
- m_style = m_root_tag.style;
- // Default simple tags definitions
- StyleList style;
- style["color"] = "#0000FF";
- style["underline"] = "true";
- m_elementtags["action"] = style;
- style.clear();
- style["bold"] = "true";
- m_elementtags["b"] = style;
- style.clear();
- style["italic"] = "true";
- m_elementtags["i"] = style;
- style.clear();
- style["underline"] = "true";
- m_elementtags["u"] = style;
- style.clear();
- style["fontstyle"] = "mono";
- m_elementtags["mono"] = style;
- style.clear();
- style["fontsize"] = m_root_tag.style["fontsize"];
- m_elementtags["normal"] = style;
- style.clear();
- style["fontsize"] = "24";
- m_elementtags["big"] = style;
- style.clear();
- style["fontsize"] = "36";
- m_elementtags["bigger"] = style;
- style.clear();
- style["halign"] = "center";
- m_paragraphtags["center"] = style;
- style.clear();
- style["halign"] = "justify";
- m_paragraphtags["justify"] = style;
- style.clear();
- style["halign"] = "left";
- m_paragraphtags["left"] = style;
- style.clear();
- style["halign"] = "right";
- m_paragraphtags["right"] = style;
- style.clear();
- m_element = NULL;
- m_paragraph = NULL;
- m_end_paragraph_reason = ER_NONE;
- parse(text);
- }
- ParsedText::~ParsedText()
- {
- for (auto &tag : m_not_root_tags)
- delete tag;
- }
- void ParsedText::parse(const wchar_t *text)
- {
- wchar_t c;
- u32 cursor = 0;
- bool escape = false;
- while ((c = text[cursor]) != L'\0') {
- cursor++;
- if (c == L'\r') { // Mac or Windows breaks
- if (text[cursor] == L'\n')
- cursor++;
- // If text has begun, don't skip empty line
- if (m_paragraph) {
- endParagraph(ER_NEWLINE);
- enterElement(ELEMENT_SEPARATOR);
- }
- escape = false;
- continue;
- }
- if (c == L'\n') { // Unix breaks
- // If text has begun, don't skip empty line
- if (m_paragraph) {
- endParagraph(ER_NEWLINE);
- enterElement(ELEMENT_SEPARATOR);
- }
- escape = false;
- continue;
- }
- if (escape) {
- escape = false;
- pushChar(c);
- continue;
- }
- if (c == L'\\') {
- escape = true;
- continue;
- }
- // Tag check
- if (c == L'<') {
- u32 newcursor = parseTag(text, cursor);
- if (newcursor > 0) {
- cursor = newcursor;
- continue;
- }
- }
- // Default behavior
- pushChar(c);
- }
- endParagraph(ER_NONE);
- }
- void ParsedText::endElement()
- {
- m_element = NULL;
- }
- void ParsedText::endParagraph(EndReason reason)
- {
- if (!m_paragraph)
- return;
- EndReason previous = m_end_paragraph_reason;
- m_end_paragraph_reason = reason;
- if (m_empty_paragraph && (reason == ER_TAG ||
- (reason == ER_NEWLINE && previous == ER_TAG))) {
- // Ignore last empty paragraph
- m_paragraph = nullptr;
- m_paragraphs.pop_back();
- return;
- }
- endElement();
- m_paragraph = NULL;
- }
- void ParsedText::enterParagraph()
- {
- if (!m_paragraph) {
- m_paragraphs.emplace_back();
- m_paragraph = &m_paragraphs.back();
- m_paragraph->setStyle(m_style);
- m_empty_paragraph = true;
- }
- }
- void ParsedText::enterElement(ElementType type)
- {
- enterParagraph();
- if (!m_element || m_element->type != type) {
- m_paragraph->elements.emplace_back();
- m_element = &m_paragraph->elements.back();
- m_element->type = type;
- m_element->tags = m_active_tags;
- m_element->setStyle(m_style);
- }
- }
- void ParsedText::pushChar(wchar_t c)
- {
- // New word if needed
- if (c == L' ' || c == L'\t') {
- if (!m_empty_paragraph)
- enterElement(ELEMENT_SEPARATOR);
- else
- return;
- } else {
- m_empty_paragraph = false;
- enterElement(ELEMENT_TEXT);
- }
- m_element->text += c;
- }
- ParsedText::Tag *ParsedText::newTag(const std::string &name, const AttrsList &attrs)
- {
- endElement();
- Tag *newtag = new Tag();
- newtag->name = name;
- newtag->attrs = attrs;
- m_not_root_tags.push_back(newtag);
- return newtag;
- }
- ParsedText::Tag *ParsedText::openTag(const std::string &name, const AttrsList &attrs)
- {
- Tag *newtag = newTag(name, attrs);
- m_active_tags.push_front(newtag);
- return newtag;
- }
- bool ParsedText::closeTag(const std::string &name)
- {
- bool found = false;
- for (auto id = m_active_tags.begin(); id != m_active_tags.end(); ++id)
- if ((*id)->name == name) {
- m_active_tags.erase(id);
- found = true;
- break;
- }
- return found;
- }
- void ParsedText::parseGenericStyleAttr(
- const std::string &name, const std::string &value, StyleList &style)
- {
- // Color styles
- if (name == "color" || name == "hovercolor") {
- if (check_color(value))
- style[name] = value;
- // Boolean styles
- } else if (name == "bold" || name == "italic" || name == "underline") {
- style[name] = is_yes(value);
- } else if (name == "size") {
- if (check_integer(value))
- style["fontsize"] = value;
- } else if (name == "font") {
- if (value == "mono" || value == "normal")
- style["fontstyle"] = value;
- }
- }
- void ParsedText::parseStyles(const AttrsList &attrs, StyleList &style)
- {
- for (auto const &attr : attrs)
- parseGenericStyleAttr(attr.first, attr.second, style);
- }
- void ParsedText::globalTag(const AttrsList &attrs)
- {
- for (const auto &attr : attrs) {
- // Only page level style
- if (attr.first == "margin") {
- if (check_integer(attr.second))
- margin = stoi(attr.second.c_str());
- } else if (attr.first == "valign") {
- if (attr.second == "top")
- valign = ParsedText::VALIGN_TOP;
- else if (attr.second == "bottom")
- valign = ParsedText::VALIGN_BOTTOM;
- else if (attr.second == "middle")
- valign = ParsedText::VALIGN_MIDDLE;
- } else if (attr.first == "background") {
- irr::video::SColor color;
- if (attr.second == "none") {
- background_type = BACKGROUND_NONE;
- } else if (parseColorString(attr.second, color, false)) {
- background_type = BACKGROUND_COLOR;
- background_color = color;
- }
- // Inheriting styles
- } else if (attr.first == "halign") {
- if (attr.second == "left" || attr.second == "center" ||
- attr.second == "right" ||
- attr.second == "justify")
- m_root_tag.style["halign"] = attr.second;
- // Generic default styles
- } else {
- parseGenericStyleAttr(attr.first, attr.second, m_root_tag.style);
- }
- }
- }
- u32 ParsedText::parseTag(const wchar_t *text, u32 cursor)
- {
- // Tag name
- bool end = false;
- std::string name = "";
- wchar_t c = text[cursor];
- if (c == L'/') {
- end = true;
- c = text[++cursor];
- if (c == L'\0')
- return 0;
- }
- while (c != ' ' && c != '>') {
- name += c;
- c = text[++cursor];
- if (c == L'\0')
- return 0;
- }
- // Tag attributes
- AttrsList attrs;
- while (c != L'>') {
- std::string attr_name = "";
- core::stringw attr_val = L"";
- while (c == ' ') {
- c = text[++cursor];
- if (c == L'\0' || c == L'=')
- return 0;
- }
- while (c != L' ' && c != L'=') {
- attr_name += (char)c;
- c = text[++cursor];
- if (c == L'\0' || c == L'>')
- return 0;
- }
- while (c == L' ') {
- c = text[++cursor];
- if (c == L'\0' || c == L'>')
- return 0;
- }
- if (c != L'=')
- return 0;
- c = text[++cursor];
- if (c == L'\0')
- return 0;
- while (c != L'>' && c != L' ') {
- attr_val += c;
- c = text[++cursor];
- if (c == L'\0')
- return 0;
- }
- attrs[attr_name] = stringw_to_utf8(attr_val);
- }
- ++cursor; // Last ">"
- // Tag specific processing
- StyleList style;
- if (name == "global") {
- if (end)
- return 0;
- globalTag(attrs);
- } else if (name == "style") {
- if (end) {
- closeTag(name);
- } else {
- parseStyles(attrs, style);
- openTag(name, attrs)->style = style;
- }
- endElement();
- } else if (name == "img" || name == "item") {
- if (end)
- return 0;
- // Name is a required attribute
- if (!attrs.count("name"))
- return 0;
- // Rotate attribute is only for <item>
- if (attrs.count("rotate") && name != "item")
- return 0;
- // Angle attribute is only for <item>
- if (attrs.count("angle") && name != "item")
- return 0;
- // Ok, element can be created
- newTag(name, attrs);
- if (name == "img")
- enterElement(ELEMENT_IMAGE);
- else
- enterElement(ELEMENT_ITEM);
- m_element->text = utf8_to_stringw(attrs["name"]);
- if (attrs.count("float")) {
- if (attrs["float"] == "left")
- m_element->floating = FLOAT_LEFT;
- if (attrs["float"] == "right")
- m_element->floating = FLOAT_RIGHT;
- }
- if (attrs.count("width")) {
- int width = stoi(attrs["width"]);
- if (width > 0)
- m_element->dim.Width = width;
- }
- if (attrs.count("height")) {
- int height = stoi(attrs["height"]);
- if (height > 0)
- m_element->dim.Height = height;
- }
- if (attrs.count("angle")) {
- std::string str = attrs["angle"];
- std::vector<std::string> parts = split(str, ',');
- if (parts.size() == 3) {
- m_element->angle = v3s16(
- rangelim(stoi(parts[0]), -180, 180),
- rangelim(stoi(parts[1]), -180, 180),
- rangelim(stoi(parts[2]), -180, 180));
- m_element->rotation = v3s16(0, 0, 0);
- }
- }
- if (attrs.count("rotate")) {
- if (attrs["rotate"] == "yes") {
- m_element->rotation = v3s16(0, 100, 0);
- } else {
- std::string str = attrs["rotate"];
- std::vector<std::string> parts = split(str, ',');
- if (parts.size() == 3) {
- m_element->rotation = v3s16 (
- rangelim(stoi(parts[0]), -1000, 1000),
- rangelim(stoi(parts[1]), -1000, 1000),
- rangelim(stoi(parts[2]), -1000, 1000));
- }
- }
- }
- endElement();
- } else if (name == "tag") {
- // Required attributes
- if (!attrs.count("name"))
- return 0;
- StyleList tagstyle;
- parseStyles(attrs, tagstyle);
- if (is_yes(attrs["paragraph"]))
- m_paragraphtags[attrs["name"]] = tagstyle;
- else
- m_elementtags[attrs["name"]] = tagstyle;
- } else if (name == "action") {
- if (end) {
- closeTag(name);
- } else {
- if (!attrs.count("name"))
- return 0;
- openTag(name, attrs)->style = m_elementtags["action"];
- }
- } else if (m_elementtags.count(name)) {
- if (end) {
- closeTag(name);
- } else {
- openTag(name, attrs)->style = m_elementtags[name];
- }
- endElement();
- } else if (m_paragraphtags.count(name)) {
- if (end) {
- closeTag(name);
- } else {
- openTag(name, attrs)->style = m_paragraphtags[name];
- }
- endParagraph(ER_TAG);
- } else
- return 0; // Unknown tag
- // Update styles accordingly
- m_style.clear();
- for (auto tag = m_active_tags.crbegin(); tag != m_active_tags.crend(); ++tag)
- for (const auto &prop : (*tag)->style)
- m_style[prop.first] = prop.second;
- return cursor;
- }
- // -----------------------------------------------------------------------------
- // Text Drawer
- TextDrawer::TextDrawer(const wchar_t *text, Client *client,
- gui::IGUIEnvironment *environment, ISimpleTextureSource *tsrc) :
- m_text(text),
- m_client(client), m_environment(environment)
- {
- // Size all elements
- for (auto &p : m_text.m_paragraphs) {
- for (auto &e : p.elements) {
- switch (e.type) {
- case ParsedText::ELEMENT_SEPARATOR:
- case ParsedText::ELEMENT_TEXT:
- if (e.font) {
- e.dim.Width = e.font->getDimension(e.text.c_str()).Width;
- e.dim.Height = e.font->getDimension(L"Yy").Height;
- #if USE_FREETYPE
- if (e.font->getType() == irr::gui::EGFT_CUSTOM) {
- e.baseline = e.dim.Height - 1 -
- ((irr::gui::CGUITTFont *)e.font)->getAscender() / 64;
- }
- #endif
- } else {
- e.dim = {0, 0};
- }
- break;
- case ParsedText::ELEMENT_IMAGE:
- case ParsedText::ELEMENT_ITEM:
- // Resize only non sized items
- if (e.dim.Height != 0 && e.dim.Width != 0)
- break;
- // Default image and item size
- core::dimension2d<u32> dim(80, 80);
- if (e.type == ParsedText::ELEMENT_IMAGE) {
- video::ITexture *texture =
- m_client->getTextureSource()->
- getTexture(stringw_to_utf8(e.text));
- if (texture)
- dim = texture->getOriginalSize();
- }
- if (e.dim.Height == 0)
- if (e.dim.Width == 0)
- e.dim = dim;
- else
- e.dim.Height = dim.Height * e.dim.Width /
- dim.Width;
- else
- e.dim.Width = dim.Width * e.dim.Height /
- dim.Height;
- break;
- }
- }
- }
- }
- // Get element at given coordinates. Coordinates are inner coordinates (starting
- // at 0,0).
- ParsedText::Element *TextDrawer::getElementAt(core::position2d<s32> pos)
- {
- pos.Y -= m_voffset;
- for (auto &p : m_text.m_paragraphs) {
- for (auto &el : p.elements) {
- core::rect<s32> rect(el.pos, el.dim);
- if (rect.isPointInside(pos))
- return ⪙
- }
- }
- return 0;
- }
- /*
- This function places all elements according to given width. Elements have
- been previously sized by constructor and will be later drawed by draw.
- It may be called each time width changes and resulting height can be
- retrieved using getHeight. See GUIHyperText constructor, it uses it once to
- test if text fits in window and eventually another time if width is reduced
- m_floating because of scrollbar added.
- */
- void TextDrawer::place(const core::rect<s32> &dest_rect)
- {
- m_floating.clear();
- s32 y = 0;
- s32 ymargin = m_text.margin;
- // Iterator used :
- // p - Current paragraph, walked only once
- // el - Current element, walked only once
- // e and f - local element and floating operators
- for (auto &p : m_text.m_paragraphs) {
- // Find and place floating stuff in paragraph
- for (auto e = p.elements.begin(); e != p.elements.end(); ++e) {
- if (e->floating != ParsedText::FLOAT_NONE) {
- if (y)
- e->pos.Y = y + std::max(ymargin, e->margin);
- else
- e->pos.Y = ymargin;
- if (e->floating == ParsedText::FLOAT_LEFT)
- e->pos.X = m_text.margin;
- if (e->floating == ParsedText::FLOAT_RIGHT)
- e->pos.X = dest_rect.getWidth() - e->dim.Width -
- m_text.margin;
- RectWithMargin floating;
- floating.rect = core::rect<s32>(e->pos, e->dim);
- floating.margin = e->margin;
- m_floating.push_back(floating);
- }
- }
- if (y)
- y = y + std::max(ymargin, p.margin);
- ymargin = p.margin;
- // Place non floating stuff
- std::vector<ParsedText::Element>::iterator el = p.elements.begin();
- while (el != p.elements.end()) {
- // Determine line width and y pos
- s32 left, right;
- s32 nexty = y;
- do {
- y = nexty;
- nexty = 0;
- // Inner left & right
- left = m_text.margin;
- right = dest_rect.getWidth() - m_text.margin;
- for (const auto &f : m_floating) {
- // Does floating rect intersect paragraph y line?
- if (f.rect.UpperLeftCorner.Y - f.margin <= y &&
- f.rect.LowerRightCorner.Y + f.margin >= y) {
- // Next Y to try if no room left
- if (!nexty || f.rect.LowerRightCorner.Y +
- std::max(f.margin, p.margin) < nexty) {
- nexty = f.rect.LowerRightCorner.Y +
- std::max(f.margin, p.margin) + 1;
- }
- if (f.rect.UpperLeftCorner.X - f.margin <= left &&
- f.rect.LowerRightCorner.X + f.margin < right) {
- // float on left
- if (f.rect.LowerRightCorner.X +
- std::max(f.margin, p.margin) > left) {
- left = f.rect.LowerRightCorner.X +
- std::max(f.margin, p.margin);
- }
- } else if (f.rect.LowerRightCorner.X + f.margin >= right &&
- f.rect.UpperLeftCorner.X - f.margin > left) {
- // float on right
- if (f.rect.UpperLeftCorner.X -
- std::max(f.margin, p.margin) < right)
- right = f.rect.UpperLeftCorner.X -
- std::max(f.margin, p.margin);
- } else if (f.rect.UpperLeftCorner.X - f.margin <= left &&
- f.rect.LowerRightCorner.X + f.margin >= right) {
- // float taking all space
- left = right;
- }
- else
- { // float in the middle -- should not occure yet, see that later
- }
- }
- }
- } while (nexty && right <= left);
- u32 linewidth = right - left;
- float x = left;
- u32 charsheight = 0;
- u32 charswidth = 0;
- u32 wordcount = 0;
- // Skip begining of line separators but include them in height
- // computation.
- while (el != p.elements.end() &&
- el->type == ParsedText::ELEMENT_SEPARATOR) {
- if (el->floating == ParsedText::FLOAT_NONE) {
- el->drawwidth = 0;
- if (charsheight < el->dim.Height)
- charsheight = el->dim.Height;
- }
- el++;
- }
- std::vector<ParsedText::Element>::iterator linestart = el;
- std::vector<ParsedText::Element>::iterator lineend = p.elements.end();
- // First pass, find elements fitting into line
- // (or at least one element)
- while (el != p.elements.end() && (charswidth == 0 ||
- charswidth + el->dim.Width <= linewidth)) {
- if (el->floating == ParsedText::FLOAT_NONE) {
- if (el->type != ParsedText::ELEMENT_SEPARATOR) {
- lineend = el;
- wordcount++;
- }
- charswidth += el->dim.Width;
- if (charsheight < el->dim.Height)
- charsheight = el->dim.Height;
- }
- el++;
- }
- // Empty line, nothing to place only go down line height
- if (lineend == p.elements.end()) {
- y += charsheight;
- continue;
- }
- // Point to the first position outside line (may be end())
- lineend++;
- // Second pass, compute printable line width and adjustments
- charswidth = 0;
- s32 top = 0;
- s32 bottom = 0;
- for (auto e = linestart; e != lineend; ++e) {
- if (e->floating == ParsedText::FLOAT_NONE) {
- charswidth += e->dim.Width;
- if (top < (s32)e->dim.Height - e->baseline)
- top = e->dim.Height - e->baseline;
- if (bottom < e->baseline)
- bottom = e->baseline;
- }
- }
- float extraspace = 0.f;
- switch (p.halign) {
- case ParsedText::HALIGN_CENTER:
- x += (linewidth - charswidth) / 2.f;
- break;
- case ParsedText::HALIGN_JUSTIFY:
- if (wordcount > 1 && // Justification only if at least two words
- !(lineend == p.elements.end())) // Don't justify last line
- extraspace = ((float)(linewidth - charswidth)) / (wordcount - 1);
- break;
- case ParsedText::HALIGN_RIGHT:
- x += linewidth - charswidth;
- break;
- case ParsedText::HALIGN_LEFT:
- break;
- }
- // Third pass, actually place everything
- for (auto e = linestart; e != lineend; ++e) {
- if (e->floating != ParsedText::FLOAT_NONE)
- continue;
- e->pos.X = x;
- e->pos.Y = y;
- switch (e->type) {
- case ParsedText::ELEMENT_TEXT:
- case ParsedText::ELEMENT_SEPARATOR:
- e->pos.X = x;
- // Align char baselines
- e->pos.Y = y + top + e->baseline - e->dim.Height;
- x += e->dim.Width;
- if (e->type == ParsedText::ELEMENT_SEPARATOR)
- x += extraspace;
- break;
- case ParsedText::ELEMENT_IMAGE:
- case ParsedText::ELEMENT_ITEM:
- x += e->dim.Width;
- break;
- }
- // Draw width for separator can be different than element
- // width. This will be important for char effects like
- // underline.
- e->drawwidth = x - e->pos.X;
- }
- y += charsheight;
- } // Elements (actually lines)
- } // Paragraph
- // Check if float goes under paragraph
- for (const auto &f : m_floating) {
- if (f.rect.LowerRightCorner.Y >= y)
- y = f.rect.LowerRightCorner.Y;
- }
- m_height = y + m_text.margin;
- // Compute vertical offset according to vertical alignment
- if (m_height < dest_rect.getHeight())
- switch (m_text.valign) {
- case ParsedText::VALIGN_BOTTOM:
- m_voffset = dest_rect.getHeight() - m_height;
- break;
- case ParsedText::VALIGN_MIDDLE:
- m_voffset = (dest_rect.getHeight() - m_height) / 2;
- break;
- case ParsedText::VALIGN_TOP:
- default:
- m_voffset = 0;
- }
- else
- m_voffset = 0;
- }
- // Draw text in a rectangle with a given offset. Items are actually placed in
- // relative (to upper left corner) coordinates.
- void TextDrawer::draw(const core::rect<s32> &clip_rect,
- const core::position2d<s32> &dest_offset)
- {
- irr::video::IVideoDriver *driver = m_environment->getVideoDriver();
- core::position2d<s32> offset = dest_offset;
- offset.Y += m_voffset;
- if (m_text.background_type == ParsedText::BACKGROUND_COLOR)
- driver->draw2DRectangle(m_text.background_color, clip_rect);
- for (auto &p : m_text.m_paragraphs) {
- for (auto &el : p.elements) {
- core::rect<s32> rect(el.pos + offset, el.dim);
- if (!rect.isRectCollided(clip_rect))
- continue;
- switch (el.type) {
- case ParsedText::ELEMENT_SEPARATOR:
- case ParsedText::ELEMENT_TEXT: {
- irr::video::SColor color = el.color;
- for (auto tag : el.tags)
- if (&(*tag) == m_hovertag)
- color = el.hovercolor;
- if (!el.font)
- break;
- if (el.type == ParsedText::ELEMENT_TEXT)
- el.font->draw(el.text, rect, color, false, true,
- &clip_rect);
- if (el.underline && el.drawwidth) {
- s32 linepos = el.pos.Y + offset.Y +
- el.dim.Height - (el.baseline >> 1);
- core::rect<s32> linerect(el.pos.X + offset.X,
- linepos - (el.baseline >> 3) - 1,
- el.pos.X + offset.X + el.drawwidth,
- linepos + (el.baseline >> 3));
- driver->draw2DRectangle(color, linerect, &clip_rect);
- }
- } break;
- case ParsedText::ELEMENT_IMAGE: {
- video::ITexture *texture =
- m_client->getTextureSource()->getTexture(
- stringw_to_utf8(el.text));
- if (texture != 0)
- m_environment->getVideoDriver()->draw2DImage(
- texture, rect,
- irr::core::rect<s32>(
- core::position2d<s32>(0, 0),
- texture->getOriginalSize()),
- &clip_rect, 0, true);
- } break;
- case ParsedText::ELEMENT_ITEM: {
- IItemDefManager *idef = m_client->idef();
- ItemStack item;
- item.deSerialize(stringw_to_utf8(el.text), idef);
- drawItemStack(
- m_environment->getVideoDriver(),
- g_fontengine->getFont(), item, rect, &clip_rect,
- m_client, IT_ROT_OTHER, el.angle, el.rotation
- );
- } break;
- }
- }
- }
- }
- // -----------------------------------------------------------------------------
- // GUIHyperText - The formated text area formspec item
- //! constructor
- GUIHyperText::GUIHyperText(const wchar_t *text, IGUIEnvironment *environment,
- IGUIElement *parent, s32 id, const core::rect<s32> &rectangle,
- Client *client, ISimpleTextureSource *tsrc) :
- IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle),
- m_client(client), m_vscrollbar(nullptr),
- m_drawer(text, client, environment, tsrc), m_text_scrollpos(0, 0)
- {
- #ifdef _DEBUG
- setDebugName("GUIHyperText");
- #endif
- IGUISkin *skin = 0;
- if (Environment)
- skin = Environment->getSkin();
- m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16;
- core::rect<s32> rect = irr::core::rect<s32>(
- RelativeRect.getWidth() - m_scrollbar_width, 0,
- RelativeRect.getWidth(), RelativeRect.getHeight());
- m_vscrollbar = new GUIScrollBar(Environment, this, -1, rect, false, true);
- m_vscrollbar->setVisible(false);
- }
- //! destructor
- GUIHyperText::~GUIHyperText()
- {
- m_vscrollbar->remove();
- m_vscrollbar->drop();
- }
- ParsedText::Element *GUIHyperText::getElementAt(s32 X, s32 Y)
- {
- core::position2d<s32> pos{X, Y};
- pos -= m_display_text_rect.UpperLeftCorner;
- pos -= m_text_scrollpos;
- return m_drawer.getElementAt(pos);
- }
- void GUIHyperText::checkHover(s32 X, s32 Y)
- {
- m_drawer.m_hovertag = nullptr;
- if (AbsoluteRect.isPointInside(core::position2d<s32>(X, Y))) {
- ParsedText::Element *element = getElementAt(X, Y);
- if (element) {
- for (auto &tag : element->tags) {
- if (tag->name == "action") {
- m_drawer.m_hovertag = tag;
- break;
- }
- }
- }
- }
- #ifndef HAVE_TOUCHSCREENGUI
- if (m_drawer.m_hovertag)
- RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
- gui::ECI_HAND);
- else
- RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
- gui::ECI_NORMAL);
- #endif
- }
- bool GUIHyperText::OnEvent(const SEvent &event)
- {
- // Scroll bar
- if (event.EventType == EET_GUI_EVENT &&
- event.GUIEvent.EventType == EGET_SCROLL_BAR_CHANGED &&
- event.GUIEvent.Caller == m_vscrollbar) {
- m_text_scrollpos.Y = -m_vscrollbar->getPos();
- }
- // Reset hover if element left
- if (event.EventType == EET_GUI_EVENT &&
- event.GUIEvent.EventType == EGET_ELEMENT_LEFT) {
- m_drawer.m_hovertag = nullptr;
- #ifndef HAVE_TOUCHSCREENGUI
- gui::ICursorControl *cursor_control =
- RenderingEngine::get_raw_device()->getCursorControl();
- if (cursor_control->isVisible())
- cursor_control->setActiveIcon(gui::ECI_NORMAL);
- #endif
- }
- if (event.EventType == EET_MOUSE_INPUT_EVENT) {
- if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
- checkHover(event.MouseInput.X, event.MouseInput.Y);
- if (event.MouseInput.Event == EMIE_MOUSE_WHEEL && m_vscrollbar->isVisible()) {
- m_vscrollbar->setPos(m_vscrollbar->getPos() -
- event.MouseInput.Wheel * m_vscrollbar->getSmallStep());
- m_text_scrollpos.Y = -m_vscrollbar->getPos();
- m_drawer.draw(m_display_text_rect, m_text_scrollpos);
- checkHover(event.MouseInput.X, event.MouseInput.Y);
- return true;
- } else if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
- ParsedText::Element *element = getElementAt(
- event.MouseInput.X, event.MouseInput.Y);
- if (element) {
- for (auto &tag : element->tags) {
- if (tag->name == "action") {
- Text = core::stringw(L"action:") +
- utf8_to_stringw(tag->attrs["name"]);
- if (Parent) {
- SEvent newEvent;
- newEvent.EventType = EET_GUI_EVENT;
- newEvent.GUIEvent.Caller = this;
- newEvent.GUIEvent.Element = 0;
- newEvent.GUIEvent.EventType = EGET_BUTTON_CLICKED;
- Parent->OnEvent(newEvent);
- }
- break;
- }
- }
- }
- }
- }
- return IGUIElement::OnEvent(event);
- }
- //! draws the element and its children
- void GUIHyperText::draw()
- {
- if (!IsVisible)
- return;
- // Text
- m_display_text_rect = AbsoluteRect;
- m_drawer.place(m_display_text_rect);
- // Show scrollbar if text overflow
- if (m_drawer.getHeight() > m_display_text_rect.getHeight()) {
- m_vscrollbar->setSmallStep(m_display_text_rect.getHeight() * 0.1f);
- m_vscrollbar->setLargeStep(m_display_text_rect.getHeight() * 0.5f);
- m_vscrollbar->setMax(m_drawer.getHeight() - m_display_text_rect.getHeight());
- m_vscrollbar->setVisible(true);
- m_vscrollbar->setPageSize(s32(m_drawer.getHeight()));
- core::rect<s32> smaller_rect = m_display_text_rect;
- smaller_rect.LowerRightCorner.X -= m_scrollbar_width;
- m_drawer.place(smaller_rect);
- } else {
- m_vscrollbar->setMax(0);
- m_vscrollbar->setPos(0);
- m_vscrollbar->setVisible(false);
- }
- m_drawer.draw(AbsoluteClippingRect,
- m_display_text_rect.UpperLeftCorner + m_text_scrollpos);
- // draw children
- IGUIElement::draw();
- }
|