aboutsummaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/CMakeLists.txt1
-rw-r--r--src/gui/guiFormSpecMenu.cpp91
-rw-r--r--src/gui/guiFormSpecMenu.h2
-rw-r--r--src/gui/guiHyperText.cpp1137
-rw-r--r--src/gui/guiHyperText.h229
5 files changed, 1446 insertions, 14 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 2307856a4..c9f750b9a 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -11,6 +11,7 @@ set(gui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiHyperText.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp
${CMAKE_CURRENT_SOURCE_DIR}/intlGUIEditBox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modalMenu.cpp
diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp
index 5def4357e..44fdf7862 100644
--- a/src/gui/guiFormSpecMenu.cpp
+++ b/src/gui/guiFormSpecMenu.cpp
@@ -57,6 +57,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/guiscalingfilter.h"
#include "guiEditBoxWithScrollbar.h"
#include "intlGUIEditBox.h"
+#include "guiHyperText.h"
#define MY_CHECKPOS(a,b) \
if (v_pos.size() != 2) { \
@@ -155,16 +156,15 @@ void GUIFormSpecMenu::removeChildren()
{
const core::list<gui::IGUIElement*> &children = getChildren();
- while(!children.empty()) {
+ while (!children.empty()) {
(*children.getLast())->remove();
}
- if(m_tooltip_element) {
+ if (m_tooltip_element) {
m_tooltip_element->remove();
m_tooltip_element->drop();
- m_tooltip_element = NULL;
+ m_tooltip_element = nullptr;
}
-
}
void GUIFormSpecMenu::setInitialFocus()
@@ -1318,7 +1318,6 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data,
void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& parts,
const std::string &type)
{
-
std::vector<std::string> v_pos = split(parts[0],',');
std::vector<std::string> v_geom = split(parts[1],',');
std::string name = parts[2];
@@ -1402,6 +1401,59 @@ void GUIFormSpecMenu::parseField(parserData* data, const std::string &element,
errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'" << std::endl;
}
+void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ';');
+
+ if (parts.size() != 4 && m_formspec_version < FORMSPEC_API_VERSION) {
+ errorstream << "Invalid text element(" << parts.size() << "): '" << element << "'" << std::endl;
+ return;
+ }
+
+ std::vector<std::string> v_pos = split(parts[0], ',');
+ std::vector<std::string> v_geom = split(parts[1], ',');
+ std::string name = parts[2];
+ std::string text = parts[3];
+
+ MY_CHECKPOS("hypertext", 0);
+ MY_CHECKGEOM("hypertext", 1);
+
+ v2s32 pos;
+ v2s32 geom;
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(false, &v_pos);
+ pos -= padding;
+
+ pos.X += stof(v_pos[0]) * spacing.X;
+ pos.Y += stof(v_pos[1]) * spacing.Y + (m_btn_height * 2);
+
+ geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
+ geom.Y = (stof(v_geom[1]) * imgsize.Y) - (spacing.Y - imgsize.Y);
+ }
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y);
+
+ if(m_form_src)
+ text = m_form_src->resolveText(text);
+
+ FieldSpec spec(
+ name,
+ utf8_to_wide(unescape_string(text)),
+ L"",
+ 258 + m_fields.size()
+ );
+
+ spec.ftype = f_Unknown;
+ new GUIHyperText(
+ spec.flabel.c_str(), Environment, this, spec.fid, rect, m_client, m_tsrc);
+
+ m_fields.push_back(spec);
+}
+
void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
{
std::vector<std::string> parts = split(element,';');
@@ -2293,6 +2345,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
return;
}
+ if (type == "hypertext") {
+ parseHyperText(data,description);
+ return;
+ }
+
if (type == "label") {
parseLabel(data,description);
return;
@@ -2879,8 +2936,8 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int layer,
if (!item.empty()) {
// Draw item stack
drawItemStack(driver, m_font, item,
- rect, &AbsoluteClippingRect, m_client,
- rotation_kind);
+ rect, &AbsoluteClippingRect, m_client, rotation_kind);
+
// Draw tooltip
if (hovering && !m_selected_item) {
std::string tooltip = item.getDescription(m_client->idef());
@@ -2900,8 +2957,8 @@ void GUIFormSpecMenu::drawSelectedItem()
if (!m_selected_item) {
drawItemStack(driver, m_font, ItemStack(),
- core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
- NULL, m_client, IT_ROT_DRAGGED);
+ core::rect<s32>(v2s32(0, 0), v2s32(0, 0)), NULL,
+ m_client, IT_ROT_DRAGGED);
return;
}
@@ -3482,9 +3539,10 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
}
}
}
- // Mouse wheel events: send to hovered element instead of focused
- if(event.EventType==EET_MOUSE_INPUT_EVENT
- && event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
+ // Mouse wheel and move events: send to hovered element instead of focused
+ if (event.EventType == EET_MOUSE_INPUT_EVENT &&
+ (event.MouseInput.Event == EMIE_MOUSE_WHEEL ||
+ event.MouseInput.Event == EMIE_MOUSE_MOVED)) {
s32 x = event.MouseInput.X;
s32 y = event.MouseInput.Y;
gui::IGUIElement *hovered =
@@ -3492,7 +3550,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
core::position2d<s32>(x, y));
if (hovered && isMyChild(hovered)) {
hovered->OnEvent(event);
- return true;
+ return event.MouseInput.Event == EMIE_MOUSE_WHEEL;
}
}
@@ -4041,8 +4099,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
}
m_old_pointer = m_pointer;
}
- if (event.EventType == EET_GUI_EVENT) {
+ if (event.EventType == EET_GUI_EVENT) {
if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED
&& isVisible()) {
// find the element that was clicked
@@ -4128,6 +4186,11 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
s.fdefault = L"Changed";
acceptInput(quit_mode_no);
s.fdefault = L"";
+ } else if ((s.ftype == f_Unknown) &&
+ (s.fid == event.GUIEvent.Caller->getID())) {
+ s.send = true;
+ acceptInput();
+ s.send = false;
}
}
}
diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h
index 46df0930c..39af1e7c2 100644
--- a/src/gui/guiFormSpecMenu.h
+++ b/src/gui/guiFormSpecMenu.h
@@ -469,6 +469,7 @@ protected:
video::SColor m_default_tooltip_bgcolor;
video::SColor m_default_tooltip_color;
+
private:
IFormSource *m_form_src;
TextDest *m_text_dst;
@@ -529,6 +530,7 @@ private:
void parseSimpleField(parserData* data,std::vector<std::string> &parts);
void parseTextArea(parserData* data,std::vector<std::string>& parts,
const std::string &type);
+ void parseHyperText(parserData *data, const std::string &element);
void parseLabel(parserData* data, const std::string &element);
void parseVertLabel(parserData* data, const std::string &element);
void parseImageButton(parserData* data, const std::string &element,
diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp
new file mode 100644
index 000000000..024e4de09
--- /dev/null
+++ b/src/gui/guiHyperText.cpp
@@ -0,0 +1,1137 @@
+/*
+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;
+
+ // TODO: find a way to check font validity
+ // Build a new fontengine ?
+ this->font =
+#if USE_FREETYPE
+ (gui::CGUITTFont *)
+#endif
+ g_fontengine->getFont(font_size, font_mode,
+ is_yes(style["bold"]), is_yes(style["italic"]));
+
+ 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"] = m_root_tag.style["color"];
+
+ m_tags.push_back(&m_root_tag);
+ m_active_tags.push_front(&m_root_tag);
+ m_style = m_root_tag.style;
+
+ // Default simple tags definitions
+ StyleList style;
+
+ style["hovercolor"] = "#FF0000";
+ 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;
+
+ parse(text);
+}
+
+ParsedText::~ParsedText()
+{
+ for (auto &tag : m_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();
+ 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();
+ 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();
+}
+
+void ParsedText::endElement()
+{
+ m_element = NULL;
+}
+
+void ParsedText::endParagraph()
+{
+ if (!m_paragraph)
+ 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);
+ }
+}
+
+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')
+ enterElement(ELEMENT_SEPARATOR);
+ else
+ 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_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 = "";
+ std::string attr_val = "";
+
+ 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 += (char)c;
+ c = text[++cursor];
+ if (c == L'\0')
+ return 0;
+ }
+
+ attrs[attr_name] = 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 = strtostrw(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();
+
+ } 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
+ e.baseline = e.dim.Height - 1 - 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(strwtostr(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 &el;
+ }
+ }
+ 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_floatingbecause 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> &dest_rect,
+ const core::position2d<s32> &dest_offset)
+{
+ irr::video::IVideoDriver *driver = m_environment->getVideoDriver();
+ core::position2d<s32> offset = dest_rect.UpperLeftCorner + dest_offset;
+ offset.Y += m_voffset;
+
+ if (m_text.background_type == ParsedText::BACKGROUND_COLOR)
+ driver->draw2DRectangle(m_text.background_color, dest_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(dest_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,
+ &dest_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, &dest_rect);
+ }
+ } break;
+
+ case ParsedText::ELEMENT_IMAGE: {
+ video::ITexture *texture =
+ m_client->getTextureSource()->getTexture(
+ strwtostr(el.text));
+ if (texture != 0)
+ m_environment->getVideoDriver()->draw2DImage(
+ texture, rect,
+ irr::core::rect<s32>(
+ core::position2d<s32>(0, 0),
+ texture->getOriginalSize()),
+ &dest_rect, 0, true);
+ } break;
+
+ case ParsedText::ELEMENT_ITEM: {
+ IItemDefManager *idef = m_client->idef();
+ ItemStack item;
+ item.deSerialize(strwtostr(el.text), idef);
+
+ drawItemStack(
+ m_environment->getVideoDriver(),
+ g_fontengine->getFont(), item, rect, &dest_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();
+}
+
+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;
+ }
+ }
+ }
+ }
+
+ if (m_drawer.m_hovertag)
+ RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
+ gui::ECI_HAND);
+ else
+ RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
+ gui::ECI_NORMAL);
+}
+
+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;
+ RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
+ gui::ECI_NORMAL);
+ }
+
+ 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->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);
+
+ } 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:") +
+ strtostrw(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(m_display_text_rect, m_text_scrollpos);
+
+ // draw children
+ IGUIElement::draw();
+}
diff --git a/src/gui/guiHyperText.h b/src/gui/guiHyperText.h
new file mode 100644
index 000000000..e3ad0e747
--- /dev/null
+++ b/src/gui/guiHyperText.h
@@ -0,0 +1,229 @@
+/*
+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.
+*/
+
+#pragma once
+
+#include "config.h" // for USE_FREETYPE
+
+using namespace irr;
+
+class ISimpleTextureSource;
+class Client;
+
+#if USE_FREETYPE
+#include "irrlicht_changes/CGUITTFont.h"
+#endif
+
+class ParsedText
+{
+public:
+ ParsedText(const wchar_t *text);
+ ~ParsedText();
+
+ enum ElementType
+ {
+ ELEMENT_TEXT,
+ ELEMENT_SEPARATOR,
+ ELEMENT_IMAGE,
+ ELEMENT_ITEM
+ };
+
+ enum BackgroundType
+ {
+ BACKGROUND_NONE,
+ BACKGROUND_COLOR
+ };
+
+ enum FloatType
+ {
+ FLOAT_NONE,
+ FLOAT_RIGHT,
+ FLOAT_LEFT
+ };
+
+ enum HalignType
+ {
+ HALIGN_CENTER,
+ HALIGN_LEFT,
+ HALIGN_RIGHT,
+ HALIGN_JUSTIFY
+ };
+
+ enum ValignType
+ {
+ VALIGN_MIDDLE,
+ VALIGN_TOP,
+ VALIGN_BOTTOM
+ };
+
+ typedef std::unordered_map<std::string, std::string> StyleList;
+ typedef std::unordered_map<std::string, std::string> AttrsList;
+
+ struct Tag
+ {
+ std::string name;
+ AttrsList attrs;
+ StyleList style;
+ };
+
+ struct Element
+ {
+ std::list<Tag *> tags;
+ ElementType type;
+ core::stringw text = "";
+
+ core::dimension2d<u32> dim;
+ core::position2d<s32> pos;
+ s32 drawwidth;
+
+ FloatType floating = FLOAT_NONE;
+
+ ValignType valign;
+
+#if USE_FREETYPE
+ gui::CGUITTFont *font;
+#else
+ gui::IGUIFont *font;
+#endif
+
+ irr::video::SColor color;
+ irr::video::SColor hovercolor;
+ bool underline;
+
+ s32 baseline = 0;
+
+ // img & item specific attributes
+ std::string name;
+ v3s16 angle{0, 0, 0};
+ v3s16 rotation{0, 0, 0};
+
+ s32 margin = 10;
+
+ void setStyle(StyleList &style);
+ };
+
+ struct Paragraph
+ {
+ std::vector<Element> elements;
+ HalignType halign;
+ s32 margin = 10;
+
+ void setStyle(StyleList &style);
+ };
+
+ std::vector<Paragraph> m_paragraphs;
+
+ // Element style
+ s32 margin = 3;
+ ValignType valign = VALIGN_TOP;
+ BackgroundType background_type = BACKGROUND_NONE;
+ irr::video::SColor background_color;
+
+ Tag m_root_tag;
+
+protected:
+ // Parser functions
+ void enterElement(ElementType type);
+ void endElement();
+ void enterParagraph();
+ void endParagraph();
+ void pushChar(wchar_t c);
+ ParsedText::Tag *newTag(const std::string &name, const AttrsList &attrs);
+ ParsedText::Tag *openTag(const std::string &name, const AttrsList &attrs);
+ bool closeTag(const std::string &name);
+ void parseGenericStyleAttr(const std::string &name, const std::string &value,
+ StyleList &style);
+ void parseStyles(const AttrsList &attrs, StyleList &style);
+ void globalTag(const ParsedText::AttrsList &attrs);
+ u32 parseTag(const wchar_t *text, u32 cursor);
+ void parse(const wchar_t *text);
+
+ std::unordered_map<std::string, StyleList> m_elementtags;
+ std::unordered_map<std::string, StyleList> m_paragraphtags;
+
+ std::vector<Tag *> m_tags;
+ std::list<Tag *> m_active_tags;
+
+ // Current values
+ StyleList m_style;
+ Element *m_element;
+ Paragraph *m_paragraph;
+};
+
+class TextDrawer
+{
+public:
+ TextDrawer(const wchar_t *text, Client *client, gui::IGUIEnvironment *environment,
+ ISimpleTextureSource *tsrc);
+
+ void place(const core::rect<s32> &dest_rect);
+ inline s32 getHeight() { return m_height; };
+ void draw(const core::rect<s32> &dest_rect,
+ const core::position2d<s32> &dest_offset);
+ ParsedText::Element *getElementAt(core::position2d<s32> pos);
+ ParsedText::Tag *m_hovertag;
+
+protected:
+ struct RectWithMargin
+ {
+ core::rect<s32> rect;
+ s32 margin;
+ };
+
+ ParsedText m_text;
+ Client *m_client;
+ gui::IGUIEnvironment *m_environment;
+ s32 m_height;
+ s32 m_voffset;
+ std::vector<RectWithMargin> m_floating;
+};
+
+class GUIHyperText : public gui::IGUIElement
+{
+public:
+ //! constructor
+ GUIHyperText(const wchar_t *text, gui::IGUIEnvironment *environment,
+ gui::IGUIElement *parent, s32 id,
+ const core::rect<s32> &rectangle, Client *client,
+ ISimpleTextureSource *tsrc);
+
+ //! destructor
+ virtual ~GUIHyperText();
+
+ //! draws the element and its children
+ virtual void draw();
+
+ core::dimension2du getTextDimension();
+
+ bool OnEvent(const SEvent &event);
+
+protected:
+ // GUI members
+ Client *m_client;
+ GUIScrollBar *m_vscrollbar;
+ TextDrawer m_drawer;
+
+ // Positioning
+ u32 m_scrollbar_width;
+ core::rect<s32> m_display_text_rect;
+ core::position2d<s32> m_text_scrollpos;
+
+ ParsedText::Element *getElementAt(s32 X, s32 Y);
+ void checkHover(s32 X, s32 Y);
+};