Add compatible, consistent coordinate system to FormSpecs. (#8524)

This commit is contained in:
v-rob 2019-06-27 05:40:49 -07:00 committed by rubenwardy
parent 91d244c522
commit 5e7004e7af
3 changed files with 542 additions and 154 deletions

@ -1875,9 +1875,15 @@ is used when the server receives user input. You must not use the name
Spaces and newlines can be inserted between the blocks, as is used in the
examples.
Position and size units are inventory slots, `X` and `Y` position the formspec
element relative to the top left of the menu or container. `W` and `H` are its
width and height values.
Position and size units are inventory slots unless the new coordinate system
is enabled. `X` and `Y` position the formspec element relative to the top left
of the menu or container. `W` and `H` are its width and height values.
If the new system is enabled, all elements have unified coordinates for all
elements with no padding or spacing in between. This is highly recommended
for new forms. See `real_coordinates[<bool>]` and `Migrating to Real
Coordinates`.
Inventories with a `player:<name>` inventory location are only sent to the
player named `<name>`.
@ -1951,6 +1957,16 @@ Elements
* Must be used after the `size`, `position`, and `anchor` elements (if present).
* Disables player:set_formspec_prepend() from applying to this formspec.
### `real_coordinates[<bool>]`
* When set to true, all following formspec elements will use the new coordinate system.
* If used immediately after `size`, `position`, `anchor`, and `no_prepend` elements
(if present), the form size will use the new coordinate system.
* **Note**: Formspec prepends are not affected by the coordinates in the main form.
They must enable it explicitly.
* For information on converting forms to the new coordinate system, see `Migrating
to Real Coordinates`.
### `container[<X>,<Y>]`
* Start of a container block, moves all physical elements in the container by
@ -1968,11 +1984,15 @@ Elements
* Show an inventory list if it has been sent to the client. Nothing will
be shown if the inventory list is of size 0.
* **Note**: With the new coordinate system, the spacing between inventory
slots is one-fourth the size of an inventory slot.
### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;<starting item index>]`
* Show an inventory list if it has been sent to the client. Nothing will
be shown if the inventory list is of size 0.
* **Note**: With the new coordinate system, the spacing between inventory
slots is one-fourth the size of an inventory slot.
### `listring[<inventory location>;<list name>]`
@ -2064,7 +2084,8 @@ Elements
* Textual password style field; will be sent to server when a button is clicked
* When enter is pressed in field, fields.key_enter_field will be sent with the
name of this field.
* Fields are a set height, but will be vertically centred on `H`
* With the old coordinate system, fields are a set height, but will be vertically
centred on `H`. With the new coordinate system, `H` will modify the height.
* `name` is the name of the field as returned in fields to `on_receive_fields`
* `label`, if not blank, will be text printed on the top left above the field
* See `field_close_on_enter` to stop enter closing the formspec
@ -2074,7 +2095,8 @@ Elements
* Textual field; will be sent to server when a button is clicked
* When enter is pressed in field, `fields.key_enter_field` will be sent with
the name of this field.
* Fields are a set height, but will be vertically centred on `H`
* With the old coordinate system, fields are a set height, but will be vertically
centred on `H`. With the new coordinate system, `H` will modify the height.
* `name` is the name of the field as returned in fields to `on_receive_fields`
* `label`, if not blank, will be text printed on the top left above the field
* `default` is the default value of the field
@ -2111,23 +2133,34 @@ Elements
* The label formspec element displays the text set in `label`
at the specified position.
* **Note**: If the new coordinate system is enabled, labels are
positioned from the center of the text, not the top.
* The text is displayed directly without automatic line breaking,
so label should not be used for big text chunks.
so label should not be used for big text chunks. Newlines can be
used to make labels multiline.
* **Note**: With the new coordinate system, newlines are spaced with
half a coordinate. With the old system, newlines are spaced 2/5 of
an inventory slot.
### `vertlabel[<X>,<Y>;<label>]`
* Textual label drawn vertically
* `label` is the text on the label
* **Note**: If the new coordinate system is enabled, vertlabels are
positioned from the center of the text, not the left.
### `button[<X>,<Y>;<W>,<H>;<name>;<label>]`
* Clickable button. When clicked, fields will be sent.
* Fixed button height. It will be vertically centred on `H`
* With the old coordinate system, buttons are a set height, but will be vertically
centred on `H`. With the new coordinate system, `H` will modify the height.
* `label` is the text on the button
### `image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
* `texture name` is the filename of an image
* **Note**: Height is supported on both the old and new coordinate systems
for image_buttons.
### `image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>;<noclip>;<drawborder>;<pressed texture name>]`
@ -2146,10 +2179,12 @@ Elements
### `button_exit[<X>,<Y>;<W>,<H>;<name>;<label>]`
* When clicked, fields will be sent and the form will quit.
* Same as `button` in all other respects.
### `image_button_exit[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
* When clicked, fields will be sent and the form will quit.
* Same as `image_button` in all other respects.
### `textlist[<X>,<Y>;<W>,<H>;<name>;<listelem 1>,<listelem 2>,...,<listelem n>]`
@ -2175,6 +2210,34 @@ Elements
### `tabheader[<X>,<Y>;<name>;<caption 1>,<caption 2>,...,<caption n>;<current_tab>;<transparent>;<draw_border>]`
* Show a tab**header** at specific position (ignores formsize)
* `X` and `Y`: position of the tabheader
* *Note*: Width and height are automatically chosen with this syntax
* `name` fieldname data is transferred to Lua
* `caption 1`...: name shown on top of tab
* `current_tab`: index of selected tab 1...
* `transparent` (optional): show transparent
* `draw_border` (optional): draw border
### `tabheader[<X>,<Y>;<H>;<name>;<caption 1>,<caption 2>,...,<caption n>;<current_tab>;<transparent>;<draw_border>]`
* Show a tab**header** at specific position (ignores formsize)
* **Important note**: This syntax for tabheaders can only be used with the
new coordinate system.
* `X` and `Y`: position of the tabheader
* `H`: height of the tabheader. Width is automatically determined with this syntax.
* `name` fieldname data is transferred to Lua
* `caption 1`...: name shown on top of tab
* `current_tab`: index of selected tab 1...
* `transparent` (optional): show transparent
* `draw_border` (optional): draw border
### `tabheader[<X>,<Y>;<W>,<H>;<name>;<caption 1>,<caption 2>,...,<caption n>;<current_tab>;<transparent>;<draw_border>]`
* Show a tab**header** at specific position (ignores formsize)
* **Important note**: This syntax for tabheaders can only be used with the
new coordinate system.
* `X` and `Y`: position of the tabheader
* `W` and `H`: width and height of the tabheader
* `name` fieldname data is transferred to Lua
* `caption 1`...: name shown on top of tab
* `current_tab`: index of selected tab 1...
@ -2193,8 +2256,22 @@ Elements
* **Important note**: There are two different operation modes:
1. handle directly on change (only changed dropdown is submitted)
2. read the value on pressing a button (all dropdown values are available)
* `x` and `y` position of dropdown
* Width of dropdown
* `X` and `Y`: position of the dropdown
* `W`: width of the dropdown. Height is automatically chosen with this syntax.
* Fieldname data is transferred to Lua
* Items to be shown in dropdown
* Index of currently selected dropdown item
### `dropdown[<X>,<Y>;<W>,<H>;<name>;<item 1>,<item 2>, ...,<item n>;<selected idx>]`
* Show a dropdown field
* **Important note**: This syntax for dropdowns can only be used with the
new coordinate system.
* **Important note**: There are two different operation modes:
1. handle directly on change (only changed dropdown is submitted)
2. read the value on pressing a button (all dropdown values are available)
* `X` and `Y`: position of the dropdown
* `W` and `H`: width and height of the dropdown
* Fieldname data is transferred to Lua
* Items to be shown in dropdown
* Index of currently selected dropdown item
@ -2205,6 +2282,8 @@ Elements
* `name` fieldname data is transferred to Lua
* `label` to be shown left of checkbox
* `selected` (optional): `true`/`false`
* **Note**: If the new coordinate system is enabled, checkboxes are
positioned from the center of the checkbox, not the top.
### `scrollbar[<X>,<Y>;<W>,<H>;<orientation>;<name>;<value>]`
@ -2281,6 +2360,41 @@ Elements
**Note**: do _not_ use a element name starting with `key_`; those names are
reserved to pass key press events to formspec!
Migrating to Real Coordinates
-----------------------------
In the old system, positions included padding and spacing. Padding is a gap between
the formspec window edges and content, and spacing is the gaps between items. For
example, two `1x1` elements at `0,0` and `1,1` would have a spacing of `5/4` between them,
and a padding of `3/8` from the formspec edge. It may be easiest to recreate old layouts
in the new coordinate system from scratch.
To recreate an old layout with padding, you'll need to pass the positions and sizes
through the following formula to re-introduce padding:
```
pos = (oldpos + 1)*spacing + padding
where
padding = 3/8
spacing = 5/4
```
You'll need to change the `size[]` tag like this:
```
size = (oldsize-1)*spacing + padding*2 + 1
```
A few elements had random offsets in the old system. Here is a table which shows these
offsets when migrating:
| Element | Position | Size | Notes
|---------|------------|---------|-------
| box | +0.3, +0.1 | 0, -0.4 |
| button | | | Buttons now support height, so set h = 2 * 15/13 * 0.35, and reposition if h ~= 15/13 * 0.35 before
| list | | | Spacing is now 0.25 for both directions, meaning lists will be taller in height
| label | 0, +0.3 | | The first line of text is now positioned centered exactly at the position specified

@ -66,8 +66,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define MY_CHECKGEOM(a,b) \
if (v_geom.size() != 2) { \
errorstream<< "Invalid pos for element " << a << "specified: \"" \
<< parts[b] << "\"" << std::endl; \
errorstream<< "Invalid geometry for element " << a << \
"specified: \"" << parts[b] << "\"" << std::endl; \
return; \
}
/*
@ -270,6 +270,25 @@ v2s32 GUIFormSpecMenu::getElementBasePos(bool absolute,
return v2s32(pos_f.X, pos_f.Y);
}
v2s32 GUIFormSpecMenu::getRealCoordinateBasePos(bool absolute,
const std::vector<std::string> &v_pos)
{
v2f32 pos_f = v2f32(0.0f, 0.0f);
pos_f.X += stof(v_pos[0]) + pos_offset.X;
pos_f.Y += stof(v_pos[1]) + pos_offset.Y;
if (absolute)
return v2s32(pos_f.X * imgsize.X + AbsoluteRect.UpperLeftCorner.X,
pos_f.Y * imgsize.Y + AbsoluteRect.UpperLeftCorner.Y);
return v2s32(pos_f.X * imgsize.X, pos_f.Y * imgsize.Y);
}
v2s32 GUIFormSpecMenu::getRealCoordinateGeometry(const std::vector<std::string> &v_geom)
{
return v2s32(stof(v_geom[0]) * imgsize.X, stof(v_geom[1]) * imgsize.Y);
}
void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element)
{
std::vector<std::string> parts = split(element,',');
@ -353,8 +372,14 @@ void GUIFormSpecMenu::parseList(parserData* data, const std::string &element)
else
loc.deSerialize(location);
v2s32 pos = getElementBasePos(true, &v_pos);
v2s32 pos;
v2s32 geom;
if (data->real_coordinates)
pos = getRealCoordinateBasePos(true, v_pos);
else
pos = getElementBasePos(true, &v_pos);
geom.X = stoi(v_geom[0]);
geom.Y = stoi(v_geom[1]);
@ -369,7 +394,7 @@ void GUIFormSpecMenu::parseList(parserData* data, const std::string &element)
if(!data->explicit_size)
warningstream<<"invalid use of list without a size[] element"<<std::endl;
m_inventorylists.emplace_back(loc, listname, pos, geom, start_i);
m_inventorylists.emplace_back(loc, listname, pos, geom, start_i, data->real_coordinates);
return;
}
errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl;
@ -430,8 +455,6 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element
MY_CHECKPOS("checkbox",0);
v2s32 pos = getElementBasePos(false, &v_pos);
bool fselected = false;
if (selected == "true")
@ -442,12 +465,27 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element
s32 cb_size = Environment->getSkin()->getSize(gui::EGDS_CHECK_BOX_WIDTH);
s32 y_center = (std::max(label_size.Height, (u32)cb_size) + 1) / 2;
core::rect<s32> rect = core::rect<s32>(
v2s32 pos;
core::rect<s32> rect;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(false, v_pos);
rect = core::rect<s32>(
pos.X,
pos.Y - y_center,
pos.X + label_size.Width + cb_size + 7,
pos.Y + y_center
);
} else {
pos = getElementBasePos(false, &v_pos);
rect = core::rect<s32>(
pos.X,
pos.Y + imgsize.Y / 2 - y_center,
pos.X + label_size.Width + cb_size + 7,
pos.Y + imgsize.Y / 2 + y_center
);
}
FieldSpec spec(
name,
@ -478,23 +516,24 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen
if (parts.size() >= 5) {
std::vector<std::string> v_pos = split(parts[0],',');
std::vector<std::string> v_dim = split(parts[1],',');
std::vector<std::string> v_geom = split(parts[1],',');
std::string name = parts[3];
std::string value = parts[4];
MY_CHECKPOS("scrollbar",0);
MY_CHECKGEOM("scrollbar",1);
v2s32 pos = getElementBasePos(false, &v_pos);
if (v_dim.size() != 2) {
errorstream<< "Invalid size for element " << "scrollbar"
<< "specified: \"" << parts[1] << "\"" << std::endl;
return;
}
v2s32 pos;
v2s32 dim;
dim.X = stof(v_dim[0]) * spacing.X;
dim.Y = stof(v_dim[1]) * spacing.Y;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(false, v_pos);
dim = getRealCoordinateGeometry(v_geom);
} else {
pos = getElementBasePos(false, &v_pos);
dim.X = stof(v_geom[0]) * spacing.X;
dim.Y = stof(v_geom[1]) * spacing.Y;
}
core::rect<s32> rect =
core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y);
@ -543,10 +582,17 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
MY_CHECKPOS("image", 0);
MY_CHECKGEOM("image", 1);
v2s32 pos = getElementBasePos(true, &v_pos);
v2s32 pos;
v2s32 geom;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(true, v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
pos = getElementBasePos(true, &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 image without a size[] element"<<std::endl;
@ -584,10 +630,17 @@ void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &elemen
MY_CHECKPOS("itemimage",0);
MY_CHECKGEOM("itemimage",1);
v2s32 pos = getElementBasePos(true, &v_pos);
v2s32 pos;
v2s32 geom;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(true, v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
pos = getElementBasePos(true, &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"<<std::endl;
@ -613,14 +666,23 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
MY_CHECKPOS("button",0);
MY_CHECKGEOM("button",1);
v2s32 pos = getElementBasePos(false, &v_pos);
v2s32 pos;
v2s32 geom;
core::rect<s32> rect;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(false, v_pos);
geom = getRealCoordinateGeometry(v_geom);
rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
pos.Y+geom.Y);
} else {
pos = getElementBasePos(false, &v_pos);
geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
core::rect<s32> rect =
core::rect<s32>(pos.X, pos.Y - m_btn_height,
rect = core::rect<s32>(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"<<std::endl;
@ -662,18 +724,30 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme
MY_CHECKPOS("background",0);
MY_CHECKGEOM("background",1);
v2s32 pos = getElementBasePos(true, &v_pos);
v2s32 pos;
v2s32 geom;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(true, v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
pos = getElementBasePos(true, &v_pos);
pos.X -= (spacing.X - (float)imgsize.X) / 2;
pos.Y -= (spacing.Y - (float)imgsize.Y) / 2;
v2s32 geom;
geom.X = stof(v_geom[0]) * spacing.X;
geom.Y = stof(v_geom[1]) * spacing.Y;
}
bool clip = false;
if (parts.size() >= 4 && is_yes(parts[3])) {
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(false, v_pos) * -1;
geom = v2s32(0, 0);
} else {
pos.X = stoi(v_pos[0]); //acts as offset
pos.Y = stoi(v_pos[1]); //acts as offset
pos.Y = stoi(v_pos[1]);
}
clip = true;
}
@ -760,10 +834,17 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
MY_CHECKPOS("table",0);
MY_CHECKGEOM("table",1);
v2s32 pos = getElementBasePos(false, &v_pos);
v2s32 pos;
v2s32 geom;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(false, v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
pos = getElementBasePos(false, &v_pos);
geom.X = stof(v_geom[0]) * spacing.X;
geom.Y = stof(v_geom[1]) * spacing.Y;
}
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
@ -827,11 +908,17 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element
MY_CHECKPOS("textlist",0);
MY_CHECKGEOM("textlist",1);
v2s32 pos = getElementBasePos(false, &v_pos);
v2s32 pos;
v2s32 geom;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(false, v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
pos = getElementBasePos(false, &v_pos);
geom.X = stof(v_geom[0]) * spacing.X;
geom.Y = stof(v_geom[1]) * spacing.Y;
}
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
@ -888,12 +975,29 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element
MY_CHECKPOS("dropdown",0);
v2s32 pos = getElementBasePos(false, &v_pos);
v2s32 pos;
v2s32 geom;
core::rect<s32> rect;
if (data->real_coordinates) {
std::vector<std::string> v_geom = split(parts[1],',');
if (v_geom.size() == 1)
v_geom.emplace_back("1");
MY_CHECKGEOM("dropdown",1);
pos = getRealCoordinateBasePos(false, v_pos);
geom = getRealCoordinateGeometry(v_geom);
rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
} else {
pos = getElementBasePos(false, &v_pos);
s32 width = stof(parts[1]) * spacing.Y;
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y,
rect = core::rect<s32>(pos.X, pos.Y,
pos.X + width, pos.Y + (m_btn_height * 2));
}
FieldSpec spec(
name,
@ -958,15 +1062,22 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
MY_CHECKPOS("pwdfield",0);
MY_CHECKGEOM("pwdfield",1);
v2s32 pos = getElementBasePos(false, &v_pos);
v2s32 pos;
v2s32 geom;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(false, v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
pos = getElementBasePos(false, &v_pos);
pos -= padding;
v2s32 geom;
geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
pos.Y -= m_btn_height;
geom.Y = m_btn_height*2;
}
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
@ -1141,11 +1252,16 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>&
MY_CHECKPOS(type,0);
MY_CHECKGEOM(type,1);
v2s32 pos = getElementBasePos(false, &v_pos);
pos -= padding;
v2s32 pos;
v2s32 geom;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(false, v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
pos = getElementBasePos(false, &v_pos);
pos -= padding;
geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
if (type == "textarea")
@ -1159,6 +1275,7 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>&
pos.Y -= m_btn_height;
geom.Y = m_btn_height*2;
}
}
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
@ -1221,16 +1338,33 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
MY_CHECKPOS("label",0);
v2s32 pos = getElementBasePos(false, nullptr);
pos.X += stof(v_pos[0]) * spacing.X;
pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y;
if(!data->explicit_size)
warningstream<<"invalid use of label without a size[] element"<<std::endl;
std::vector<std::string> lines = split(text, '\n');
for (unsigned int i = 0; i != lines.size(); i++) {
std::wstring wlabel = utf8_to_wide(unescape_string(lines[i]));
core::rect<s32> 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(false, v_pos);
// Labels are positioned by their center, not their top.
pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2);
rect = core::rect<s32>(
pos.X, pos.Y,
pos.X + m_font->getDimension(wlabel.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
@ -1240,12 +1374,19 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
// than multiply by 0.4, to get exact results
// in the integer cases: 0.4 is not exactly
// representable in binary floating point.
s32 posy = pos.Y + ((float)i) * spacing.Y * 2.0 / 5.0;
std::wstring wlabel = utf8_to_wide(unescape_string(lines[i]));
core::rect<s32> rect = core::rect<s32>(
pos.X, posy - m_btn_height,
v2s32 pos = getElementBasePos(false, 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<s32>(
pos.X, pos.Y - m_btn_height,
pos.X + m_font->getDimension(wlabel.c_str()).Width,
posy + m_btn_height);
pos.Y + m_btn_height);
}
FieldSpec spec(
"",
wlabel,
@ -1260,7 +1401,8 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
return;
}
errorstream<< "Invalid label element(" << parts.size() << "): '" << element << "'" << std::endl;
errorstream << "Invalid label element(" << parts.size() << "): '" << element
<< "'" << std::endl;
}
void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &element)
@ -1276,15 +1418,35 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen
MY_CHECKPOS("vertlabel",1);
v2s32 pos = getElementBasePos(false, &v_pos);
v2s32 pos;
core::rect<s32> rect;
core::rect<s32> rect = core::rect<s32>(
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(false, v_pos);
// 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<s32>(pos.X, pos.Y,
pos.X + imgsize.X,
pos.Y + font_line_height(m_font) *
(text.length() + 1));
} else {
pos = getElementBasePos(false, &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<s32>(
pos.X, pos.Y+((imgsize.Y/2) - m_btn_height),
pos.X+15, pos.Y +
font_line_height(m_font)
* (text.length()+1)
+((imgsize.Y/2)- m_btn_height));
//actually text.length() would be correct but adding +1 avoids to break all mods
font_line_height(m_font) *
(text.length() + 1) +
((imgsize.Y/2) - m_btn_height));
}
if(!data->explicit_size)
warningstream<<"invalid use of label without a size[] element"<<std::endl;
@ -1328,11 +1490,6 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
MY_CHECKPOS("imagebutton",0);
MY_CHECKGEOM("imagebutton",1);
v2s32 pos = getElementBasePos(false, &v_pos);
v2s32 geom;
geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y);
bool noclip = false;
bool drawborder = true;
std::string pressed_image_name;
@ -1348,7 +1505,20 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
pressed_image_name = parts[7];
}
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
v2s32 pos;
v2s32 geom;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(false, v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
pos = getElementBasePos(false, &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);
}
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
pos.Y+geom.Y);
if (!data->explicit_size)
warningstream<<"invalid use of image_button without a size[] element"<<std::endl;
@ -1402,23 +1572,41 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
{
std::vector<std::string> parts = split(element, ';');
if (((parts.size() == 4) || (parts.size() == 6)) ||
((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
if (((parts.size() == 4) || (parts.size() == 6)) || (parts.size() == 7 &&
data->real_coordinates) || ((parts.size() > 6) &&
(m_formspec_version > FORMSPEC_API_VERSION)))
{
std::vector<std::string> v_pos = split(parts[0],',');
std::string name = parts[1];
std::vector<std::string> buttons = split(parts[2],',');
std::string str_index = parts[3];
// 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<std::string> v_geom = {"1", "0.75"}; // Dummy width and default 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;
}
std::string name = parts[i+1];
std::vector<std::string> 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;
MY_CHECKPOS("tabheader", 0);
if (parts.size() == 6) {
if (parts[4] == "true")
if (parts.size() == 6 + i) {
if (parts[4+i] == "true")
show_background = false;
if (parts[5] == "false")
if (parts[5+i] == "false")
show_border = false;
}
@ -1432,15 +1620,26 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
spec.ftype = f_TabHeader;
v2s32 pos;
{
v2s32 geom;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(false, v_pos);
geom = getRealCoordinateGeometry(v_geom);
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 geom;
geom.X = DesiredRect.getWidth();
geom.Y = m_btn_height * 2;
geom.X = DesiredRect.getWidth();
}
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
pos.Y+geom.Y);
@ -1449,7 +1648,7 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
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(m_btn_height*2);
e->setTabHeight(geom.Y);
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
@ -1500,10 +1699,17 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &
MY_CHECKPOS("itemimagebutton",0);
MY_CHECKGEOM("itemimagebutton",1);
v2s32 pos = getElementBasePos(false, &v_pos);
v2s32 pos;
v2s32 geom;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(false, v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
pos = getElementBasePos(false, &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);
}
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
@ -1537,7 +1743,11 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &
spec.rect=rect;
m_fields.push_back(spec);
if (data->real_coordinates)
pos = getRealCoordinateBasePos(true, v_pos);
else
pos = getElementBasePos(true, &v_pos);
m_itemimages.emplace_back("", item_name, e, pos, geom);
m_static_texts.emplace_back(utf8_to_wide(label), rect, e);
return;
@ -1558,10 +1768,17 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
MY_CHECKPOS("box",0);
MY_CHECKGEOM("box",1);
v2s32 pos = getElementBasePos(true, &v_pos);
v2s32 pos;
v2s32 geom;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(true, v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
pos = getElementBasePos(true, &v_pos);
geom.X = stof(v_geom[0]) * spacing.X;
geom.Y = stof(v_geom[1]) * spacing.Y;
}
video::SColor tmp_color;
@ -1667,10 +1884,17 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element)
MY_CHECKPOS("tooltip", 0);
MY_CHECKGEOM("tooltip", 1);
v2s32 pos = getElementBasePos(true, &v_pos);
v2s32 pos;
v2s32 geom;
if (data->real_coordinates) {
pos = getRealCoordinateBasePos(true, v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
pos = getElementBasePos(true, &v_pos);
geom.X = stof(v_geom[0]) * spacing.X;
geom.Y = stof(v_geom[1]) * spacing.Y;
}
irr::core::rect<s32> rect(pos, pos + geom);
m_tooltip_rects.emplace_back(rect, spec);
@ -1956,6 +2180,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
return;
}
if (type == "real_coordinates") {
data->real_coordinates = is_yes(description);
return;
}
// Ignore others
infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
<< std::endl;
@ -2120,6 +2349,17 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
break;
}
/* Copy of the "real_coordinates" element for after the form size. */
mydata.real_coordinates = false;
for (; i < elements.size(); i++) {
std::vector<std::string> parts = split(elements[i], '[');
std::string name = trim(parts[0]);
if (name != "real_coordinates" || parts.size() != 2)
break; // Invalid format
mydata.real_coordinates = is_yes(trim(parts[1]));
}
if (mydata.explicit_size) {
// compute scaling for specified form size
if (m_lock) {
@ -2210,10 +2450,18 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
m_font = g_fontengine->getFont();
if (mydata.real_coordinates) {
mydata.size = v2s32(
mydata.invsize.X*imgsize.X,
mydata.invsize.Y*imgsize.Y
);
} else {
mydata.size = v2s32(
padding.X*2+spacing.X*(mydata.invsize.X-1.0)+imgsize.X,
padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0
);
}
DesiredRect = mydata.rect = core::rect<s32>(
(s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * (f32)mydata.size.X) + offset.X,
(s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * (f32)mydata.size.Y) + offset.Y,
@ -2245,9 +2493,13 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
pos_offset = v2f32();
if (enable_prepends) {
// Backup the coordinates so that prepends can use the coordinates of choice.
bool rc_backup = mydata.real_coordinates;
mydata.real_coordinates = false; // Old coordinates by default.
std::vector<std::string> prepend_elements = split(m_formspec_prepend, ']');
for (const auto &element : prepend_elements)
parseElement(&mydata, element);
mydata.real_coordinates = rc_backup; // Restore coordinates
}
for (; i< elements.size(); i++) {
@ -2337,8 +2589,16 @@ GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
for(s32 i=0; i<s.geom.X*s.geom.Y; i++) {
s32 item_i = i + s.start_item_i;
s32 x = (i%s.geom.X) * spacing.X;
s32 y = (i/s.geom.X) * spacing.Y;
s32 x;
s32 y;
if (s.real_coordinates) {
x = (i%s.geom.X) * (imgsize.X * 1.25);
y = (i/s.geom.X) * (imgsize.Y * 1.25);
} else {
x = (i%s.geom.X) * spacing.X;
y = (i/s.geom.X) * spacing.Y;
}
v2s32 p0(x,y);
core::rect<s32> rect = imgrect + s.pos + p0;
if(rect.isPointInside(p))
@ -2380,8 +2640,15 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int layer,
if (item_i >= (s32)ilist->getSize())
break;
s32 x = (i%s.geom.X) * spacing.X;
s32 y = (i/s.geom.X) * spacing.Y;
s32 x;
s32 y;
if (s.real_coordinates) {
x = (i%s.geom.X) * (imgsize.X * 1.25);
y = (i/s.geom.X) * (imgsize.Y * 1.25);
} else {
x = (i%s.geom.X) * spacing.X;
y = (i/s.geom.X) * spacing.Y;
}
v2s32 p(x,y);
core::rect<s32> rect = imgrect + s.pos + p;
ItemStack item = ilist->getItem(item_i);

@ -99,12 +99,14 @@ class GUIFormSpecMenu : public GUIModalMenu
ListDrawSpec(const InventoryLocation &a_inventoryloc,
const std::string &a_listname,
v2s32 a_pos, v2s32 a_geom, s32 a_start_item_i):
v2s32 a_pos, v2s32 a_geom, s32 a_start_item_i,
bool a_real_coordinates):
inventoryloc(a_inventoryloc),
listname(a_listname),
pos(a_pos),
geom(a_geom),
start_item_i(a_start_item_i)
start_item_i(a_start_item_i),
real_coordinates(a_real_coordinates)
{
}
@ -113,6 +115,7 @@ class GUIFormSpecMenu : public GUIModalMenu
v2s32 pos;
v2s32 geom;
s32 start_item_i;
bool real_coordinates;
};
struct ListRingSpec
@ -394,6 +397,9 @@ protected:
std::string getNameByID(s32 id);
v2s32 getElementBasePos(bool absolute,
const std::vector<std::string> *v_pos);
v2s32 getRealCoordinateBasePos(bool absolute,
const std::vector<std::string> &v_pos);
v2s32 getRealCoordinateGeometry(const std::vector<std::string> &v_geom);
v2s32 padding;
v2f32 spacing;
@ -463,6 +469,7 @@ private:
typedef struct {
bool explicit_size;
bool real_coordinates;
v2f invsize;
v2s32 size;
v2f32 offset;