3 Copyright (C) 2019 EvicenceBKidscode / Pierre-Yves Rollo <dev@pyrollo.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "IGUIEnvironment.h"
21 #include "IGUIElement.h"
22 #include "guiScrollBar.h"
26 #include <unordered_map>
27 using namespace irr::gui;
28 #include "client/fontengine.h"
30 #include "client/tile.h"
31 #include "IVideoDriver.h"
32 #include "client/client.h"
33 #include "client/renderingengine.h"
35 #include "guiHyperText.h"
36 #include "util/string.h"
38 bool check_color(const std::string &str)
40 irr::video::SColor color;
41 return parseColorString(str, color, false);
44 bool check_integer(const std::string &str)
49 char *endptr = nullptr;
50 strtol(str.c_str(), &endptr, 10);
52 return *endptr == '\0';
55 // -----------------------------------------------------------------------------
56 // ParsedText - A text parser
58 void ParsedText::Element::setStyle(StyleList &style)
60 this->underline = is_yes(style["underline"]);
64 if (parseColorString(style["color"], color, false))
66 if (parseColorString(style["hovercolor"], color, false))
67 this->hovercolor = color;
69 unsigned int font_size = std::atoi(style["fontsize"].c_str());
70 FontMode font_mode = FM_Standard;
71 if (style["fontstyle"] == "mono")
74 FontSpec spec(font_size, font_mode,
75 is_yes(style["bold"]), is_yes(style["italic"]));
77 // TODO: find a way to check font validity
78 // Build a new fontengine ?
79 this->font = g_fontengine->getFont(spec);
82 printf("No font found ! Size=%d, mode=%d, bold=%s, italic=%s\n",
83 font_size, font_mode, style["bold"].c_str(),
84 style["italic"].c_str());
87 void ParsedText::Paragraph::setStyle(StyleList &style)
89 if (style["halign"] == "center")
90 this->halign = HALIGN_CENTER;
91 else if (style["halign"] == "right")
92 this->halign = HALIGN_RIGHT;
93 else if (style["halign"] == "justify")
94 this->halign = HALIGN_JUSTIFY;
96 this->halign = HALIGN_LEFT;
99 ParsedText::ParsedText(const wchar_t *text)
102 m_root_tag.name = "root";
103 m_root_tag.style["fontsize"] = "16";
104 m_root_tag.style["fontstyle"] = "normal";
105 m_root_tag.style["bold"] = "false";
106 m_root_tag.style["italic"] = "false";
107 m_root_tag.style["underline"] = "false";
108 m_root_tag.style["halign"] = "left";
109 m_root_tag.style["color"] = "#EEEEEE";
110 m_root_tag.style["hovercolor"] = "#FF0000";
112 m_active_tags.push_front(&m_root_tag);
113 m_style = m_root_tag.style;
115 // Default simple tags definitions
118 style["color"] = "#0000FF";
119 style["underline"] = "true";
120 m_elementtags["action"] = style;
123 style["bold"] = "true";
124 m_elementtags["b"] = style;
127 style["italic"] = "true";
128 m_elementtags["i"] = style;
131 style["underline"] = "true";
132 m_elementtags["u"] = style;
135 style["fontstyle"] = "mono";
136 m_elementtags["mono"] = style;
139 style["fontsize"] = m_root_tag.style["fontsize"];
140 m_elementtags["normal"] = style;
143 style["fontsize"] = "24";
144 m_elementtags["big"] = style;
147 style["fontsize"] = "36";
148 m_elementtags["bigger"] = style;
151 style["halign"] = "center";
152 m_paragraphtags["center"] = style;
155 style["halign"] = "justify";
156 m_paragraphtags["justify"] = style;
159 style["halign"] = "left";
160 m_paragraphtags["left"] = style;
163 style["halign"] = "right";
164 m_paragraphtags["right"] = style;
169 m_end_paragraph_reason = ER_NONE;
174 ParsedText::~ParsedText()
176 for (auto &tag : m_not_root_tags)
180 void ParsedText::parse(const wchar_t *text)
186 while ((c = text[cursor]) != L'\0') {
189 if (c == L'\r') { // Mac or Windows breaks
190 if (text[cursor] == L'\n')
192 // If text has begun, don't skip empty line
194 endParagraph(ER_NEWLINE);
195 enterElement(ELEMENT_SEPARATOR);
201 if (c == L'\n') { // Unix breaks
202 // If text has begun, don't skip empty line
204 endParagraph(ER_NEWLINE);
205 enterElement(ELEMENT_SEPARATOR);
224 u32 newcursor = parseTag(text, cursor);
235 endParagraph(ER_NONE);
238 void ParsedText::endElement()
243 void ParsedText::endParagraph(EndReason reason)
248 EndReason previous = m_end_paragraph_reason;
249 m_end_paragraph_reason = reason;
250 if (m_empty_paragraph && (reason == ER_TAG ||
251 (reason == ER_NEWLINE && previous == ER_TAG))) {
252 // Ignore last empty paragraph
253 m_paragraph = nullptr;
254 m_paragraphs.pop_back();
261 void ParsedText::enterParagraph()
264 m_paragraphs.emplace_back();
265 m_paragraph = &m_paragraphs.back();
266 m_paragraph->setStyle(m_style);
267 m_empty_paragraph = true;
271 void ParsedText::enterElement(ElementType type)
275 if (!m_element || m_element->type != type) {
276 m_paragraph->elements.emplace_back();
277 m_element = &m_paragraph->elements.back();
278 m_element->type = type;
279 m_element->tags = m_active_tags;
280 m_element->setStyle(m_style);
284 void ParsedText::pushChar(wchar_t c)
286 // New word if needed
287 if (c == L' ' || c == L'\t') {
288 if (!m_empty_paragraph)
289 enterElement(ELEMENT_SEPARATOR);
293 m_empty_paragraph = false;
294 enterElement(ELEMENT_TEXT);
296 m_element->text += c;
299 ParsedText::Tag *ParsedText::newTag(const std::string &name, const AttrsList &attrs)
302 Tag *newtag = new Tag();
304 newtag->attrs = attrs;
305 m_not_root_tags.push_back(newtag);
309 ParsedText::Tag *ParsedText::openTag(const std::string &name, const AttrsList &attrs)
311 Tag *newtag = newTag(name, attrs);
312 m_active_tags.push_front(newtag);
316 bool ParsedText::closeTag(const std::string &name)
319 for (auto id = m_active_tags.begin(); id != m_active_tags.end(); ++id)
320 if ((*id)->name == name) {
321 m_active_tags.erase(id);
328 void ParsedText::parseGenericStyleAttr(
329 const std::string &name, const std::string &value, StyleList &style)
332 if (name == "color" || name == "hovercolor") {
333 if (check_color(value))
337 } else if (name == "bold" || name == "italic" || name == "underline") {
338 style[name] = is_yes(value);
340 } else if (name == "size") {
341 if (check_integer(value))
342 style["fontsize"] = value;
344 } else if (name == "font") {
345 if (value == "mono" || value == "normal")
346 style["fontstyle"] = value;
350 void ParsedText::parseStyles(const AttrsList &attrs, StyleList &style)
352 for (auto const &attr : attrs)
353 parseGenericStyleAttr(attr.first, attr.second, style);
356 void ParsedText::globalTag(const AttrsList &attrs)
358 for (const auto &attr : attrs) {
359 // Only page level style
360 if (attr.first == "margin") {
361 if (check_integer(attr.second))
362 margin = stoi(attr.second.c_str());
364 } else if (attr.first == "valign") {
365 if (attr.second == "top")
366 valign = ParsedText::VALIGN_TOP;
367 else if (attr.second == "bottom")
368 valign = ParsedText::VALIGN_BOTTOM;
369 else if (attr.second == "middle")
370 valign = ParsedText::VALIGN_MIDDLE;
371 } else if (attr.first == "background") {
372 irr::video::SColor color;
373 if (attr.second == "none") {
374 background_type = BACKGROUND_NONE;
375 } else if (parseColorString(attr.second, color, false)) {
376 background_type = BACKGROUND_COLOR;
377 background_color = color;
382 } else if (attr.first == "halign") {
383 if (attr.second == "left" || attr.second == "center" ||
384 attr.second == "right" ||
385 attr.second == "justify")
386 m_root_tag.style["halign"] = attr.second;
388 // Generic default styles
391 parseGenericStyleAttr(attr.first, attr.second, m_root_tag.style);
396 u32 ParsedText::parseTag(const wchar_t *text, u32 cursor)
400 std::string name = "";
401 wchar_t c = text[cursor];
410 while (c != ' ' && c != '>') {
420 std::string attr_name = "";
421 core::stringw attr_val = L"";
425 if (c == L'\0' || c == L'=')
429 while (c != L' ' && c != L'=') {
430 attr_name += (char)c;
432 if (c == L'\0' || c == L'>')
438 if (c == L'\0' || c == L'>')
450 while (c != L'>' && c != L' ') {
457 attrs[attr_name] = stringw_to_utf8(attr_val);
460 ++cursor; // Last ">"
462 // Tag specific processing
465 if (name == "global") {
470 } else if (name == "style") {
474 parseStyles(attrs, style);
475 openTag(name, attrs)->style = style;
478 } else if (name == "img" || name == "item") {
482 // Name is a required attribute
483 if (!attrs.count("name"))
486 // Rotate attribute is only for <item>
487 if (attrs.count("rotate") && name != "item")
490 // Angle attribute is only for <item>
491 if (attrs.count("angle") && name != "item")
494 // Ok, element can be created
498 enterElement(ELEMENT_IMAGE);
500 enterElement(ELEMENT_ITEM);
502 m_element->text = utf8_to_stringw(attrs["name"]);
504 if (attrs.count("float")) {
505 if (attrs["float"] == "left")
506 m_element->floating = FLOAT_LEFT;
507 if (attrs["float"] == "right")
508 m_element->floating = FLOAT_RIGHT;
511 if (attrs.count("width")) {
512 int width = stoi(attrs["width"]);
514 m_element->dim.Width = width;
517 if (attrs.count("height")) {
518 int height = stoi(attrs["height"]);
520 m_element->dim.Height = height;
523 if (attrs.count("angle")) {
524 std::string str = attrs["angle"];
525 std::vector<std::string> parts = split(str, ',');
526 if (parts.size() == 3) {
527 m_element->angle = v3s16(
528 rangelim(stoi(parts[0]), -180, 180),
529 rangelim(stoi(parts[1]), -180, 180),
530 rangelim(stoi(parts[2]), -180, 180));
531 m_element->rotation = v3s16(0, 0, 0);
535 if (attrs.count("rotate")) {
536 if (attrs["rotate"] == "yes") {
537 m_element->rotation = v3s16(0, 100, 0);
539 std::string str = attrs["rotate"];
540 std::vector<std::string> parts = split(str, ',');
541 if (parts.size() == 3) {
542 m_element->rotation = v3s16 (
543 rangelim(stoi(parts[0]), -1000, 1000),
544 rangelim(stoi(parts[1]), -1000, 1000),
545 rangelim(stoi(parts[2]), -1000, 1000));
552 } else if (name == "tag") {
553 // Required attributes
554 if (!attrs.count("name"))
558 parseStyles(attrs, tagstyle);
560 if (is_yes(attrs["paragraph"]))
561 m_paragraphtags[attrs["name"]] = tagstyle;
563 m_elementtags[attrs["name"]] = tagstyle;
565 } else if (name == "action") {
569 if (!attrs.count("name"))
571 openTag(name, attrs)->style = m_elementtags["action"];
574 } else if (m_elementtags.count(name)) {
578 openTag(name, attrs)->style = m_elementtags[name];
582 } else if (m_paragraphtags.count(name)) {
586 openTag(name, attrs)->style = m_paragraphtags[name];
588 endParagraph(ER_TAG);
591 return 0; // Unknown tag
593 // Update styles accordingly
595 for (auto tag = m_active_tags.crbegin(); tag != m_active_tags.crend(); ++tag)
596 for (const auto &prop : (*tag)->style)
597 m_style[prop.first] = prop.second;
602 // -----------------------------------------------------------------------------
605 TextDrawer::TextDrawer(const wchar_t *text, Client *client,
606 gui::IGUIEnvironment *environment, ISimpleTextureSource *tsrc) :
608 m_client(client), m_environment(environment)
611 for (auto &p : m_text.m_paragraphs) {
612 for (auto &e : p.elements) {
614 case ParsedText::ELEMENT_SEPARATOR:
615 case ParsedText::ELEMENT_TEXT:
617 e.dim.Width = e.font->getDimension(e.text.c_str()).Width;
618 e.dim.Height = e.font->getDimension(L"Yy").Height;
620 if (e.font->getType() == irr::gui::EGFT_CUSTOM) {
621 e.baseline = e.dim.Height - 1 -
622 ((irr::gui::CGUITTFont *)e.font)->getAscender() / 64;
630 case ParsedText::ELEMENT_IMAGE:
631 case ParsedText::ELEMENT_ITEM:
632 // Resize only non sized items
633 if (e.dim.Height != 0 && e.dim.Width != 0)
636 // Default image and item size
637 core::dimension2d<u32> dim(80, 80);
639 if (e.type == ParsedText::ELEMENT_IMAGE) {
640 video::ITexture *texture =
641 m_client->getTextureSource()->
642 getTexture(stringw_to_utf8(e.text));
644 dim = texture->getOriginalSize();
647 if (e.dim.Height == 0)
648 if (e.dim.Width == 0)
651 e.dim.Height = dim.Height * e.dim.Width /
654 e.dim.Width = dim.Width * e.dim.Height /
662 // Get element at given coordinates. Coordinates are inner coordinates (starting
664 ParsedText::Element *TextDrawer::getElementAt(core::position2d<s32> pos)
667 for (auto &p : m_text.m_paragraphs) {
668 for (auto &el : p.elements) {
669 core::rect<s32> rect(el.pos, el.dim);
670 if (rect.isPointInside(pos))
678 This function places all elements according to given width. Elements have
679 been previously sized by constructor and will be later drawed by draw.
680 It may be called each time width changes and resulting height can be
681 retrieved using getHeight. See GUIHyperText constructor, it uses it once to
682 test if text fits in window and eventually another time if width is reduced
683 m_floating because of scrollbar added.
685 void TextDrawer::place(const core::rect<s32> &dest_rect)
689 s32 ymargin = m_text.margin;
692 // p - Current paragraph, walked only once
693 // el - Current element, walked only once
694 // e and f - local element and floating operators
696 for (auto &p : m_text.m_paragraphs) {
697 // Find and place floating stuff in paragraph
698 for (auto e = p.elements.begin(); e != p.elements.end(); ++e) {
699 if (e->floating != ParsedText::FLOAT_NONE) {
701 e->pos.Y = y + std::max(ymargin, e->margin);
705 if (e->floating == ParsedText::FLOAT_LEFT)
706 e->pos.X = m_text.margin;
707 if (e->floating == ParsedText::FLOAT_RIGHT)
708 e->pos.X = dest_rect.getWidth() - e->dim.Width -
711 RectWithMargin floating;
712 floating.rect = core::rect<s32>(e->pos, e->dim);
713 floating.margin = e->margin;
715 m_floating.push_back(floating);
720 y = y + std::max(ymargin, p.margin);
724 // Place non floating stuff
725 std::vector<ParsedText::Element>::iterator el = p.elements.begin();
727 while (el != p.elements.end()) {
728 // Determine line width and y pos
735 // Inner left & right
736 left = m_text.margin;
737 right = dest_rect.getWidth() - m_text.margin;
739 for (const auto &f : m_floating) {
740 // Does floating rect intersect paragraph y line?
741 if (f.rect.UpperLeftCorner.Y - f.margin <= y &&
742 f.rect.LowerRightCorner.Y + f.margin >= y) {
744 // Next Y to try if no room left
745 if (!nexty || f.rect.LowerRightCorner.Y +
746 std::max(f.margin, p.margin) < nexty) {
747 nexty = f.rect.LowerRightCorner.Y +
748 std::max(f.margin, p.margin) + 1;
751 if (f.rect.UpperLeftCorner.X - f.margin <= left &&
752 f.rect.LowerRightCorner.X + f.margin < right) {
754 if (f.rect.LowerRightCorner.X +
755 std::max(f.margin, p.margin) > left) {
756 left = f.rect.LowerRightCorner.X +
757 std::max(f.margin, p.margin);
759 } else if (f.rect.LowerRightCorner.X + f.margin >= right &&
760 f.rect.UpperLeftCorner.X - f.margin > left) {
762 if (f.rect.UpperLeftCorner.X -
763 std::max(f.margin, p.margin) < right)
764 right = f.rect.UpperLeftCorner.X -
765 std::max(f.margin, p.margin);
767 } else if (f.rect.UpperLeftCorner.X - f.margin <= left &&
768 f.rect.LowerRightCorner.X + f.margin >= right) {
769 // float taking all space
773 { // float in the middle -- should not occure yet, see that later
777 } while (nexty && right <= left);
779 u32 linewidth = right - left;
786 // Skip begining of line separators but include them in height
788 while (el != p.elements.end() &&
789 el->type == ParsedText::ELEMENT_SEPARATOR) {
790 if (el->floating == ParsedText::FLOAT_NONE) {
792 if (charsheight < el->dim.Height)
793 charsheight = el->dim.Height;
798 std::vector<ParsedText::Element>::iterator linestart = el;
799 std::vector<ParsedText::Element>::iterator lineend = p.elements.end();
801 // First pass, find elements fitting into line
802 // (or at least one element)
803 while (el != p.elements.end() && (charswidth == 0 ||
804 charswidth + el->dim.Width <= linewidth)) {
805 if (el->floating == ParsedText::FLOAT_NONE) {
806 if (el->type != ParsedText::ELEMENT_SEPARATOR) {
810 charswidth += el->dim.Width;
811 if (charsheight < el->dim.Height)
812 charsheight = el->dim.Height;
817 // Empty line, nothing to place only go down line height
818 if (lineend == p.elements.end()) {
823 // Point to the first position outside line (may be end())
826 // Second pass, compute printable line width and adjustments
830 for (auto e = linestart; e != lineend; ++e) {
831 if (e->floating == ParsedText::FLOAT_NONE) {
832 charswidth += e->dim.Width;
833 if (top < (s32)e->dim.Height - e->baseline)
834 top = e->dim.Height - e->baseline;
835 if (bottom < e->baseline)
836 bottom = e->baseline;
840 float extraspace = 0.f;
843 case ParsedText::HALIGN_CENTER:
844 x += (linewidth - charswidth) / 2.f;
846 case ParsedText::HALIGN_JUSTIFY:
847 if (wordcount > 1 && // Justification only if at least two words
848 !(lineend == p.elements.end())) // Don't justify last line
849 extraspace = ((float)(linewidth - charswidth)) / (wordcount - 1);
851 case ParsedText::HALIGN_RIGHT:
852 x += linewidth - charswidth;
854 case ParsedText::HALIGN_LEFT:
858 // Third pass, actually place everything
859 for (auto e = linestart; e != lineend; ++e) {
860 if (e->floating != ParsedText::FLOAT_NONE)
867 case ParsedText::ELEMENT_TEXT:
868 case ParsedText::ELEMENT_SEPARATOR:
871 // Align char baselines
872 e->pos.Y = y + top + e->baseline - e->dim.Height;
875 if (e->type == ParsedText::ELEMENT_SEPARATOR)
879 case ParsedText::ELEMENT_IMAGE:
880 case ParsedText::ELEMENT_ITEM:
885 // Draw width for separator can be different than element
886 // width. This will be important for char effects like
888 e->drawwidth = x - e->pos.X;
891 } // Elements (actually lines)
894 // Check if float goes under paragraph
895 for (const auto &f : m_floating) {
896 if (f.rect.LowerRightCorner.Y >= y)
897 y = f.rect.LowerRightCorner.Y;
900 m_height = y + m_text.margin;
901 // Compute vertical offset according to vertical alignment
902 if (m_height < dest_rect.getHeight())
903 switch (m_text.valign) {
904 case ParsedText::VALIGN_BOTTOM:
905 m_voffset = dest_rect.getHeight() - m_height;
907 case ParsedText::VALIGN_MIDDLE:
908 m_voffset = (dest_rect.getHeight() - m_height) / 2;
910 case ParsedText::VALIGN_TOP:
918 // Draw text in a rectangle with a given offset. Items are actually placed in
919 // relative (to upper left corner) coordinates.
920 void TextDrawer::draw(const core::rect<s32> &clip_rect,
921 const core::position2d<s32> &dest_offset)
923 irr::video::IVideoDriver *driver = m_environment->getVideoDriver();
924 core::position2d<s32> offset = dest_offset;
925 offset.Y += m_voffset;
927 if (m_text.background_type == ParsedText::BACKGROUND_COLOR)
928 driver->draw2DRectangle(m_text.background_color, clip_rect);
930 for (auto &p : m_text.m_paragraphs) {
931 for (auto &el : p.elements) {
932 core::rect<s32> rect(el.pos + offset, el.dim);
933 if (!rect.isRectCollided(clip_rect))
937 case ParsedText::ELEMENT_SEPARATOR:
938 case ParsedText::ELEMENT_TEXT: {
939 irr::video::SColor color = el.color;
941 for (auto tag : el.tags)
942 if (&(*tag) == m_hovertag)
943 color = el.hovercolor;
948 if (el.type == ParsedText::ELEMENT_TEXT)
949 el.font->draw(el.text, rect, color, false, true,
952 if (el.underline && el.drawwidth) {
953 s32 linepos = el.pos.Y + offset.Y +
954 el.dim.Height - (el.baseline >> 1);
956 core::rect<s32> linerect(el.pos.X + offset.X,
957 linepos - (el.baseline >> 3) - 1,
958 el.pos.X + offset.X + el.drawwidth,
959 linepos + (el.baseline >> 3));
961 driver->draw2DRectangle(color, linerect, &clip_rect);
965 case ParsedText::ELEMENT_IMAGE: {
966 video::ITexture *texture =
967 m_client->getTextureSource()->getTexture(
968 stringw_to_utf8(el.text));
970 m_environment->getVideoDriver()->draw2DImage(
972 irr::core::rect<s32>(
973 core::position2d<s32>(0, 0),
974 texture->getOriginalSize()),
975 &clip_rect, 0, true);
978 case ParsedText::ELEMENT_ITEM: {
979 IItemDefManager *idef = m_client->idef();
981 item.deSerialize(stringw_to_utf8(el.text), idef);
984 m_environment->getVideoDriver(),
985 g_fontengine->getFont(), item, rect, &clip_rect,
986 m_client, IT_ROT_OTHER, el.angle, el.rotation
994 // -----------------------------------------------------------------------------
995 // GUIHyperText - The formated text area formspec item
998 GUIHyperText::GUIHyperText(const wchar_t *text, IGUIEnvironment *environment,
999 IGUIElement *parent, s32 id, const core::rect<s32> &rectangle,
1000 Client *client, ISimpleTextureSource *tsrc) :
1001 IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle),
1002 m_client(client), m_vscrollbar(nullptr),
1003 m_drawer(text, client, environment, tsrc), m_text_scrollpos(0, 0)
1007 setDebugName("GUIHyperText");
1012 skin = Environment->getSkin();
1014 m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16;
1016 core::rect<s32> rect = irr::core::rect<s32>(
1017 RelativeRect.getWidth() - m_scrollbar_width, 0,
1018 RelativeRect.getWidth(), RelativeRect.getHeight());
1020 m_vscrollbar = new GUIScrollBar(Environment, this, -1, rect, false, true);
1021 m_vscrollbar->setVisible(false);
1025 GUIHyperText::~GUIHyperText()
1027 m_vscrollbar->remove();
1028 m_vscrollbar->drop();
1031 ParsedText::Element *GUIHyperText::getElementAt(s32 X, s32 Y)
1033 core::position2d<s32> pos{X, Y};
1034 pos -= m_display_text_rect.UpperLeftCorner;
1035 pos -= m_text_scrollpos;
1036 return m_drawer.getElementAt(pos);
1039 void GUIHyperText::checkHover(s32 X, s32 Y)
1041 m_drawer.m_hovertag = nullptr;
1043 if (AbsoluteRect.isPointInside(core::position2d<s32>(X, Y))) {
1044 ParsedText::Element *element = getElementAt(X, Y);
1047 for (auto &tag : element->tags) {
1048 if (tag->name == "action") {
1049 m_drawer.m_hovertag = tag;
1056 #ifndef HAVE_TOUCHSCREENGUI
1057 if (m_drawer.m_hovertag)
1058 RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
1061 RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
1066 bool GUIHyperText::OnEvent(const SEvent &event)
1069 if (event.EventType == EET_GUI_EVENT &&
1070 event.GUIEvent.EventType == EGET_SCROLL_BAR_CHANGED &&
1071 event.GUIEvent.Caller == m_vscrollbar) {
1072 m_text_scrollpos.Y = -m_vscrollbar->getPos();
1075 // Reset hover if element left
1076 if (event.EventType == EET_GUI_EVENT &&
1077 event.GUIEvent.EventType == EGET_ELEMENT_LEFT) {
1078 m_drawer.m_hovertag = nullptr;
1079 #ifndef HAVE_TOUCHSCREENGUI
1080 gui::ICursorControl *cursor_control =
1081 RenderingEngine::get_raw_device()->getCursorControl();
1082 if (cursor_control->isVisible())
1083 cursor_control->setActiveIcon(gui::ECI_NORMAL);
1087 if (event.EventType == EET_MOUSE_INPUT_EVENT) {
1088 if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
1089 checkHover(event.MouseInput.X, event.MouseInput.Y);
1091 if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
1092 m_vscrollbar->setPos(m_vscrollbar->getPos() -
1093 event.MouseInput.Wheel * m_vscrollbar->getSmallStep());
1094 m_text_scrollpos.Y = -m_vscrollbar->getPos();
1095 m_drawer.draw(m_display_text_rect, m_text_scrollpos);
1096 checkHover(event.MouseInput.X, event.MouseInput.Y);
1099 } else if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
1100 ParsedText::Element *element = getElementAt(
1101 event.MouseInput.X, event.MouseInput.Y);
1104 for (auto &tag : element->tags) {
1105 if (tag->name == "action") {
1106 Text = core::stringw(L"action:") +
1107 utf8_to_stringw(tag->attrs["name"]);
1110 newEvent.EventType = EET_GUI_EVENT;
1111 newEvent.GUIEvent.Caller = this;
1112 newEvent.GUIEvent.Element = 0;
1113 newEvent.GUIEvent.EventType = EGET_BUTTON_CLICKED;
1114 Parent->OnEvent(newEvent);
1123 return IGUIElement::OnEvent(event);
1126 //! draws the element and its children
1127 void GUIHyperText::draw()
1133 m_display_text_rect = AbsoluteRect;
1134 m_drawer.place(m_display_text_rect);
1136 // Show scrollbar if text overflow
1137 if (m_drawer.getHeight() > m_display_text_rect.getHeight()) {
1138 m_vscrollbar->setSmallStep(m_display_text_rect.getHeight() * 0.1f);
1139 m_vscrollbar->setLargeStep(m_display_text_rect.getHeight() * 0.5f);
1140 m_vscrollbar->setMax(m_drawer.getHeight() - m_display_text_rect.getHeight());
1142 m_vscrollbar->setVisible(true);
1144 m_vscrollbar->setPageSize(s32(m_drawer.getHeight()));
1146 core::rect<s32> smaller_rect = m_display_text_rect;
1148 smaller_rect.LowerRightCorner.X -= m_scrollbar_width;
1149 m_drawer.place(smaller_rect);
1151 m_vscrollbar->setMax(0);
1152 m_vscrollbar->setPos(0);
1153 m_vscrollbar->setVisible(false);
1155 m_drawer.draw(AbsoluteClippingRect,
1156 m_display_text_rect.UpperLeftCorner + m_text_scrollpos);
1159 IGUIElement::draw();