#include <limits>
#include <sstream>
#include "guiFormSpecMenu.h"
-#include "guiScrollBar.h"
-#include "guiTable.h"
#include "constants.h"
#include "gamedef.h"
#include "client/keycode.h"
#include "guiEditBoxWithScrollbar.h"
#include "guiInventoryList.h"
#include "guiItemImage.h"
-#include "guiScrollBar.h"
-#include "guiTable.h"
+#include "guiScrollContainer.h"
#include "intlGUIEditBox.h"
#include "guiHyperText.h"
+#include "guiScene.h"
#define MY_CHECKPOS(a,b) \
if (v_pos.size() != 2) { \
GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick,
gui::IGUIElement *parent, s32 id, IMenuManager *menumgr,
Client *client, ISimpleTextureSource *tsrc, IFormSource *fsrc, TextDest *tdst,
- const std::string &formspecPrepend,
- bool remap_dbl_click):
- GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr),
+ const std::string &formspecPrepend, bool remap_dbl_click):
+ GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr, remap_dbl_click),
m_invmgr(client),
m_tsrc(tsrc),
m_client(client),
m_formspec_prepend(formspecPrepend),
m_form_src(fsrc),
m_text_dst(tdst),
- m_joystick(joystick),
- m_remap_dbl_click(remap_dbl_click)
+ m_joystick(joystick)
{
current_keys_pending.key_down = false;
current_keys_pending.key_up = false;
current_keys_pending.key_enter = false;
current_keys_pending.key_escape = false;
- m_doubleclickdetect[0].time = 0;
- m_doubleclickdetect[1].time = 0;
-
- m_doubleclickdetect[0].pos = v2s32(0, 0);
- m_doubleclickdetect[1].pos = v2s32(0, 0);
-
m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay");
m_tooltip_append_itemname = g_settings->getBool("tooltip_append_itemname");
}
background_it->drop();
for (auto &tooltip_rect_it : m_tooltip_rects)
tooltip_rect_it.first->drop();
+ for (auto &clickthrough_it : m_clickthrough_elements)
+ clickthrough_it->drop();
+ for (auto &scroll_container_it : m_scroll_containers)
+ scroll_container_it.second->drop();
delete m_selected_item;
delete m_form_src;
}
}
+void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ';');
+
+ if (parts.size() < 4 ||
+ (parts.size() > 5 && m_formspec_version <= FORMSPEC_API_VERSION)) {
+ errorstream << "Invalid scroll_container start element (" << parts.size()
+ << "): '" << element << "'" << std::endl;
+ return;
+ }
+
+ std::vector<std::string> v_pos = split(parts[0], ',');
+ std::vector<std::string> v_geom = split(parts[1], ',');
+ std::string scrollbar_name = parts[2];
+ std::string orientation = parts[3];
+ f32 scroll_factor = 0.1f;
+ if (parts.size() >= 5 && !parts[4].empty())
+ scroll_factor = stof(parts[4]);
+
+ MY_CHECKPOS("scroll_container", 0);
+ MY_CHECKGEOM("scroll_container", 1);
+
+ v2s32 pos = getRealCoordinateBasePos(v_pos);
+ v2s32 geom = getRealCoordinateGeometry(v_geom);
+
+ if (orientation == "vertical")
+ scroll_factor *= -imgsize.Y;
+ else if (orientation == "horizontal")
+ scroll_factor *= -imgsize.X;
+ else
+ warningstream << "GUIFormSpecMenu::parseScrollContainer(): "
+ << "Invalid scroll_container orientation: " << orientation
+ << std::endl;
+
+ // old parent (at first: this)
+ // ^ is parent of clipper
+ // ^ is parent of mover
+ // ^ is parent of other elements
+
+ // make clipper
+ core::rect<s32> rect_clipper = core::rect<s32>(pos, pos + geom);
+
+ gui::IGUIElement *clipper = new gui::IGUIElement(EGUIET_ELEMENT, Environment,
+ data->current_parent, 0, rect_clipper);
+
+ // make mover
+ FieldSpec spec_mover(
+ "",
+ L"",
+ L"",
+ 258 + m_fields.size()
+ );
+
+ core::rect<s32> rect_mover = core::rect<s32>(0, 0, geom.X, geom.Y);
+
+ GUIScrollContainer *mover = new GUIScrollContainer(Environment,
+ clipper, spec_mover.fid, rect_mover, orientation, scroll_factor);
+
+ data->current_parent = mover;
+
+ m_scroll_containers.emplace_back(scrollbar_name, mover);
+
+ m_fields.push_back(spec_mover);
+
+ clipper->drop();
+
+ // remove interferring offset of normal containers
+ container_stack.push(pos_offset);
+ pos_offset.X = 0.0f;
+ pos_offset.Y = 0.0f;
+}
+
+void GUIFormSpecMenu::parseScrollContainerEnd(parserData *data)
+{
+ if (data->current_parent == this || data->current_parent->getParent() == this ||
+ container_stack.empty()) {
+ errorstream << "Invalid scroll_container end element, "
+ << "no matching scroll_container start element" << std::endl;
+ return;
+ }
+
+ if (pos_offset.getLengthSQ() != 0.0f) {
+ // pos_offset is only set by containers and scroll_containers.
+ // scroll_containers always set it to 0,0 which means that if it is
+ // not 0,0, it is a normal container that was opened last, not a
+ // scroll_container
+ errorstream << "Invalid scroll_container end element, "
+ << "an inner container was left open" << std::endl;
+ return;
+ }
+
+ data->current_parent = data->current_parent->getParent()->getParent();
+ pos_offset = container_stack.top();
+ container_stack.pop();
+}
+
void GUIFormSpecMenu::parseList(parserData *data, const std::string &element)
{
if (m_client == 0) {
start_i = stoi(startindex);
if (geom.X < 0 || geom.Y < 0 || start_i < 0) {
- errorstream<< "Invalid list element: '" << element << "'" << std::endl;
+ errorstream << "Invalid list element: '" << element << "'" << std::endl;
return;
}
- // check for the existence of inventory and list
- Inventory *inv = m_invmgr->getInventory(loc);
- if (!inv) {
- warningstream << "GUIFormSpecMenu::parseList(): "
- << "The inventory location "
- << "\"" << loc.dump() << "\" doesn't exist"
- << std::endl;
- return;
- }
- InventoryList *ilist = inv->getList(listname);
- if (!ilist) {
- warningstream << "GUIFormSpecMenu::parseList(): "
- << "The inventory list \"" << listname << "\" "
- << "@ \"" << loc.dump() << "\" doesn't exist"
- << std::endl;
- return;
- }
-
- // trim geom if it is larger than the actual inventory size
- s32 list_size = (s32)ilist->getSize();
- if (list_size < geom.X * geom.Y + start_i) {
- list_size -= MYMAX(start_i, 0);
- geom.Y = list_size / geom.X;
- geom.Y += list_size % geom.X > 0 ? 1 : 0;
- if (geom.Y <= 1)
- geom.X = list_size;
- }
-
if (!data->explicit_size)
warningstream << "invalid use of list without a size[] element" << std::endl;
pos.X + (geom.X - 1) * slot_spacing.X + imgsize.X,
pos.Y + (geom.Y - 1) * slot_spacing.Y + imgsize.Y);
- GUIInventoryList *e = new GUIInventoryList(Environment, this, spec.fid,
- rect, m_invmgr, loc, listname, geom, start_i, imgsize, slot_spacing,
- this, data->inventorylist_options, m_font);
+ GUIInventoryList *e = new GUIInventoryList(Environment, data->current_parent,
+ spec.fid, rect, m_invmgr, loc, listname, geom, start_i, imgsize,
+ slot_spacing, this, data->inventorylist_options, m_font);
m_inventorylists.push_back(e);
m_fields.push_back(spec);
spec.ftype = f_CheckBox;
- gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect, this,
- spec.fid, spec.flabel.c_str());
+ gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect,
+ data->current_parent, spec.fid, spec.flabel.c_str());
- auto style = getStyleForElement("checkbox", name);
+ 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);
}
spec.ftype = f_ScrollBar;
spec.send = true;
- GUIScrollBar *e = new GUIScrollBar(Environment, this, spec.fid, rect,
- is_horizontal, true);
+ GUIScrollBar *e = new GUIScrollBar(Environment, data->current_parent,
+ spec.fid, rect, is_horizontal, true);
- auto style = getStyleForElement("scrollbar", name);
+ auto style = getDefaultStyleForElement("scrollbar", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
e->setArrowsVisible(data->scrollbar_options.arrow_visiblity);
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;
1
);
core::rect<s32> rect(pos, pos + geom);
- gui::IGUIImage *e = Environment->addImage(rect, this, spec.fid, 0, true);
+ gui::IGUIImage *e = Environment->addImage(rect, data->current_parent,
+ spec.fid, 0, true);
e->setImage(texture);
e->setScaleImage(true);
- auto style = getStyleForElement("image", spec.fname);
+ auto style = getDefaultStyleForElement("image", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
m_fields.push_back(spec);
+ // images should let events through
+ e->grab();
+ m_clickthrough_elements.push_back(e);
return;
}
L"",
258 + m_fields.size()
);
- gui::IGUIImage *e = Environment->addImage(texture, pos, true, this,
- spec.fid, 0);
- auto style = getStyleForElement("image", spec.fname);
+ gui::IGUIImage *e = Environment->addImage(texture, pos, true,
+ data->current_parent, spec.fid, 0);
+ auto style = getDefaultStyleForElement("image", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
m_fields.push_back(spec);
+ // images should let events through
+ e->grab();
+ m_clickthrough_elements.push_back(e);
return;
}
errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'" << std::endl;
{
std::vector<std::string> parts = split(element, ';');
- if (parts.size() != 3 &&
- !(parts.size() > 3 && m_formspec_version > FORMSPEC_API_VERSION)) {
- errorstream << "Invalid animated image element(" << parts.size()
- << "): '" << element << "'" << std::endl;
+ if (parts.size() != 6 && parts.size() != 7 &&
+ !(parts.size() > 7 && m_formspec_version > FORMSPEC_API_VERSION)) {
+ errorstream << "Invalid animated_image element(" << parts.size()
+ << "): '" << element << "'" << std::endl;
return;
}
- std::vector<std::string> v_pos = split(parts[0], ',');
- std::vector<std::string> v_geom = split(parts[1], ',');
- std::string name = unescape_string(parts[2]);
+ std::vector<std::string> v_pos = split(parts[0], ',');
+ std::vector<std::string> v_geom = split(parts[1], ',');
+ std::string name = parts[2];
+ std::string texture_name = unescape_string(parts[3]);
+ s32 frame_count = stoi(parts[4]);
+ s32 frame_duration = stoi(parts[5]);
MY_CHECKPOS("animated_image", 0);
MY_CHECKGEOM("animated_image", 1);
}
if (!data->explicit_size)
- warningstream << "invalid use of animated_image without a size[] element" << std::endl;
+ warningstream << "Invalid use of animated_image without a size[] element" << std::endl;
FieldSpec spec(
- "",
- L"",
- L"",
- 258 + m_fields.size()
+ name,
+ L"",
+ L"",
+ 258 + m_fields.size()
);
+ spec.ftype = f_AnimatedImage;
+ spec.send = true;
core::rect<s32> rect = core::rect<s32>(pos, pos + geom);
- gui::IGUIElement *e = new GUIAnimatedImage(Environment, this, spec.fid,
- rect, name, m_tsrc);
+ GUIAnimatedImage *e = new GUIAnimatedImage(Environment, this, spec.fid,
+ rect, texture_name, frame_count, frame_duration, m_tsrc);
+
+ if (parts.size() >= 7)
+ e->setFrameIndex(stoi(parts[6]) - 1);
- auto style = getStyleForElement("animated_image", spec.fname);
+ auto style = getDefaultStyleForElement("animated_image", spec.fname, "image");
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
- e->drop();
+
+ // Animated images should let events through
+ m_clickthrough_elements.push_back(e);
m_fields.push_back(spec);
}
);
spec.ftype = f_ItemImage;
- GUIItemImage *e = new GUIItemImage(Environment, this, spec.fid,
+ GUIItemImage *e = new GUIItemImage(Environment, data->current_parent, spec.fid,
core::rect<s32>(pos, pos + geom), name, m_font, m_client);
- auto style = getStyleForElement("item_image", spec.fname);
+ auto style = getDefaultStyleForElement("item_image", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
- e->drop();
+
+ // item images should let events through
+ m_clickthrough_elements.push_back(e);
m_fields.push_back(spec);
return;
if(type == "button_exit")
spec.is_exit = true;
- GUIButton *e = GUIButton::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str());
+ GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc,
+ data->current_parent, spec.fid, spec.flabel.c_str());
auto style = getStyleForElement(type, name, (type != "button") ? "button" : "");
- e->setFromStyle(style, m_tsrc);
+ e->setStyles(style);
- if (spec.fname == data->focused_fieldname) {
+ if (spec.fname == m_focused_element) {
Environment->setFocus(e);
}
}
//now really show table
- GUITable *e = new GUITable(Environment, this, spec.fid, rect, m_tsrc);
+ 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);
}
if (!str_initial_selection.empty() && str_initial_selection != "0")
e->setSelected(stoi(str_initial_selection));
- auto style = getStyleForElement("table", name);
+ auto style = getDefaultStyleForElement("table", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+ e->setOverrideFont(style.getFont());
m_tables.emplace_back(spec, e);
m_fields.push_back(spec);
}
//now really show list
- GUITable *e = new GUITable(Environment, this, spec.fid, rect, m_tsrc);
+ 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);
}
if (!str_initial_selection.empty() && str_initial_selection != "0")
e->setSelected(stoi(str_initial_selection));
- auto style = getStyleForElement("textlist", name);
+ auto style = getDefaultStyleForElement("textlist", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+ e->setOverrideFont(style.getFont());
m_tables.emplace_back(spec, e);
m_fields.push_back(spec);
errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'" << std::endl;
}
-
void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element)
{
- std::vector<std::string> parts = split(element,';');
+ std::vector<std::string> parts = split(element, ';');
- if ((parts.size() == 5) ||
- ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ if (parts.size() == 5 || parts.size() == 6 ||
+ (parts.size() > 6 && m_formspec_version > FORMSPEC_API_VERSION))
{
- std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_pos = split(parts[0], ',');
std::string name = parts[2];
- std::vector<std::string> items = split(parts[3],',');
- std::string str_initial_selection;
- str_initial_selection = parts[4];
+ std::vector<std::string> items = split(parts[3], ',');
+ std::string str_initial_selection = parts[4];
+
+ if (parts.size() >= 6 && is_yes(parts[5]))
+ m_dropdown_index_event[name] = true;
MY_CHECKPOS("dropdown",0);
spec.send = true;
//now really show list
- gui::IGUIComboBox *e = Environment->addComboBox(rect, this, spec.fid);
+ 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);
}
if (!str_initial_selection.empty())
e->setSelected(stoi(str_initial_selection)-1);
- auto style = getStyleForElement("dropdown", name);
+ auto style = getDefaultStyleForElement("dropdown", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
m_fields.push_back(spec);
return;
}
- errorstream << "Invalid dropdown element(" << parts.size() << "): '"
- << element << "'" << std::endl;
+ errorstream << "Invalid dropdown element(" << parts.size() << "): '" << element
+ << "'" << std::endl;
}
void GUIFormSpecMenu::parseFieldCloseOnEnter(parserData *data, const std::string &element)
{
std::vector<std::string> parts = split(element,';');
- if ((parts.size() == 4) ||
- ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ if (parts.size() == 4 ||
+ (parts.size() > 4 && m_formspec_version > FORMSPEC_API_VERSION))
{
std::vector<std::string> v_pos = split(parts[0],',');
std::vector<std::string> v_geom = split(parts[1],',');
);
spec.send = true;
- gui::IGUIEditBox * e = Environment->addEditBox(0, rect, true, this, spec.fid);
+ 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);
}
rect.UpperLeftCorner.Y -= font_height;
rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
- this, 0);
+ data->current_parent, 0);
}
e->setPasswordBox(true,L'*');
- auto style = getStyleForElement("pwdfield", name, "field");
+ auto style = getDefaultStyleForElement("pwdfield", name, "field");
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
+ e->setOverrideFont(style.getFont());
irr::SEvent evt;
evt.EventType = EET_KEY_INPUT_EVENT;
if (!is_editable && !is_multiline) {
// spec field id to 0, this stops submit searching for a value that isn't there
gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
- this, spec.fid);
+ data->current_parent, 0);
return;
}
if (use_intl_edit_box && g_settings->getBool("freetype")) {
e = new gui::intlGUIEditBox(spec.fdefault.c_str(), true, Environment,
- this, spec.fid, rect, is_editable, is_multiline);
+ data->current_parent, spec.fid, rect, is_editable, is_multiline);
} else {
if (is_multiline) {
- e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true,
- Environment, this, spec.fid, rect, is_editable, true);
+ e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, Environment,
+ data->current_parent, spec.fid, rect, is_editable, true);
} else if (is_editable) {
- e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this,
- spec.fid);
+ e = Environment->addEditBox(spec.fdefault.c_str(), rect, true,
+ data->current_parent, spec.fid);
e->grab();
}
}
- auto style = getStyleForElement(is_multiline ? "textarea" : "field", spec.fname);
+ 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) {
if (style.get(StyleSpec::BGCOLOR, "") == "transparent") {
e->setDrawBackground(false);
}
+ e->setOverrideFont(style.getFont());
e->drop();
}
rect.UpperLeftCorner.Y -= font_height;
rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
IGUIElement *t = gui::StaticText::add(Environment, spec.flabel.c_str(),
- rect, false, true, this, 0);
+ rect, false, true, data->current_parent, 0);
if (t)
t->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
pos = getElementBasePos(&v_pos);
pos -= padding;
- pos.X += stof(v_pos[0]) * spacing.X;
- pos.Y += stof(v_pos[1]) * spacing.Y + (m_btn_height * 2);
-
geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
- geom.Y = (stof(v_geom[1]) * imgsize.Y) - (spacing.Y - imgsize.Y);
+ geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y - imgsize.Y);
+ pos.Y += m_btn_height;
}
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y);
FieldSpec spec(
name,
- utf8_to_wide(unescape_string(text)),
+ translate_string(utf8_to_wide(unescape_string(text))),
L"",
258 + m_fields.size()
);
- spec.ftype = f_Unknown;
- GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, this,
- spec.fid, rect, m_client, m_tsrc);
+ spec.ftype = f_HyperText;
+ GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment,
+ data->current_parent, spec.fid, rect, m_client, m_tsrc);
e->drop();
m_fields.push_back(spec);
std::vector<std::string> lines = split(text, '\n');
+ auto style = getDefaultStyleForElement("label", "");
+ gui::IGUIFont *font = style.getFont();
+ if (!font)
+ font = m_font;
+
for (unsigned int i = 0; i != lines.size(); i++) {
std::wstring wlabel_colors = translate_string(
utf8_to_wide(unescape_string(lines[i])));
rect = core::rect<s32>(
pos.X, pos.Y,
- pos.X + m_font->getDimension(wlabel_plain.c_str()).Width,
+ pos.X + font->getDimension(wlabel_plain.c_str()).Width,
pos.Y + imgsize.Y);
} else {
rect = core::rect<s32>(
pos.X, pos.Y - m_btn_height,
- pos.X + m_font->getDimension(wlabel_plain.c_str()).Width,
+ pos.X + font->getDimension(wlabel_plain.c_str()).Width,
pos.Y + m_btn_height);
}
4
);
gui::IGUIStaticText *e = gui::StaticText::add(Environment,
- spec.flabel.c_str(), rect, false, false, this, spec.fid);
+ spec.flabel.c_str(), rect, false, false, data->current_parent,
+ spec.fid);
e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER);
- auto style = getStyleForElement("label", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
+ e->setOverrideFont(font);
m_fields.push_back(spec);
+
+ // labels should let events through
+ e->grab();
+ m_clickthrough_elements.push_back(e);
}
return;
MY_CHECKPOS("vertlabel",1);
+ auto style = getDefaultStyleForElement("vertlabel", "", "label");
+ gui::IGUIFont *font = style.getFont();
+ if (!font)
+ font = m_font;
+
v2s32 pos;
core::rect<s32> 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) *
+ pos.Y + font_line_height(font) *
(text.length() + 1));
} else {
rect = core::rect<s32>(
pos.X, pos.Y+((imgsize.Y/2) - m_btn_height),
pos.X+15, pos.Y +
- font_line_height(m_font) *
+ font_line_height(font) *
(text.length() + 1) +
((imgsize.Y/2) - m_btn_height));
}
258 + m_fields.size()
);
gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(),
- rect, false, false, this, spec.fid);
+ rect, false, false, data->current_parent, spec.fid);
e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
- auto style = getStyleForElement("vertlabel", spec.fname, "label");
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
+ e->setOverrideFont(font);
m_fields.push_back(spec);
+
+ // vertlabels should let events through
+ e->grab();
+ m_clickthrough_elements.push_back(e);
return;
}
errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'" << std::endl;
MY_CHECKPOS("imagebutton",0);
MY_CHECKGEOM("imagebutton",1);
- bool noclip = false;
- bool drawborder = true;
std::string pressed_image_name;
- if (parts.size() >= 7) {
- if (parts[5] == "true")
- noclip = true;
- if (parts[6] == "false")
- drawborder = false;
- }
-
if (parts.size() >= 8) {
pressed_image_name = parts[7];
}
if (type == "image_button_exit")
spec.is_exit = true;
- GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str());
+ 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);
}
auto style = getStyleForElement("image_button", spec.fname);
- e->setFromStyle(style, m_tsrc);
- // We explicitly handle these arguments *after* the style properties in
- // order to override them if they are provided
+ // Override style properties with values specified directly in the element
if (!image_name.empty())
- {
- video::ITexture *texture = m_tsrc->getTexture(image_name);
- e->setForegroundImage(guiScalingImageButton(
- Environment->getVideoDriver(), texture, geom.X, geom.Y));
- }
- if (!pressed_image_name.empty()) {
- video::ITexture *pressed_texture = m_tsrc->getTexture(pressed_image_name);
- e->setPressedForegroundImage(guiScalingImageButton(
- Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
- }
- e->setScaleImage(true);
+ style[StyleSpec::STATE_DEFAULT].set(StyleSpec::FGIMG, image_name);
+
+ if (!pressed_image_name.empty())
+ style[StyleSpec::STATE_PRESSED].set(StyleSpec::FGIMG, pressed_image_name);
if (parts.size() >= 7) {
- e->setNotClipped(noclip);
- e->setDrawBorder(drawborder);
+ style[StyleSpec::STATE_DEFAULT].set(StyleSpec::NOCLIP, parts[5]);
+ style[StyleSpec::STATE_DEFAULT].set(StyleSpec::BORDER, parts[6]);
}
+ e->setStyles(style);
+ e->setScaleImage(true);
+
m_fields.push_back(spec);
return;
}
// 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
+ std::vector<std::string> v_geom = {"1", "1"}; // Dummy width and height
bool auto_width = true;
if (parts.size() == 7) {
i++;
pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
+ // Set default height
+ if (parts.size() <= 6)
+ geom.Y = m_btn_height * 2;
pos.Y -= geom.Y; // TabHeader base pos is the bottom, not the top.
if (auto_width)
geom.X = DesiredRect.getWidth(); // Set automatic width
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
pos.Y+geom.Y);
- gui::IGUITabControl *e = Environment->addTabControl(rect, this,
- show_background, show_border, spec.fid);
+ gui::IGUITabControl *e = Environment->addTabControl(rect,
+ data->current_parent, 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(geom.Y);
- if (spec.fname == data->focused_fieldname) {
- Environment->setFocus(e);
- }
-
- auto style = getStyleForElement("tabheader", name);
+ auto style = getDefaultStyleForElement("tabheader", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true));
for (const std::string &button : buttons) {
void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element)
{
-
if (m_client == 0) {
warningstream << "invalid use of item_image_button with m_client==0"
<< std::endl;
2
);
- GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment, rect, this, spec_btn.fid, spec_btn.flabel.c_str(), item_name, m_client);
+ GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment,
+ rect, m_tsrc, data->current_parent, spec_btn.fid, spec_btn.flabel.c_str(),
+ item_name, m_client);
auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button");
- e_btn->setFromStyle(style, m_tsrc);
+ e_btn->setStyles(style);
- if (spec_btn.fname == data->focused_fieldname) {
+ if (spec_btn.fname == m_focused_element) {
Environment->setFocus(e_btn);
}
void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
{
- std::vector<std::string> parts = split(element,';');
+ std::vector<std::string> parts = split(element, ';');
if ((parts.size() == 3) ||
((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
{
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_geom = split(parts[1],',');
+ std::vector<std::string> v_pos = split(parts[0], ',');
+ std::vector<std::string> v_geom = split(parts[1], ',');
- MY_CHECKPOS("box",0);
- MY_CHECKGEOM("box",1);
+ MY_CHECKPOS("box", 0);
+ MY_CHECKGEOM("box", 1);
v2s32 pos;
v2s32 geom;
geom.Y = stof(v_geom[1]) * spacing.Y;
}
- video::SColor tmp_color;
-
- if (parseColorString(parts[2], tmp_color, false, 0x8C)) {
- FieldSpec spec(
- "",
- L"",
- L"",
- 258 + m_fields.size(),
- -2
- );
- spec.ftype = f_Box;
+ FieldSpec spec(
+ "",
+ L"",
+ L"",
+ 258 + m_fields.size(),
+ -2
+ );
+ spec.ftype = f_Box;
- core::rect<s32> rect(pos, pos + geom);
+ auto style = getDefaultStyleForElement("box", spec.fname);
- GUIBox *e = new GUIBox(Environment, this, spec.fid, rect, tmp_color);
+ video::SColor tmp_color;
+ std::array<video::SColor, 4> colors;
+ std::array<video::SColor, 4> bordercolors = {0x0, 0x0, 0x0, 0x0};
+ std::array<s32, 4> borderwidths = {0, 0, 0, 0};
- auto style = getStyleForElement("box", spec.fname);
- e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
+ if (parseColorString(parts[2], tmp_color, true, 0x8C)) {
+ colors = {tmp_color, tmp_color, tmp_color, tmp_color};
+ } else {
+ colors = style.getColorArray(StyleSpec::COLORS, {0x0, 0x0, 0x0, 0x0});
+ bordercolors = style.getColorArray(StyleSpec::BORDERCOLORS,
+ {0x0, 0x0, 0x0, 0x0});
+ borderwidths = style.getIntArray(StyleSpec::BORDERWIDTHS, {0, 0, 0, 0});
+ }
- e->drop();
+ core::rect<s32> rect(pos, pos + geom);
- m_fields.push_back(spec);
+ GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, rect,
+ colors, bordercolors, borderwidths);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
+ e->drop();
- } else {
- errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl;
- }
+ m_fields.push_back(spec);
return;
}
- errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'" << std::endl;
+ errorstream << "Invalid Box element(" << parts.size() << "): '" << element
+ << "'" << std::endl;
}
void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element)
core::rect<s32> rect(pos, pos + geom);
gui::IGUIElement *e = new gui::IGUIElement(EGUIET_ELEMENT, Environment,
- this, fieldspec.fid, rect);
+ data->current_parent, fieldspec.fid, rect);
// the element the rect tooltip is bound to should not block mouse-clicks
e->setVisible(false);
StyleSpec spec;
+ // Parse properties
for (size_t i = 1; i < parts.size(); i++) {
size_t equal_pos = parts[i].find('=');
if (equal_pos == std::string::npos) {
<< "'" << std::endl;
property_warned.insert(propname);
}
- return false;
+ continue;
}
spec.set(prop, value);
for (size_t sel = 0; sel < selectors.size(); sel++) {
std::string selector = trim(selectors[sel]);
- if (selector.empty()) {
- errorstream << "Invalid style element (Empty selector): '" << element
- << "'" << std::endl;
+ // Copy the style properties to a new StyleSpec
+ // This allows a separate state mask per-selector
+ StyleSpec selector_spec = spec;
+
+ // Parse state information, if it exists
+ bool state_valid = true;
+ size_t state_pos = selector.find(':');
+ if (state_pos != std::string::npos) {
+ std::string state_str = selector.substr(state_pos + 1);
+ selector = selector.substr(0, state_pos);
+
+ if (state_str.empty()) {
+ errorstream << "Invalid style element (Invalid state): '" << element
+ << "'" << std::endl;
+ state_valid = false;
+ } else {
+ std::vector<std::string> states = split(state_str, '+');
+ for (std::string &state : states) {
+ StyleSpec::State converted = StyleSpec::getStateByName(state);
+ if (converted == StyleSpec::STATE_INVALID) {
+ infostream << "Unknown style state " << state <<
+ " in element '" << element << "'" << std::endl;
+ state_valid = false;
+ break;
+ }
+
+ selector_spec.addState(converted);
+ }
+ }
+ }
+
+ if (!state_valid) {
+ // Skip this selector
continue;
}
if (style_type) {
- theme_by_type[selector] |= spec;
+ theme_by_type[selector].push_back(selector_spec);
} else {
- theme_by_name[selector] |= spec;
+ theme_by_name[selector].push_back(selector_spec);
+ }
+
+ // Backwards-compatibility for existing _hovered/_pressed properties
+ if (selector_spec.hasProperty(StyleSpec::BGCOLOR_HOVERED)
+ || selector_spec.hasProperty(StyleSpec::BGIMG_HOVERED)
+ || selector_spec.hasProperty(StyleSpec::FGIMG_HOVERED)) {
+ StyleSpec hover_spec;
+ hover_spec.addState(StyleSpec::STATE_HOVERED);
+
+ if (selector_spec.hasProperty(StyleSpec::BGCOLOR_HOVERED)) {
+ hover_spec.set(StyleSpec::BGCOLOR, selector_spec.get(StyleSpec::BGCOLOR_HOVERED, ""));
+ }
+ if (selector_spec.hasProperty(StyleSpec::BGIMG_HOVERED)) {
+ hover_spec.set(StyleSpec::BGIMG, selector_spec.get(StyleSpec::BGIMG_HOVERED, ""));
+ }
+ if (selector_spec.hasProperty(StyleSpec::FGIMG_HOVERED)) {
+ hover_spec.set(StyleSpec::FGIMG, selector_spec.get(StyleSpec::FGIMG_HOVERED, ""));
+ }
+
+ if (style_type) {
+ theme_by_type[selector].push_back(hover_spec);
+ } else {
+ theme_by_name[selector].push_back(hover_spec);
+ }
+ }
+ if (selector_spec.hasProperty(StyleSpec::BGCOLOR_PRESSED)
+ || selector_spec.hasProperty(StyleSpec::BGIMG_PRESSED)
+ || selector_spec.hasProperty(StyleSpec::FGIMG_PRESSED)) {
+ StyleSpec press_spec;
+ press_spec.addState(StyleSpec::STATE_PRESSED);
+
+ if (selector_spec.hasProperty(StyleSpec::BGCOLOR_PRESSED)) {
+ press_spec.set(StyleSpec::BGCOLOR, selector_spec.get(StyleSpec::BGCOLOR_PRESSED, ""));
+ }
+ if (selector_spec.hasProperty(StyleSpec::BGIMG_PRESSED)) {
+ press_spec.set(StyleSpec::BGIMG, selector_spec.get(StyleSpec::BGIMG_PRESSED, ""));
+ }
+ if (selector_spec.hasProperty(StyleSpec::FGIMG_PRESSED)) {
+ press_spec.set(StyleSpec::FGIMG, selector_spec.get(StyleSpec::FGIMG_PRESSED, ""));
+ }
+
+ if (style_type) {
+ theme_by_type[selector].push_back(press_spec);
+ } else {
+ theme_by_name[selector].push_back(press_spec);
+ }
}
}
return true;
}
-void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
+void GUIFormSpecMenu::parseSetFocus(const std::string &element)
{
- //some prechecks
- if (element.empty())
+ 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;
+ }
- if (parseVersionDirect(element))
+ errorstream << "Invalid set_focus element (" << parts.size() << "): '" << element
+ << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ';');
+
+ if (parts.size() < 5 || (parts.size() > 8 &&
+ m_formspec_version <= FORMSPEC_API_VERSION)) {
+ errorstream << "Invalid model element (" << parts.size() << "): '" << element
+ << "'" << std::endl;
return;
+ }
- std::vector<std::string> parts = split(element,'[');
+ // Avoid length checks by resizing
+ if (parts.size() < 8)
+ parts.resize(8);
- // ugly workaround to keep compatibility
- if (parts.size() > 2) {
- if (trim(parts[0]) == "image") {
- for (unsigned int i=2;i< parts.size(); i++) {
- parts[1] += "[" + parts[i];
- }
- }
- else { return; }
+ std::vector<std::string> v_pos = split(parts[0], ',');
+ std::vector<std::string> v_geom = split(parts[1], ',');
+ std::string name = unescape_string(parts[2]);
+ std::string meshstr = unescape_string(parts[3]);
+ std::vector<std::string> textures = split(parts[4], ',');
+ std::vector<std::string> vec_rot = split(parts[5], ',');
+ bool inf_rotation = is_yes(parts[6]);
+ bool mousectrl = is_yes(parts[7]) || parts[7].empty(); // default true
+
+ MY_CHECKPOS("model", 0);
+ MY_CHECKGEOM("model", 1);
+
+ v2s32 pos;
+ v2s32 geom;
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(&v_pos);
+ geom.X = stof(v_geom[0]) * (float)imgsize.X;
+ geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
}
- if (parts.size() < 2) {
+ if (!data->explicit_size)
+ warningstream << "invalid use of model without a size[] element" << std::endl;
+
+ scene::IAnimatedMesh *mesh = m_client->getMesh(meshstr);
+
+ if (!mesh) {
+ errorstream << "Invalid model element: Unable to load mesh:"
+ << std::endl << "\t" << meshstr << std::endl;
return;
}
- std::string type = trim(parts[0]);
- std::string description = trim(parts[1]);
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258 + m_fields.size()
+ );
+
+ core::rect<s32> rect(pos, pos + geom);
+
+ GUIScene *e = new GUIScene(Environment, RenderingEngine::get_scene_manager(),
+ data->current_parent, rect, spec.fid);
+
+ auto meshnode = e->setMesh(mesh);
+
+ for (u32 i = 0; i < textures.size() && i < meshnode->getMaterialCount(); ++i)
+ e->setTexture(i, m_tsrc->getTexture(textures[i]));
+
+ if (vec_rot.size() >= 2)
+ e->setRotation(v2f(stof(vec_rot[0]), stof(vec_rot[1])));
+
+ e->enableContinuousRotation(inf_rotation);
+ e->enableMouseControl(mousectrl);
+
+ auto style = getStyleForElement("model", spec.fname);
+ e->setStyles(style);
+ e->drop();
+
+ m_fields.push_back(spec);
+}
+
+void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
+{
+ //some prechecks
+ if (element.empty())
+ return;
+
+ if (parseVersionDirect(element))
+ return;
+
+ size_t pos = element.find('[');
+ if (pos == std::string::npos)
+ return;
+
+ std::string type = trim(element.substr(0, pos));
+ std::string description = element.substr(pos+1);
if (type == "container") {
parseContainer(data, description);
return;
}
+ if (type == "scroll_container") {
+ parseScrollContainer(data, description);
+ return;
+ }
+
+ if (type == "scroll_container_end") {
+ parseScrollContainerEnd(data);
+ return;
+ }
+
+ if (type == "set_focus") {
+ parseSetFocus(description);
+ return;
+ }
+
+ if (type == "model") {
+ parseModel(data, 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
background_it->drop();
for (auto &tooltip_rect_it : m_tooltip_rects)
tooltip_rect_it.first->drop();
+ for (auto &clickthrough_it : m_clickthrough_elements)
+ clickthrough_it->drop();
+ for (auto &scroll_container_it : m_scroll_containers)
+ scroll_container_it.second->drop();
- mydata.size= v2s32(100,100);
+ mydata.size = v2s32(100, 100);
mydata.screensize = screensize;
mydata.offset = v2f32(0.5f, 0.5f);
mydata.anchor = v2f32(0.5f, 0.5f);
// Base position of contents of form
mydata.basepos = getBasePos();
+ // the parent for the parsed elements
+ mydata.current_parent = this;
+
m_inventorylists.clear();
m_backgrounds.clear();
m_tables.clear();
m_tooltip_rects.clear();
m_inventory_rings.clear();
m_dropdowns.clear();
+ m_scroll_containers.clear();
theme_by_name.clear();
theme_by_type.clear();
+ m_clickthrough_elements.clear();
+ field_close_on_enter.clear();
+ m_dropdown_index_event.clear();
m_bgnonfullscreen = true;
m_bgfullscreen = false;
// and default scaling (1.00).
use_imgsize = 0.5555 * screen_dpi * gui_scaling;
} else {
- // In variable-size mode, we prefer to make the
- // inventory image size 1/15 of screen height,
- // multiplied by the gui_scaling config parameter.
- // If the preferred size won't fit the whole
- // form on the screen, either horizontally or
- // vertically, then we scale it down to fit.
- // (The magic numbers in the computation of what
- // fits arise from the scaling factors in the
- // following stanza, including the form border,
- // help text space, and 0.1 inventory slot spare.)
- // However, a minimum size is also set, that
- // the image size can't be less than 0.3 inch
- // multiplied by gui_scaling, even if this means
- // the form doesn't fit the screen.
+ // Variables for the maximum imgsize that can fit in the screen.
+ double fitx_imgsize;
+ double fity_imgsize;
+
+ // Pad the screensize with 5% of the screensize on all sides to ensure
+ // that even the largest formspecs don't touch the screen borders.
+ v2f padded_screensize(
+ mydata.screensize.X * 0.9f,
+ mydata.screensize.Y * 0.9f
+ );
+
+ if (mydata.real_coordinates) {
+ fitx_imgsize = padded_screensize.X / mydata.invsize.X;
+ fity_imgsize = padded_screensize.Y / mydata.invsize.Y;
+ } else {
+ // The maximum imgsize in the old coordinate system also needs to
+ // factor in padding and spacing along with 0.1 inventory slot spare
+ // and help text space, hence the magic numbers.
+ fitx_imgsize = padded_screensize.X /
+ ((5.0 / 4.0) * (0.5 + mydata.invsize.X));
+ fity_imgsize = padded_screensize.Y /
+ ((15.0 / 13.0) * (0.85 + mydata.invsize.Y));
+ }
+
#ifdef __ANDROID__
- // For mobile devices these magic numbers are
- // different and forms should always use the
- // maximum screen space available.
- double prefer_imgsize = mydata.screensize.Y / 10 * gui_scaling;
- double fitx_imgsize = mydata.screensize.X /
- ((12.0 / 8.0) * (0.5 + mydata.invsize.X));
- double fity_imgsize = mydata.screensize.Y /
- ((15.0 / 11.0) * (0.85 + mydata.invsize.Y));
- use_imgsize = MYMIN(prefer_imgsize,
- MYMIN(fitx_imgsize, fity_imgsize));
+ // In Android, the preferred imgsize should be larger to accommodate the
+ // smaller screensize.
+ double prefer_imgsize = padded_screensize.Y / 10 * gui_scaling;
#else
- double prefer_imgsize = mydata.screensize.Y / 15 * gui_scaling;
- double fitx_imgsize = mydata.screensize.X /
- ((5.0 / 4.0) * (0.5 + mydata.invsize.X));
- double fity_imgsize = mydata.screensize.Y /
- ((15.0 / 13.0) * (0.85 * mydata.invsize.Y));
- double screen_dpi = RenderingEngine::getDisplayDensity() * 96;
- double min_imgsize = 0.3 * screen_dpi * gui_scaling;
- use_imgsize = MYMAX(min_imgsize, MYMIN(prefer_imgsize,
- MYMIN(fitx_imgsize, fity_imgsize)));
+ // Desktop computers have more space, so try to fit 15 coordinates.
+ double prefer_imgsize = padded_screensize.Y / 15 * gui_scaling;
#endif
+ // Try to use the preferred imgsize, but if that's bigger than the maximum
+ // size, use the maximum size.
+ use_imgsize = std::min(prefer_imgsize,
+ std::min(fitx_imgsize, fity_imgsize));
}
// Everything else is scaled in proportion to the
parseElement(&mydata, elements[i]);
}
- if (!container_stack.empty()) {
+ if (mydata.current_parent != this) {
+ errorstream << "Invalid formspec string: scroll_container was never closed!"
+ << std::endl;
+ } else if (!container_stack.empty()) {
errorstream << "Invalid formspec string: container was never closed!"
<< std::endl;
}
+ // get the scrollbar elements for scroll_containers
+ for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers) {
+ for (const std::pair<FieldSpec, GUIScrollBar *> &b : m_scrollbars) {
+ if (c.first == b.first.fname) {
+ c.second->setScrollBar(b.second);
+ break;
+ }
+ }
+ }
+
// If there are fields without explicit size[], add a "Proceed"
// button and adjust size to fit all the fields.
if (mydata.simple_field_count > 0 && !mydata.explicit_size) {
size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2
);
const wchar_t *text = wgettext("Proceed");
- GUIButton::addButton(Environment, mydata.rect, this, 257, text);
+ GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, 257, text);
delete[] text;
}
}
- //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)
// 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)
if (!hasAndroidUIInput())
return false;
+ // still waiting
+ if (porting::getInputDialogState() == -1)
+ return true;
+
std::string fieldname = m_jni_field_name;
m_jni_field_name.clear();
- for (std::vector<FieldSpec>::iterator iter = m_fields.begin();
- iter != m_fields.end(); ++iter) {
-
- if (iter->fname != fieldname) {
+ for (const FieldSpec &field : m_fields) {
+ if (field.fname != fieldname)
continue;
- }
- IGUIElement *tochange = getElementFromId(iter->fid, true);
- if (tochange == 0) {
- return false;
- }
+ IGUIElement *element = getElementFromId(field.fid, true);
- if (tochange->getType() != irr::gui::EGUIET_EDIT_BOX) {
+ if (!element || element->getType() != irr::gui::EGUIET_EDIT_BOX)
return false;
- }
std::string text = porting::getInputDialogValue();
-
- ((gui::IGUIEditBox *)tochange)->setText(utf8_to_wide(text).c_str());
+ ((gui::IGUIEditBox *)element)->setText(utf8_to_wide(text).c_str());
}
return false;
}
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);
}
}
e->setVisible(false);
}
+ // Some elements are only visible while being drawn
+ for (gui::IGUIElement *e : m_clickthrough_elements)
+ e->setVisible(true);
+
/*
- Call base class
- (This is where all the drawing happens.)
+ This is where all the drawing happens.
*/
- gui::IGUIElement::draw();
+ core::list<IGUIElement*>::Iterator it = Children.begin();
+ for (; it != Children.end(); ++it)
+ if ((*it)->isNotClipped() ||
+ AbsoluteClippingRect.isRectCollided(
+ (*it)->getAbsolutePosition()))
+ (*it)->draw();
+
+ for (gui::IGUIElement *e : m_clickthrough_elements)
+ e->setVisible(false);
// Draw hovered item tooltips
for (const std::string &tooltip : m_hovered_item_tooltips) {
bool hovered_element_found = false;
if (hovered != NULL) {
- s32 id = hovered->getID();
+ if (m_show_debug) {
+ core::rect<s32> rect = hovered->getAbsoluteClippingRect();
+ driver->draw2DRectangle(0x22FFFF00, rect, &rect);
+ }
+ s32 id = hovered->getID();
u64 delta = 0;
if (id == -1) {
m_old_tooltip_id = id;
}
#ifndef HAVE_TOUCHSCREENGUI
- if (current_cursor_icon != field.fcursor_icon)
+ if (field.ftype != f_HyperText && // Handled directly in guiHyperText
+ current_cursor_icon != field.fcursor_icon)
cursor_control->setActiveIcon(field.fcursor_icon);
#endif
tooltip_offset_y = 0;
if (m_pointer.X > (s32)screenSize.X / 2)
tooltip_offset_x = -(tooltip_offset_x + tooltip_width);
+
+ // Hide tooltip after ETIE_LEFT_UP
+ if (m_pointer.X == 0)
+ return;
#endif
// Calculate and set the tooltip position
}
for (const GUIFormSpecMenu::FieldSpec &s : m_fields) {
- if(s.send) {
+ if (s.send) {
std::string name = s.fname;
if (s.ftype == f_Button) {
fields[name] = wide_to_utf8(s.flabel);
if (table) {
fields[name] = table->checkEvent();
}
- }
- else if(s.ftype == f_DropDown) {
- // no dynamic cast possible due to some distributions shipped
- // without rtti support in irrlicht
+ } else if (s.ftype == f_DropDown) {
+ // No dynamic cast possible due to some distributions shipped
+ // without rtti support in Irrlicht
IGUIElement *element = getElementFromId(s.fid, true);
gui::IGUIComboBox *e = NULL;
if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) {
- e = static_cast<gui::IGUIComboBox*>(element);
+ e = static_cast<gui::IGUIComboBox *>(element);
} else {
warningstream << "GUIFormSpecMenu::acceptInput: dropdown "
<< "field without dropdown element" << std::endl;
}
s32 selected = e->getSelected();
if (selected >= 0) {
- std::vector<std::string> *dropdown_values =
- getDropDownValues(s.fname);
- if (dropdown_values && selected < (s32)dropdown_values->size()) {
- fields[name] = (*dropdown_values)[selected];
+ if (m_dropdown_index_event.find(s.fname) !=
+ m_dropdown_index_event.end()) {
+ fields[name] = std::to_string(selected + 1);
+ } else {
+ std::vector<std::string> *dropdown_values =
+ getDropDownValues(s.fname);
+ if (dropdown_values && selected < (s32)dropdown_values->size())
+ fields[name] = (*dropdown_values)[selected];
}
}
- }
- else if (s.ftype == f_TabHeader) {
- // no dynamic cast possible due to some distributions shipped
- // without rttzi support in irrlicht
+ } else if (s.ftype == f_TabHeader) {
+ // No dynamic cast possible due to some distributions shipped
+ // without rtti support in Irrlicht
IGUIElement *element = getElementFromId(s.fid, true);
gui::IGUITabControl *e = nullptr;
if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) {
ss << (e->getActiveTab() +1);
fields[name] = ss.str();
}
- }
- else if (s.ftype == f_CheckBox) {
- // no dynamic cast possible due to some distributions shipped
- // without rtti support in irrlicht
+ } else if (s.ftype == f_CheckBox) {
+ // No dynamic cast possible due to some distributions shipped
+ // without rtti support in Irrlicht
IGUIElement *element = getElementFromId(s.fid, true);
gui::IGUICheckBox *e = nullptr;
if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) {
else
fields[name] = "false";
}
- }
- else if (s.ftype == f_ScrollBar) {
- // no dynamic cast possible due to some distributions shipped
- // without rtti support in irrlicht
+ } else if (s.ftype == f_ScrollBar) {
+ // No dynamic cast possible due to some distributions shipped
+ // without rtti support in Irrlicht
IGUIElement *element = getElementFromId(s.fid, true);
GUIScrollBar *e = nullptr;
if (element && element->getType() == gui::EGUIET_ELEMENT)
else
fields[name] = "VAL:" + os.str();
}
- }
- else {
+ } else if (s.ftype == f_AnimatedImage) {
+ // No dynamic cast possible due to some distributions shipped
+ // without rtti support in Irrlicht
+ IGUIElement *element = getElementFromId(s.fid, true);
+ GUIAnimatedImage *e = nullptr;
+ if (element && element->getType() == gui::EGUIET_ELEMENT)
+ e = static_cast<GUIAnimatedImage *>(element);
+
+ if (e)
+ fields[name] = std::to_string(e->getFrameIndex() + 1);
+ } else {
IGUIElement *e = getElementFromId(s.fid, true);
if (e)
fields[name] = wide_to_utf8(e->getText());
}
}
-static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent)
-{
- while (tocheck) {
- if (tocheck == parent) {
- return true;
- }
- tocheck = tocheck->getParent();
- }
- return false;
-}
-
bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
{
// The IGUITabControl renders visually using the skin's selected
// Mouse wheel and move events: send to hovered element instead of focused
if (event.EventType == EET_MOUSE_INPUT_EVENT &&
(event.MouseInput.Event == EMIE_MOUSE_WHEEL ||
- event.MouseInput.Event == EMIE_MOUSE_MOVED)) {
+ (event.MouseInput.Event == EMIE_MOUSE_MOVED &&
+ event.MouseInput.ButtonStates == 0))) {
s32 x = event.MouseInput.X;
s32 y = event.MouseInput.Y;
gui::IGUIElement *hovered =
}
}
- if (event.EventType == EET_MOUSE_INPUT_EVENT) {
- s32 x = event.MouseInput.X;
- s32 y = event.MouseInput.Y;
- gui::IGUIElement *hovered =
- Environment->getRootGUIElement()->getElementFromPoint(
- core::position2d<s32>(x, y));
- if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
- m_old_tooltip_id = -1;
- }
- if (!isChild(hovered, this)) {
- if (DoubleClickDetection(event)) {
- return true;
- }
- }
- }
-
if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
/* TODO add a check like:
if (event.JoystickEvent != joystick_we_listen_for)
return GUIModalMenu::preprocessEvent(event);
}
-/******************************************************************************/
-bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event)
-{
- /* The following code is for capturing double-clicks of the mouse button
- * and translating the double-click into an EET_KEY_INPUT_EVENT event
- * -- which closes the form -- under some circumstances.
- *
- * There have been many github issues reporting this as a bug even though it
- * was an intended feature. For this reason, remapping the double-click as
- * an ESC must be explicitly set when creating this class via the
- * /p remap_dbl_click parameter of the constructor.
- */
-
- if (!m_remap_dbl_click)
- return false;
-
- if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
- m_doubleclickdetect[0].pos = m_doubleclickdetect[1].pos;
- m_doubleclickdetect[0].time = m_doubleclickdetect[1].time;
-
- m_doubleclickdetect[1].pos = m_pointer;
- m_doubleclickdetect[1].time = porting::getTimeMs();
- }
- else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
- u64 delta = porting::getDeltaMs(m_doubleclickdetect[0].time, porting::getTimeMs());
- if (delta > 400) {
- return false;
- }
-
- double squaredistance =
- m_doubleclickdetect[0].pos
- .getDistanceFromSQ(m_doubleclickdetect[1].pos);
-
- if (squaredistance > (30*30)) {
- return false;
- }
-
- SEvent* translated = new SEvent();
- assert(translated != 0);
- //translate doubleclick to escape
- memset(translated, 0, sizeof(SEvent));
- translated->EventType = irr::EET_KEY_INPUT_EVENT;
- translated->KeyInput.Key = KEY_ESCAPE;
- translated->KeyInput.Control = false;
- translated->KeyInput.Shift = false;
- translated->KeyInput.PressedDown = true;
- translated->KeyInput.Char = 0;
- OnEvent(*translated);
-
- // no need to send the key up event as we're already deleted
- // and no one else did notice this event
- delete translated;
- return true;
- }
-
- return false;
-}
-
void GUIFormSpecMenu::tryClose()
{
if (m_allowclose) {
(kp == getKeySetting("keymap_screenshot"))) {
m_client->makeScreenshot();
}
+
+ if (event.KeyInput.PressedDown && kp == getKeySetting("keymap_toggle_debug"))
+ m_show_debug = !m_show_debug;
+
if (event.KeyInput.PressedDown &&
(event.KeyInput.Key==KEY_RETURN ||
event.KeyInput.Key==KEY_UP ||
(event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
(event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) ||
(event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) {
- unsigned int btn_id = event.GUIEvent.Caller->getID();
+ s32 caller_id = event.GUIEvent.Caller->getID();
- if (btn_id == 257) {
+ if (caller_id == 257) {
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
// if its a button, set the send field so
// lua knows which button was pressed
- if ((s.ftype == f_Button || s.ftype == f_CheckBox) &&
- s.fid == event.GUIEvent.Caller->getID()) {
+
+ if (caller_id != s.fid)
+ continue;
+
+ if (s.ftype == f_Button || s.ftype == f_CheckBox) {
s.send = true;
if (s.is_exit) {
if (m_allowclose) {
s.send = false;
return true;
- } else if ((s.ftype == f_DropDown) &&
- (s.fid == event.GUIEvent.Caller->getID())) {
+ } else if (s.ftype == f_DropDown) {
// only send the changed dropdown
for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
if (s2.ftype == f_DropDown) {
}
}
return true;
- } else if ((s.ftype == f_ScrollBar) &&
- (s.fid == event.GUIEvent.Caller->getID())) {
+ } else if (s.ftype == f_ScrollBar) {
s.fdefault = L"Changed";
acceptInput(quit_mode_no);
s.fdefault = L"";
- } else if ((s.ftype == f_Unknown) &&
- (s.fid == event.GUIEvent.Caller->getID())) {
+ } else if (s.ftype == f_Unknown || s.ftype == f_HyperText) {
s.send = true;
acceptInput();
s.send = false;
}
}
+ if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
+ // move scroll_containers
+ for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers)
+ c.second->onScrollEvent(event.GUIEvent.Caller);
+ }
+
if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
if (event.GUIEvent.Caller->getID() > 257) {
bool close_on_enter = true;
return L"";
}
-StyleSpec GUIFormSpecMenu::getStyleForElement(const std::string &type,
+StyleSpec GUIFormSpecMenu::getDefaultStyleForElement(const std::string &type,
const std::string &name, const std::string &parent_type) {
- StyleSpec ret;
+ return getStyleForElement(type, name, parent_type)[StyleSpec::STATE_DEFAULT];
+}
+
+std::array<StyleSpec, StyleSpec::NUM_STATES> GUIFormSpecMenu::getStyleForElement(
+ const std::string &type, const std::string &name, const std::string &parent_type)
+{
+ std::array<StyleSpec, StyleSpec::NUM_STATES> ret;
+
+ auto it = theme_by_type.find("*");
+ if (it != theme_by_type.end()) {
+ for (const StyleSpec &spec : it->second)
+ ret[(u32)spec.getState()] |= spec;
+ }
+
+ it = theme_by_name.find("*");
+ if (it != theme_by_name.end()) {
+ for (const StyleSpec &spec : it->second)
+ ret[(u32)spec.getState()] |= spec;
+ }
if (!parent_type.empty()) {
- auto it = theme_by_type.find(parent_type);
+ it = theme_by_type.find(parent_type);
if (it != theme_by_type.end()) {
- ret |= it->second;
+ for (const StyleSpec &spec : it->second)
+ ret[(u32)spec.getState()] |= spec;
}
}
- auto it = theme_by_type.find(type);
+ it = theme_by_type.find(type);
if (it != theme_by_type.end()) {
- ret |= it->second;
+ for (const StyleSpec &spec : it->second)
+ ret[(u32)spec.getState()] |= spec;
}
it = theme_by_name.find(name);
if (it != theme_by_name.end()) {
- ret |= it->second;
+ for (const StyleSpec &spec : it->second)
+ ret[(u32)spec.getState()] |= spec;
}
return ret;