diff options
-rw-r--r-- | doc/lua_api.txt | 132 | ||||
-rw-r--r-- | src/gui/guiFormSpecMenu.cpp | 551 | ||||
-rw-r--r-- | src/gui/guiFormSpecMenu.h | 11 |
3 files changed, 541 insertions, 153 deletions
diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 155da14e3..d881c4ef1 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -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 + diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 9240dc4c8..da462c7e3 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -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,','); @@ -348,13 +367,19 @@ void GUIFormSpecMenu::parseList(parserData* data, const std::string &element) InventoryLocation loc; - if(location == "context" || location == "current_name") + if (location == "context" || location == "current_name") loc = m_current_inventory_location; 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>( - pos.X, - pos.Y + imgsize.Y / 2 - y_center, - pos.X + label_size.Width + cb_size + 7, - pos.Y + imgsize.Y / 2 + y_center - ); + 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,24 +516,25 @@ 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); + v2s32 pos; + v2s32 dim; - if (v_dim.size() != 2) { - errorstream<< "Invalid size for element " << "scrollbar" - << "specified: \"" << parts[1] << "\"" << std::endl; - return; + 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; } - v2s32 dim; - dim.X = stof(v_dim[0]) * spacing.X; - dim.Y = stof(v_dim[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; - geom.X = stof(v_geom[0]) * (float)imgsize.X; - geom.Y = stof(v_geom[1]) * (float)imgsize.Y; + + 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; - geom.X = stof(v_geom[0]) * (float)imgsize.X; - geom.Y = stof(v_geom[1]) * (float)imgsize.Y; + + 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; - 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> rect = - core::rect<s32>(pos.X, pos.Y - m_btn_height, + 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; + + 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); - pos.X -= (spacing.X - (float)imgsize.X) / 2; - pos.Y -= (spacing.Y - (float)imgsize.Y) / 2; - + v2s32 pos; v2s32 geom; - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; + + 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; + + 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])) { - pos.X = stoi(v_pos[0]); //acts as offset - pos.Y = stoi(v_pos[1]); //acts as offset + 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]); + } 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; - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; + + 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; - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; + 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],','); - s32 width = stof(parts[1]) * spacing.Y; + if (v_geom.size() == 1) + v_geom.emplace_back("1"); - core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, - pos.X + width, pos.Y + (m_btn_height * 2)); + 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; + + 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); - pos -= padding; - + v2s32 pos; 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; + 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); + + 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,23 +1252,29 @@ 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; - geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(false, v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(false, &v_pos); + pos -= padding; - if (type == "textarea") - { - geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y); - pos.Y += m_btn_height; - } - else - { - pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; - pos.Y -= m_btn_height; - geom.Y = m_btn_height*2; + geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); + + if (type == "textarea") + { + geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y); + pos.Y += m_btn_height; + } + else + { + 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); @@ -1221,31 +1338,55 @@ 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++) { - // Lines are spaced at the nominal distance of - // 2/5 inventory slot, even if the font doesn't - // quite match that. This provides consistent - // form layout, at the expense of sometimes - // having sub-optimal spacing for the font. - // We multiply by 2 and then divide by 5, rather - // 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, - pos.X + m_font->getDimension(wlabel.c_str()).Width, - posy + m_btn_height); + + 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 + // form layout, at the expense of sometimes + // having sub-optimal spacing for the font. + // We multiply by 2 and then divide by 5, rather + // than multiply by 0.4, to get exact results + // in the integer cases: 0.4 is not exactly + // representable in binary floating point. + + 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, + 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; + + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(false, v_pos); + + // Vertlabels are positioned by center, not left. + pos.X -= imgsize.X / 2; - core::rect<s32> rect = core::rect<s32>( - pos.X, pos.Y+((imgsize.Y/2)- m_btn_height), + // 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,9 +1505,22 @@ 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->explicit_size) + 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; image_name = unescape_string(image_name); @@ -1365,7 +1535,7 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem 258+m_fields.size() ); spec.ftype = f_Button; - if(type == "image_button_exit") + if (type == "image_button_exit") spec.is_exit = true; video::ITexture *texture = 0; @@ -1400,25 +1570,43 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); + 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; + int tab_index = stoi(str_index) - 1; - MY_CHECKPOS("tabheader",0); + 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); + + geom.Y = m_btn_height * 2; + geom.X = DesiredRect.getWidth(); } - v2s32 geom; - geom.X = DesiredRect.getWidth(); - 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); @@ -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; - geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); - geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); + + 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); - pos = getElementBasePos(true, &v_pos); + 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; - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; + + 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; @@ -1664,13 +1881,20 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element) std::vector<std::string> v_pos = split(parts[0], ','); std::vector<std::string> v_geom = split(parts[1], ','); - MY_CHECKPOS("tooltip", 0); + MY_CHECKPOS("tooltip", 0); MY_CHECKGEOM("tooltip", 1); - v2s32 pos = getElementBasePos(true, &v_pos); + v2s32 pos; v2s32 geom; - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; + + 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(); - 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 - ); + 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); diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index b1ca9a48a..9c1ecb635 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -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; |