Skip to content
Permalink
Browse files

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[<name>;<always set>]".
  • Loading branch information
v-rob committed Jul 12, 2020
1 parent d80def5 commit e0499731a867c76005f7cd83ee18c1a9503da719
Showing with 102 additions and 39 deletions.
  1. +24 −2 doc/lua_api.txt
  2. +71 −35 src/gui/guiFormSpecMenu.cpp
  3. +7 −2 src/gui/guiFormSpecMenu.h
@@ -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[<name>;<force>]`

* 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,
@@ -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<std::string> 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,43 +2877,50 @@ 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;
}

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();
}

//set focus
if (!m_focused_element.empty())
mydata.focused_fieldname = m_focused_element;
// 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();
}

//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<IGUIElement *>::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);
}
}
@@ -168,6 +168,7 @@ class GUIFormSpecMenu : public GUIModalMenu
{
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 @@ class GUIFormSpecMenu : public GUIModalMenu
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<GUIInventoryList *> m_inventorylists;
std::vector<ListRingSpec> m_inventory_rings;
std::vector<gui::IGUIElement *> m_backgrounds;
@@ -339,10 +344,10 @@ class GUIFormSpecMenu : public GUIModalMenu
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 @@ class GUIFormSpecMenu : public GUIModalMenu
core::rect<s32> 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 @@ class GUIFormSpecMenu : public GUIModalMenu
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();

0 comments on commit e049973

Please sign in to comment.
You can’t perform that action at this time.