X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fgui%2FguiFormSpecMenu.cpp;h=68222b319def52a913c0350da1302550494a7086;hb=69fc20610947610b7829f3bfad82e23ed705b764;hp=7b37de6f807bd7cd84e49ee57d36f82c7ebb598a;hpb=009e39e73b9aa003c369fe6bc88f366fdc91610e;p=minetest.git diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 7b37de6f8..68222b319 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include +#include #include #include #include @@ -64,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 "intlGUIEditBox.h" #include "guiHyperText.h" #include "guiScene.h" @@ -81,6 +81,13 @@ with this program; if not, write to the Free Software Foundation, Inc., " specified: \"" << parts[b] << "\"" << std::endl; \ return; \ } + +#define MY_CHECKCLIENT(a) \ + if (!m_client) { \ + errorstream << "Attempted to use element " << a << " with m_client == nullptr." << std::endl; \ + return; \ + } + /* GUIFormSpecMenu */ @@ -96,10 +103,10 @@ inline u32 clamp_u8(s32 value) GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick, gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, - Client *client, ISimpleTextureSource *tsrc, ISoundManager *sound_manager, - IFormSource *fsrc, TextDest *tdst, + Client *client, gui::IGUIEnvironment *guienv, ISimpleTextureSource *tsrc, + ISoundManager *sound_manager, IFormSource *fsrc, TextDest *tdst, const std::string &formspecPrepend, bool remap_dbl_click): - GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr, remap_dbl_click), + GUIModalMenu(guienv, parent, id, menumgr, remap_dbl_click), m_invmgr(client), m_tsrc(tsrc), m_sound_manager(sound_manager), @@ -120,24 +127,7 @@ GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick, GUIFormSpecMenu::~GUIFormSpecMenu() { - removeChildren(); - - for (auto &table_it : m_tables) - table_it.second->drop(); - for (auto &inventorylist_it : m_inventorylists) - inventorylist_it->drop(); - for (auto &checkbox_it : m_checkboxes) - checkbox_it.second->drop(); - for (auto &scrollbar_it : m_scrollbars) - scrollbar_it.second->drop(); - for (auto &background_it : m_backgrounds) - background_it->drop(); - for (auto &tooltip_rect_it : m_tooltip_rects) - tooltip_rect_it.first->drop(); - for (auto &clickthrough_it : m_clickthrough_elements) - clickthrough_it->drop(); - for (auto &scroll_container_it : m_scroll_containers) - scroll_container_it.second->drop(); + removeAll(); delete m_selected_item; delete m_form_src; @@ -145,12 +135,12 @@ GUIFormSpecMenu::~GUIFormSpecMenu() } void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client, - JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest, - const std::string &formspecPrepend, ISoundManager *sound_manager) + gui::IGUIEnvironment *guienv, JoystickController *joystick, IFormSource *fs_src, + TextDest *txt_dest, const std::string &formspecPrepend, ISoundManager *sound_manager) { if (cur_formspec == nullptr) { cur_formspec = new GUIFormSpecMenu(joystick, guiroot, -1, &g_menumgr, - client, client->getTextureSource(), sound_manager, fs_src, + client, guienv, client->getTextureSource(), sound_manager, fs_src, txt_dest, formspecPrepend); cur_formspec->doPause = false; @@ -169,14 +159,8 @@ void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client, } } -void GUIFormSpecMenu::removeChildren() +void GUIFormSpecMenu::removeTooltip() { - const core::list &children = getChildren(); - - while (!children.empty()) { - (*children.getLast())->remove(); - } - if (m_tooltip_element) { m_tooltip_element->remove(); m_tooltip_element->drop(); @@ -194,16 +178,7 @@ void GUIFormSpecMenu::setInitialFocus() // 5. first focusable (not statictext, not tabheader) // 6. first child element - core::list children = getChildren(); - - // in case "children" contains any NULL elements, remove them - for (core::list::Iterator it = children.begin(); - it != children.end();) { - if (*it) - ++it; - else - it = children.erase(it); - } + const auto& children = getChildren(); // 1. first empty editbox for (gui::IGUIElement *it : children) { @@ -231,8 +206,7 @@ void GUIFormSpecMenu::setInitialFocus() } // 4. last button - for (core::list::Iterator it = children.getLast(); - it != children.end(); --it) { + for (auto it = children.rbegin(); it != children.rend(); ++it) { if ((*it)->getType() == gui::EGUIET_BUTTON) { Environment->setFocus(*it); return; @@ -252,7 +226,7 @@ void GUIFormSpecMenu::setInitialFocus() if (children.empty()) Environment->setFocus(this); else - Environment->setFocus(*(children.begin())); + Environment->setFocus(children.front()); } GUITable* GUIFormSpecMenu::getTable(const std::string &tablename) @@ -294,8 +268,20 @@ v2s32 GUIFormSpecMenu::getRealCoordinateGeometry(const std::vector return v2s32(stof(v_geom[0]) * imgsize.X, stof(v_geom[1]) * imgsize.Y); } +bool GUIFormSpecMenu::precheckElement(const std::string &name, const std::string &element, + size_t args_min, size_t args_max, std::vector &parts) +{ + parts = split(element, ';'); + if (parts.size() >= args_min && (parts.size() <= args_max || m_formspec_version > FORMSPEC_API_VERSION)) + return true; + + errorstream << "Invalid " << name << " element(" << parts.size() << "): '" << element << "'" << std::endl; + return false; +} + void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element) { + // Note: do not use precheckElement due to "," separator. std::vector parts = split(element,','); if (((parts.size() == 2) || parts.size() == 3) || @@ -308,7 +294,7 @@ void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element) data->invsize.Y = MYMAX(0, stof(parts[1])); lockSize(false); -#ifndef __ANDROID__ +#ifndef HAVE_TOUCHSCREENGUI if (parts.size() == 3) { if (parts[2] == "true") { lockSize(true,v2u32(800,600)); @@ -349,14 +335,9 @@ void GUIFormSpecMenu::parseContainerEnd(parserData* data) void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string &element) { - std::vector parts = split(element, ';'); - - if (parts.size() < 4 || - (parts.size() > 5 && m_formspec_version <= FORMSPEC_API_VERSION)) { - errorstream << "Invalid scroll_container start element (" << parts.size() - << "): '" << element << "'" << std::endl; + std::vector parts; + if (!precheckElement("scroll_container start", element, 4, 5, parts)) return; - } std::vector v_pos = split(parts[0], ','); std::vector v_geom = split(parts[1], ','); @@ -445,105 +426,95 @@ void GUIFormSpecMenu::parseScrollContainerEnd(parserData *data) void GUIFormSpecMenu::parseList(parserData *data, const std::string &element) { - if (m_client == 0) { - warningstream<<"invalid use of 'list' with m_client==0"< parts; + if (!precheckElement("list", element, 4, 5, parts)) return; - } - std::vector parts = split(element,';'); + std::string location = parts[0]; + std::string listname = parts[1]; + std::vector v_pos = split(parts[2],','); + std::vector v_geom = split(parts[3],','); + std::string startindex; + if (parts.size() == 5) + startindex = parts[4]; - if (((parts.size() == 4) || (parts.size() == 5)) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::string location = parts[0]; - std::string listname = parts[1]; - std::vector v_pos = split(parts[2],','); - std::vector v_geom = split(parts[3],','); - std::string startindex; - if (parts.size() == 5) - startindex = parts[4]; + MY_CHECKPOS("list",2); + MY_CHECKGEOM("list",3); - MY_CHECKPOS("list",2); - MY_CHECKGEOM("list",3); + InventoryLocation loc; - InventoryLocation loc; + if (location == "context" || location == "current_name") + loc = m_current_inventory_location; + else + loc.deSerialize(location); - if (location == "context" || location == "current_name") - loc = m_current_inventory_location; - else - loc.deSerialize(location); + v2s32 geom; + geom.X = stoi(v_geom[0]); + geom.Y = stoi(v_geom[1]); - v2s32 geom; - geom.X = stoi(v_geom[0]); - geom.Y = stoi(v_geom[1]); + s32 start_i = 0; + if (!startindex.empty()) + start_i = stoi(startindex); - s32 start_i = 0; - if (!startindex.empty()) - start_i = stoi(startindex); + if (geom.X < 0 || geom.Y < 0 || start_i < 0) { + errorstream << "Invalid list element: '" << element << "'" << std::endl; + return; + } - if (geom.X < 0 || geom.Y < 0 || start_i < 0) { - errorstream << "Invalid list element: '" << element << "'" << std::endl; - return; - } + if (!data->explicit_size) + warningstream << "invalid use of list without a size[] element" << std::endl; - if (!data->explicit_size) - warningstream << "invalid use of list without a size[] element" << std::endl; + FieldSpec spec( + "", + L"", + L"", + 258 + m_fields.size(), + 3 + ); - FieldSpec spec( - "", - L"", - L"", - 258 + m_fields.size(), - 3 - ); + auto style = getDefaultStyleForElement("list", spec.fname); - auto style = getDefaultStyleForElement("list", spec.fname); + v2f32 slot_scale = style.getVector2f(StyleSpec::SIZE, v2f32(0, 0)); + v2f32 slot_size( + slot_scale.X <= 0 ? imgsize.X : std::max(slot_scale.X * imgsize.X, 1), + slot_scale.Y <= 0 ? imgsize.Y : std::max(slot_scale.Y * imgsize.Y, 1) + ); - v2f32 slot_scale = style.getVector2f(StyleSpec::SIZE, v2f32(0, 0)); - v2s32 slot_size( - slot_scale.X <= 0 ? imgsize.X : slot_scale.X * imgsize.X, - slot_scale.Y <= 0 ? imgsize.Y : slot_scale.Y * imgsize.Y - ); + v2f32 slot_spacing = style.getVector2f(StyleSpec::SPACING, v2f32(-1, -1)); + v2f32 default_spacing = data->real_coordinates ? + v2f32(imgsize.X * 0.25f, imgsize.Y * 0.25f) : + v2f32(spacing.X - imgsize.X, spacing.Y - imgsize.Y); - v2f32 slot_spacing = style.getVector2f(StyleSpec::SPACING, v2f32(-1, -1)); - if (data->real_coordinates) { - slot_spacing.X = slot_spacing.X < 0 ? imgsize.X * 1.25f : - slot_spacing.X * imgsize.X + imgsize.X; - slot_spacing.Y = slot_spacing.Y < 0 ? imgsize.Y * 1.25f : - slot_spacing.Y * imgsize.Y + imgsize.Y; - } else { - slot_spacing.X = slot_spacing.X < 0 ? spacing.X : - slot_spacing.X * spacing.X; - slot_spacing.Y = slot_spacing.Y < 0 ? spacing.Y : - slot_spacing.Y * spacing.Y; - } + slot_spacing.X = slot_spacing.X < 0 ? default_spacing.X : + imgsize.X * slot_spacing.X; + slot_spacing.Y = slot_spacing.Y < 0 ? default_spacing.Y : + imgsize.Y * slot_spacing.Y; - v2s32 pos = data->real_coordinates ? getRealCoordinateBasePos(v_pos) : - getElementBasePos(&v_pos); + slot_spacing += slot_size; - core::rect rect = core::rect(pos.X, pos.Y, - pos.X + (geom.X - 1) * slot_spacing.X + imgsize.X, - pos.Y + (geom.Y - 1) * slot_spacing.Y + imgsize.Y); + v2s32 pos = data->real_coordinates ? getRealCoordinateBasePos(v_pos) : + getElementBasePos(&v_pos); - GUIInventoryList *e = new GUIInventoryList(Environment, data->current_parent, - spec.fid, rect, m_invmgr, loc, listname, geom, start_i, slot_size, - slot_spacing, this, data->inventorylist_options, m_font); + core::rect rect = core::rect(pos.X, pos.Y, + pos.X + (geom.X - 1) * slot_spacing.X + slot_size.X, + pos.Y + (geom.Y - 1) * slot_spacing.Y + slot_size.Y); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + GUIInventoryList *e = new GUIInventoryList(Environment, data->current_parent, + spec.fid, rect, m_invmgr, loc, listname, geom, start_i, + v2s32(slot_size.X, slot_size.Y), slot_spacing, this, + data->inventorylist_options, m_font); - m_inventorylists.push_back(e); - m_fields.push_back(spec); - return; - } - errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl; + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + + m_inventorylists.push_back(e); + m_fields.push_back(spec); } void GUIFormSpecMenu::parseListRing(parserData *data, const std::string &element) { - if (m_client == 0) { - errorstream << "WARNING: invalid use of 'listring' with m_client==0" << std::endl; - return; - } + MY_CHECKCLIENT("listring"); std::vector parts = split(element, ';'); @@ -578,157 +549,150 @@ void GUIFormSpecMenu::parseListRing(parserData *data, const std::string &element void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); - - if (((parts.size() >= 3) && (parts.size() <= 4)) || - ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::string name = parts[1]; - std::string label = parts[2]; - std::string selected; + std::vector parts; + if (!precheckElement("checkbox", element, 3, 4, parts)) + return; - if (parts.size() >= 4) - selected = parts[3]; + std::vector v_pos = split(parts[0],','); + std::string name = parts[1]; + std::string label = parts[2]; + std::string selected; - MY_CHECKPOS("checkbox",0); + if (parts.size() >= 4) + selected = parts[3]; - bool fselected = false; + MY_CHECKPOS("checkbox",0); - if (selected == "true") - fselected = true; + bool fselected = false; - std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); - const core::dimension2d label_size = m_font->getDimension(wlabel.c_str()); - s32 cb_size = Environment->getSkin()->getSize(gui::EGDS_CHECK_BOX_WIDTH); - s32 y_center = (std::max(label_size.Height, (u32)cb_size) + 1) / 2; + if (selected == "true") + fselected = true; - v2s32 pos; - core::rect rect; + std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); + const core::dimension2d label_size = m_font->getDimension(wlabel.c_str()); + s32 cb_size = Environment->getSkin()->getSize(gui::EGDS_CHECK_BOX_WIDTH); + s32 y_center = (std::max(label_size.Height, (u32)cb_size) + 1) / 2; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); + v2s32 pos; + core::rect rect; - rect = core::rect( - pos.X, - pos.Y - y_center, - pos.X + label_size.Width + cb_size + 7, - pos.Y + y_center - ); - } else { - pos = getElementBasePos(&v_pos); - rect = core::rect( - pos.X, - pos.Y + imgsize.Y / 2 - y_center, - pos.X + label_size.Width + cb_size + 7, - pos.Y + imgsize.Y / 2 + y_center - ); - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); - FieldSpec spec( - name, - wlabel, //Needed for displaying text on MSVC - wlabel, - 258+m_fields.size() + rect = core::rect( + pos.X, + pos.Y - y_center, + pos.X + label_size.Width + cb_size + 7, + pos.Y + y_center + ); + } else { + pos = getElementBasePos(&v_pos); + rect = core::rect( + pos.X, + pos.Y + imgsize.Y / 2 - y_center, + pos.X + label_size.Width + cb_size + 7, + pos.Y + imgsize.Y / 2 + y_center ); + } - spec.ftype = f_CheckBox; + FieldSpec spec( + name, + wlabel, //Needed for displaying text on MSVC + wlabel, + 258+m_fields.size() + ); - gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect, - data->current_parent, spec.fid, spec.flabel.c_str()); + spec.ftype = f_CheckBox; - auto style = getDefaultStyleForElement("checkbox", name); + gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect, + data->current_parent, spec.fid, spec.flabel.c_str()); - spec.sound = style.get(StyleSpec::Property::SOUND, ""); + auto style = getDefaultStyleForElement("checkbox", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + spec.sound = style.get(StyleSpec::Property::SOUND, ""); - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->grab(); - m_checkboxes.emplace_back(spec, e); - m_fields.push_back(spec); - return; + if (spec.fname == m_focused_element) { + Environment->setFocus(e); } - errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'" << std::endl; + + e->grab(); + m_checkboxes.emplace_back(spec, e); + m_fields.push_back(spec); } void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); - - if (parts.size() >= 5) { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); - std::string name = parts[3]; - std::string value = parts[4]; + std::vector parts; + if (!precheckElement("scrollbar", element, 5, 5, parts)) + return; - MY_CHECKPOS("scrollbar",0); - MY_CHECKGEOM("scrollbar",1); + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = parts[3]; + std::string value = parts[4]; - v2s32 pos; - v2s32 dim; + MY_CHECKPOS("scrollbar",0); + MY_CHECKGEOM("scrollbar",1); - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - dim = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - dim.X = stof(v_geom[0]) * spacing.X; - dim.Y = stof(v_geom[1]) * spacing.Y; - } + v2s32 pos; + v2s32 dim; - core::rect rect = - core::rect(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y); + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + dim = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + dim.X = stof(v_geom[0]) * spacing.X; + dim.Y = stof(v_geom[1]) * spacing.Y; + } - FieldSpec spec( - name, - L"", - L"", - 258+m_fields.size() - ); + core::rect rect = + core::rect(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y); - bool is_horizontal = true; + FieldSpec spec( + name, + L"", + L"", + 258+m_fields.size() + ); - if (parts[2] == "vertical") - is_horizontal = false; + bool is_horizontal = true; - spec.ftype = f_ScrollBar; - spec.send = true; - GUIScrollBar *e = new GUIScrollBar(Environment, data->current_parent, - spec.fid, rect, is_horizontal, true); + if (parts[2] == "vertical") + is_horizontal = false; - auto style = getDefaultStyleForElement("scrollbar", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setArrowsVisible(data->scrollbar_options.arrow_visiblity); + spec.ftype = f_ScrollBar; + spec.send = true; + GUIScrollBar *e = new GUIScrollBar(Environment, data->current_parent, + spec.fid, rect, is_horizontal, true); - s32 max = data->scrollbar_options.max; - s32 min = data->scrollbar_options.min; + auto style = getDefaultStyleForElement("scrollbar", name); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setArrowsVisible(data->scrollbar_options.arrow_visiblity); - e->setMax(max); - e->setMin(min); + s32 max = data->scrollbar_options.max; + s32 min = data->scrollbar_options.min; - e->setPos(stoi(parts[4])); + e->setMax(max); + e->setMin(min); - e->setSmallStep(data->scrollbar_options.small_step); - e->setLargeStep(data->scrollbar_options.large_step); + e->setPos(stoi(value)); - s32 scrollbar_size = is_horizontal ? dim.X : dim.Y; + e->setSmallStep(data->scrollbar_options.small_step); + e->setLargeStep(data->scrollbar_options.large_step); - e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size); + s32 scrollbar_size = is_horizontal ? dim.X : dim.Y; - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size); - m_scrollbars.emplace_back(spec,e); - m_fields.push_back(spec); - return; + if (spec.fname == m_focused_element) { + Environment->setFocus(e); } - errorstream << "Invalid scrollbar element(" << parts.size() << "): '" << element - << "'" << std::endl; + + m_scrollbars.emplace_back(spec,e); + m_fields.push_back(spec); } void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string &element) @@ -786,110 +750,101 @@ void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts; + if (!precheckElement("image", element, 2, 4, parts)) + return; - if ((parts.size() == 3) || - ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); - std::string name = unescape_string(parts[2]); + size_t offset = parts.size() >= 3; - MY_CHECKPOS("image", 0); + std::vector v_pos = split(parts[0],','); + MY_CHECKPOS("image", 0); + + std::vector v_geom; + if (parts.size() >= 3) { + v_geom = split(parts[1],','); MY_CHECKGEOM("image", 1); + } - v2s32 pos; - v2s32 geom; + std::string name = unescape_string(parts[1 + offset]); + video::ITexture *texture = m_tsrc->getTexture(name); - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); + v2s32 pos; + v2s32 geom; + + if (parts.size() < 3) { + if (texture != nullptr) { + core::dimension2du dim = texture->getOriginalSize(); + geom.X = dim.Width; + geom.Y = dim.Height; } else { - pos = getElementBasePos(&v_pos); + geom = v2s32(0); + } + } + + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + if (parts.size() >= 3) + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + if (parts.size() >= 3) { geom.X = stof(v_geom[0]) * (float)imgsize.X; geom.Y = stof(v_geom[1]) * (float)imgsize.Y; } + } - if (!data->explicit_size) - warningstream<<"invalid use of image without a size[] element"<getTexture(name); - if (!texture) { - errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:" - << std::endl << "\t" << name << std::endl; - return; - } + if (!data->explicit_size) + warningstream << "Invalid use of image without a size[] element" << std::endl; - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size(), - 1 - ); - core::rect rect(pos, pos + geom); - gui::IGUIImage *e = Environment->addImage(rect, data->current_parent, - spec.fid, 0, true); - e->setImage(texture); - e->setScaleImage(true); - auto style = getDefaultStyleForElement("image", spec.fname); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); - m_fields.push_back(spec); + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size(), + 1 + ); - // images should let events through - e->grab(); - m_clickthrough_elements.push_back(e); - return; - } + core::rect rect = core::rect(pos, pos + geom); - if (parts.size() == 2) { - std::vector v_pos = split(parts[0],','); - std::string name = unescape_string(parts[1]); + core::rect middle; + if (parts.size() >= 4) + parseMiddleRect(parts[3], &middle); - MY_CHECKPOS("image", 0); + // Temporary fix for issue #12581 in 5.6.0. + // Use legacy image when not rendering 9-slice image because GUIAnimatedImage + // uses NNAA filter which causes visual artifacts when image uses alpha blending. - v2s32 pos = getElementBasePos(&v_pos); + gui::IGUIElement *e; + if (middle.getArea() > 0) { + GUIAnimatedImage *image = new GUIAnimatedImage(Environment, data->current_parent, + spec.fid, rect); - if (!data->explicit_size) - warningstream<<"invalid use of image without a size[] element"<setTexture(texture); + image->setMiddleRect(middle); + e = image; + } + else { + gui::IGUIImage *image = Environment->addImage(rect, data->current_parent, spec.fid, nullptr, true); + image->setImage(texture); + image->setScaleImage(true); + image->grab(); // compensate for drop in addImage + e = image; + } - video::ITexture *texture = m_tsrc->getTexture(name); - if (!texture) { - errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:" - << std::endl << "\t" << name << std::endl; - return; - } + auto style = getDefaultStyleForElement("image", spec.fname); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); - gui::IGUIImage *e = Environment->addImage(texture, pos, true, - data->current_parent, spec.fid, 0); - auto style = getDefaultStyleForElement("image", spec.fname); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); - m_fields.push_back(spec); + // Animated images should let events through + m_clickthrough_elements.push_back(e); - // images should let events through - e->grab(); - m_clickthrough_elements.push_back(e); - return; - } - errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'" << std::endl; + m_fields.push_back(spec); } void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &element) { - std::vector parts = split(element, ';'); - - if (parts.size() != 6 && parts.size() != 7 && - !(parts.size() > 7 && m_formspec_version > FORMSPEC_API_VERSION)) { - errorstream << "Invalid animated_image element(" << parts.size() - << "): '" << element << "'" << std::endl; + std::vector parts; + if (!precheckElement("animated_image", element, 6, 8, parts)) return; - } std::vector v_pos = split(parts[0], ','); std::vector v_geom = split(parts[1], ','); @@ -914,7 +869,8 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el } if (!data->explicit_size) - warningstream << "Invalid use of animated_image without a size[] element" << std::endl; + warningstream << "Invalid use of animated_image without a size[] element" + << std::endl; FieldSpec spec( name, @@ -927,9 +883,17 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el core::rect rect = core::rect(pos, pos + geom); - GUIAnimatedImage *e = new GUIAnimatedImage(Environment, this, spec.fid, - rect, texture_name, frame_count, frame_duration, m_tsrc); + core::rect middle; + if (parts.size() >= 8) + parseMiddleRect(parts[7], &middle); + GUIAnimatedImage *e = new GUIAnimatedImage(Environment, data->current_parent, + spec.fid, rect); + + e->setTexture(m_tsrc->getTexture(texture_name)); + e->setMiddleRect(middle); + e->setFrameDuration(frame_duration); + e->setFrameCount(frame_count); if (parts.size() >= 7) e->setFrameIndex(stoi(parts[6]) - 1); @@ -944,218 +908,217 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts; + if (!precheckElement("item_image", element, 3, 3, parts)) + return; - if ((parts.size() == 3) || - ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); - std::string name = parts[2]; + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = parts[2]; - MY_CHECKPOS("itemimage",0); - MY_CHECKGEOM("itemimage",1); + MY_CHECKPOS("item_image",0); + MY_CHECKGEOM("item_image",1); - v2s32 pos; - v2s32 geom; + v2s32 pos; + v2s32 geom; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - geom.X = stof(v_geom[0]) * (float)imgsize.X; - geom.Y = stof(v_geom[1]) * (float)imgsize.Y; - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = stof(v_geom[0]) * (float)imgsize.X; + geom.Y = stof(v_geom[1]) * (float)imgsize.Y; + } - if(!data->explicit_size) - warningstream<<"invalid use of item_image without a size[] element"<explicit_size) + warningstream<<"invalid use of item_image without a size[] element"<current_parent, spec.fid, - core::rect(pos, pos + geom), name, m_font, m_client); - auto style = getDefaultStyleForElement("item_image", spec.fname); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + GUIItemImage *e = new GUIItemImage(Environment, data->current_parent, spec.fid, + core::rect(pos, pos + geom), name, m_font, m_client); + auto style = getDefaultStyleForElement("item_image", spec.fname); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - // item images should let events through - m_clickthrough_elements.push_back(e); + // item images should let events through + m_clickthrough_elements.push_back(e); - m_fields.push_back(spec); - return; - } - errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'" << std::endl; + m_fields.push_back(spec); } void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, const std::string &type) { - std::vector parts = split(element,';'); + std::vector parts; + if (!precheckElement("button", element, 4, 4, parts)) + return; - if ((parts.size() == 4) || - ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); - std::string name = parts[2]; - std::string label = parts[3]; + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = parts[2]; + std::string label = parts[3]; - MY_CHECKPOS("button",0); - MY_CHECKGEOM("button",1); + MY_CHECKPOS("button",0); + MY_CHECKGEOM("button",1); - v2s32 pos; - v2s32 geom; - core::rect rect; + v2s32 pos; + v2s32 geom; + core::rect rect; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - rect = core::rect(pos.X, pos.Y, pos.X+geom.X, - pos.Y+geom.Y); - } else { - pos = getElementBasePos(&v_pos); - geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); - pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + rect = core::rect(pos.X, pos.Y, pos.X+geom.X, + pos.Y+geom.Y); + } else { + pos = getElementBasePos(&v_pos); + geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; - rect = core::rect(pos.X, pos.Y - m_btn_height, - pos.X + geom.X, pos.Y + m_btn_height); - } + rect = core::rect(pos.X, pos.Y - m_btn_height, + pos.X + geom.X, pos.Y + m_btn_height); + } - if(!data->explicit_size) - warningstream<<"invalid use of button without a size[] element"<explicit_size) + warningstream<<"invalid use of button without a size[] element"<current_parent, spec.fid, spec.flabel.c_str()); + GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc, + data->current_parent, spec.fid, spec.flabel.c_str()); - auto style = getStyleForElement(type, name, (type != "button") ? "button" : ""); + auto style = getStyleForElement(type, name, (type != "button") ? "button" : ""); - spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); + spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); - e->setStyles(style); + e->setStyles(style); - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } - m_fields.push_back(spec); - return; + m_fields.push_back(spec); +} + +bool GUIFormSpecMenu::parseMiddleRect(const std::string &value, core::rect *parsed_rect) +{ + core::rect rect; + std::vector v_rect = split(value, ','); + + if (v_rect.size() == 1) { + s32 x = stoi(v_rect[0]); + rect.UpperLeftCorner = core::vector2di(x, x); + rect.LowerRightCorner = core::vector2di(-x, -x); + } else if (v_rect.size() == 2) { + s32 x = stoi(v_rect[0]); + s32 y = stoi(v_rect[1]); + rect.UpperLeftCorner = core::vector2di(x, y); + rect.LowerRightCorner = core::vector2di(-x, -y); + // `-x` is interpreted as `w - x` + } else if (v_rect.size() == 4) { + rect.UpperLeftCorner = core::vector2di(stoi(v_rect[0]), stoi(v_rect[1])); + rect.LowerRightCorner = core::vector2di(stoi(v_rect[2]), stoi(v_rect[3])); + } else { + warningstream << "Invalid rectangle string format: \"" << value + << "\"" << std::endl; + return false; } - errorstream<< "Invalid button element(" << parts.size() << "): '" << element << "'" << std::endl; + + *parsed_rect = rect; + + return true; } void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); - - if ((parts.size() >= 3 && parts.size() <= 5) || - (parts.size() > 5 && m_formspec_version > FORMSPEC_API_VERSION)) { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); - std::string name = unescape_string(parts[2]); + std::vector parts; + if (!precheckElement("background", element, 3, 5, parts)) + return; - MY_CHECKPOS("background",0); - MY_CHECKGEOM("background",1); + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = unescape_string(parts[2]); - v2s32 pos; - v2s32 geom; + MY_CHECKPOS("background",0); + MY_CHECKGEOM("background",1); - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - pos.X -= (spacing.X - (float)imgsize.X) / 2; - pos.Y -= (spacing.Y - (float)imgsize.Y) / 2; + v2s32 pos; + v2s32 geom; - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + pos.X -= (spacing.X - (float)imgsize.X) / 2; + pos.Y -= (spacing.Y - (float)imgsize.Y) / 2; - bool clip = false; - if (parts.size() >= 4 && is_yes(parts[3])) { - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos) * -1; - geom = v2s32(0, 0); - } else { - pos.X = stoi(v_pos[0]); //acts as offset - pos.Y = stoi(v_pos[1]); - } - clip = true; - } + geom.X = stof(v_geom[0]) * spacing.X; + geom.Y = stof(v_geom[1]) * spacing.Y; + } - core::rect middle; - if (parts.size() >= 5) { - std::vector v_middle = split(parts[4], ','); - if (v_middle.size() == 1) { - s32 x = stoi(v_middle[0]); - middle.UpperLeftCorner = core::vector2di(x, x); - middle.LowerRightCorner = core::vector2di(-x, -x); - } else if (v_middle.size() == 2) { - s32 x = stoi(v_middle[0]); - s32 y = stoi(v_middle[1]); - middle.UpperLeftCorner = core::vector2di(x, y); - middle.LowerRightCorner = core::vector2di(-x, -y); - // `-x` is interpreted as `w - x` - } else if (v_middle.size() == 4) { - middle.UpperLeftCorner = core::vector2di(stoi(v_middle[0]), stoi(v_middle[1])); - middle.LowerRightCorner = core::vector2di(stoi(v_middle[2]), stoi(v_middle[3])); - } else { - warningstream << "Invalid rectangle given to middle param of background[] element" << std::endl; - } + bool clip = false; + if (parts.size() >= 4 && is_yes(parts[3])) { + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos) * -1; + geom = v2s32(0, 0); + } else { + pos.X = stoi(v_pos[0]); //acts as offset + pos.Y = stoi(v_pos[1]); } + clip = true; + } - if (!data->explicit_size && !clip) - warningstream << "invalid use of unclipped background without a size[] element" << std::endl; + core::rect middle; + if (parts.size() >= 5) + parseMiddleRect(parts[4], &middle); - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + if (!data->explicit_size && !clip) + warningstream << "invalid use of unclipped background without a size[] element" << std::endl; - core::rect rect; - if (!clip) { - // no auto_clip => position like normal image - rect = core::rect(pos, pos + geom); - } else { - // it will be auto-clipped when drawing - rect = core::rect(-pos, pos); - } + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); - GUIBackgroundImage *e = new GUIBackgroundImage(Environment, this, spec.fid, - rect, name, middle, m_tsrc, clip); + core::rect rect; + if (!clip) { + // no auto_clip => position like normal image + rect = core::rect(pos, pos + geom); + } else { + // it will be auto-clipped when drawing + rect = core::rect(-pos, pos); + } - FATAL_ERROR_IF(!e, "Failed to create background formspec element"); + GUIBackgroundImage *e = new GUIBackgroundImage(Environment, data->background_parent.get(), + spec.fid, rect, name, middle, m_tsrc, clip); - e->setNotClipped(true); + FATAL_ERROR_IF(!e, "Failed to create background formspec element"); - e->setVisible(false); // the element is drawn manually before all others + e->setNotClipped(true); - m_backgrounds.push_back(e); - m_fields.push_back(spec); - return; - } - errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'" << std::endl; + m_fields.push_back(spec); + e->drop(); } void GUIFormSpecMenu::parseTableOptions(parserData* data, const std::string &element) @@ -1192,338 +1155,319 @@ void GUIFormSpecMenu::parseTableColumns(parserData* data, const std::string &ele void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts; + if (!precheckElement("table", element, 4, 5, parts)) + return; - if (((parts.size() == 4) || (parts.size() == 5)) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); - std::string name = parts[2]; - std::vector items = split(parts[3],','); - std::string str_initial_selection; - std::string str_transparent = "false"; + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = parts[2]; + std::vector items = split(parts[3],','); + std::string str_initial_selection; - if (parts.size() >= 5) - str_initial_selection = parts[4]; + if (parts.size() >= 5) + str_initial_selection = parts[4]; - MY_CHECKPOS("table",0); - MY_CHECKGEOM("table",1); + MY_CHECKPOS("table",0); + MY_CHECKGEOM("table",1); - v2s32 pos; - v2s32 geom; + v2s32 pos; + v2s32 geom; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = stof(v_geom[0]) * spacing.X; + geom.Y = stof(v_geom[1]) * spacing.Y; + } - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); - spec.ftype = f_Table; + spec.ftype = f_Table; - for (std::string &item : items) { - item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item)))); - } + for (std::string &item : items) { + item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item)))); + } - //now really show table - GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, - rect, m_tsrc); + //now really show table + GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, + rect, m_tsrc); - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } - e->setTable(data->table_options, data->table_columns, items); + e->setTable(data->table_options, data->table_columns, items); - if (data->table_dyndata.find(name) != data->table_dyndata.end()) { - e->setDynamicData(data->table_dyndata[name]); - } + if (data->table_dyndata.find(name) != data->table_dyndata.end()) { + e->setDynamicData(data->table_dyndata[name]); + } - if (!str_initial_selection.empty() && str_initial_selection != "0") - e->setSelected(stoi(str_initial_selection)); + if (!str_initial_selection.empty() && str_initial_selection != "0") + e->setSelected(stoi(str_initial_selection)); - auto style = getDefaultStyleForElement("table", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideFont(style.getFont()); + auto style = getDefaultStyleForElement("table", name); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideFont(style.getFont()); - m_tables.emplace_back(spec, e); - m_fields.push_back(spec); - return; - } - errorstream<< "Invalid table element(" << parts.size() << "): '" << element << "'" << std::endl; + m_tables.emplace_back(spec, e); + m_fields.push_back(spec); } void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts; + if (!precheckElement("textlist", element, 4, 6, parts)) + return; - if (((parts.size() == 4) || (parts.size() == 5) || (parts.size() == 6)) || - ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); - std::string name = parts[2]; - std::vector items = split(parts[3],','); - std::string str_initial_selection; - std::string str_transparent = "false"; + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = parts[2]; + std::vector items = split(parts[3],','); + std::string str_initial_selection; + std::string str_transparent = "false"; - if (parts.size() >= 5) - str_initial_selection = parts[4]; + if (parts.size() >= 5) + str_initial_selection = parts[4]; - if (parts.size() >= 6) - str_transparent = parts[5]; + if (parts.size() >= 6) + str_transparent = parts[5]; - MY_CHECKPOS("textlist",0); - MY_CHECKGEOM("textlist",1); + MY_CHECKPOS("textlist",0); + MY_CHECKGEOM("textlist",1); - v2s32 pos; - v2s32 geom; + v2s32 pos; + v2s32 geom; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = stof(v_geom[0]) * spacing.X; + geom.Y = stof(v_geom[1]) * spacing.Y; + } - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); - spec.ftype = f_Table; + spec.ftype = f_Table; - for (std::string &item : items) { - item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item)))); - } + for (std::string &item : items) { + item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item)))); + } - //now really show list - GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, - rect, m_tsrc); + //now really show list + GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, + rect, m_tsrc); - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } - e->setTextList(items, is_yes(str_transparent)); + e->setTextList(items, is_yes(str_transparent)); - if (data->table_dyndata.find(name) != data->table_dyndata.end()) { - e->setDynamicData(data->table_dyndata[name]); - } + if (data->table_dyndata.find(name) != data->table_dyndata.end()) { + e->setDynamicData(data->table_dyndata[name]); + } - if (!str_initial_selection.empty() && str_initial_selection != "0") - e->setSelected(stoi(str_initial_selection)); + if (!str_initial_selection.empty() && str_initial_selection != "0") + e->setSelected(stoi(str_initial_selection)); - auto style = getDefaultStyleForElement("textlist", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideFont(style.getFont()); + auto style = getDefaultStyleForElement("textlist", name); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideFont(style.getFont()); - m_tables.emplace_back(spec, e); - m_fields.push_back(spec); - return; - } - errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'" << std::endl; + m_tables.emplace_back(spec, e); + m_fields.push_back(spec); } void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element) { - std::vector parts = split(element, ';'); - - if (parts.size() == 5 || parts.size() == 6 || - (parts.size() > 6 && m_formspec_version > FORMSPEC_API_VERSION)) - { - std::vector v_pos = split(parts[0], ','); - std::string name = parts[2]; - std::vector items = split(parts[3], ','); - std::string str_initial_selection = parts[4]; + std::vector parts; + if (!precheckElement("dropdown", element, 5, 6, parts)) + return; - if (parts.size() >= 6 && is_yes(parts[5])) - m_dropdown_index_event[name] = true; + std::vector v_pos = split(parts[0], ','); + std::string name = parts[2]; + std::vector items = split(parts[3], ','); + std::string str_initial_selection = parts[4]; - MY_CHECKPOS("dropdown",0); + if (parts.size() >= 6 && is_yes(parts[5])) + m_dropdown_index_event[name] = true; - v2s32 pos; - v2s32 geom; - core::rect rect; + MY_CHECKPOS("dropdown",0); - if (data->real_coordinates) { - std::vector v_geom = split(parts[1],','); + v2s32 pos; + v2s32 geom; + core::rect rect; - if (v_geom.size() == 1) - v_geom.emplace_back("1"); + if (data->real_coordinates) { + std::vector v_geom = split(parts[1],','); - MY_CHECKGEOM("dropdown",1); + if (v_geom.size() == 1) + v_geom.emplace_back("1"); - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - } else { - pos = getElementBasePos(&v_pos); + MY_CHECKGEOM("dropdown",1); - s32 width = stof(parts[1]) * spacing.Y; + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + } else { + pos = getElementBasePos(&v_pos); - rect = core::rect(pos.X, pos.Y, - pos.X + width, pos.Y + (m_btn_height * 2)); - } + s32 width = stof(parts[1]) * spacing.Y; - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + rect = core::rect(pos.X, pos.Y, + pos.X + width, pos.Y + (m_btn_height * 2)); + } - spec.ftype = f_DropDown; - spec.send = true; + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); - //now really show list - gui::IGUIComboBox *e = Environment->addComboBox(rect, data->current_parent, - spec.fid); + spec.ftype = f_DropDown; + spec.send = true; - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + //now really show list + gui::IGUIComboBox *e = Environment->addComboBox(rect, data->current_parent, + spec.fid); - for (const std::string &item : items) { - e->addItem(unescape_translate(unescape_string( - utf8_to_wide(item))).c_str()); - } + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } - if (!str_initial_selection.empty()) - e->setSelected(stoi(str_initial_selection)-1); + for (const std::string &item : items) { + e->addItem(unescape_translate(unescape_string( + utf8_to_wide(item))).c_str()); + } - auto style = getDefaultStyleForElement("dropdown", name); + if (!str_initial_selection.empty()) + e->setSelected(stoi(str_initial_selection)-1); - spec.sound = style.get(StyleSpec::Property::SOUND, ""); + auto style = getDefaultStyleForElement("dropdown", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + spec.sound = style.get(StyleSpec::Property::SOUND, ""); - m_fields.push_back(spec); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - m_dropdowns.emplace_back(spec, std::vector()); - std::vector &values = m_dropdowns.back().second; - for (const std::string &item : items) { - values.push_back(unescape_string(item)); - } + m_fields.push_back(spec); - return; + m_dropdowns.emplace_back(spec, std::vector()); + std::vector &values = m_dropdowns.back().second; + for (const std::string &item : items) { + values.push_back(unescape_string(item)); } - errorstream << "Invalid dropdown element(" << parts.size() << "): '" << element - << "'" << std::endl; } void GUIFormSpecMenu::parseFieldCloseOnEnter(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); - if (parts.size() == 2 || - (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) { - field_close_on_enter[parts[0]] = is_yes(parts[1]); - } + std::vector parts; + if (!precheckElement("field_close_on_enter", element, 2, 2, parts)) + return; + + field_close_on_enter[parts[0]] = is_yes(parts[1]); } void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); - - if (parts.size() == 4 || - (parts.size() > 4 && m_formspec_version > FORMSPEC_API_VERSION)) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); - std::string name = parts[2]; - std::string label = parts[3]; - - MY_CHECKPOS("pwdfield",0); - MY_CHECKGEOM("pwdfield",1); + std::vector parts; + if (!precheckElement("pwdfield", element, 4, 4, parts)) + return; - v2s32 pos; - v2s32 geom; + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = parts[2]; + std::string label = parts[3]; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - pos -= padding; + MY_CHECKPOS("pwdfield",0); + MY_CHECKGEOM("pwdfield",1); - geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); + v2s32 pos; + v2s32 geom; - pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; - pos.Y -= m_btn_height; - geom.Y = m_btn_height*2; - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + pos -= padding; - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); - std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + pos.Y -= m_btn_height; + geom.Y = m_btn_height*2; + } - FieldSpec spec( - name, - wlabel, - L"", - 258 + m_fields.size(), - 0, - ECI_IBEAM - ); + core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - spec.send = true; - gui::IGUIEditBox *e = Environment->addEditBox(0, rect, true, - data->current_parent, spec.fid); + std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + FieldSpec spec( + name, + wlabel, + L"", + 258 + m_fields.size(), + 0, + ECI_IBEAM + ); - if (label.length() >= 1) { - int font_height = g_fontengine->getTextHeight(); - rect.UpperLeftCorner.Y -= font_height; - rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; - gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true, - data->current_parent, 0); - } + spec.send = true; + gui::IGUIEditBox *e = Environment->addEditBox(0, rect, true, + data->current_parent, spec.fid); - e->setPasswordBox(true,L'*'); + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } - auto style = getDefaultStyleForElement("pwdfield", name, "field"); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); - e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); - e->setOverrideFont(style.getFont()); + if (label.length() >= 1) { + int font_height = g_fontengine->getTextHeight(); + rect.UpperLeftCorner.Y -= font_height; + rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; + gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true, + data->current_parent, 0); + } - irr::SEvent evt; - evt.EventType = EET_KEY_INPUT_EVENT; - evt.KeyInput.Key = KEY_END; - evt.KeyInput.Char = 0; - evt.KeyInput.Control = false; - evt.KeyInput.Shift = false; - evt.KeyInput.PressedDown = true; - e->OnEvent(evt); + e->setPasswordBox(true,L'*'); - // Note: Before 5.2.0 "parts.size() >= 5" resulted in a - // warning referring to field_close_on_enter[]! + auto style = getDefaultStyleForElement("pwdfield", name, "field"); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); + e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideFont(style.getFont()); + + irr::SEvent evt; + evt.EventType = EET_KEY_INPUT_EVENT; + evt.KeyInput.Key = KEY_END; + evt.KeyInput.Char = 0; + evt.KeyInput.Control = false; + evt.KeyInput.Shift = false; + evt.KeyInput.PressedDown = true; + e->OnEvent(evt); + + // Note: Before 5.2.0 "parts.size() >= 5" resulted in a + // warning referring to field_close_on_enter[]! - m_fields.push_back(spec); - return; - } - errorstream<< "Invalid pwdfield element(" << parts.size() << "): '" << element << "'" << std::endl; + m_fields.push_back(spec); } void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, @@ -1546,21 +1490,13 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, } gui::IGUIEditBox *e = nullptr; - static constexpr bool use_intl_edit_box = USE_FREETYPE && - IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9; - - if (use_intl_edit_box && g_settings->getBool("freetype")) { - e = new gui::intlGUIEditBox(spec.fdefault.c_str(), true, Environment, - data->current_parent, spec.fid, rect, is_editable, is_multiline); - } else { - if (is_multiline) { - e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, Environment, - data->current_parent, spec.fid, rect, is_editable, true); - } else if (is_editable) { - e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, - data->current_parent, spec.fid); - e->grab(); - } + if (is_multiline) { + e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, Environment, + data->current_parent, spec.fid, rect, is_editable, true); + } else if (is_editable) { + e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, + data->current_parent, spec.fid); + e->grab(); } auto style = getDefaultStyleForElement(is_multiline ? "textarea" : "field", spec.fname); @@ -1585,11 +1521,10 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, } e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); - if (style.get(StyleSpec::BGCOLOR, "") == "transparent") { - e->setDrawBackground(false); - } + bool border = style.getBool(StyleSpec::BORDER, true); + e->setDrawBorder(border); + e->setDrawBackground(border); e->setOverrideFont(style.getFont()); e->drop(); @@ -1719,30 +1654,26 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector& void GUIFormSpecMenu::parseField(parserData* data, const std::string &element, const std::string &type) { - std::vector parts = split(element,';'); + std::vector parts; + if (!precheckElement(type, element, 3, 5, parts)) + return; if (parts.size() == 3 || parts.size() == 4) { - parseSimpleField(data,parts); + parseSimpleField(data, parts); return; } - if ((parts.size() == 5) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - parseTextArea(data,parts,type); - return; - } - errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'" << std::endl; + // Else: >= 5 arguments in "parts" + parseTextArea(data, parts, type); } void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &element) { - std::vector parts = split(element, ';'); + MY_CHECKCLIENT("hypertext"); - if (parts.size() != 4 && m_formspec_version < FORMSPEC_API_VERSION) { - errorstream << "Invalid text element(" << parts.size() << "): '" << element << "'" << std::endl; + std::vector parts; + if (!precheckElement("hypertext", element, 4, 4, parts)) return; - } std::vector v_pos = split(parts[0], ','); std::vector v_geom = split(parts[1], ','); @@ -1793,546 +1724,530 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts; + if (!precheckElement("label", element, 2, 2, parts)) + return; - if ((parts.size() == 2) || - ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::string text = parts[1]; + std::vector v_pos = split(parts[0],','); - MY_CHECKPOS("label",0); + MY_CHECKPOS("label",0); - if(!data->explicit_size) - warningstream<<"invalid use of label without a size[] element"<explicit_size) + warningstream<<"invalid use of label without a size[] element"< lines = split(text, '\n'); + auto style = getDefaultStyleForElement("label", ""); + gui::IGUIFont *font = style.getFont(); + if (!font) + font = m_font; - auto style = getDefaultStyleForElement("label", ""); - gui::IGUIFont *font = style.getFont(); - if (!font) - font = m_font; + EnrichedString str(unescape_string(utf8_to_wide(parts[1]))); + size_t str_pos = 0; - for (unsigned int i = 0; i != lines.size(); i++) { - std::wstring wlabel_colors = translate_string( - utf8_to_wide(unescape_string(lines[i]))); - // Without color escapes to get the font dimensions - std::wstring wlabel_plain = unescape_enriched(wlabel_colors); + for (size_t i = 0; str_pos < str.size(); ++i) { + // Split per line + size_t str_nl = str.getString().find(L'\n', str_pos); + if (str_nl == std::wstring::npos) + str_nl = str.getString().size(); + EnrichedString line = str.substr(str_pos, str_nl - str_pos); + str_pos += line.size() + 1; - core::rect rect; + core::rect rect; - if (data->real_coordinates) { - // Lines are spaced at the distance of 1/2 imgsize. - // This alows lines that line up with the new elements - // easily without sacrificing good line distance. If - // it was one whole imgsize, it would have too much - // spacing. - v2s32 pos = getRealCoordinateBasePos(v_pos); + if (data->real_coordinates) { + // Lines are spaced at the distance of 1/2 imgsize. + // This alows lines that line up with the new elements + // easily without sacrificing good line distance. If + // it was one whole imgsize, it would have too much + // spacing. + v2s32 pos = getRealCoordinateBasePos(v_pos); - // Labels are positioned by their center, not their top. - pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2); + // Labels are positioned by their center, not their top. + pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2); - rect = core::rect( - pos.X, pos.Y, - pos.X + font->getDimension(wlabel_plain.c_str()).Width, - pos.Y + imgsize.Y); + rect = core::rect( + pos.X, pos.Y, + pos.X + font->getDimension(line.c_str()).Width, + pos.Y + imgsize.Y); - } else { - // Lines are spaced at the nominal distance of - // 2/5 inventory slot, even if the font doesn't - // quite match that. This provides consistent - // form layout, at the expense of sometimes - // having sub-optimal spacing for the font. - // We multiply by 2 and then divide by 5, rather - // than multiply by 0.4, to get exact results - // in the integer cases: 0.4 is not exactly - // representable in binary floating point. - - v2s32 pos = getElementBasePos(nullptr); - pos.X += stof(v_pos[0]) * spacing.X; - pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y; - - pos.Y += ((float) i) * spacing.Y * 2.0 / 5.0; - - rect = core::rect( - pos.X, pos.Y - m_btn_height, - pos.X + font->getDimension(wlabel_plain.c_str()).Width, - pos.Y + m_btn_height); - } + } else { + // Lines are spaced at the nominal distance of + // 2/5 inventory slot, even if the font doesn't + // quite match that. This provides consistent + // form layout, at the expense of sometimes + // having sub-optimal spacing for the font. + // We multiply by 2 and then divide by 5, rather + // than multiply by 0.4, to get exact results + // in the integer cases: 0.4 is not exactly + // representable in binary floating point. + + v2s32 pos = getElementBasePos(nullptr); + pos.X += stof(v_pos[0]) * spacing.X; + pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y; + + pos.Y += ((float) i) * spacing.Y * 2.0 / 5.0; - FieldSpec spec( - "", - wlabel_colors, - L"", - 258 + m_fields.size(), - 4 - ); - gui::IGUIStaticText *e = gui::StaticText::add(Environment, - spec.flabel.c_str(), rect, false, false, data->current_parent, - spec.fid); - e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); + rect = core::rect( + pos.X, pos.Y - m_btn_height, + pos.X + font->getDimension(line.c_str()).Width, + pos.Y + m_btn_height); + } - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); - e->setOverrideFont(font); + FieldSpec spec( + "", + L"", + L"", + 258 + m_fields.size(), + 4 + ); + gui::IGUIStaticText *e = gui::StaticText::add(Environment, + line, rect, false, false, data->current_parent, + spec.fid); + e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); - m_fields.push_back(spec); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideFont(font); - // labels should let events through - e->grab(); - m_clickthrough_elements.push_back(e); - } + m_fields.push_back(spec); - return; + // labels should let events through + e->grab(); + m_clickthrough_elements.push_back(e); } - errorstream << "Invalid label element(" << parts.size() << "): '" << element - << "'" << std::endl; } void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts; + if (!precheckElement("vertlabel", element, 2, 2, parts)) + return; - if ((parts.size() == 2) || - ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::wstring text = unescape_translate( - unescape_string(utf8_to_wide(parts[1]))); + std::vector v_pos = split(parts[0],','); + std::wstring text = unescape_translate( + unescape_string(utf8_to_wide(parts[1]))); - MY_CHECKPOS("vertlabel",1); + MY_CHECKPOS("vertlabel",1); - auto style = getDefaultStyleForElement("vertlabel", "", "label"); - gui::IGUIFont *font = style.getFont(); - if (!font) - font = m_font; + auto style = getDefaultStyleForElement("vertlabel", "", "label"); + gui::IGUIFont *font = style.getFont(); + if (!font) + font = m_font; - v2s32 pos; - core::rect rect; + v2s32 pos; + core::rect rect; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); - // Vertlabels are positioned by center, not left. - pos.X -= imgsize.X / 2; + // Vertlabels are positioned by center, not left. + pos.X -= imgsize.X / 2; - // We use text.length + 1 because without it, the rect - // isn't quite tall enough and cuts off the text. - rect = core::rect(pos.X, pos.Y, - pos.X + imgsize.X, - pos.Y + font_line_height(font) * - (text.length() + 1)); + // We use text.length + 1 because without it, the rect + // isn't quite tall enough and cuts off the text. + rect = core::rect(pos.X, pos.Y, + pos.X + imgsize.X, + pos.Y + font_line_height(font) * + (text.length() + 1)); - } else { - pos = getElementBasePos(&v_pos); + } else { + pos = getElementBasePos(&v_pos); - // As above, the length must be one longer. The width of - // the rect (15 pixels) seems rather arbitrary, but - // changing it might break something. - rect = core::rect( - pos.X, pos.Y+((imgsize.Y/2) - m_btn_height), - pos.X+15, pos.Y + - font_line_height(font) * - (text.length() + 1) + - ((imgsize.Y/2) - m_btn_height)); - } + // As above, the length must be one longer. The width of + // the rect (15 pixels) seems rather arbitrary, but + // changing it might break something. + rect = core::rect( + pos.X, pos.Y+((imgsize.Y/2) - m_btn_height), + pos.X+15, pos.Y + + font_line_height(font) * + (text.length() + 1) + + ((imgsize.Y/2) - m_btn_height)); + } - if(!data->explicit_size) - warningstream<<"invalid use of label without a size[] element"<explicit_size) + warningstream<<"invalid use of label without a size[] element"<current_parent, spec.fid); - e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); + FieldSpec spec( + "", + label, + L"", + 258 + m_fields.size() + ); + gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(), + rect, false, false, data->current_parent, spec.fid); + e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); - e->setOverrideFont(font); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideFont(font); - m_fields.push_back(spec); + m_fields.push_back(spec); - // vertlabels should let events through - e->grab(); - m_clickthrough_elements.push_back(e); - return; - } - errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'" << std::endl; + // vertlabels should let events through + e->grab(); + m_clickthrough_elements.push_back(e); } void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &element, const std::string &type) { - std::vector parts = split(element,';'); - - if ((((parts.size() >= 5) && (parts.size() <= 8)) && (parts.size() != 6)) || - ((parts.size() > 8) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); - std::string image_name = parts[2]; - std::string name = parts[3]; - std::string label = parts[4]; + std::vector parts; + if (!precheckElement("image_button", element, 5, 8, parts)) + return; - MY_CHECKPOS("imagebutton",0); - MY_CHECKGEOM("imagebutton",1); + if (parts.size() == 6) { + // Invalid argument count. + errorstream << "Invalid image_button element(" << parts.size() << "): '" << element << "'" << std::endl; + return; + } - std::string pressed_image_name; + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string image_name = parts[2]; + std::string name = parts[3]; + std::string label = parts[4]; - if (parts.size() >= 8) { - pressed_image_name = parts[7]; - } + MY_CHECKPOS("image_button",0); + MY_CHECKGEOM("image_button",1); - v2s32 pos; - v2s32 geom; + std::string pressed_image_name; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); - geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); - } + if (parts.size() >= 8) { + pressed_image_name = parts[7]; + } - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, - pos.Y+geom.Y); + v2s32 pos; + v2s32 geom; - if (!data->explicit_size) - warningstream<<"invalid use of image_button without a size[] element"<real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); + geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); + } - image_name = unescape_string(image_name); - pressed_image_name = unescape_string(pressed_image_name); + core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, + pos.Y+geom.Y); - std::wstring wlabel = utf8_to_wide(unescape_string(label)); + if (!data->explicit_size) + warningstream<<"invalid use of image_button without a size[] element"<current_parent, spec.fid, spec.flabel.c_str()); + std::wstring wlabel = utf8_to_wide(unescape_string(label)); - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + FieldSpec spec( + name, + wlabel, + utf8_to_wide(image_name), + 258 + m_fields.size() + ); + spec.ftype = f_Button; + if (type == "image_button_exit") + spec.is_exit = true; - auto style = getStyleForElement("image_button", spec.fname); + GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, m_tsrc, + data->current_parent, spec.fid, spec.flabel.c_str()); - spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } - // Override style properties with values specified directly in the element - if (!image_name.empty()) - style[StyleSpec::STATE_DEFAULT].set(StyleSpec::FGIMG, image_name); + auto style = getStyleForElement("image_button", spec.fname); - if (!pressed_image_name.empty()) - style[StyleSpec::STATE_PRESSED].set(StyleSpec::FGIMG, pressed_image_name); + spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); - if (parts.size() >= 7) { - style[StyleSpec::STATE_DEFAULT].set(StyleSpec::NOCLIP, parts[5]); - style[StyleSpec::STATE_DEFAULT].set(StyleSpec::BORDER, parts[6]); - } + // Override style properties with values specified directly in the element + if (!image_name.empty()) + style[StyleSpec::STATE_DEFAULT].set(StyleSpec::FGIMG, image_name); - e->setStyles(style); - e->setScaleImage(true); + if (!pressed_image_name.empty()) + style[StyleSpec::STATE_PRESSED].set(StyleSpec::FGIMG, pressed_image_name); - m_fields.push_back(spec); - return; + if (parts.size() >= 7) { + style[StyleSpec::STATE_DEFAULT].set(StyleSpec::NOCLIP, parts[5]); + style[StyleSpec::STATE_DEFAULT].set(StyleSpec::BORDER, parts[6]); } - errorstream<< "Invalid imagebutton element(" << parts.size() << "): '" << element << "'" << std::endl; + e->setStyles(style); + e->setScaleImage(true); + + m_fields.push_back(spec); } void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &element) { - std::vector parts = split(element, ';'); - - if (((parts.size() == 4) || (parts.size() == 6)) || (parts.size() == 7 && - data->real_coordinates) || ((parts.size() > 6) && - (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - - // If we're using real coordinates, add an extra field for height. - // Width is not here because tabs are the width of the text, and - // there's no reason to change that. - unsigned int i = 0; - std::vector v_geom = {"1", "1"}; // Dummy width and height - bool auto_width = true; - if (parts.size() == 7) { - i++; + std::vector parts; + if (!precheckElement("tabheader", element, 4, 7, parts)) + return; - v_geom = split(parts[1], ','); - if (v_geom.size() == 1) - v_geom.insert(v_geom.begin(), "1"); // Dummy value - else - auto_width = false; - } + // Length 7: Additional "height" parameter after "pos". Only valid with real_coordinates. + // Note: New arguments for the "height" syntax cannot be added without breaking older clients. + if (parts.size() == 5 || (parts.size() == 7 && !data->real_coordinates)) { + errorstream << "Invalid tabheader element(" << parts.size() << "): '" + << element << "'" << std::endl; + return; + } - std::string name = parts[i+1]; - std::vector buttons = split(parts[i+2], ','); - std::string str_index = parts[i+3]; - bool show_background = true; - bool show_border = true; - int tab_index = stoi(str_index) - 1; + std::vector v_pos = split(parts[0],','); - MY_CHECKPOS("tabheader", 0); + // If we're using real coordinates, add an extra field for height. + // Width is not here because tabs are the width of the text, and + // there's no reason to change that. + unsigned int i = 0; + std::vector v_geom = {"1", "1"}; // Dummy width and height + bool auto_width = true; + if (parts.size() == 7) { + i++; + + v_geom = split(parts[1], ','); + if (v_geom.size() == 1) + v_geom.insert(v_geom.begin(), "1"); // Dummy value + else + auto_width = false; + } - if (parts.size() == 6 + i) { - if (parts[4+i] == "true") - show_background = false; - if (parts[5+i] == "false") - show_border = false; - } + std::string name = parts[i+1]; + std::vector buttons = split(parts[i+2], ','); + std::string str_index = parts[i+3]; + bool show_background = true; + bool show_border = true; + int tab_index = stoi(str_index) - 1; - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + MY_CHECKPOS("tabheader", 0); - spec.ftype = f_TabHeader; + if (parts.size() == 6 + i) { + if (parts[4+i] == "true") + show_background = false; + if (parts[5+i] == "false") + show_border = false; + } - v2s32 pos; - v2s32 geom; + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); + spec.ftype = f_TabHeader; - geom = getRealCoordinateGeometry(v_geom); - // Set default height - if (parts.size() <= 6) - geom.Y = m_btn_height * 2; - pos.Y -= geom.Y; // TabHeader base pos is the bottom, not the top. - if (auto_width) - geom.X = DesiredRect.getWidth(); // Set automatic width - - MY_CHECKGEOM("tabheader", 1); - } else { - v2f32 pos_f = pos_offset * spacing; - pos_f.X += stof(v_pos[0]) * spacing.X; - pos_f.Y += stof(v_pos[1]) * spacing.Y - m_btn_height * 2; - pos = v2s32(pos_f.X, pos_f.Y); + v2s32 pos; + v2s32 geom; + + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + // Set default height + if (parts.size() <= 6) geom.Y = m_btn_height * 2; - geom.X = DesiredRect.getWidth(); - } + pos.Y -= geom.Y; // TabHeader base pos is the bottom, not the top. + if (auto_width) + geom.X = DesiredRect.getWidth(); // Set automatic width - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, - pos.Y+geom.Y); + MY_CHECKGEOM("tabheader", 1); + } else { + v2f32 pos_f = pos_offset * spacing; + pos_f.X += stof(v_pos[0]) * spacing.X; + pos_f.Y += stof(v_pos[1]) * spacing.Y - m_btn_height * 2; + pos = v2s32(pos_f.X, pos_f.Y); - gui::IGUITabControl *e = Environment->addTabControl(rect, - data->current_parent, show_background, show_border, spec.fid); - e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT, - irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT); - e->setTabHeight(geom.Y); + geom.Y = m_btn_height * 2; + geom.X = DesiredRect.getWidth(); + } - auto style = getDefaultStyleForElement("tabheader", name); + core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, + pos.Y+geom.Y); - spec.sound = style.get(StyleSpec::Property::SOUND, ""); + gui::IGUITabControl *e = Environment->addTabControl(rect, + data->current_parent, show_background, show_border, spec.fid); + e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT, + irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT); + e->setTabHeight(geom.Y); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true)); + auto style = getDefaultStyleForElement("tabheader", name); - for (const std::string &button : buttons) { - auto tab = e->addTab(unescape_translate(unescape_string( - utf8_to_wide(button))).c_str(), -1); - if (style.isNotDefault(StyleSpec::BGCOLOR)) - tab->setBackgroundColor(style.getColor(StyleSpec::BGCOLOR)); + spec.sound = style.get(StyleSpec::Property::SOUND, ""); - tab->setTextColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); - } + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true)); - if ((tab_index >= 0) && - (buttons.size() < INT_MAX) && - (tab_index < (int) buttons.size())) - e->setActiveTab(tab_index); + for (const std::string &button : buttons) { + auto tab = e->addTab(unescape_translate(unescape_string( + utf8_to_wide(button))).c_str(), -1); + if (style.isNotDefault(StyleSpec::BGCOLOR)) + tab->setBackgroundColor(style.getColor(StyleSpec::BGCOLOR)); - m_fields.push_back(spec); - return; + tab->setTextColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); } - errorstream << "Invalid TabHeader element(" << parts.size() << "): '" - << element << "'" << std::endl; + + if ((tab_index >= 0) && + (buttons.size() < INT_MAX) && + (tab_index < (int) buttons.size())) + e->setActiveTab(tab_index); + + m_fields.push_back(spec); } void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element) { - if (m_client == 0) { - warningstream << "invalid use of item_image_button with m_client==0" - << std::endl; - return; - } + MY_CHECKCLIENT("item_image_button"); - std::vector parts = split(element,';'); - - if ((parts.size() == 5) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); - std::string item_name = parts[2]; - std::string name = parts[3]; - std::string label = parts[4]; + std::vector parts; + if (!precheckElement("item_image_button", element, 5, 5, parts)) + return; - label = unescape_string(label); - item_name = unescape_string(item_name); + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string item_name = parts[2]; + std::string name = parts[3]; + std::string label = parts[4]; - MY_CHECKPOS("itemimagebutton",0); - MY_CHECKGEOM("itemimagebutton",1); + label = unescape_string(label); + item_name = unescape_string(item_name); - v2s32 pos; - v2s32 geom; + MY_CHECKPOS("item_image_button",0); + MY_CHECKGEOM("item_image_button",1); - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); - geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); - } + v2s32 pos; + v2s32 geom; - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); + geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); + } - if(!data->explicit_size) - warningstream<<"invalid use of item_image_button without a size[] element"< rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - IItemDefManager *idef = m_client->idef(); - ItemStack item; - item.deSerialize(item_name, idef); + if(!data->explicit_size) + warningstream<<"invalid use of item_image_button without a size[] element"<idef(); + ItemStack item; + item.deSerialize(item_name, idef); - // the spec for the button - FieldSpec spec_btn( - name, - utf8_to_wide(label), - utf8_to_wide(item_name), - 258 + m_fields.size(), - 2 - ); + m_tooltips[name] = + TooltipSpec(utf8_to_wide(item.getDefinition(idef).description), + m_default_tooltip_bgcolor, + m_default_tooltip_color); - GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment, - rect, m_tsrc, data->current_parent, spec_btn.fid, spec_btn.flabel.c_str(), - item_name, m_client); + // the spec for the button + FieldSpec spec_btn( + name, + utf8_to_wide(label), + utf8_to_wide(item_name), + 258 + m_fields.size(), + 2 + ); - auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button"); + GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment, + rect, m_tsrc, data->current_parent, spec_btn.fid, spec_btn.flabel.c_str(), + item_name, m_client); - spec_btn.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); + auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button"); - e_btn->setStyles(style); + spec_btn.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); - if (spec_btn.fname == m_focused_element) { - Environment->setFocus(e_btn); - } + e_btn->setStyles(style); - spec_btn.ftype = f_Button; - rect += data->basepos-padding; - spec_btn.rect = rect; - m_fields.push_back(spec_btn); - return; + if (spec_btn.fname == m_focused_element) { + Environment->setFocus(e_btn); } - errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'" << std::endl; + + spec_btn.ftype = f_Button; + rect += data->basepos-padding; + spec_btn.rect = rect; + m_fields.push_back(spec_btn); } void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element) { - std::vector parts = split(element, ';'); + std::vector parts; + if (!precheckElement("box", element, 3, 3, parts)) + return; - if ((parts.size() == 3) || - ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0], ','); - std::vector v_geom = split(parts[1], ','); + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); - MY_CHECKPOS("box", 0); - MY_CHECKGEOM("box", 1); + MY_CHECKPOS("box", 0); + MY_CHECKGEOM("box", 1); - v2s32 pos; - v2s32 geom; + v2s32 pos; + v2s32 geom; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = stof(v_geom[0]) * spacing.X; + geom.Y = stof(v_geom[1]) * spacing.Y; + } - FieldSpec spec( - "", - L"", - L"", - 258 + m_fields.size(), - -2 - ); - spec.ftype = f_Box; + FieldSpec spec( + "", + L"", + L"", + 258 + m_fields.size(), + -2 + ); + spec.ftype = f_Box; - auto style = getDefaultStyleForElement("box", spec.fname); + auto style = getDefaultStyleForElement("box", spec.fname); - video::SColor tmp_color; - std::array colors; - std::array bordercolors = {0x0, 0x0, 0x0, 0x0}; - std::array borderwidths = {0, 0, 0, 0}; + video::SColor tmp_color; + std::array colors; + std::array bordercolors = {0x0, 0x0, 0x0, 0x0}; + std::array borderwidths = {0, 0, 0, 0}; - if (parseColorString(parts[2], tmp_color, true, 0x8C)) { - colors = {tmp_color, tmp_color, tmp_color, tmp_color}; - } else { - colors = style.getColorArray(StyleSpec::COLORS, {0x0, 0x0, 0x0, 0x0}); - bordercolors = style.getColorArray(StyleSpec::BORDERCOLORS, - {0x0, 0x0, 0x0, 0x0}); - borderwidths = style.getIntArray(StyleSpec::BORDERWIDTHS, {0, 0, 0, 0}); - } + if (parseColorString(parts[2], tmp_color, true, 0x8C)) { + colors = {tmp_color, tmp_color, tmp_color, tmp_color}; + } else { + colors = style.getColorArray(StyleSpec::COLORS, {0x0, 0x0, 0x0, 0x0}); + bordercolors = style.getColorArray(StyleSpec::BORDERCOLORS, + {0x0, 0x0, 0x0, 0x0}); + borderwidths = style.getIntArray(StyleSpec::BORDERWIDTHS, {0, 0, 0, 0}); + } - core::rect rect(pos, pos + geom); + core::rect rect(pos, pos + geom); - GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, rect, - colors, bordercolors, borderwidths); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); - e->drop(); + GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, rect, + colors, bordercolors, borderwidths); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); + e->drop(); - m_fields.push_back(spec); - return; - } - errorstream << "Invalid Box element(" << parts.size() << "): '" << element - << "'" << std::endl; + m_fields.push_back(spec); } void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts; + if (!precheckElement("bgcolor", element, 1, 3, parts)) + return; + const u32 parameter_count = parts.size(); - if ((parameter_count > 2 && m_formspec_version < 3) || - (parameter_count > 3 && m_formspec_version <= FORMSPEC_API_VERSION)) { + if (parameter_count > 2 && m_formspec_version < 3) { errorstream << "Invalid bgcolor element(" << parameter_count << "): '" << element << "'" << std::endl; return; } // bgcolor - if (parameter_count >= 1 && parts[0] != "") + if (parameter_count >= 1 && !parts[0].empty()) parseColorString(parts[0], m_bgcolor, false); // fullscreen @@ -2343,62 +2258,64 @@ void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string & } else if (parts[1] == "neither") { m_bgnonfullscreen = false; m_bgfullscreen = false; - } else if (parts[1] != "" || m_formspec_version < 3) { + } else if (!parts[1].empty() || m_formspec_version < 3) { m_bgfullscreen = is_yes(parts[1]); m_bgnonfullscreen = !m_bgfullscreen; } } // fbgcolor - if (parameter_count >= 3 && parts[2] != "") + if (parameter_count >= 3 && !parts[2].empty()) parseColorString(parts[2], m_fullscreen_bgcolor, false); } void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts; + // Legacy Note: If clients older than 5.5.0-dev are supplied with additional arguments, + // the tooltip colors will be ignored. + if (!precheckElement("listcolors", element, 2, 5, parts)) + return; - if (((parts.size() == 2) || (parts.size() == 3) || (parts.size() == 5)) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - parseColorString(parts[0], data->inventorylist_options.slotbg_n, false); - parseColorString(parts[1], data->inventorylist_options.slotbg_h, false); + if (parts.size() == 4) { + // Invalid argument combination + errorstream << "Invalid listcolors element(" << parts.size() << "): '" + << element << "'" << std::endl; + return; + } - if (parts.size() >= 3) { - if (parseColorString(parts[2], data->inventorylist_options.slotbordercolor, - false)) { - data->inventorylist_options.slotborder = true; - } - } - if (parts.size() == 5) { - video::SColor tmp_color; + parseColorString(parts[0], data->inventorylist_options.slotbg_n, false); + parseColorString(parts[1], data->inventorylist_options.slotbg_h, false); - if (parseColorString(parts[3], tmp_color, false)) - m_default_tooltip_bgcolor = tmp_color; - if (parseColorString(parts[4], tmp_color, false)) - m_default_tooltip_color = tmp_color; + if (parts.size() >= 3) { + if (parseColorString(parts[2], data->inventorylist_options.slotbordercolor, + false)) { + data->inventorylist_options.slotborder = true; } + } + if (parts.size() >= 5) { + video::SColor tmp_color; - // update all already parsed inventorylists - for (GUIInventoryList *e : m_inventorylists) { - e->setSlotBGColors(data->inventorylist_options.slotbg_n, - data->inventorylist_options.slotbg_h); - e->setSlotBorders(data->inventorylist_options.slotborder, - data->inventorylist_options.slotbordercolor); - } - return; + if (parseColorString(parts[3], tmp_color, false)) + m_default_tooltip_bgcolor = tmp_color; + if (parseColorString(parts[4], tmp_color, false)) + m_default_tooltip_color = tmp_color; + } + + // update all already parsed inventorylists + for (GUIInventoryList *e : m_inventorylists) { + e->setSlotBGColors(data->inventorylist_options.slotbg_n, + data->inventorylist_options.slotbg_h); + e->setSlotBorders(data->inventorylist_options.slotborder, + data->inventorylist_options.slotbordercolor); } - errorstream<< "Invalid listcolors element(" << parts.size() << "): '" << element << "'" << std::endl; } void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); - if (parts.size() < 2) { - errorstream << "Invalid tooltip element(" << parts.size() << "): '" - << element << "'" << std::endl; + std::vector parts; + if (!precheckElement("tooltip", element, 2, 5, parts)) return; - } // Get mode and check size bool rect_mode = parts[0].find(',') != std::string::npos; @@ -2538,11 +2455,16 @@ bool GUIFormSpecMenu::parsePositionDirect(parserData *data, const std::string &e void GUIFormSpecMenu::parsePosition(parserData *data, const std::string &element) { - std::vector parts = split(element, ','); + std::vector parts = split(element, ';'); - if (parts.size() == 2) { - data->offset.X = stof(parts[0]); - data->offset.Y = stof(parts[1]); + if (parts.size() == 1 || + (parts.size() > 1 && m_formspec_version > FORMSPEC_API_VERSION)) { + std::vector v_geom = split(parts[0], ','); + + MY_CHECKGEOM("position", 0); + + data->offset.X = stof(v_geom[0]); + data->offset.Y = stof(v_geom[1]); return; } @@ -2572,11 +2494,16 @@ bool GUIFormSpecMenu::parseAnchorDirect(parserData *data, const std::string &ele void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element) { - std::vector parts = split(element, ','); + std::vector parts = split(element, ';'); - if (parts.size() == 2) { - data->anchor.X = stof(parts[0]); - data->anchor.Y = stof(parts[1]); + if (parts.size() == 1 || + (parts.size() > 1 && m_formspec_version > FORMSPEC_API_VERSION)) { + std::vector v_geom = split(parts[0], ','); + + MY_CHECKGEOM("anchor", 0); + + data->anchor.X = stof(v_geom[0]); + data->anchor.Y = stof(v_geom[1]); return; } @@ -2584,6 +2511,46 @@ void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element) << "'" << std::endl; } +bool GUIFormSpecMenu::parsePaddingDirect(parserData *data, const std::string &element) +{ + if (element.empty()) + return false; + + std::vector parts = split(element, '['); + + if (parts.size() != 2) + return false; + + std::string type = trim(parts[0]); + std::string description = trim(parts[1]); + + if (type != "padding") + return false; + + parsePadding(data, description); + + return true; +} + +void GUIFormSpecMenu::parsePadding(parserData *data, const std::string &element) +{ + std::vector parts = split(element, ';'); + + if (parts.size() == 1 || + (parts.size() > 1 && m_formspec_version > FORMSPEC_API_VERSION)) { + std::vector v_geom = split(parts[0], ','); + + MY_CHECKGEOM("padding", 0); + + data->padding.X = stof(v_geom[0]); + data->padding.Y = stof(v_geom[1]); + return; + } + + errorstream << "Invalid padding element (" << parts.size() << "): '" << element + << "'" << std::endl; +} + bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, bool style_type) { std::vector parts = split(element, ';'); @@ -2722,39 +2689,29 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b void GUIFormSpecMenu::parseSetFocus(const std::string &element) { - std::vector parts = split(element, ';'); - - if (parts.size() <= 2 || - (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) - { - if (m_is_form_regenerated) - return; // Never focus on resizing - - bool force_focus = parts.size() >= 2 && is_yes(parts[1]); - if (force_focus || m_text_dst->m_formname != m_last_formname) - setFocus(parts[0]); - + std::vector parts; + if (!precheckElement("set_focus", element, 1, 2, parts)) return; - } - errorstream << "Invalid set_focus element (" << parts.size() << "): '" << element - << "'" << std::endl; + if (m_is_form_regenerated) + return; // Never focus on resizing + + bool force_focus = parts.size() >= 2 && is_yes(parts[1]); + if (force_focus || m_text_dst->m_formname != m_last_formname) + setFocus(parts[0]); } void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element) { - std::vector parts = split(element, ';'); + MY_CHECKCLIENT("model"); - if (parts.size() < 5 || (parts.size() > 9 && - m_formspec_version <= FORMSPEC_API_VERSION)) { - errorstream << "Invalid model element (" << parts.size() << "): '" << element - << "'" << std::endl; + std::vector parts; + if (!precheckElement("model", element, 5, 10, parts)) return; - } // Avoid length checks by resizing - if (parts.size() < 9) - parts.resize(9); + if (parts.size() < 10) + parts.resize(10); std::vector v_pos = split(parts[0], ','); std::vector v_geom = split(parts[1], ','); @@ -2765,6 +2722,7 @@ void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element) bool inf_rotation = is_yes(parts[6]); bool mousectrl = is_yes(parts[7]) || parts[7].empty(); // default true std::vector frame_loop = split(parts[8], ','); + std::string speed = unescape_string(parts[9]); MY_CHECKPOS("model", 0); MY_CHECKGEOM("model", 1); @@ -2801,7 +2759,7 @@ void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element) core::rect rect(pos, pos + geom); - GUIScene *e = new GUIScene(Environment, RenderingEngine::get_scene_manager(), + GUIScene *e = new GUIScene(Environment, m_client->getSceneManager(), data->current_parent, rect, spec.fid); auto meshnode = e->setMesh(mesh); @@ -2824,6 +2782,7 @@ void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element) } e->setFrameLoop(frame_loop_begin, frame_loop_end); + e->setAnimationSpeed(stof(speed)); auto style = getStyleForElement("model", spec.fname); e->setStyles(style); @@ -2832,6 +2791,28 @@ void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element) m_fields.push_back(spec); } +void GUIFormSpecMenu::removeAll() +{ + // Remove children + removeAllChildren(); + removeTooltip(); + + for (auto &table_it : m_tables) + table_it.second->drop(); + for (auto &inventorylist_it : m_inventorylists) + inventorylist_it->drop(); + for (auto &checkbox_it : m_checkboxes) + checkbox_it.second->drop(); + for (auto &scrollbar_it : m_scrollbars) + scrollbar_it.second->drop(); + for (auto &tooltip_rect_it : m_tooltip_rects) + tooltip_rect_it.first->drop(); + for (auto &clickthrough_it : m_clickthrough_elements) + clickthrough_it->drop(); + for (auto &scroll_container_it : m_scroll_containers) + scroll_container_it.second->drop(); +} + void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) { //some prechecks @@ -3071,33 +3052,16 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) } } else { // Don't keep old focus value - m_focused_element = ""; + m_focused_element = nullopt; } - // Remove children - removeChildren(); - - for (auto &table_it : m_tables) - table_it.second->drop(); - for (auto &inventorylist_it : m_inventorylists) - inventorylist_it->drop(); - for (auto &checkbox_it : m_checkboxes) - checkbox_it.second->drop(); - for (auto &scrollbar_it : m_scrollbars) - scrollbar_it.second->drop(); - for (auto &background_it : m_backgrounds) - background_it->drop(); - for (auto &tooltip_rect_it : m_tooltip_rects) - tooltip_rect_it.first->drop(); - for (auto &clickthrough_it : m_clickthrough_elements) - clickthrough_it->drop(); - for (auto &scroll_container_it : m_scroll_containers) - scroll_container_it.second->drop(); + removeAll(); mydata.size = v2s32(100, 100); mydata.screensize = screensize; mydata.offset = v2f32(0.5f, 0.5f); mydata.anchor = v2f32(0.5f, 0.5f); + mydata.padding = v2f32(0.05f, 0.05f); mydata.simple_field_count = 0; // Base position of contents of form @@ -3107,7 +3071,6 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) mydata.current_parent = this; m_inventorylists.clear(); - m_backgrounds.clear(); m_tables.clear(); m_checkboxes.clear(); m_scrollbars.clear(); @@ -3200,7 +3163,14 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) } } - /* "no_prepend" element is always after "position" (or "size" element) if it used */ + /* "padding" element is always after "anchor" and previous if it is used */ + for (; i < elements.size(); i++) { + if (!parsePaddingDirect(&mydata, elements[i])) { + break; + } + } + + /* "no_prepend" element is always after "padding" and previous if it used */ bool enable_prepends = true; for (; i < elements.size(); i++) { if (elements[i].empty()) @@ -3247,8 +3217,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) offset = v2s32(0,0); } - double gui_scaling = g_settings->getFloat("gui_scaling"); - double screen_dpi = RenderingEngine::getDisplayDensity() * 96; + const double gui_scaling = g_settings->getFloat("gui_scaling", 0.5f, 42.0f); + const double screen_dpi = RenderingEngine::getDisplayDensity() * 96; double use_imgsize; if (m_lock) { @@ -3265,11 +3235,9 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) double fitx_imgsize; double fity_imgsize; - // Pad the screensize with 5% of the screensize on all sides to ensure - // that even the largest formspecs don't touch the screen borders. v2f padded_screensize( - mydata.screensize.X * 0.9f, - mydata.screensize.Y * 0.9f + mydata.screensize.X * (1.0f - mydata.padding.X * 2.0f), + mydata.screensize.Y * (1.0f - mydata.padding.Y * 2.0f) ); if (mydata.real_coordinates) { @@ -3285,13 +3253,15 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) ((15.0 / 13.0) * (0.85 + mydata.invsize.Y)); } -#ifdef __ANDROID__ + s32 min_screen_dim = std::min(padded_screensize.X, padded_screensize.Y); + +#ifdef HAVE_TOUCHSCREENGUI // In Android, the preferred imgsize should be larger to accommodate the // smaller screensize. - double prefer_imgsize = padded_screensize.Y / 10 * gui_scaling; + double prefer_imgsize = min_screen_dim / 10 * gui_scaling; #else // Desktop computers have more space, so try to fit 15 coordinates. - double prefer_imgsize = padded_screensize.Y / 15 * gui_scaling; + double prefer_imgsize = min_screen_dim / 15 * gui_scaling; #endif // Try to use the preferred imgsize, but if that's bigger than the maximum // size, use the maximum size. @@ -3354,10 +3324,19 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) gui::IGUIFont *old_font = skin->getFont(); skin->setFont(m_font); + // Add a new element that will hold all the background elements as its children. + // Because it is the first added element, all backgrounds will be behind all + // the other elements. + // (We use an arbitrarily big rect. The actual size is determined later by + // clipping to `this`.) + core::rect background_parent_rect(0, 0, 100000, 100000); + mydata.background_parent.reset(new gui::IGUIElement(EGUIET_ELEMENT, Environment, + this, -1, background_parent_rect)); + pos_offset = v2f32(); // used for formspec versions < 3 - core::list::Iterator legacy_sort_start = Children.getLast(); + std::list::iterator legacy_sort_start = std::prev(Children.end()); // last element if (enable_prepends) { // Backup the coordinates so that prepends can use the coordinates of choice. @@ -3372,7 +3351,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // legacy sorting for formspec versions < 3 if (m_formspec_version >= 3) // prepends do not need to be reordered - legacy_sort_start = Children.getLast(); + legacy_sort_start = std::prev(Children.end()); // last element else if (version_backup >= 3) // only prepends elements have to be reordered legacySortElements(legacy_sort_start); @@ -3453,7 +3432,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) } } -void GUIFormSpecMenu::legacySortElements(core::list::Iterator from) +void GUIFormSpecMenu::legacySortElements(std::list::iterator from) { /* Draw order for formspec_version <= 2: @@ -3470,17 +3449,16 @@ void GUIFormSpecMenu::legacySortElements(core::list::Iterator fro if (from == Children.end()) from = Children.begin(); else - from++; + ++from; - core::list::Iterator to = Children.end(); + std::list::iterator to = Children.end(); // 1: Copy into a sortable container - std::vector elements; - for (auto it = from; it != to; ++it) - elements.emplace_back(*it); + std::vector elements(from, to); // 2: Sort the container std::stable_sort(elements.begin(), elements.end(), [this] (const IGUIElement *a, const IGUIElement *b) -> bool { + // TODO: getSpecByID is a linear search. It should made O(1), or cached here. const FieldSpec *spec_a = getSpecByID(a->getID()); const FieldSpec *spec_b = getSpecByID(b->getID()); return spec_a && spec_b && @@ -3488,10 +3466,7 @@ void GUIFormSpecMenu::legacySortElements(core::list::Iterator fro }); // 3: Re-assign the pointers - for (auto e : elements) { - *from = e; - from++; - } + reorderChildren(from, to, elements); } #ifdef __ANDROID__ @@ -3529,10 +3504,10 @@ GUIInventoryList::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const s32 item_index = e->getItemIndexAtPos(p); if (item_index != -1) return GUIInventoryList::ItemSpec(e->getInventoryloc(), e->getListname(), - item_index); + item_index, e->getSlotSize()); } - return GUIInventoryList::ItemSpec(InventoryLocation(), "", -1); + return GUIInventoryList::ItemSpec(InventoryLocation(), "", -1, {0,0}); } void GUIFormSpecMenu::drawSelectedItem() @@ -3554,7 +3529,8 @@ void GUIFormSpecMenu::drawSelectedItem() ItemStack stack = list->getItem(m_selected_item->i); stack.count = m_selected_amount; - core::rect imgrect(0,0,imgsize.X,imgsize.Y); + v2s32 slotsize = m_selected_item->slotsize; + core::rect imgrect(0, 0, slotsize.X, slotsize.Y); core::rect rect = imgrect + (m_pointer - imgrect.getCenter()); rect.constrainTo(driver->getViewPort()); drawItemStack(driver, m_font, stack, rect, NULL, m_client, IT_ROT_DRAGGED); @@ -3609,15 +3585,6 @@ void GUIFormSpecMenu::drawMenu() } } - /* - Draw backgrounds - */ - for (gui::IGUIElement *e : m_backgrounds) { - e->setVisible(true); - e->draw(); - e->setVisible(false); - } - // Some elements are only visible while being drawn for (gui::IGUIElement *e : m_clickthrough_elements) e->setVisible(true); @@ -3625,12 +3592,11 @@ void GUIFormSpecMenu::drawMenu() /* This is where all the drawing happens. */ - core::list::Iterator it = Children.begin(); - for (; it != Children.end(); ++it) - if ((*it)->isNotClipped() || + for (auto child : Children) + if (child->isNotClipped() || AbsoluteClippingRect.isRectCollided( - (*it)->getAbsolutePosition())) - (*it)->draw(); + child->getAbsolutePosition())) + child->draw(); for (gui::IGUIElement *e : m_clickthrough_elements) e->setVisible(false); @@ -3666,13 +3632,21 @@ void GUIFormSpecMenu::drawMenu() #endif bool hovered_element_found = false; - if (hovered != NULL) { + if (hovered) { if (m_show_debug) { core::rect rect = hovered->getAbsoluteClippingRect(); driver->draw2DRectangle(0x22FFFF00, rect, &rect); } - s32 id = hovered->getID(); + // find the formspec-element of the hovered IGUIElement (a parent) + s32 id; + for (gui::IGUIElement *hovered_fselem = hovered; hovered_fselem; + hovered_fselem = hovered_fselem->getParent()) { + id = hovered_fselem->getID(); + if (id != -1) + break; + } + u64 delta = 0; if (id == -1) { m_old_tooltip_id = id; @@ -3748,7 +3722,7 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text, v2u32 screenSize = Environment->getVideoDriver()->getScreenSize(); int tooltip_offset_x = m_btn_height; int tooltip_offset_y = m_btn_height; -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI tooltip_offset_x *= 3; tooltip_offset_y = 0; if (m_pointer.X > (s32)screenSize.X / 2) @@ -3807,6 +3781,7 @@ void GUIFormSpecMenu::updateSelectedItem() m_selected_item->inventoryloc = e->getInventoryloc(); m_selected_item->listname = "craftresult"; m_selected_item->i = 0; + m_selected_item->slotsize = e->getSlotSize(); m_selected_amount = item.count; m_selected_dragging = false; break; @@ -3888,7 +3863,7 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode) if (!current_field_enter_pending.empty()) { fields["key_enter_field"] = current_field_enter_pending; - current_field_enter_pending = ""; + current_field_enter_pending.clear(); } if (current_keys_pending.key_escape) { @@ -3940,9 +3915,7 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode) } if (e != 0) { - std::stringstream ss; - ss << (e->getActiveTab() +1); - fields[name] = ss.str(); + fields[name] = itos(e->getActiveTab() + 1); } } else if (s.ftype == f_CheckBox) { // No dynamic cast possible due to some distributions shipped @@ -3968,12 +3941,10 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode) e = static_cast(element); if (e) { - std::stringstream os; - os << e->getPos(); if (s.fdefault == L"Changed") - fields[name] = "CHG:" + os.str(); + fields[name] = "CHG:" + itos(e->getPos()); else - fields[name] = "VAL:" + os.str(); + fields[name] = "VAL:" + itos(e->getPos()); } } else if (s.ftype == f_AnimatedImage) { // No dynamic cast possible due to some distributions shipped @@ -4542,7 +4513,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if ((s.ftype == f_TabHeader) && (s.fid == event.GUIEvent.Caller->getID())) { if (!s.sound.empty() && m_sound_manager) - m_sound_manager->playSound(s.sound, false, 1.0f); + m_sound_manager->playSound(SimpleSoundSpec(s.sound, 1.0f)); s.send = true; acceptInput(); s.send = false; @@ -4587,7 +4558,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if (s.ftype == f_Button || s.ftype == f_CheckBox) { if (!s.sound.empty() && m_sound_manager) - m_sound_manager->playSound(s.sound, false, 1.0f); + m_sound_manager->playSound(SimpleSoundSpec(s.sound, 1.0f)); s.send = true; if (s.is_exit) { @@ -4612,7 +4583,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } } if (!s.sound.empty() && m_sound_manager) - m_sound_manager->playSound(s.sound, false, 1.0f); + m_sound_manager->playSound(SimpleSoundSpec(s.sound, 1.0f)); s.send = true; acceptInput(quit_mode_no); @@ -4627,10 +4598,10 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } else if (s.ftype == f_ScrollBar) { s.fdefault = L"Changed"; acceptInput(quit_mode_no); - s.fdefault = L""; + s.fdefault.clear(); } else if (s.ftype == f_Unknown || s.ftype == f_HyperText) { if (!s.sound.empty() && m_sound_manager) - m_sound_manager->playSound(s.sound, false, 1.0f); + m_sound_manager->playSound(SimpleSoundSpec(s.sound, 1.0f)); s.send = true; acceptInput(); s.send = false;