From e0499731a867c76005f7cd83ee18c1a9503da719 Mon Sep 17 00:00:00 2001 From: v-rob Date: Sun, 12 Jul 2020 00:47:05 -0700 Subject: [PATCH] Allow FormSpec elements to be focused with `set_focus` (#9353) This allows you to specify a FormSpec element to set the focus of with "set_focus[;]". --- doc/lua_api.txt | 26 ++++++++- src/gui/guiFormSpecMenu.cpp | 106 ++++++++++++++++++++++++------------ src/gui/guiFormSpecMenu.h | 9 ++- 3 files changed, 102 insertions(+), 39 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 2ac4d6766..4e6983be8 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2492,7 +2492,7 @@ Elements * There are two ways to use it: 1. handle the changed event (only changed scrollbar is available) 2. read the value on pressing a button (all scrollbars are available) -* `orientation`: `vertical`/`horizontal` +* `orientation`: `vertical`/`horizontal`. Default horizontal. * Fieldname data is transferred to Lua * Value of this trackbar is set to (`0`-`1000`) by default * See also `minetest.explode_scrollbar_event` @@ -2606,6 +2606,28 @@ Elements * All provided states must be active for the style to apply. * See [Styling Formspecs]. +### `set_focus[;]` + +* Sets the focus to the element with the same `name` parameter. +* **Note**: This element must be placed before the element it focuses. +* `force` (optional, default `false`): By default, focus is not applied for + re-sent formspecs with the same name so that player-set focus is kept. + `true` sets the focus to the specified element for every sent formspec. +* The following elements have the ability to be focused: + * checkbox + * button + * button_exit + * image_button + * image_button_exit + * item_image_button + * table + * textlist + * dropdown + * field + * pwdfield + * textarea + * scrollbar + Migrating to Real Coordinates ----------------------------- @@ -4485,7 +4507,7 @@ Call these functions only at load time! * a button was pressed, * Enter was pressed while the focus was on a text field * a checkbox was toggled, - * something was selecteed in a drop-down list, + * something was selected in a dropdown list, * a different tab was selected, * selection was changed in a textlist or table, * an entry was double-clicked in a textlist or table, diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 54fc94993..9618e2134 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -627,7 +627,7 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element auto style = getDefaultStyleForElement("checkbox", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -703,6 +703,10 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size); + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } + m_scrollbars.emplace_back(spec,e); m_fields.push_back(spec); return; @@ -1029,7 +1033,7 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, auto style = getStyleForElement(type, name, (type != "button") ? "button" : ""); e->setStyles(style); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -1218,7 +1222,7 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, rect, m_tsrc); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -1295,7 +1299,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, rect, m_tsrc); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -1373,7 +1377,7 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element gui::IGUIComboBox *e = Environment->addComboBox(rect, data->current_parent, spec.fid); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -1460,7 +1464,7 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element gui::IGUIEditBox *e = Environment->addEditBox(0, rect, true, data->current_parent, spec.fid); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -1537,7 +1541,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, auto style = getDefaultStyleForElement(is_multiline ? "textarea" : "field", spec.fname); if (e) { - if (is_editable && spec.fname == data->focused_fieldname) + if (is_editable && spec.fname == m_focused_element) Environment->setFocus(e); if (is_multiline) { @@ -1986,7 +1990,7 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, m_tsrc, data->current_parent, spec.fid, spec.flabel.c_str()); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -2099,10 +2103,6 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT); e->setTabHeight(geom.Y); - if (spec.fname == data->focused_fieldname) { - Environment->setFocus(e); - } - auto style = getDefaultStyleForElement("tabheader", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true)); @@ -2194,7 +2194,7 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button"); e_btn->setStyles(style); - if (spec_btn.fname == data->focused_fieldname) { + if (spec_btn.fname == m_focused_element) { Environment->setFocus(e_btn); } @@ -2665,6 +2665,27 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b return true; } +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]); + + return; + } + + errorstream << "Invalid set_focus element (" << parts.size() << "): '" << element + << "'" << std::endl; +} + void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) { //some prechecks @@ -2856,6 +2877,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) return; } + if (type == "set_focus") { + parseSetFocus(description); + return; + } + // Ignore others infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\"" << std::endl; @@ -2863,36 +2889,38 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) void GUIFormSpecMenu::regenerateGui(v2u32 screensize) { - /* useless to regenerate without a screensize */ + // Useless to regenerate without a screensize if ((screensize.X <= 0) || (screensize.Y <= 0)) { return; } parserData mydata; - //preserve tables - for (auto &m_table : m_tables) { - std::string tablename = m_table.first.fname; - GUITable *table = m_table.second; - mydata.table_dyndata[tablename] = table->getDynamicData(); - } + // Preserve stuff only on same form, not on a new form. + if (m_text_dst->m_formname == m_last_formname) { + // Preserve tables/textlists + for (auto &m_table : m_tables) { + std::string tablename = m_table.first.fname; + GUITable *table = m_table.second; + mydata.table_dyndata[tablename] = table->getDynamicData(); + } - //set focus - if (!m_focused_element.empty()) - mydata.focused_fieldname = m_focused_element; - - //preserve focus - gui::IGUIElement *focused_element = Environment->getFocus(); - if (focused_element && focused_element->getParent() == this) { - s32 focused_id = focused_element->getID(); - if (focused_id > 257) { - for (const GUIFormSpecMenu::FieldSpec &field : m_fields) { - if (field.fid == focused_id) { - mydata.focused_fieldname = field.fname; - break; + // Preserve focus + gui::IGUIElement *focused_element = Environment->getFocus(); + if (focused_element && focused_element->getParent() == this) { + s32 focused_id = focused_element->getID(); + if (focused_id > 257) { + for (const GUIFormSpecMenu::FieldSpec &field : m_fields) { + if (field.fid == focused_id) { + m_focused_element = field.fname; + break; + } } } } + } else { + // Don't keep old focus value + m_focused_element = ""; } // Remove children @@ -3253,8 +3281,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) } } - //set initial focus if parser didn't set it - focused_element = Environment->getFocus(); + // Set initial focus if parser didn't set it + gui::IGUIElement *focused_element = Environment->getFocus(); if (!focused_element || !isMyChild(focused_element) || focused_element->getType() == gui::EGUIET_TAB_CONTROL) @@ -3265,6 +3293,13 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // legacy sorting if (m_formspec_version < 3) legacySortElements(legacy_sort_start); + + // Formname and regeneration setting + if (!m_is_form_regenerated) { + // Only set previous form name if we purposefully showed a new formspec + m_last_formname = m_text_dst->m_formname; + m_is_form_regenerated = true; + } } void GUIFormSpecMenu::legacySortElements(core::list::Iterator from) @@ -3382,6 +3417,7 @@ void GUIFormSpecMenu::drawMenu() const std::string &newform = m_form_src->getForm(); if (newform != m_formspec_string) { m_formspec_string = newform; + m_is_form_regenerated = false; regenerateGui(m_screensize_old); } } diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index a91afd2f7..19026bd34 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -168,6 +168,7 @@ public: { m_formspec_string = formspec_string; m_current_inventory_location = current_inventory_location; + m_is_form_regenerated = false; regenerateGui(m_screensize_old); } @@ -299,6 +300,10 @@ protected: std::string m_formspec_prepend; InventoryLocation m_current_inventory_location; + // Default true because we can't control regeneration on resizing, but + // we can control cases when the formspec is shown intentionally. + bool m_is_form_regenerated = true; + std::vector m_inventorylists; std::vector m_inventory_rings; std::vector m_backgrounds; @@ -339,10 +344,10 @@ protected: video::SColor m_default_tooltip_bgcolor; video::SColor m_default_tooltip_color; - private: IFormSource *m_form_src; TextDest *m_text_dst; + std::string m_last_formname; u16 m_formspec_version = 1; std::string m_focused_element = ""; JoystickController *m_joystick; @@ -359,7 +364,6 @@ private: core::rect rect; v2s32 basepos; v2u32 screensize; - std::string focused_fieldname; GUITable::TableOptions table_options; GUITable::TableColumns table_columns; gui::IGUIElement *current_parent = nullptr; @@ -439,6 +443,7 @@ private: bool parseAnchorDirect(parserData *data, const std::string &element); void parseAnchor(parserData *data, const std::string &element); bool parseStyle(parserData *data, const std::string &element, bool style_type); + void parseSetFocus(const std::string &element); void tryClose();