Skip to content

Commit e049973

Browse files
authored
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>]".
1 parent d80def5 commit e049973

File tree

3 files changed

+102
-39
lines changed

3 files changed

+102
-39
lines changed

doc/lua_api.txt

+24-2
Original file line numberDiff line numberDiff line change
@@ -2492,7 +2492,7 @@ Elements
24922492
* There are two ways to use it:
24932493
1. handle the changed event (only changed scrollbar is available)
24942494
2. read the value on pressing a button (all scrollbars are available)
2495-
* `orientation`: `vertical`/`horizontal`
2495+
* `orientation`: `vertical`/`horizontal`. Default horizontal.
24962496
* Fieldname data is transferred to Lua
24972497
* Value of this trackbar is set to (`0`-`1000`) by default
24982498
* See also `minetest.explode_scrollbar_event`
@@ -2606,6 +2606,28 @@ Elements
26062606
* All provided states must be active for the style to apply.
26072607
* See [Styling Formspecs].
26082608

2609+
### `set_focus[<name>;<force>]`
2610+
2611+
* Sets the focus to the element with the same `name` parameter.
2612+
* **Note**: This element must be placed before the element it focuses.
2613+
* `force` (optional, default `false`): By default, focus is not applied for
2614+
re-sent formspecs with the same name so that player-set focus is kept.
2615+
`true` sets the focus to the specified element for every sent formspec.
2616+
* The following elements have the ability to be focused:
2617+
* checkbox
2618+
* button
2619+
* button_exit
2620+
* image_button
2621+
* image_button_exit
2622+
* item_image_button
2623+
* table
2624+
* textlist
2625+
* dropdown
2626+
* field
2627+
* pwdfield
2628+
* textarea
2629+
* scrollbar
2630+
26092631
Migrating to Real Coordinates
26102632
-----------------------------
26112633

@@ -4485,7 +4507,7 @@ Call these functions only at load time!
44854507
* a button was pressed,
44864508
* Enter was pressed while the focus was on a text field
44874509
* a checkbox was toggled,
4488-
* something was selecteed in a drop-down list,
4510+
* something was selected in a dropdown list,
44894511
* a different tab was selected,
44904512
* selection was changed in a textlist or table,
44914513
* an entry was double-clicked in a textlist or table,

src/gui/guiFormSpecMenu.cpp

+71-35
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element
627627
auto style = getDefaultStyleForElement("checkbox", name);
628628
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
629629

630-
if (spec.fname == data->focused_fieldname) {
630+
if (spec.fname == m_focused_element) {
631631
Environment->setFocus(e);
632632
}
633633

@@ -703,6 +703,10 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen
703703

704704
e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size);
705705

706+
if (spec.fname == m_focused_element) {
707+
Environment->setFocus(e);
708+
}
709+
706710
m_scrollbars.emplace_back(spec,e);
707711
m_fields.push_back(spec);
708712
return;
@@ -1029,7 +1033,7 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
10291033
auto style = getStyleForElement(type, name, (type != "button") ? "button" : "");
10301034
e->setStyles(style);
10311035

