diff options
Diffstat (limited to 'src/gui/guiFormSpecMenu.cpp')
-rw-r--r-- | src/gui/guiFormSpecMenu.cpp | 508 |
1 files changed, 354 insertions, 154 deletions
diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 59cd130ef..37edf3c4b 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -65,6 +65,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiInventoryList.h" #include "guiItemImage.h" #include "guiScrollBar.h" +#include "guiScrollContainer.h" #include "guiTable.h" #include "intlGUIEditBox.h" #include "guiHyperText.h" @@ -143,6 +144,8 @@ GUIFormSpecMenu::~GUIFormSpecMenu() 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(); delete m_selected_item; delete m_form_src; @@ -351,6 +354,102 @@ void GUIFormSpecMenu::parseContainerEnd(parserData* data) } } +void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string &element) +{ + std::vector<std::string> 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; + return; + } + + std::vector<std::string> v_pos = split(parts[0], ','); + std::vector<std::string> v_geom = split(parts[1], ','); + std::string scrollbar_name = parts[2]; + std::string orientation = parts[3]; + f32 scroll_factor = 0.1f; + if (parts.size() >= 5 && !parts[4].empty()) + scroll_factor = stof(parts[4]); + + MY_CHECKPOS("scroll_container", 0); + MY_CHECKGEOM("scroll_container", 1); + + v2s32 pos = getRealCoordinateBasePos(v_pos); + v2s32 geom = getRealCoordinateGeometry(v_geom); + + if (orientation == "vertical") + scroll_factor *= -imgsize.Y; + else if (orientation == "horizontal") + scroll_factor *= -imgsize.X; + else + warningstream << "GUIFormSpecMenu::parseScrollContainer(): " + << "Invalid scroll_container orientation: " << orientation + << std::endl; + + // old parent (at first: this) + // ^ is parent of clipper + // ^ is parent of mover + // ^ is parent of other elements + + // make clipper + core::rect<s32> rect_clipper = core::rect<s32>(pos, pos + geom); + + gui::IGUIElement *clipper = new gui::IGUIElement(EGUIET_ELEMENT, Environment, + data->current_parent, 0, rect_clipper); + + // make mover + FieldSpec spec_mover( + "", + L"", + L"", + 258 + m_fields.size() + ); + + core::rect<s32> rect_mover = core::rect<s32>(0, 0, geom.X, geom.Y); + + GUIScrollContainer *mover = new GUIScrollContainer(Environment, + clipper, spec_mover.fid, rect_mover, orientation, scroll_factor); + + data->current_parent = mover; + + m_scroll_containers.emplace_back(scrollbar_name, mover); + + m_fields.push_back(spec_mover); + + clipper->drop(); + + // remove interferring offset of normal containers + container_stack.push(pos_offset); + pos_offset.X = 0.0f; + pos_offset.Y = 0.0f; +} + +void GUIFormSpecMenu::parseScrollContainerEnd(parserData *data) +{ + if (data->current_parent == this || data->current_parent->getParent() == this || + container_stack.empty()) { + errorstream << "Invalid scroll_container end element, " + << "no matching scroll_container start element" << std::endl; + return; + } + + if (pos_offset.getLengthSQ() != 0.0f) { + // pos_offset is only set by containers and scroll_containers. + // scroll_containers always set it to 0,0 which means that if it is + // not 0,0, it is a normal container that was opened last, not a + // scroll_container + errorstream << "Invalid scroll_container end element, " + << "an inner container was left open" << std::endl; + return; + } + + data->current_parent = data->current_parent->getParent()->getParent(); + pos_offset = container_stack.top(); + container_stack.pop(); +} + void GUIFormSpecMenu::parseList(parserData *data, const std::string &element) { if (m_client == 0) { @@ -390,38 +489,10 @@ void GUIFormSpecMenu::parseList(parserData *data, const std::string &element) start_i = stoi(startindex); if (geom.X < 0 || geom.Y < 0 || start_i < 0) { - errorstream<< "Invalid list element: '" << element << "'" << std::endl; + errorstream << "Invalid list element: '" << element << "'" << std::endl; return; } - // check for the existence of inventory and list - Inventory *inv = m_invmgr->getInventory(loc); - if (!inv) { - warningstream << "GUIFormSpecMenu::parseList(): " - << "The inventory location " - << "\"" << loc.dump() << "\" doesn't exist" - << std::endl; - return; - } - InventoryList *ilist = inv->getList(listname); - if (!ilist) { - warningstream << "GUIFormSpecMenu::parseList(): " - << "The inventory list \"" << listname << "\" " - << "@ \"" << loc.dump() << "\" doesn't exist" - << std::endl; - return; - } - - // trim geom if it is larger than the actual inventory size - s32 list_size = (s32)ilist->getSize(); - if (list_size < geom.X * geom.Y + start_i) { - list_size -= MYMAX(start_i, 0); - geom.Y = list_size / geom.X; - geom.Y += list_size % geom.X > 0 ? 1 : 0; - if (geom.Y <= 1) - geom.X = list_size; - } - if (!data->explicit_size) warningstream << "invalid use of list without a size[] element" << std::endl; @@ -443,9 +514,9 @@ void GUIFormSpecMenu::parseList(parserData *data, const std::string &element) pos.X + (geom.X - 1) * slot_spacing.X + imgsize.X, pos.Y + (geom.Y - 1) * slot_spacing.Y + imgsize.Y); - GUIInventoryList *e = new GUIInventoryList(Environment, this, spec.fid, - rect, m_invmgr, loc, listname, geom, start_i, imgsize, slot_spacing, - this, data->inventorylist_options, m_font); + GUIInventoryList *e = new GUIInventoryList(Environment, data->current_parent, + spec.fid, rect, m_invmgr, loc, listname, geom, start_i, imgsize, + slot_spacing, this, data->inventorylist_options, m_font); m_inventorylists.push_back(e); m_fields.push_back(spec); @@ -550,10 +621,10 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element spec.ftype = f_CheckBox; - gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect, this, - spec.fid, spec.flabel.c_str()); + gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect, + data->current_parent, spec.fid, spec.flabel.c_str()); - auto style = getStyleForElement("checkbox", name); + auto style = getDefaultStyleForElement("checkbox", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); if (spec.fname == data->focused_fieldname) { @@ -610,10 +681,10 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen spec.ftype = f_ScrollBar; spec.send = true; - GUIScrollBar *e = new GUIScrollBar(Environment, this, spec.fid, rect, - is_horizontal, true); + GUIScrollBar *e = new GUIScrollBar(Environment, data->current_parent, + spec.fid, rect, is_horizontal, true); - auto style = getStyleForElement("scrollbar", name); + auto style = getDefaultStyleForElement("scrollbar", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setArrowsVisible(data->scrollbar_options.arrow_visiblity); @@ -737,10 +808,11 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) 1 ); core::rect<s32> rect(pos, pos + geom); - gui::IGUIImage *e = Environment->addImage(rect, this, spec.fid, 0, true); + gui::IGUIImage *e = Environment->addImage(rect, data->current_parent, + spec.fid, 0, true); e->setImage(texture); e->setScaleImage(true); - auto style = getStyleForElement("image", spec.fname); + auto style = getDefaultStyleForElement("image", spec.fname); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); m_fields.push_back(spec); @@ -774,9 +846,9 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) L"", 258 + m_fields.size() ); - gui::IGUIImage *e = Environment->addImage(texture, pos, true, this, - spec.fid, 0); - auto style = getStyleForElement("image", spec.fname); + 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); @@ -841,9 +913,11 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el if (parts.size() >= 7) e->setFrameIndex(stoi(parts[6]) - 1); - auto style = getStyleForElement("animated_image", spec.fname, "image"); + auto style = getDefaultStyleForElement("animated_image", spec.fname, "image"); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->drop(); + + // Animated images should let events through + m_clickthrough_elements.push_back(e); m_fields.push_back(spec); } @@ -886,9 +960,9 @@ void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &elemen ); spec.ftype = f_ItemImage; - GUIItemImage *e = new GUIItemImage(Environment, this, spec.fid, + GUIItemImage *e = new GUIItemImage(Environment, data->current_parent, spec.fid, core::rect<s32>(pos, pos + geom), name, m_font, m_client); - auto style = getStyleForElement("item_image", spec.fname); + auto style = getDefaultStyleForElement("item_image", spec.fname); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); // item images should let events through @@ -949,10 +1023,11 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, if(type == "button_exit") spec.is_exit = true; - GUIButton *e = GUIButton::addButton(Environment, rect, this, 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" : ""); - e->setFromStyle(style, m_tsrc); + e->setStyles(style); if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); @@ -1140,7 +1215,8 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) } //now really show table - GUITable *e = new GUITable(Environment, this, spec.fid, rect, m_tsrc); + GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, + rect, m_tsrc); if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); @@ -1155,7 +1231,7 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) if (!str_initial_selection.empty() && str_initial_selection != "0") e->setSelected(stoi(str_initial_selection)); - auto style = getStyleForElement("table", name); + auto style = getDefaultStyleForElement("table", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); m_tables.emplace_back(spec, e); @@ -1216,7 +1292,8 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element } //now really show list - GUITable *e = new GUITable(Environment, this, spec.fid, rect, m_tsrc); + GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, + rect, m_tsrc); if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); @@ -1231,7 +1308,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element if (!str_initial_selection.empty() && str_initial_selection != "0") e->setSelected(stoi(str_initial_selection)); - auto style = getStyleForElement("textlist", name); + auto style = getDefaultStyleForElement("textlist", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); m_tables.emplace_back(spec, e); @@ -1292,7 +1369,8 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element spec.send = true; //now really show list - gui::IGUIComboBox *e = Environment->addComboBox(rect, this, spec.fid); + gui::IGUIComboBox *e = Environment->addComboBox(rect, data->current_parent, + spec.fid); if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); @@ -1306,7 +1384,7 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element if (!str_initial_selection.empty()) e->setSelected(stoi(str_initial_selection)-1); - auto style = getStyleForElement("dropdown", name); + auto style = getDefaultStyleForElement("dropdown", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); m_fields.push_back(spec); @@ -1378,7 +1456,8 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element ); spec.send = true; - gui::IGUIEditBox * e = Environment->addEditBox(0, rect, true, this, spec.fid); + gui::IGUIEditBox *e = Environment->addEditBox(0, rect, true, + data->current_parent, spec.fid); if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); @@ -1389,12 +1468,12 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element rect.UpperLeftCorner.Y -= font_height; rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true, - this, 0); + data->current_parent, 0); } e->setPasswordBox(true,L'*'); - auto style = getStyleForElement("pwdfield", name, "field"); + 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))); @@ -1424,7 +1503,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, if (!is_editable && !is_multiline) { // spec field id to 0, this stops submit searching for a value that isn't there gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true, - this, spec.fid); + data->current_parent, 0); return; } @@ -1442,19 +1521,19 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, if (use_intl_edit_box && g_settings->getBool("freetype")) { e = new gui::intlGUIEditBox(spec.fdefault.c_str(), true, Environment, - this, spec.fid, rect, is_editable, is_multiline); + data->current_parent, spec.fid, rect, is_editable, is_multiline); } else { if (is_multiline) { - e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, - Environment, this, spec.fid, rect, is_editable, true); + 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, this, - spec.fid); + e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, + data->current_parent, spec.fid); e->grab(); } } - auto style = getStyleForElement(is_multiline ? "textarea" : "field", spec.fname); + auto style = getDefaultStyleForElement(is_multiline ? "textarea" : "field", spec.fname); if (e) { if (is_editable && spec.fname == data->focused_fieldname) @@ -1490,7 +1569,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, rect.UpperLeftCorner.Y -= font_height; rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; IGUIElement *t = gui::StaticText::add(Environment, spec.flabel.c_str(), - rect, false, true, this, 0); + rect, false, true, data->current_parent, 0); if (t) t->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); @@ -1670,8 +1749,8 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen ); spec.ftype = f_HyperText; - GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, this, - spec.fid, rect, m_client, m_tsrc); + GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, + data->current_parent, spec.fid, rect, m_client, m_tsrc); e->drop(); m_fields.push_back(spec); @@ -1749,10 +1828,11 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) 4 ); gui::IGUIStaticText *e = gui::StaticText::add(Environment, - spec.flabel.c_str(), rect, false, false, this, spec.fid); + spec.flabel.c_str(), rect, false, false, data->current_parent, + spec.fid); e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); - auto style = getStyleForElement("label", spec.fname); + auto style = getDefaultStyleForElement("label", spec.fname); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); @@ -1829,10 +1909,10 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen 258 + m_fields.size() ); gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(), - rect, false, false, this, spec.fid); + rect, false, false, data->current_parent, spec.fid); e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); - auto style = getStyleForElement("vertlabel", spec.fname, "label"); + auto style = getDefaultStyleForElement("vertlabel", spec.fname, "label"); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); @@ -1863,17 +1943,8 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem MY_CHECKPOS("imagebutton",0); MY_CHECKGEOM("imagebutton",1); - bool noclip = false; - bool drawborder = true; std::string pressed_image_name; - if (parts.size() >= 7) { - if (parts[5] == "true") - noclip = true; - if (parts[6] == "false") - drawborder = false; - } - if (parts.size() >= 8) { pressed_image_name = parts[7]; } @@ -1911,35 +1982,30 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem if (type == "image_button_exit") spec.is_exit = true; - GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str()); + GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, m_tsrc, + data->current_parent, spec.fid, spec.flabel.c_str()); if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); } auto style = getStyleForElement("image_button", spec.fname); - e->setFromStyle(style, m_tsrc); - // We explicitly handle these arguments *after* the style properties in - // order to override them if they are provided + // Override style properties with values specified directly in the element if (!image_name.empty()) - { - video::ITexture *texture = m_tsrc->getTexture(image_name); - e->setForegroundImage(guiScalingImageButton( - Environment->getVideoDriver(), texture, geom.X, geom.Y)); - } - if (!pressed_image_name.empty()) { - video::ITexture *pressed_texture = m_tsrc->getTexture(pressed_image_name); - e->setPressedForegroundImage(guiScalingImageButton( - Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y)); - } - e->setScaleImage(true); + style[StyleSpec::STATE_DEFAULT].set(StyleSpec::FGIMG, image_name); + + if (!pressed_image_name.empty()) + style[StyleSpec::STATE_PRESSED].set(StyleSpec::FGIMG, pressed_image_name); if (parts.size() >= 7) { - e->setNotClipped(noclip); - e->setDrawBorder(drawborder); + style[StyleSpec::STATE_DEFAULT].set(StyleSpec::NOCLIP, parts[5]); + style[StyleSpec::STATE_DEFAULT].set(StyleSpec::BORDER, parts[6]); } + e->setStyles(style); + e->setScaleImage(true); + m_fields.push_back(spec); return; } @@ -1961,7 +2027,7 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen // 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<std::string> v_geom = {"1", "0.75"}; // Dummy width and default height + std::vector<std::string> v_geom = {"1", "1"}; // Dummy width and height bool auto_width = true; if (parts.size() == 7) { i++; @@ -2005,6 +2071,9 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen pos = getRealCoordinateBasePos(v_pos); 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 @@ -2023,8 +2092,8 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - gui::IGUITabControl *e = Environment->addTabControl(rect, this, - show_background, show_border, spec.fid); + 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); @@ -2033,7 +2102,7 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen Environment->setFocus(e); } - auto style = getStyleForElement("tabheader", name); + auto style = getDefaultStyleForElement("tabheader", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true)); for (const std::string &button : buttons) { @@ -2059,7 +2128,6 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen 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; @@ -2118,10 +2186,12 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & 2 ); - GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment, rect, this, spec_btn.fid, spec_btn.flabel.c_str(), item_name, m_client); + GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment, + rect, m_tsrc, data->current_parent, spec_btn.fid, spec_btn.flabel.c_str(), + item_name, m_client); auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button"); - e_btn->setFromStyle(style, m_tsrc); + e_btn->setStyles(style); if (spec_btn.fname == data->focused_fieldname) { Environment->setFocus(e_btn); @@ -2175,9 +2245,10 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element) core::rect<s32> rect(pos, pos + geom); - GUIBox *e = new GUIBox(Environment, this, spec.fid, rect, tmp_color); + GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, + rect, tmp_color); - auto style = getStyleForElement("box", spec.fname); + auto style = getDefaultStyleForElement("box", spec.fname); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); e->drop(); @@ -2327,7 +2398,7 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element) core::rect<s32> rect(pos, pos + geom); gui::IGUIElement *e = new gui::IGUIElement(EGUIET_ELEMENT, Environment, - this, fieldspec.fid, rect); + data->current_parent, fieldspec.fid, rect); // the element the rect tooltip is bound to should not block mouse-clicks e->setVisible(false); @@ -2469,6 +2540,7 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b StyleSpec spec; + // Parse properties for (size_t i = 1; i < parts.size(); i++) { size_t equal_pos = parts[i].find('='); if (equal_pos == std::string::npos) { @@ -2490,7 +2562,7 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b << "'" << std::endl; property_warned.insert(propname); } - return false; + continue; } spec.set(prop, value); @@ -2500,16 +2572,92 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b for (size_t sel = 0; sel < selectors.size(); sel++) { std::string selector = trim(selectors[sel]); - if (selector.empty()) { - errorstream << "Invalid style element (Empty selector): '" << element - << "'" << std::endl; + // Copy the style properties to a new StyleSpec + // This allows a separate state mask per-selector + StyleSpec selector_spec = spec; + + // Parse state information, if it exists + bool state_valid = true; + size_t state_pos = selector.find(':'); + if (state_pos != std::string::npos) { + std::string state_str = selector.substr(state_pos + 1); + selector = selector.substr(0, state_pos); + + if (state_str.empty()) { + errorstream << "Invalid style element (Invalid state): '" << element + << "'" << std::endl; + state_valid = false; + } else { + std::vector<std::string> states = split(state_str, '+'); + for (std::string &state : states) { + StyleSpec::State converted = StyleSpec::getStateByName(state); + if (converted == StyleSpec::STATE_INVALID) { + infostream << "Unknown style state " << state << + " in element '" << element << "'" << std::endl; + state_valid = false; + break; + } + + selector_spec.addState(converted); + } + } + } + + if (!state_valid) { + // Skip this selector continue; } if (style_type) { - theme_by_type[selector] |= spec; + theme_by_type[selector].push_back(selector_spec); } else { - theme_by_name[selector] |= spec; + theme_by_name[selector].push_back(selector_spec); + } + + // Backwards-compatibility for existing _hovered/_pressed properties + if (selector_spec.hasProperty(StyleSpec::BGCOLOR_HOVERED) + || selector_spec.hasProperty(StyleSpec::BGIMG_HOVERED) + || selector_spec.hasProperty(StyleSpec::FGIMG_HOVERED)) { + StyleSpec hover_spec; + hover_spec.addState(StyleSpec::STATE_HOVERED); + + if (selector_spec.hasProperty(StyleSpec::BGCOLOR_HOVERED)) { + hover_spec.set(StyleSpec::BGCOLOR, selector_spec.get(StyleSpec::BGCOLOR_HOVERED, "")); + } + if (selector_spec.hasProperty(StyleSpec::BGIMG_HOVERED)) { + hover_spec.set(StyleSpec::BGIMG, selector_spec.get(StyleSpec::BGIMG_HOVERED, "")); + } + if (selector_spec.hasProperty(StyleSpec::FGIMG_HOVERED)) { + hover_spec.set(StyleSpec::FGIMG, selector_spec.get(StyleSpec::FGIMG_HOVERED, "")); + } + + if (style_type) { + theme_by_type[selector].push_back(hover_spec); + } else { + theme_by_name[selector].push_back(hover_spec); + } + } + if (selector_spec.hasProperty(StyleSpec::BGCOLOR_PRESSED) + || selector_spec.hasProperty(StyleSpec::BGIMG_PRESSED) + || selector_spec.hasProperty(StyleSpec::FGIMG_PRESSED)) { + StyleSpec press_spec; + press_spec.addState(StyleSpec::STATE_PRESSED); + + if (selector_spec.hasProperty(StyleSpec::BGCOLOR_PRESSED)) { + press_spec.set(StyleSpec::BGCOLOR, selector_spec.get(StyleSpec::BGCOLOR_PRESSED, "")); + } + if (selector_spec.hasProperty(StyleSpec::BGIMG_PRESSED)) { + press_spec.set(StyleSpec::BGIMG, selector_spec.get(StyleSpec::BGIMG_PRESSED, "")); + } + if (selector_spec.hasProperty(StyleSpec::FGIMG_PRESSED)) { + press_spec.set(StyleSpec::FGIMG, selector_spec.get(StyleSpec::FGIMG_PRESSED, "")); + } + + if (style_type) { + theme_by_type[selector].push_back(press_spec); + } else { + theme_by_name[selector].push_back(press_spec); + } } } @@ -2525,24 +2673,12 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) if (parseVersionDirect(element)) return; - std::vector<std::string> parts = split(element,'['); - - // ugly workaround to keep compatibility - if (parts.size() > 2) { - if (trim(parts[0]) == "image") { - for (unsigned int i=2;i< parts.size(); i++) { - parts[1] += "[" + parts[i]; - } - } - else { return; } - } - - if (parts.size() < 2) { + size_t pos = element.find('['); + if (pos == std::string::npos) return; - } - std::string type = trim(parts[0]); - std::string description = trim(parts[1]); + std::string type = trim(element.substr(0, pos)); + std::string description = element.substr(pos+1); if (type == "container") { parseContainer(data, description); @@ -2709,6 +2845,16 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) return; } + if (type == "scroll_container") { + parseScrollContainer(data, description); + return; + } + + if (type == "scroll_container_end") { + parseScrollContainerEnd(data); + return; + } + // Ignore others infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\"" << std::endl; @@ -2765,8 +2911,10 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) 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(); - mydata.size= v2s32(100,100); + mydata.size = v2s32(100, 100); mydata.screensize = screensize; mydata.offset = v2f32(0.5f, 0.5f); mydata.anchor = v2f32(0.5f, 0.5f); @@ -2775,6 +2923,9 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // Base position of contents of form mydata.basepos = getBasePos(); + // the parent for the parsed elements + mydata.current_parent = this; + m_inventorylists.clear(); m_backgrounds.clear(); m_tables.clear(); @@ -2785,6 +2936,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) m_tooltip_rects.clear(); m_inventory_rings.clear(); m_dropdowns.clear(); + m_scroll_containers.clear(); theme_by_name.clear(); theme_by_type.clear(); m_clickthrough_elements.clear(); @@ -3051,11 +3203,24 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) parseElement(&mydata, elements[i]); } - if (!container_stack.empty()) { + if (mydata.current_parent != this) { + errorstream << "Invalid formspec string: scroll_container was never closed!" + << std::endl; + } else if (!container_stack.empty()) { errorstream << "Invalid formspec string: container was never closed!" << std::endl; } + // get the scrollbar elements for scroll_containers + for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers) { + for (const std::pair<FieldSpec, GUIScrollBar *> &b : m_scrollbars) { + if (c.first == b.first.fname) { + c.second->setScrollBar(b.second); + break; + } + } + } + // If there are fields without explicit size[], add a "Proceed" // button and adjust size to fit all the fields. if (mydata.simple_field_count > 0 && !mydata.explicit_size) { @@ -3080,7 +3245,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2 ); const wchar_t *text = wgettext("Proceed"); - GUIButton::addButton(Environment, mydata.rect, this, 257, text); + GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, 257, text); delete[] text; } } @@ -3146,28 +3311,24 @@ bool GUIFormSpecMenu::getAndroidUIInput() if (!hasAndroidUIInput()) return false; + // still waiting + if (porting::getInputDialogState() == -1) + return true; + std::string fieldname = m_jni_field_name; m_jni_field_name.clear(); - for (std::vector<FieldSpec>::iterator iter = m_fields.begin(); - iter != m_fields.end(); ++iter) { - - if (iter->fname != fieldname) { + for (const FieldSpec &field : m_fields) { + if (field.fname != fieldname) continue; - } - IGUIElement *tochange = getElementFromId(iter->fid, true); - if (tochange == 0) { - return false; - } + IGUIElement *element = getElementFromId(field.fid, true); - if (tochange->getType() != irr::gui::EGUIET_EDIT_BOX) { + if (!element || element->getType() != irr::gui::EGUIET_EDIT_BOX) return false; - } std::string text = porting::getInputDialogValue(); - - ((gui::IGUIEditBox *)tochange)->setText(utf8_to_wide(text).c_str()); + ((gui::IGUIEditBox *)element)->setText(utf8_to_wide(text).c_str()); } return false; } @@ -3314,8 +3475,12 @@ void GUIFormSpecMenu::drawMenu() bool hovered_element_found = false; if (hovered != NULL) { - s32 id = hovered->getID(); + if (m_show_debug) { + core::rect<s32> rect = hovered->getAbsoluteClippingRect(); + driver->draw2DRectangle(0x22FFFF00, rect, &rect); + } + s32 id = hovered->getID(); u64 delta = 0; if (id == -1) { m_old_tooltip_id = id; @@ -3395,6 +3560,10 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text, tooltip_offset_y = 0; if (m_pointer.X > (s32)screenSize.X / 2) tooltip_offset_x = -(tooltip_offset_x + tooltip_width); + + // Hide tooltip after ETIE_LEFT_UP + if (m_pointer.X == 0) + return; #endif // Calculate and set the tooltip position @@ -3837,6 +4006,10 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) (kp == getKeySetting("keymap_screenshot"))) { m_client->makeScreenshot(); } + + if (event.KeyInput.PressedDown && kp == getKeySetting("keymap_toggle_debug")) + m_show_debug = !m_show_debug; + if (event.KeyInput.PressedDown && (event.KeyInput.Key==KEY_RETURN || event.KeyInput.Key==KEY_UP || @@ -4344,6 +4517,12 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } } + if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) { + // move scroll_containers + for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers) + c.second->onScrollEvent(event.GUIEvent.Caller); + } + if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) { if (event.GUIEvent.Caller->getID() > 257) { bool close_on_enter = true; @@ -4432,25 +4611,46 @@ std::wstring GUIFormSpecMenu::getLabelByID(s32 id) return L""; } -StyleSpec GUIFormSpecMenu::getStyleForElement(const std::string &type, +StyleSpec GUIFormSpecMenu::getDefaultStyleForElement(const std::string &type, const std::string &name, const std::string &parent_type) { - StyleSpec ret; + return getStyleForElement(type, name, parent_type)[StyleSpec::STATE_DEFAULT]; +} + +std::array<StyleSpec, StyleSpec::NUM_STATES> GUIFormSpecMenu::getStyleForElement( + const std::string &type, const std::string &name, const std::string &parent_type) +{ + std::array<StyleSpec, StyleSpec::NUM_STATES> ret; + + auto it = theme_by_type.find("*"); + if (it != theme_by_type.end()) { + for (const StyleSpec &spec : it->second) + ret[(u32)spec.getState()] |= spec; + } + + it = theme_by_name.find("*"); + if (it != theme_by_name.end()) { + for (const StyleSpec &spec : it->second) + ret[(u32)spec.getState()] |= spec; + } if (!parent_type.empty()) { - auto it = theme_by_type.find(parent_type); + it = theme_by_type.find(parent_type); if (it != theme_by_type.end()) { - ret |= it->second; + for (const StyleSpec &spec : it->second) + ret[(u32)spec.getState()] |= spec; } } - auto it = theme_by_type.find(type); + it = theme_by_type.find(type); if (it != theme_by_type.end()) { - ret |= it->second; + for (const StyleSpec &spec : it->second) + ret[(u32)spec.getState()] |= spec; } it = theme_by_name.find(name); if (it != theme_by_name.end()) { - ret |= it->second; + for (const StyleSpec &spec : it->second) + ret[(u32)spec.getState()] |= spec; } return ret; |