diff --git a/doc/lua_api.md b/doc/lua_api.md index fde2e726f..5a51673a0 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -2849,6 +2849,16 @@ Elements * `bgcolor` tooltip background color as `ColorString` (optional) * `fontcolor` tooltip font color as `ColorString` (optional) +### `supertip[,;,;,;;;]` + +* Displays a formatted text using `Markup Language` in a tooltip. +* `x`, `y`, `w` and `h` set the mouse hover area that allows the tooltip to pop-up. +* `posX` and `posY` set the static positioning of the tooltip (optional). + If not set, the tooltip is floating (moving with the pointer). +* `width` sets the tooltip width. +* `name` is the name of the field. +* `text` is the formatted text using `Markup Language` described below. + ### `image[,;,;;]` * Show an image. @@ -3412,6 +3422,7 @@ Some types may inherit styles from parent types. * model * pwdfield, inherits from field * scrollbar +* supertip * tabheader * table * textarea @@ -3512,6 +3523,13 @@ Some types may inherit styles from parent types. * sound - a sound to be played when triggered. * scrollbar * noclip - boolean, set to true to allow the element to exceed formspec bounds. +* supertip + * noclip - boolean, set to true to allow the element to exceed formspec bounds. + * bgcolor - color, sets background color. + * border - boolean, draw border. Set to false to hide the bevelled tooltip pane. Default false. + * bgimg - standard background image. Defaults to none. + * bgimg_middle - Makes the bgimg textures render in 9-sliced mode and defines the middle rect. + See background9[] documentation for more details. * tabheader * noclip - boolean, set to true to allow the element to exceed formspec bounds. * sound - a sound to be played when a different tab is selected. diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 2e54b32dc..91cc0210b 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -65,7 +65,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiInventoryList.h" #include "guiItemImage.h" #include "guiScrollContainer.h" -#include "guiHyperText.h" #include "guiScene.h" #define MY_CHECKPOS(a,b) \ @@ -1755,6 +1754,77 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen m_fields.push_back(spec); } +void GUIFormSpecMenu::parseSuperTip(parserData *data, const std::string &element) +{ + constexpr char max_parts = 6; + std::vector parts; + + if (!precheckElement("supertip", element, 5, max_parts, parts)) + return; + + std::vector v_stpos; + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); + bool floating = parts.size() == max_parts - 1; + + if (!floating) + v_stpos = split(parts[2], ','); + + const char i = max_parts - parts.size(); + s32 width = stof(parts[3-i]) * spacing.Y; + std::string name = parts[4-i]; + std::string text = parts[5-i]; + + MY_CHECKPOS("supertip", 0); + MY_CHECKGEOM("supertip", 1); + MY_CHECKPOS("supertip", 2); + + v2s32 pos; + v2s32 geom; + v2s32 stpos; + + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + + if (!floating) + stpos = getRealCoordinateBasePos(v_stpos); + } else { + pos = getElementBasePos(&v_pos); + geom.X = stof(v_geom[0]) * spacing.X; + geom.Y = stof(v_geom[1]) * spacing.Y; + + if (!floating) + stpos = getElementBasePos(&v_stpos); + } + + core::rect rect(pos, pos + geom); + + if (m_form_src) + text = m_form_src->resolveText(text); + + FieldSpec spec( + name, + translate_string(utf8_to_wide(unescape_string(text))), + L"", + 258 + m_fields.size() + ); + + GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, + data->current_parent, spec.fid, rect, m_client, m_tsrc); + + auto style = getStyleForElement("supertip", spec.fname); + e->setStyles(style); + + SuperTipSpec geospec(e->getAbsoluteClippingRect(), stpos, width, floating); + + m_fields.push_back(spec); + m_supertips.emplace_back(e, geospec); + + e->setVisible(false); + e->drop(); +} + void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) { std::vector parts; @@ -2958,6 +3028,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) return; } + if (type == "supertip") { + parseSuperTip(data,description); + return; + } + if (type == "label") { parseLabel(data,description); return; @@ -3110,6 +3185,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) m_scrollbars.clear(); m_fields.clear(); m_tooltips.clear(); + m_supertips.clear(); m_tooltip_rects.clear(); m_inventory_rings.clear(); m_dropdowns.clear(); @@ -3639,6 +3715,17 @@ void GUIFormSpecMenu::drawMenu() } } + /* + Draw supertip + */ + for (const auto &pair : m_supertips) { + const auto &hover_rect = pair.second.hover_rect; + if (hover_rect.getArea() > 0 && hover_rect.isPointInside(m_pointer)) + showSuperTip(pair.first, pair.second); + else + pair.first->setVisible(false); + } + // Some elements are only visible while being drawn for (gui::IGUIElement *e : m_clickthrough_elements) e->setVisible(true); @@ -3797,6 +3884,39 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text, bringToFront(m_tooltip_element); } +void GUIFormSpecMenu::showSuperTip(GUIHyperText *e, const SuperTipSpec &spec) +{ + // Supertip size and offset + s32 W = spec.width; + s32 H = e->getTextHeight(); + s32 X,Y; + + if (spec.floating) { + /* Issue with floating tooltips' positioning here. + Set hardcoded values that only works on 16:10/4K displays. */ + X = m_pointer.X - 1120; + Y = m_pointer.Y - 400; + } else { + /* Static tooltips */ + X = spec.stpos[0]; + Y = spec.stpos[1]; + } + + v2u32 screenSize = Environment->getVideoDriver()->getScreenSize(); + + if (X + W > (s32)screenSize.X) + X = (s32)screenSize.X - W - m_btn_height; + if (Y + H > (s32)screenSize.Y) + Y = (s32)screenSize.Y - H - m_btn_height; + + e->setRelativePosition(core::rect(core::position2d(X,Y), core::dimension2d(W,H))); + + // Display the supertip + e->setVisible(true); + bringToFront(e); +} + + void GUIFormSpecMenu::updateSelectedItem() { // Don't update when dragging an item diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 616971bc6..1296aacd9 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiInventoryList.h" #include "guiScrollBar.h" #include "guiTable.h" +#include "guiHyperText.h" #include "network/networkprotocol.h" #include "client/joystick_controller.h" #include "util/string.h" @@ -165,6 +166,24 @@ class GUIFormSpecMenu : public GUIModalMenu irr::video::SColor color; }; + struct SuperTipSpec + { + SuperTipSpec() = default; + SuperTipSpec(const core::rect &a_rect, v2s32 a_stpos, s32 a_width, + bool a_floating) : + hover_rect(a_rect), + stpos(a_stpos), + width(a_width), + floating(a_floating) + { + } + + core::rect hover_rect; + v2s32 stpos; + s32 width; + bool floating; + }; + public: GUIFormSpecMenu(JoystickController *joystick, gui::IGUIElement* parent, s32 id, @@ -343,6 +362,7 @@ class GUIFormSpecMenu : public GUIModalMenu std::vector> m_tables; std::vector> m_checkboxes; std::map m_tooltips; + std::vector> m_supertips; std::vector> m_tooltip_rects; std::vector> m_scrollbars; std::vector>> m_dropdowns; @@ -464,6 +484,7 @@ class GUIFormSpecMenu : public GUIModalMenu void parseTextArea(parserData* data,std::vector& parts, const std::string &type); void parseHyperText(parserData *data, const std::string &element); + void parseSuperTip(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, @@ -494,6 +515,7 @@ class GUIFormSpecMenu : public GUIModalMenu void showTooltip(const std::wstring &text, const irr::video::SColor &color, const irr::video::SColor &bgcolor); + void showSuperTip(GUIHyperText *e, const SuperTipSpec &spec); /** * In formspec version < 2 the elements were not ordered properly. Some element diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp index 45e5d6e98..3be56bfa2 100644 --- a/src/gui/guiHyperText.cpp +++ b/src/gui/guiHyperText.cpp @@ -29,12 +29,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlicht_changes/CGUITTFont.h" #include "mainmenumanager.h" #include "porting.h" - -using namespace irr::gui; +#include "client/guiscalingfilter.h" static bool check_color(const std::string &str) { - irr::video::SColor color; + video::SColor color; return parseColorString(str, color, false); } @@ -372,7 +371,7 @@ void ParsedText::globalTag(const AttrsList &attrs) else if (attr.second == "middle") valign = ParsedText::VALIGN_MIDDLE; } else if (attr.first == "background") { - irr::video::SColor color; + video::SColor color; if (attr.second == "none") { background_type = BACKGROUND_NONE; } else if (parseColorString(attr.second, color, false)) { @@ -643,7 +642,7 @@ TextDrawer::TextDrawer(const wchar_t *text, Client *client, if (e.font) { e.dim.Width = e.font->getDimension(e.text.c_str()).Width; e.dim.Height = e.font->getDimension(L"Yy").Height; - if (e.font->getType() == irr::gui::EGFT_CUSTOM) { + if (e.font->getType() == gui::EGFT_CUSTOM) { CGUITTFont *tmp = static_cast(e.font); e.baseline = e.dim.Height - 1 - tmp->getAscender() / 64; } @@ -940,18 +939,49 @@ void TextDrawer::place(const core::rect &dest_rect) m_voffset = 0; } +void TextDrawer::drawBackgroundImage( + video::IVideoDriver *driver, const ParsedText &m_text, const core::rect &clip_rect) +{ + auto size = m_text.background_image->getOriginalSize(); + + if (m_text.background_middle.getArea() > 0) { + draw2DImage9Slice(driver, m_text.background_image, clip_rect, + core::rect(0, 0, size.Width, size.Height), m_text.background_middle); + } else { + const video::SColor color(255, 255, 255, 255); + const video::SColor colors[] = {color, color, color, color}; + + draw2DImageFilterScaled(driver, m_text.background_image, clip_rect, + core::rect(0, 0, size.Width, size.Height), nullptr, colors, true); + } +} + // 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 &clip_rect, const core::position2d &dest_offset) { - irr::video::IVideoDriver *driver = m_guienv->getVideoDriver(); + video::IVideoDriver *driver = m_guienv->getVideoDriver(); core::position2d offset = dest_offset; offset.Y += m_voffset; if (m_text.background_type == ParsedText::BACKGROUND_COLOR) driver->draw2DRectangle(m_text.background_color, clip_rect); + if (m_text.border) { + const video::SColor color(255,0,0,0); + const auto &UpperLeft = clip_rect.UpperLeftCorner; + const auto &LowerRight = clip_rect.LowerRightCorner; + + driver->draw2DLine(UpperLeft, core::position2di(LowerRight.X, UpperLeft.Y), color); + driver->draw2DLine(core::position2di(LowerRight.X, UpperLeft.Y), LowerRight, color); + driver->draw2DLine(LowerRight, core::position2di(UpperLeft.X, LowerRight.Y), color); + driver->draw2DLine(core::position2di(UpperLeft.X, LowerRight.Y), UpperLeft, color); + } + + if (m_text.background_image) + drawBackgroundImage(driver, m_text, clip_rect); + for (auto &p : m_text.m_paragraphs) { for (auto &el : p.elements) { core::rect rect(el.pos + offset, el.dim); @@ -961,7 +991,7 @@ void TextDrawer::draw(const core::rect &clip_rect, switch (el.type) { case ParsedText::ELEMENT_SEPARATOR: case ParsedText::ELEMENT_TEXT: { - irr::video::SColor color = el.color; + video::SColor color = el.color; for (auto tag : el.tags) if (&(*tag) == m_hovertag) @@ -992,9 +1022,9 @@ void TextDrawer::draw(const core::rect &clip_rect, m_tsrc->getTexture( stringw_to_utf8(el.text)); if (texture != 0) - m_guienv->getVideoDriver()->draw2DImage( + driver->draw2DImage( texture, rect, - irr::core::rect( + core::rect( core::position2d(0, 0), texture->getOriginalSize()), &clip_rect, 0, true); @@ -1006,7 +1036,7 @@ void TextDrawer::draw(const core::rect &clip_rect, ItemStack item; item.deSerialize(stringw_to_utf8(el.text), idef); - drawItemStack(m_guienv->getVideoDriver(), + drawItemStack(driver, g_fontengine->getFont(), item, rect, &clip_rect, m_client, IT_ROT_OTHER, el.angle, el.rotation); } @@ -1032,13 +1062,13 @@ GUIHyperText::GUIHyperText(const wchar_t *text, IGUIEnvironment *environment, setDebugName("GUIHyperText"); #endif - IGUISkin *skin = 0; + IGUISkin *skin = nullptr; if (Environment) skin = Environment->getSkin(); m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16; - core::rect rect = irr::core::rect( + core::rect rect = core::rect( RelativeRect.getWidth() - m_scrollbar_width, 0, RelativeRect.getWidth(), RelativeRect.getHeight()); @@ -1084,6 +1114,25 @@ void GUIHyperText::checkHover(s32 X, s32 Y) cursor_control->setActiveIcon(m_drawer.m_hovertag ? gui::ECI_HAND : gui::ECI_NORMAL); } +void GUIHyperText::setStyles(const std::array &styles) +{ + StyleSpec::State state = StyleSpec::STATE_DEFAULT; + StyleSpec style = StyleSpec::getStyleFromStatePropagation(styles, state); + + ParsedText &text = m_drawer.getText(); + text.background_middle = style.getRect(StyleSpec::BGIMG_MIDDLE, core::rect()); + text.border = style.getBool(StyleSpec::BORDER, false); + setNotClipped(style.getBool(StyleSpec::NOCLIP, true)); + + if (text.background_type != text.BackgroundType::BACKGROUND_COLOR) { + text.background_type = text.BackgroundType::BACKGROUND_COLOR; + text.background_color = style.getColor(StyleSpec::BGCOLOR, video::SColor(255,110,130,60)); + } + + if (style.isNotDefault(StyleSpec::BGIMG)) + text.background_image = style.getTexture(StyleSpec::BGIMG, m_tsrc); +} + bool GUIHyperText::OnEvent(const SEvent &event) { // Scroll bar diff --git a/src/gui/guiHyperText.h b/src/gui/guiHyperText.h index 0616a37ce..46d96d450 100644 --- a/src/gui/guiHyperText.h +++ b/src/gui/guiHyperText.h @@ -24,8 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include "irrlichttypes_extrabloated.h" - -using namespace irr; +#include "StyleSpec.h" class ISimpleTextureSource; class Client; @@ -99,8 +98,8 @@ class ParsedText gui::IGUIFont *font; - irr::video::SColor color; - irr::video::SColor hovercolor; + video::SColor color; + video::SColor hovercolor; bool underline; s32 baseline = 0; @@ -130,7 +129,10 @@ class ParsedText s32 margin = 3; ValignType valign = VALIGN_TOP; BackgroundType background_type = BACKGROUND_NONE; - irr::video::SColor background_color; + video::SColor background_color; + video::ITexture *background_image = nullptr; + core::rect background_middle; + bool border = false; Tag m_root_tag; @@ -177,6 +179,8 @@ class TextDrawer inline s32 getHeight() { return m_height; }; void draw(const core::rect &clip_rect, const core::position2d &dest_offset); + void drawBackgroundImage(video::IVideoDriver *driver, const ParsedText &m_text, const core::rect &clip_rect); + ParsedText &getText() { return m_text; } ParsedText::Element *getElementAt(core::position2d pos); ParsedText::Tag *m_hovertag; @@ -211,10 +215,13 @@ class GUIHyperText : public gui::IGUIElement //! draws the element and its children virtual void draw(); - core::dimension2du getTextDimension(); + //! Returns the height of the text in pixels when it is drawn. + s32 getTextHeight() { return m_drawer.getHeight(); } bool OnEvent(const SEvent &event); + void setStyles(const std::array &styles); + protected: // GUI members ISimpleTextureSource *m_tsrc;