1032-
if (spec.fname == data->focused_fieldname) {
1036+
if (spec.fname == m_focused_element) {
10331037
Environment->setFocus(e);
10341038
}
10351039

@@ -1218,7 +1222,7 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
12181222
GUITable *e = new GUITable(Environment, data->current_parent, spec.fid,
12191223
rect, m_tsrc);
12201224

1221-
if (spec.fname == data->focused_fieldname) {
1225+
if (spec.fname == m_focused_element) {
12221226
Environment->setFocus(e);
12231227
}
12241228

@@ -1295,7 +1299,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element
12951299
GUITable *e = new GUITable(Environment, data->current_parent, spec.fid,
12961300
rect, m_tsrc);
12971301

1298-
if (spec.fname == data->focused_fieldname) {
1302+
if (spec.fname == m_focused_element) {
12991303
Environment->setFocus(e);
13001304
}
13011305

@@ -1373,7 +1377,7 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element
13731377
gui::IGUIComboBox *e = Environment->addComboBox(rect, data->current_parent,
13741378
spec.fid);
13751379

1376-
if (spec.fname == data->focused_fieldname) {
1380+
if (spec.fname == m_focused_element) {
13771381
Environment->setFocus(e);
13781382
}
13791383

@@ -1460,7 +1464,7 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
14601464
gui::IGUIEditBox *e = Environment->addEditBox(0, rect, true,
14611465
data->current_parent, spec.fid);
14621466

1463-
if (spec.fname == data->focused_fieldname) {
1467+
if (spec.fname == m_focused_element) {
14641468
Environment->setFocus(e);
14651469
}
14661470

@@ -1537,7 +1541,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
15371541
auto style = getDefaultStyleForElement(is_multiline ? "textarea" : "field", spec.fname);
15381542

15391543
if (e) {
1540-
if (is_editable && spec.fname == data->focused_fieldname)
1544+
if (is_editable && spec.fname == m_focused_element)
15411545
Environment->setFocus(e);
15421546

15431547
if (is_multiline) {
@@ -1986,7 +1990,7 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
19861990
GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, m_tsrc,
19871991
data->current_parent, spec.fid, spec.flabel.c_str());
19881992

1989-
if (spec.fname == data->focused_fieldname) {
1993+
if (spec.fname == m_focused_element) {
19901994
Environment->setFocus(e);
19911995
}
19921996

@@ -2099,10 +2103,6 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
20992103
irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT);
21002104
e->setTabHeight(geom.Y);
21012105

2102-
if (spec.fname == data->focused_fieldname) {
2103-
Environment->setFocus(e);
2104-
}
2105-
21062106
auto style = getDefaultStyleForElement("tabheader", name);
21072107
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true));
21082108

@@ -2194,7 +2194,7 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &
21942194
auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button");
21952195
e_btn->setStyles(style);
21962196

2197-
if (spec_btn.fname == data->focused_fieldname) {
2197+
if (spec_btn.fname == m_focused_element) {
21982198
Environment->setFocus(e_btn);
21992199
}
22002200

@@ -2665,6 +2665,27 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b
26652665
return true;
26662666
}
26672667

2668+
void GUIFormSpecMenu::parseSetFocus(const std::string &element)
2669+
{
2670+
std::vector<std::string> parts = split(element, ';');
2671+
2672+
if (parts.size() <= 2 ||
2673+
(parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION))
2674+
{
2675+
if (m_is_form_regenerated)
2676+
return; // Never focus on resizing
2677+
2678+
bool force_focus = parts.size() >= 2 && is_yes(parts[1]);
2679+
if (force_focus || m_text_dst->m_formname != m_last_formname)
2680+
setFocus(parts[0]);
2681+
2682+
return;
2683+
}
2684+
2685+
errorstream << "Invalid set_focus element (" << parts.size() << "): '" << element
2686+
<< "'" << std::endl;
2687+
}
2688+
26682689
void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
26692690
{
26702691
//some prechecks
@@ -2856,43 +2877,50 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
28562877
return;
28572878
}
28582879

2880+
if (type == "set_focus") {
2881+
parseSetFocus(description);
2882+
return;
2883+
}
2884+
28592885
// Ignore others
28602886
infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
28612887
<< std::endl;
28622888
}
28632889

28642890
void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
28652891
{
2866-
/* useless to regenerate without a screensize */
2892+
// Useless to regenerate without a screensize
28672893
if ((screensize.X <= 0) || (screensize.Y <= 0)) {
28682894
return;
28692895
}
28702896

28712897
parserData mydata;
28722898

2873-
//preserve tables
2874-
for (auto &m_table : m_tables) {
2875-
std::string tablename = m_table.first.fname;
2876-
GUITable *table = m_table.second;
2877-
mydata.table_dyndata[tablename] = table->getDynamicData();
2878-
}
2879-
2880-
//set focus
2881-
if (!m_focused_element.empty())
2882-
mydata.focused_fieldname = m_focused_element;
2899+
// Preserve stuff only on same form, not on a new form.
2900+
if (m_text_dst->m_formname == m_last_formname) {
2901+
// Preserve tables/textlists
2902+
for (auto &m_table : m_tables) {
2903+
std::string tablename = m_table.first.fname;
2904+
GUITable *table = m_table.second;
2905+
mydata.table_dyndata[tablename] = table->getDynamicData();
2906+
}
28832907

2884-
//preserve focus
2885-
gui::IGUIElement *focused_element = Environment->getFocus();
2886-
if (focused_element && focused_element->getParent() == this) {
2887-
s32 focused_id = focused_element->getID();
2888-
if (focused_id > 257) {
2889-
for (const GUIFormSpecMenu::FieldSpec &field : m_fields) {
2890-
if (field.fid == focused_id) {
2891-
mydata.focused_fieldname = field.fname;
2892-
break;
2908+
// Preserve focus
2909+
gui::IGUIElement *focused_element = Environment->getFocus();
2910+
if (focused_element && focused_element->getParent() == this) {
2911+
s32 focused_id = focused_element->getID();
2912+
if (focused_id > 257) {
2913+
for (const GUIFormSpecMenu::FieldSpec &field : m_fields) {
2914+
if (field.fid == focused_id) {
2915+
m_focused_element = field.fname;
2916+
break;
2917+
}
28932918
}
28942919
}
28952920
}
2921+
} else {
2922+
// Don't keep old focus value
2923+
m_focused_element = "";
28962924
}
28972925

28982926
// Remove children
@@ -3253,8 +3281,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
32533281
}
32543282
}
32553283

3256-
//set initial focus if parser didn't set it
3257-
focused_element = Environment->getFocus();
3284+
// Set initial focus if parser didn't set it
3285+
gui::IGUIElement *focused_element = Environment->getFocus();
32583286
if (!focused_element
32593287
|| !isMyChild(focused_element)
32603288
|| focused_element->getType() == gui::EGUIET_TAB_CONTROL)
@@ -3265,6 +3293,13 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
32653293
// legacy sorting
32663294
if (m_formspec_version < 3)
32673295
legacySortElements(legacy_sort_start);
3296+
3297+
// Formname and regeneration setting
3298+
if (!m_is_form_regenerated) {
3299+
// Only set previous form name if we purposefully showed a new formspec
3300+
m_last_formname = m_text_dst->m_formname;
3301+
m_is_form_regenerated = true;
3302+
}
32683303
}
32693304

32703305
void GUIFormSpecMenu::legacySortElements(core::list<IGUIElement *>::Iterator from)
@@ -3382,6 +3417,7 @@ void GUIFormSpecMenu::drawMenu()
33823417
const std::string &newform = m_form_src->getForm();
33833418
if (newform != m_formspec_string) {
33843419
m_formspec_string = newform;
3420+
m_is_form_regenerated = false;
33853421
regenerateGui(m_screensize_old);
33863422
}
33873423
}

src/gui/guiFormSpecMenu.h

+7-2
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ class GUIFormSpecMenu : public GUIModalMenu
168168
{
169169
m_formspec_string = formspec_string;
170170
m_current_inventory_location = current_inventory_location;
171+
m_is_form_regenerated = false;
171172
regenerateGui(m_screensize_old);
172173
}
173174

@@ -299,6 +300,10 @@ class GUIFormSpecMenu : public GUIModalMenu
299300
std::string m_formspec_prepend;
300301
InventoryLocation m_current_inventory_location;
301302

303+
// Default true because we can't control regeneration on resizing, but
304+
// we can control cases when the formspec is shown intentionally.
305+
bool m_is_form_regenerated = true;
306+
302307
std::vector<GUIInventoryList *> m_inventorylists;
303308
std::vector<ListRingSpec> m_inventory_rings;
304309
std::vector<gui::IGUIElement *> m_backgrounds;
@@ -339,10 +344,10 @@ class GUIFormSpecMenu : public GUIModalMenu
339344
video::SColor m_default_tooltip_bgcolor;
340345
video::SColor m_default_tooltip_color;
341346

342-
343347
private:
344348
IFormSource *m_form_src;
345349
TextDest *m_text_dst;
350+
std::string m_last_formname;
346351
u16 m_formspec_version = 1;
347352
std::string m_focused_element = "";
348353
JoystickController *m_joystick;
@@ -359,7 +364,6 @@ class GUIFormSpecMenu : public GUIModalMenu
359364
core::rect<s32> rect;
360365
v2s32 basepos;
361366
v2u32 screensize;
362-
std::string focused_fieldname;
363367
GUITable::TableOptions table_options;
364368
GUITable::TableColumns table_columns;
365369
gui::IGUIElement *current_parent = nullptr;
@@ -439,6 +443,7 @@ class GUIFormSpecMenu : public GUIModalMenu
439443
bool parseAnchorDirect(parserData *data, const std::string &element);
440444
void parseAnchor(parserData *data, const std::string &element);
441445
bool parseStyle(parserData *data, const std::string &element, bool style_type);
446+
void parseSetFocus(const std::string &element);
442447

443448
void tryClose();
444449

0 commit comments

Comments
 (0)