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"] = m_root_tag.style["color"];
112 m_tags.push_back(&m_root_tag);
113 m_active_tags.push_front(&m_root_tag);
114 m_style = m_root_tag.style;
116 // Default simple tags definitions
119 style["hovercolor"] = "#FF0000";
120 style["color"] = "#0000FF";
121 style["underline"] = "true";
122 m_elementtags["action"] = style;
125 style["bold"] = "true";
126 m_elementtags["b"] = style;
129 style["italic"] = "true";
130 m_elementtags["i"] = style;
133 style["underline"] = "true";
134 m_elementtags["u"] = style;
137 style["fontstyle"] = "mono";
138 m_elementtags["mono"] = style;
141 style["fontsize"] = m_root_tag.style["fontsize"];
142 m_elementtags["normal"] = style;
145 style["fontsize"] = "24";
146 m_elementtags["big"] = style;
149 style["fontsize"] = "36";
150 m_elementtags["bigger"] = style;
153 style["halign"] = "center";
154 m_paragraphtags["center"] = style;
157 style["halign"] = "justify";
158 m_paragraphtags["justify"] = style;
161 style["halign"] = "left";
162 m_paragraphtags["left"] = style;
165 style["halign"] = "right";
166 m_paragraphtags["right"] = style;
175 ParsedText::~ParsedText()
177 for (auto &tag : m_tags)
181 void ParsedText::parse(const wchar_t *text)
187 while ((c = text[cursor]) != L'\0') {
190 if (c == L'\r') { // Mac or Windows breaks
191 if (text[cursor] == L'\n')
193 // If text has begun, don't skip empty line
196 enterElement(ELEMENT_SEPARATOR);
202 if (c == L'\n') { // Unix breaks
203 // If text has begun, don't skip empty line
206 enterElement(ELEMENT_SEPARATOR);
225 u32 newcursor = parseTag(text, cursor);
239 void ParsedText::endElement()
244 void ParsedText::endParagraph()
253 void ParsedText::enterParagraph()
256 m_paragraphs.emplace_back();
257 m_paragraph = &m_paragraphs.back();
258 m_paragraph->setStyle(m_style);
262 void ParsedText::enterElement(ElementType type)
266 if (!m_element || m_element->type != type) {
267 m_paragraph->elements.emplace_back();
268 m_element = &m_paragraph->elements.back();
269 m_element->type = type;
270 m_element->tags = m_active_tags;
271 m_element->setStyle(m_style);
275 void ParsedText::pushChar(wchar_t c)
277 // New word if needed
278 if (c == L' ' || c == L'\t')
279 enterElement(ELEMENT_SEPARATOR);
281 enterElement(ELEMENT_TEXT);
283 m_element->text += c;
286 ParsedText::Tag *ParsedText::newTag(const std::string &name, const AttrsList &attrs)
289 Tag *newtag = new Tag();
291 newtag->attrs = attrs;
292 m_tags.push_back(newtag);
296 ParsedText::Tag *ParsedText::openTag(const std::string &name, const AttrsList &attrs)
298 Tag *newtag = newTag(name, attrs);
299 m_active_tags.push_front(newtag);
303 bool ParsedText::closeTag(const std::string &name)
306 for (auto id = m_active_tags.begin(); id != m_active_tags.end(); ++id)
307 if ((*id)->name == name) {
308 m_active_tags.erase(id);
315 void ParsedText::parseGenericStyleAttr(
316 const std::string &name, const std::string &value, StyleList &style)
319 if (name == "color" || name == "hovercolor") {
320 if (check_color(value))
324 } else if (name == "bold" || name == "italic" || name == "underline") {
325 style[name] = is_yes(value);
327 } else if (name == "size") {
328 if (check_integer(value))
329 style["fontsize"] = value;
331 } else if (name == "font") {
332 if (value == "mono" || value == "normal")
333 style["fontstyle"] = value;
337 void ParsedText::parseStyles(const AttrsList &attrs, StyleList &style)
339 for (auto const &attr : attrs)
340 parseGenericStyleAttr(attr.first, attr.second, style);
343 void ParsedText::globalTag(const AttrsList &attrs)
345 for (const auto &attr : attrs) {
346 // Only page level style
347 if (attr.first == "margin") {
348 if (check_integer(attr.second))
349 margin = stoi(attr.second.c_str());
351 } else if (attr.first == "valign") {
352 if (attr.second == "top")
353 valign = ParsedText::VALIGN_TOP;
354 else if (attr.second == "bottom")
355 valign = ParsedText::VALIGN_BOTTOM;
356 else if (attr.second == "middle")
357 valign = ParsedText::VALIGN_MIDDLE;
358 } else if (attr.first == "background") {
359 irr::video::SColor color;
360 if (attr.second == "none") {
361 background_type = BACKGROUND_NONE;
362 } else if (parseColorString(attr.second, color, false)) {
363 background_type = BACKGROUND_COLOR;
364 background_color = color;
369 } else if (attr.first == "halign") {
370 if (attr.second == "left" || attr.second == "center" ||
371 attr.second == "right" ||
372 attr.second == "justify")
373 m_root_tag.style["halign"] = attr.second;
375 // Generic default styles
378 parseGenericStyleAttr(attr.first, attr.second, m_root_tag.style);
383 u32 ParsedText::parseTag(const wchar_t *text, u32 cursor)
387 std::string name = "";
388 wchar_t c = text[cursor];
397 while (c != ' ' && c != '>') {
407 std::string attr_name = "";
408 std::string attr_val = "";
412 if (c == L'\0' || c == L'=')
416 while (c != L' ' && c != L'=') {
417 attr_name += (char)c;
419 if (c == L'\0' || c == L'>')
425 if (c == L'\0' || c == L'>')
437 while (c != L'>' && c != L' ') {
444 attrs[attr_name] = attr_val;
447 ++cursor; // Last ">"
449 // Tag specific processing
452 if (name == "global") {
457 } else if (name == "style") {
461 parseStyles(attrs, style);
462 openTag(name, attrs)->style = style;
465 } else if (name == "img" || name == "item") {
469 // Name is a required attribute
470 if (!attrs.count("name"))
473 // Rotate attribute is only for <item>
474 if (attrs.count("rotate") && name != "item")
477 // Angle attribute is only for <item>
478 if (attrs.count("angle") && name != "item")
481 // Ok, element can be created
485 enterElement(ELEMENT_IMAGE);
487 enterElement(ELEMENT_ITEM);
489 m_element->text = strtostrw(attrs["name"]);
491 if (attrs.count("float")) {
492 if (attrs["float"] == "left")
493 m_element->floating = FLOAT_LEFT;
494 if (attrs["float"] == "right")
495 m_element->floating = FLOAT_RIGHT;
498 if (attrs.count("width")) {
499 int width = stoi(attrs["width"]);
501 m_element->dim.Width = width;
504 if (attrs.count("height")) {
505 int height = stoi(attrs["height"]);
507 m_element->dim.Height = height;
510 if (attrs.count("angle")) {
511 std::string str = attrs["angle"];
512 std::vector<std::string> parts = split(str, ',');
513 if (parts.size() == 3) {
514 m_element->angle = v3s16(
515 rangelim(stoi(parts[0]), -180, 180),
516 rangelim(stoi(parts[1]), -180, 180),
517 rangelim(stoi(parts[2]), -180, 180));
518 m_element->rotation = v3s16(0, 0, 0);
522 if (attrs.count("rotate")) {
523 if (attrs["rotate"] == "yes") {
524 m_element->rotation = v3s16(0, 100, 0);
526 std::string str = attrs["rotate"];
527 std::vector<std::string> parts = split(str, ',');
528 if (parts.size() == 3) {
529 m_element->rotation = v3s16 (
530 rangelim(stoi(parts[0]), -1000, 1000),
531 rangelim(stoi(parts[1]), -1000, 1000),
532 rangelim(stoi(parts[2]), -1000, 1000));
539 } else if (name == "tag") {
540 // Required attributes
541 if (!attrs.count("name"))
545 parseStyles(attrs, tagstyle);
547 if (is_yes(attrs["paragraph"]))
548 m_paragraphtags[attrs["name"]] = tagstyle;
550 m_elementtags[attrs["name"]] = tagstyle;
552 } else if (name == "action") {
556 if (!attrs.count("name"))
558 openTag(name, attrs)->style = m_elementtags["action"];
561 } else if (m_elementtags.count(name)) {
565 openTag(name, attrs)->style = m_elementtags[name];
569 } else if (m_paragraphtags.count(name)) {
573 openTag(name, attrs)->style = m_paragraphtags[name];
578 return 0; // Unknown tag
580 // Update styles accordingly
582 for (auto tag = m_active_tags.crbegin(); tag != m_active_tags.crend(); ++tag)
583 for (const auto &prop : (*tag)->style)
584 m_style[prop.first] = prop.second;
589 // -----------------------------------------------------------------------------
592 TextDrawer::TextDrawer(const wchar_t *text, Client *client,
593 gui::IGUIEnvironment *environment, ISimpleTextureSource *tsrc) :
595 m_client(client), m_environment(environment)
598 for (auto &p : m_text.m_paragraphs) {
599 for (auto &e : p.elements) {
601 case ParsedText::ELEMENT_SEPARATOR:
602 case ParsedText::ELEMENT_TEXT:
604 e.dim.Width = e.font->getDimension(e.text.c_str()).Width;
605 e.dim.Height = e.font->getDimension(L"Yy").Height;
607 if (e.font->getType() == irr::gui::EGFT_CUSTOM) {
608 e.baseline = e.dim.Height - 1 -
609 ((irr::gui::CGUITTFont *)e.font)->getAscender() / 64;
617 case ParsedText::ELEMENT_IMAGE:
618 case ParsedText::ELEMENT_ITEM:
619 // Resize only non sized items
620 if (e.dim.Height != 0 && e.dim.Width != 0)
623 // Default image and item size
624 core::dimension2d<u32> dim(80, 80);
626 if (e.type == ParsedText::ELEMENT_IMAGE) {
627 video::ITexture *texture =
628 m_client->getTextureSource()->
629 getTexture(strwtostr(e.text));
631 dim = texture->getOriginalSize();
634 if (e.dim.Height == 0)
635 if (e.dim.Width == 0)
638 e.dim.Height = dim.Height * e.dim.Width /
641 e.dim.Width = dim.Width * e.dim.Height /
649 // Get element at given coordinates. Coordinates are inner coordinates (starting
651 ParsedText::Element *TextDrawer::getElementAt(core::position2d<s32> pos)
654 for (auto &p : m_text.m_paragraphs) {
655 for (auto &el : p.elements) {
656 core::rect<s32> rect(el.pos, el.dim);
657 if (rect.isPointInside(pos))
665 This function places all elements according to given width. Elements have
666 been previously sized by constructor and will be later drawed by draw.
667 It may be called each time width changes and resulting height can be
668 retrieved using getHeight. See GUIHyperText constructor, it uses it once to
669 test if text fits in window and eventually another time if width is reduced
670 m_floating because of scrollbar added.
672 void TextDrawer::place(const core::rect<s32> &dest_rect)
676 s32 ymargin = m_text.margin;
679 // p - Current paragraph, walked only once
680 // el - Current element, walked only once
681 // e and f - local element and floating operators
683 for (auto &p : m_text.m_paragraphs) {
684 // Find and place floating stuff in paragraph
685 for (auto e = p.elements.begin(); e != p.elements.end(); ++e) {
686 if (e->floating != ParsedText::FLOAT_NONE) {
688 e->pos.Y = y + std::max(ymargin, e->margin);
692 if (e->floating == ParsedText::FLOAT_LEFT)
693 e->pos.X = m_text.margin;
694 if (e->floating == ParsedText::FLOAT_RIGHT)
695 e->pos.X = dest_rect.getWidth() - e->dim.Width -
698 RectWithMargin floating;
699 floating.rect = core::rect<s32>(e->pos, e->dim);
700 floating.margin = e->margin;
702 m_floating.push_back(floating);
707 y = y + std::max(ymargin, p.margin);
711 // Place non floating stuff
712 std::vector<ParsedText::Element>::iterator el = p.elements.begin();
714 while (el != p.elements.end()) {
715 // Determine line width and y pos
722 // Inner left & right
723 left = m_text.margin;
724 right = dest_rect.getWidth() - m_text.margin;
726 for (const auto &f : m_floating) {
727 // Does floating rect intersect paragraph y line?
728 if (f.rect.UpperLeftCorner.Y - f.margin <= y &&
729 f.rect.LowerRightCorner.Y + f.margin >= y) {
731 // Next Y to try if no room left
732 if (!nexty || f.rect.LowerRightCorner.Y +
733 std::max(f.margin, p.margin) < nexty) {
734 nexty = f.rect.LowerRightCorner.Y +
735 std::max(f.margin, p.margin) + 1;
738 if (f.rect.UpperLeftCorner.X - f.margin <= left &&
739 f.rect.LowerRightCorner.X + f.margin < right) {
741 if (f.rect.LowerRightCorner.X +
742 std::max(f.margin, p.margin) > left) {
743 left = f.rect.LowerRightCorner.X +
744 std::max(f.margin, p.margin);
746 } else if (f.rect.LowerRightCorner.X + f.margin >= right &&
747 f.rect.UpperLeftCorner.X - f.margin > left) {
749 if (f.rect.UpperLeftCorner.X -
750 std::max(f.margin, p.margin) < right)
751 right = f.rect.UpperLeftCorner.X -
752 std::max(f.margin, p.margin);
754 } else if (f.rect.UpperLeftCorner.X - f.margin <= left &&
755 f.rect.LowerRightCorner.X + f.margin >= right) {
756 // float taking all space
760 { // float in the middle -- should not occure yet, see that later
764 } while (nexty && right <= left);
766 u32 linewidth = right - left;
773 // Skip begining of line separators but include them in height
775 while (el != p.elements.end() &&
776 el->type == ParsedText::ELEMENT_SEPARATOR) {
777 if (el->floating == ParsedText::FLOAT_NONE) {
779 if (charsheight < el->dim.Height)
780 charsheight = el->dim.Height;
785 std::vector<ParsedText::Element>::iterator linestart = el;
786 std::vector<ParsedText::Element>::iterator lineend = p.elements.end();
788 // First pass, find elements fitting into line
789 // (or at least one element)
790 while (el != p.elements.end() && (charswidth == 0 ||
791 charswidth + el->dim.Width <= linewidth)) {
792 if (el->floating == ParsedText::FLOAT_NONE) {
793 if (el->type != ParsedText::ELEMENT_SEPARATOR) {
797 charswidth += el->dim.Width;
798 if (charsheight < el->dim.Height)
799 charsheight = el->dim.Height;
804 // Empty line, nothing to place only go down line height
805 if (lineend == p.elements.end()) {
810 // Point to the first position outside line (may be end())
813 // Second pass, compute printable line width and adjustments
817 for (auto e = linestart; e != lineend; ++e) {
818 if (e->floating == ParsedText::FLOAT_NONE) {
819 charswidth += e->dim.Width;
820 if (top < (s32)e->dim.Height - e->baseline)
821 top = e->dim.Height - e->baseline;
822 if (bottom < e->baseline)
823 bottom = e->baseline;
827 float extraspace = 0.f;
830 case ParsedText::HALIGN_CENTER:
831 x += (linewidth - charswidth) / 2.f;
833 case ParsedText::HALIGN_JUSTIFY:
834 if (wordcount > 1 && // Justification only if at least two words
835 !(lineend == p.elements.end())) // Don't justify last line
836 extraspace = ((float)(linewidth - charswidth)) / (wordcount - 1);
838 case ParsedText::HALIGN_RIGHT:
839 x += linewidth - charswidth;
841 case ParsedText::HALIGN_LEFT:
845 // Third pass, actually place everything
846 for (auto e = linestart; e != lineend; ++e) {
847 if (e->floating != ParsedText::FLOAT_NONE)
854 case ParsedText::ELEMENT_TEXT:
855 case ParsedText::ELEMENT_SEPARATOR:
858 // Align char baselines
859 e->pos.Y = y + top + e->baseline - e->dim.Height;
862 if (e->type == ParsedText::ELEMENT_SEPARATOR)
866 case ParsedText::ELEMENT_IMAGE:
867 case ParsedText::ELEMENT_ITEM:
872 // Draw width for separator can be different than element
873 // width. This will be important for char effects like
875 e->drawwidth = x - e->pos.X;
878 } // Elements (actually lines)
881 // Check if float goes under paragraph
882 for (const auto &f : m_floating) {
883 if (f.rect.LowerRightCorner.Y >= y)
884 y = f.rect.LowerRightCorner.Y;
887 m_height = y + m_text.margin;
888 // Compute vertical offset according to vertical alignment
889 if (m_height < dest_rect.getHeight())
890 switch (m_text.valign) {
891 case ParsedText::VALIGN_BOTTOM:
892 m_voffset = dest_rect.getHeight() - m_height;
894 case ParsedText::VALIGN_MIDDLE:
895 m_voffset = (dest_rect.getHeight() - m_height) / 2;
897 case ParsedText::VALIGN_TOP:
905 // Draw text in a rectangle with a given offset. Items are actually placed in
906 // relative (to upper left corner) coordinates.
907 void TextDrawer::draw(const core::rect<s32> &dest_rect,
908 const core::position2d<s32> &dest_offset)
910 irr::video::IVideoDriver *driver = m_environment->getVideoDriver();
911 core::position2d<s32> offset = dest_rect.UpperLeftCorner + dest_offset;
912 offset.Y += m_voffset;
914 if (m_text.background_type == ParsedText::BACKGROUND_COLOR)
915 driver->draw2DRectangle(m_text.background_color, dest_rect);
917 for (auto &p : m_text.m_paragraphs) {
918 for (auto &el : p.elements) {
919 core::rect<s32> rect(el.pos + offset, el.dim);
920 if (!rect.isRectCollided(dest_rect))
924 case ParsedText::ELEMENT_SEPARATOR:
925 case ParsedText::ELEMENT_TEXT: {
926 irr::video::SColor color = el.color;
928 for (auto tag : el.tags)
929 if (&(*tag) == m_hovertag)
930 color = el.hovercolor;
935 if (el.type == ParsedText::ELEMENT_TEXT)
936 el.font->draw(el.text, rect, color, false, true,
939 if (el.underline && el.drawwidth) {
940 s32 linepos = el.pos.Y + offset.Y +
941 el.dim.Height - (el.baseline >> 1);
943 core::rect<s32> linerect(el.pos.X + offset.X,
944 linepos - (el.baseline >> 3) - 1,
945 el.pos.X + offset.X + el.drawwidth,
946 linepos + (el.baseline >> 3));
948 driver->draw2DRectangle(color, linerect, &dest_rect);
952 case ParsedText::ELEMENT_IMAGE: {
953 video::ITexture *texture =
954 m_client->getTextureSource()->getTexture(
957 m_environment->getVideoDriver()->draw2DImage(
959 irr::core::rect<s32>(
960 core::position2d<s32>(0, 0),
961 texture->getOriginalSize()),
962 &dest_rect, 0, true);
965 case ParsedText::ELEMENT_ITEM: {
966 IItemDefManager *idef = m_client->idef();
968 item.deSerialize(strwtostr(el.text), idef);
971 m_environment->getVideoDriver(),
972 g_fontengine->getFont(), item, rect, &dest_rect,
973 m_client, IT_ROT_OTHER, el.angle, el.rotation
981 // -----------------------------------------------------------------------------
982 // GUIHyperText - The formated text area formspec item
985 GUIHyperText::GUIHyperText(const wchar_t *text, IGUIEnvironment *environment,
986 IGUIElement *parent, s32 id, const core::rect<s32> &rectangle,
987 Client *client, ISimpleTextureSource *tsrc) :
988 IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle),
989 m_client(client), m_vscrollbar(nullptr),
990 m_drawer(text, client, environment, tsrc), m_text_scrollpos(0, 0)
994 setDebugName("GUIHyperText");
999 skin = Environment->getSkin();
1001 m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16;
1003 core::rect<s32> rect = irr::core::rect<s32>(
1004 RelativeRect.getWidth() - m_scrollbar_width, 0,
1005 RelativeRect.getWidth(), RelativeRect.getHeight());
1007 m_vscrollbar = new GUIScrollBar(Environment, this, -1, rect, false, true);
1008 m_vscrollbar->setVisible(false);
1012 GUIHyperText::~GUIHyperText()
1014 m_vscrollbar->remove();
1017 ParsedText::Element *GUIHyperText::getElementAt(s32 X, s32 Y)
1019 core::position2d<s32> pos{X, Y};
1020 pos -= m_display_text_rect.UpperLeftCorner;
1021 pos -= m_text_scrollpos;
1022 return m_drawer.getElementAt(pos);
1025 void GUIHyperText::checkHover(s32 X, s32 Y)
1027 m_drawer.m_hovertag = nullptr;
1029 if (AbsoluteRect.isPointInside(core::position2d<s32>(X, Y))) {
1030 ParsedText::Element *element = getElementAt(X, Y);
1033 for (auto &tag : element->tags) {
1034 if (tag->name == "action") {
1035 m_drawer.m_hovertag = tag;
1042 if (m_drawer.m_hovertag)
1043 RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
1046 RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
1050 bool GUIHyperText::OnEvent(const SEvent &event)
1053 if (event.EventType == EET_GUI_EVENT &&
1054 event.GUIEvent.EventType == EGET_SCROLL_BAR_CHANGED &&
1055 event.GUIEvent.Caller == m_vscrollbar) {
1056 m_text_scrollpos.Y = -m_vscrollbar->getPos();
1059 // Reset hover if element left
1060 if (event.EventType == EET_GUI_EVENT &&
1061 event.GUIEvent.EventType == EGET_ELEMENT_LEFT) {
1062 m_drawer.m_hovertag = nullptr;
1063 RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
1067 if (event.EventType == EET_MOUSE_INPUT_EVENT) {
1068 if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
1069 checkHover(event.MouseInput.X, event.MouseInput.Y);
1071 if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
1072 m_vscrollbar->setPos(m_vscrollbar->getPos() -
1073 event.MouseInput.Wheel * m_vscrollbar->getSmallStep());
1074 m_text_scrollpos.Y = -m_vscrollbar->getPos();
1075 m_drawer.draw(m_display_text_rect, m_text_scrollpos);
1076 checkHover(event.MouseInput.X, event.MouseInput.Y);
1078 } else if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
1079 ParsedText::Element *element = getElementAt(
1080 event.MouseInput.X, event.MouseInput.Y);
1083 for (auto &tag : element->tags) {
1084 if (tag->name == "action") {
1085 Text = core::stringw(L"action:") +
1086 strtostrw(tag->attrs["name"]);
1089 newEvent.EventType = EET_GUI_EVENT;
1090 newEvent.GUIEvent.Caller = this;
1091 newEvent.GUIEvent.Element = 0;
1092 newEvent.GUIEvent.EventType = EGET_BUTTON_CLICKED;
1093 Parent->OnEvent(newEvent);
1102 return IGUIElement::OnEvent(event);
1105 //! draws the element and its children
1106 void GUIHyperText::draw()
1112 m_display_text_rect = AbsoluteRect;
1113 m_drawer.place(m_display_text_rect);
1115 // Show scrollbar if text overflow
1116 if (m_drawer.getHeight() > m_display_text_rect.getHeight()) {
1117 m_vscrollbar->setSmallStep(m_display_text_rect.getHeight() * 0.1f);
1118 m_vscrollbar->setLargeStep(m_display_text_rect.getHeight() * 0.5f);
1119 m_vscrollbar->setMax(m_drawer.getHeight() - m_display_text_rect.getHeight());
1121 m_vscrollbar->setVisible(true);
1123 m_vscrollbar->setPageSize(s32(m_drawer.getHeight()));
1125 core::rect<s32> smaller_rect = m_display_text_rect;
1127 smaller_rect.LowerRightCorner.X -= m_scrollbar_width;
1128 m_drawer.place(smaller_rect);
1130 m_vscrollbar->setMax(0);
1131 m_vscrollbar->setPos(0);
1132 m_vscrollbar->setVisible(false);
1134 m_drawer.draw(m_display_text_rect, m_text_scrollpos);
1137 IGUIElement::draw();