Add scroll_container formspec element (redo) (#9101)

New formspec elements:

 - `scroll_container[<X>,<Y>;<W>,<H>;<scrollbar name>;<orientation>;<scroll factor>]`
 - `scroll_container_end[]`

Other elements can be embedded in this element. Scrollbar must be placed manually.
This commit is contained in:
DS 2020-04-13 10:50:07 +02:00 committed by GitHub
parent 6cf15cf872
commit 0ac999ded7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 411 additions and 50 deletions

@ -194,6 +194,7 @@ LOCAL_SRC_FILES := \
jni/src/gui/guiPasswordChange.cpp \ jni/src/gui/guiPasswordChange.cpp \
jni/src/gui/guiPathSelectMenu.cpp \ jni/src/gui/guiPathSelectMenu.cpp \
jni/src/gui/guiScrollBar.cpp \ jni/src/gui/guiScrollBar.cpp \
jni/src/gui/guiScrollContainer.cpp \
jni/src/gui/guiSkin.cpp \ jni/src/gui/guiSkin.cpp \
jni/src/gui/guiTable.cpp \ jni/src/gui/guiTable.cpp \
jni/src/gui/guiVolumeChange.cpp \ jni/src/gui/guiVolumeChange.cpp \

@ -2102,6 +2102,26 @@ Elements
* End of a container, following elements are no longer relative to this * End of a container, following elements are no longer relative to this
container. container.
### `scroll_container[<X>,<Y>;<W>,<H>;<scrollbar name>;<orientation>;<scroll factor>]`
* Start of a scroll_container block. All contained elements will ...
* take the scroll_container coordinate as position origin,
* be additionally moved by the current value of the scrollbar with the name
`scrollbar name` times `scroll factor` along the orientation `orientation` and
* be clipped to the rectangle defined by `X`, `Y`, `W` and `H`.
* `orientation`: possible values are `vertical` and `horizontal`.
* `scroll factor`: optional, defaults to `0.1`.
* Nesting is possible.
* Some elements might work a little different if they are in a scroll_container.
* Note: If you want the scroll_container to actually work, you also need to add a
scrollbar element with the specified name. Furthermore, it is highly recommended
to use a scrollbaroptions element on this scrollbar.
### `scroll_container_end[]`
* End of a scroll_container, following elements are no longer bound to this
container.
### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;]` ### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;]`
* Show an inventory list if it has been sent to the client. Nothing will * Show an inventory list if it has been sent to the client. Nothing will

@ -1,5 +1,35 @@
local color = minetest.colorize local color = minetest.colorize
local hypertext = minetest.formspec_escape([[
<global halign=justify valign=center background=#CCF color=#444 actioncolor=darkblue margin=10>
<center><bigger>Furnace</bigger></center>
<style color=black>Furnaces</style> are <action name="crafting">crafted</action> and used by the player for the purpose of cooking food and <action name="smelting">smelting</action> various items.
<item name=default:furnace float=right width=128 rotate=yes>
<style color=black>Type:</style> Solid block
<style color=black>Drops:</style> Itself
<style color=black>Physics:</style> No
<style color=black>Luminance:</style> Inactive:No Active:Yes (8)
<style color=black>Flammable:</style> No
<style color=black>Generated:</style> No
<style color=black>Renewable:</style> Yes
<style color=black>Stackable:</style> Yes (99)
<style color=black>Itemstring:</style> default:furnace default:furnace_active
<big>Usage</big>
The furnace menu can be accessed by <action name="using">using</action> the furnace.
The furnace has 3 <action name="inventory">inventories</action>: An input slot, a fuel slot and 4 output slots. The fire in the furnace will automatically start when there is a smeltable item in the input slot and a fuel in the fuel slot.
As long as the fire is on, the furnace will smelt any smeltable item in the input slot, one by one, until it is empty. When the fire goes off, it will smelt the next item until there are no smeltable items and no fuel items left.
The current stage of cooking can be seen by <action name="pointing">pointing</action> the furnace or by viewing the furnace menu. In the furnace menu, the flame symbol roughly shows the remaining burning time. The arrow symbol shows the progress of the current smelting process.
<big>Renewing</big>
Furnaces can be crafted from e.g. <action name="default:cobblestone">cobblestone</action>, a renewable resource.
<big>Crafting</big>
Sorry no way to display crafting yet in formspec pages.
<big>Fuel</big>
See <action name="smelting">Smelting</action> for a list of furnace fuels.
<big>Recipes</big>
See the <action name="smelting">Smelting</action> page.
]])
local clip_fs = [[ local clip_fs = [[
style_type[label,button,image_button,item_image_button, style_type[label,button,image_button,item_image_button,
tabheader,scrollbar,table,animated_image tabheader,scrollbar,table,animated_image
@ -188,13 +218,48 @@ Number]
animated_image[3,4.25;1,1;;test_animation.png;4;0;3] animated_image[3,4.25;1,1;;test_animation.png;4;0;3]
animated_image[5.5,0.5;5,2;;test_animation.png;4;100] animated_image[5.5,0.5;5,2;;test_animation.png;4;100]
animated_image[5.5,2.75;5,2;;test_animation.jpg;4;100] animated_image[5.5,2.75;5,2;;test_animation.jpg;4;100]
]] ]],
"formspec_version[3]"..
"size[12,12]"..
"button[8.5,1;1,1;bla;Bla]"..
"box[1,1;8,6;#00aa]"..
"scroll_container[1,1;8,6;scrbar;vertical]"..
"button[0,1;1,1;lorem;Lorem]"..
"button[0,10;1,1;ipsum;Ipsum]"..
"pwdfield[2,2;1,1;lorem2;Lorem]"..
"list[current_player;main;4,4;1,5;]"..
"box[2,5;3,2;#ffff00]"..
"image[1,10;3,2;bubble.png]"..
"image[3,1;bubble.png]"..
"item_image[2,6;3,2;default:mese]"..
"label[2,15;bla Bli\nfoo bar]"..
"item_image_button[2,3;1,1;default:dirt_with_grass;itemimagebutton;ItemImageButton]"..
"tooltip[0,11;3,2;Buz;#f00;#000]"..
"box[0,11;3,2;#00ff00]"..
"hypertext[3,13;3,3;;" .. hypertext .. "]" ..
"container[0,18]"..
"box[1,2;3,2;#0a0a]"..
"scroll_container[1,2;3,2;scrbar2;horizontal;0.06]"..
"button[0,0;6,1;butnest;Nest]"..
"label[10,0.5;nest]"..
"scroll_container_end[]"..
"scrollbar[1,0;3.5,0.3;horizontal;scrbar2;0]"..
"container_end[]"..
"dropdown[0,6;2;hmdrpdwn;apple,bulb;1]"..
"image_button[0,4;2,2;bubble.png;bubblebutton;bbbbtt;false;true;heart.png]"..
"box[1,22.5;4,1;#a00a]"..
"scroll_container_end[]"..
"scrollbaroptions[max=170]".. -- lowest seen pos is: 0.1*170+6=23 (factor*max+height)
"scrollbar[7.5,0;0.3,4;vertical;scrbar;0]"..
"scrollbar[8,0;0.3,4;vertical;scrbarhmmm;0]"..
"dropdown[0,6;2;hmdrpdwnnn;apple,bulb;1]",
} }
local function show_test_formspec(pname, page_id) local function show_test_formspec(pname, page_id)
page_id = page_id or 2 page_id = page_id or 2
local fs = pages[page_id] .. "tabheader[0,0;6,0.65;maintabs;Real Coord,Styles,Noclip,MiscEle;" .. page_id .. ";false;false]" local fs = pages[page_id] .. "tabheader[0,0;8,0.65;maintabs;Real Coord,Styles,Noclip,MiscEle,Scroll Container;" .. page_id .. ";false;false]"
minetest.show_formspec(pname, "test:formspec", fs) minetest.show_formspec(pname, "test:formspec", fs)
end end

@ -16,6 +16,7 @@ set(gui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiScrollContainer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiHyperText.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiHyperText.cpp

@ -65,6 +65,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "guiInventoryList.h" #include "guiInventoryList.h"
#include "guiItemImage.h" #include "guiItemImage.h"
#include "guiScrollBar.h" #include "guiScrollBar.h"
#include "guiScrollContainer.h"
#include "guiTable.h" #include "guiTable.h"
#include "intlGUIEditBox.h" #include "intlGUIEditBox.h"
#include "guiHyperText.h" #include "guiHyperText.h"
@ -143,6 +144,8 @@ GUIFormSpecMenu::~GUIFormSpecMenu()
tooltip_rect_it.first->drop(); tooltip_rect_it.first->drop();
for (auto &clickthrough_it : m_clickthrough_elements) for (auto &clickthrough_it : m_clickthrough_elements)
clickthrough_it->drop(); clickthrough_it->drop();
for (auto &scroll_container_it : m_scroll_containers)
scroll_container_it.second->drop();
delete m_selected_item; delete m_selected_item;
delete m_form_src; delete m_form_src;
@ -351,6 +354,102 @@ void GUIFormSpecMenu::parseContainerEnd(parserData* data)
} }
} }
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) void GUIFormSpecMenu::parseList(parserData *data, const std::string &element)
{ {
if (m_client == 0) { if (m_client == 0) {
@ -443,9 +542,9 @@ void GUIFormSpecMenu::parseList(parserData *data, const std::string &element)
pos.X + (geom.X - 1) * slot_spacing.X + imgsize.X, pos.X + (geom.X - 1) * slot_spacing.X + imgsize.X,
pos.Y + (geom.Y - 1) * slot_spacing.Y + imgsize.Y); pos.Y + (geom.Y - 1) * slot_spacing.Y + imgsize.Y);
GUIInventoryList *e = new GUIInventoryList(Environment, this, spec.fid, GUIInventoryList *e = new GUIInventoryList(Environment, data->current_parent,
rect, m_invmgr, loc, listname, geom, start_i, imgsize, slot_spacing, spec.fid, rect, m_invmgr, loc, listname, geom, start_i, imgsize,
this, data->inventorylist_options, m_font); slot_spacing, this, data->inventorylist_options, m_font);
m_inventorylists.push_back(e); m_inventorylists.push_back(e);
m_fields.push_back(spec); m_fields.push_back(spec);
@ -550,8 +649,8 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element
spec.ftype = f_CheckBox; spec.ftype = f_CheckBox;
gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect, this, gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect,
spec.fid, spec.flabel.c_str()); data->current_parent, spec.fid, spec.flabel.c_str());
auto style = getDefaultStyleForElement("checkbox", name); auto style = getDefaultStyleForElement("checkbox", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
@ -610,8 +709,8 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen
spec.ftype = f_ScrollBar; spec.ftype = f_ScrollBar;
spec.send = true; spec.send = true;
GUIScrollBar *e = new GUIScrollBar(Environment, this, spec.fid, rect, GUIScrollBar *e = new GUIScrollBar(Environment, data->current_parent,
is_horizontal, true); spec.fid, rect, is_horizontal, true);
auto style = getDefaultStyleForElement("scrollbar", name); auto style = getDefaultStyleForElement("scrollbar", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
@ -737,7 +836,8 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
1 1
); );
core::rect<s32> rect(pos, pos + geom); 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->setImage(texture);
e->setScaleImage(true); e->setScaleImage(true);
auto style = getDefaultStyleForElement("image", spec.fname); auto style = getDefaultStyleForElement("image", spec.fname);
@ -774,8 +874,8 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
L"", L"",
258 + m_fields.size() 258 + m_fields.size()
); );
gui::IGUIImage *e = Environment->addImage(texture, pos, true, this, gui::IGUIImage *e = Environment->addImage(texture, pos, true,
spec.fid, 0); data->current_parent, spec.fid, 0);
auto style = getDefaultStyleForElement("image", spec.fname); auto style = getDefaultStyleForElement("image", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
m_fields.push_back(spec); m_fields.push_back(spec);
@ -886,7 +986,7 @@ void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &elemen
); );
spec.ftype = f_ItemImage; 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); core::rect<s32>(pos, pos + geom), name, m_font, m_client);
auto style = getDefaultStyleForElement("item_image", spec.fname); auto style = getDefaultStyleForElement("item_image", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
@ -949,8 +1049,8 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
if(type == "button_exit") if(type == "button_exit")
spec.is_exit = true; spec.is_exit = true;
GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc, this, GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc,
spec.fid, spec.flabel.c_str()); data->current_parent, spec.fid, spec.flabel.c_str());
auto style = getStyleForElement(type, name, (type != "button") ? "button" : ""); auto style = getStyleForElement(type, name, (type != "button") ? "button" : "");
e->setStyles(style); e->setStyles(style);
@ -1141,7 +1241,8 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
} }
//now really show table //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 == data->focused_fieldname) {
Environment->setFocus(e); Environment->setFocus(e);
@ -1217,7 +1318,8 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element
} }
//now really show list //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 == data->focused_fieldname) {
Environment->setFocus(e); Environment->setFocus(e);
@ -1293,7 +1395,8 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element
spec.send = true; spec.send = true;
//now really show list //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 == data->focused_fieldname) {
Environment->setFocus(e); Environment->setFocus(e);
@ -1379,7 +1482,8 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
); );
spec.send = true; 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 == data->focused_fieldname) {
Environment->setFocus(e); Environment->setFocus(e);
@ -1390,7 +1494,7 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
rect.UpperLeftCorner.Y -= font_height; rect.UpperLeftCorner.Y -= font_height;
rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true, gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
this, 0); data->current_parent, 0);
} }
e->setPasswordBox(true,L'*'); e->setPasswordBox(true,L'*');
@ -1425,7 +1529,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
if (!is_editable && !is_multiline) { if (!is_editable && !is_multiline) {
// spec field id to 0, this stops submit searching for a value that isn't there // 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, gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
this, 0); data->current_parent, 0);
return; return;
} }
@ -1443,14 +1547,14 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
if (use_intl_edit_box && g_settings->getBool("freetype")) { if (use_intl_edit_box && g_settings->getBool("freetype")) {
e = new gui::intlGUIEditBox(spec.fdefault.c_str(), true, Environment, 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 { } else {
if (is_multiline) { if (is_multiline) {
e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, Environment,
Environment, this, spec.fid, rect, is_editable, true); data->current_parent, spec.fid, rect, is_editable, true);
} else if (is_editable) { } else if (is_editable) {
e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, e = Environment->addEditBox(spec.fdefault.c_str(), rect, true,
spec.fid); data->current_parent, spec.fid);
e->grab(); e->grab();
} }
} }
@ -1491,7 +1595,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
rect.UpperLeftCorner.Y -= font_height; rect.UpperLeftCorner.Y -= font_height;
rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
IGUIElement *t = gui::StaticText::add(Environment, spec.flabel.c_str(), IGUIElement *t = gui::StaticText::add(Environment, spec.flabel.c_str(),
rect, false, true, this, 0); rect, false, true, data->current_parent, 0);
if (t) if (t)
t->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); t->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
@ -1671,8 +1775,8 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen
); );
spec.ftype = f_HyperText; spec.ftype = f_HyperText;
GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, this, GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment,
spec.fid, rect, m_client, m_tsrc); data->current_parent, spec.fid, rect, m_client, m_tsrc);
e->drop(); e->drop();
m_fields.push_back(spec); m_fields.push_back(spec);
@ -1750,7 +1854,8 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
4 4
); );
gui::IGUIStaticText *e = gui::StaticText::add(Environment, 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); e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER);
auto style = getDefaultStyleForElement("label", spec.fname); auto style = getDefaultStyleForElement("label", spec.fname);
@ -1830,7 +1935,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen
258 + m_fields.size() 258 + m_fields.size()
); );
gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(), 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); e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
auto style = getDefaultStyleForElement("vertlabel", spec.fname, "label"); auto style = getDefaultStyleForElement("vertlabel", spec.fname, "label");
@ -1904,7 +2009,7 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
spec.is_exit = true; spec.is_exit = true;
GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, m_tsrc, GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, m_tsrc,
this, spec.fid, spec.flabel.c_str()); data->current_parent, spec.fid, spec.flabel.c_str());
if (spec.fname == data->focused_fieldname) { if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e); Environment->setFocus(e);
@ -2010,8 +2115,8 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
pos.Y+geom.Y); pos.Y+geom.Y);
gui::IGUITabControl *e = Environment->addTabControl(rect, this, gui::IGUITabControl *e = Environment->addTabControl(rect,
show_background, show_border, spec.fid); data->current_parent, show_background, show_border, spec.fid);
e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT, e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT,
irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT); irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT);
e->setTabHeight(geom.Y); e->setTabHeight(geom.Y);
@ -2046,7 +2151,6 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element) void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element)
{ {
if (m_client == 0) { if (m_client == 0) {
warningstream << "invalid use of item_image_button with m_client==0" warningstream << "invalid use of item_image_button with m_client==0"
<< std::endl; << std::endl;
@ -2106,7 +2210,7 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &
); );
GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment, GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment,
rect, m_tsrc, this, spec_btn.fid, spec_btn.flabel.c_str(), rect, m_tsrc, data->current_parent, spec_btn.fid, spec_btn.flabel.c_str(),
item_name, m_client); item_name, m_client);
auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button"); auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button");
@ -2164,7 +2268,8 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
core::rect<s32> rect(pos, pos + geom); core::rect<s32> rect(pos, pos + geom);
GUIBox *e = new GUIBox(Environment, this, spec.fid, rect, tmp_color); GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid,
rect, tmp_color);
auto style = getDefaultStyleForElement("box", spec.fname); auto style = getDefaultStyleForElement("box", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
@ -2316,7 +2421,7 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element)
core::rect<s32> rect(pos, pos + geom); core::rect<s32> rect(pos, pos + geom);
gui::IGUIElement *e = new gui::IGUIElement(EGUIET_ELEMENT, Environment, 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 // the element the rect tooltip is bound to should not block mouse-clicks
e->setVisible(false); e->setVisible(false);
@ -2775,6 +2880,16 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
return; return;
} }
if (type == "scroll_container") {
parseScrollContainer(data, description);
return;
}
if (type == "scroll_container_end") {
parseScrollContainerEnd(data);
return;
}
// Ignore others // Ignore others
infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\"" infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
<< std::endl; << std::endl;
@ -2831,6 +2946,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
tooltip_rect_it.first->drop(); tooltip_rect_it.first->drop();
for (auto &clickthrough_it : m_clickthrough_elements) for (auto &clickthrough_it : m_clickthrough_elements)
clickthrough_it->drop(); 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.screensize = screensize;
@ -2841,6 +2958,9 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
// Base position of contents of form // Base position of contents of form
mydata.basepos = getBasePos(); mydata.basepos = getBasePos();
// the parent for the parsed elements
mydata.current_parent = this;
m_inventorylists.clear(); m_inventorylists.clear();
m_backgrounds.clear(); m_backgrounds.clear();
m_tables.clear(); m_tables.clear();
@ -2851,6 +2971,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
m_tooltip_rects.clear(); m_tooltip_rects.clear();
m_inventory_rings.clear(); m_inventory_rings.clear();
m_dropdowns.clear(); m_dropdowns.clear();
m_scroll_containers.clear();
theme_by_name.clear(); theme_by_name.clear();
theme_by_type.clear(); theme_by_type.clear();
m_clickthrough_elements.clear(); m_clickthrough_elements.clear();
@ -3117,11 +3238,24 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
parseElement(&mydata, elements[i]); 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!" errorstream << "Invalid formspec string: container was never closed!"
<< std::endl; << 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" // If there are fields without explicit size[], add a "Proceed"
// button and adjust size to fit all the fields. // button and adjust size to fit all the fields.
if (mydata.simple_field_count > 0 && !mydata.explicit_size) { if (mydata.simple_field_count > 0 && !mydata.explicit_size) {
@ -4418,6 +4552,12 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} }
} }
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.EventType == gui::EGET_EDITBOX_ENTER) {
if (event.GUIEvent.Caller->getID() > 257) { if (event.GUIEvent.Caller->getID() > 257) {
bool close_on_enter = true; bool close_on_enter = true;

@ -39,6 +39,7 @@ class ISimpleTextureSource;
class Client; class Client;
class GUIScrollBar; class GUIScrollBar;
class TexturePool; class TexturePool;
class GUIScrollContainer;
typedef enum { typedef enum {
f_Button, f_Button,
@ -310,6 +311,7 @@ protected:
std::vector<std::pair<FieldSpec, GUIScrollBar *>> m_scrollbars; std::vector<std::pair<FieldSpec, GUIScrollBar *>> m_scrollbars;
std::vector<std::pair<FieldSpec, std::vector<std::string>>> m_dropdowns; std::vector<std::pair<FieldSpec, std::vector<std::string>>> m_dropdowns;
std::vector<gui::IGUIElement *> m_clickthrough_elements; std::vector<gui::IGUIElement *> m_clickthrough_elements;
std::vector<std::pair<std::string, GUIScrollContainer *>> m_scroll_containers;
GUIInventoryList::ItemSpec *m_selected_item = nullptr; GUIInventoryList::ItemSpec *m_selected_item = nullptr;
u16 m_selected_amount = 0; u16 m_selected_amount = 0;
@ -359,6 +361,7 @@ private:
std::string focused_fieldname; std::string focused_fieldname;
GUITable::TableOptions table_options; GUITable::TableOptions table_options;
GUITable::TableColumns table_columns; GUITable::TableColumns table_columns;
gui::IGUIElement *current_parent = nullptr;
GUIInventoryList::Options inventorylist_options; GUIInventoryList::Options inventorylist_options;
@ -391,6 +394,8 @@ private:
void parseSize(parserData* data, const std::string &element); void parseSize(parserData* data, const std::string &element);
void parseContainer(parserData* data, const std::string &element); void parseContainer(parserData* data, const std::string &element);
void parseContainerEnd(parserData* data); void parseContainerEnd(parserData* data);
void parseScrollContainer(parserData *data, const std::string &element);
void parseScrollContainerEnd(parserData *data);
void parseList(parserData* data, const std::string &element); void parseList(parserData* data, const std::string &element);
void parseListRing(parserData* data, const std::string &element); void parseListRing(parserData* data, const std::string &element);
void parseCheckbox(parserData* data, const std::string &element); void parseCheckbox(parserData* data, const std::string &element);

@ -917,20 +917,20 @@ void TextDrawer::place(const core::rect<s32> &dest_rect)
// Draw text in a rectangle with a given offset. Items are actually placed in // Draw text in a rectangle with a given offset. Items are actually placed in
// relative (to upper left corner) coordinates. // relative (to upper left corner) coordinates.
void TextDrawer::draw(const core::rect<s32> &dest_rect, void TextDrawer::draw(const core::rect<s32> &clip_rect,
const core::position2d<s32> &dest_offset) const core::position2d<s32> &dest_offset)
{ {
irr::video::IVideoDriver *driver = m_environment->getVideoDriver(); irr::video::IVideoDriver *driver = m_environment->getVideoDriver();
core::position2d<s32> offset = dest_rect.UpperLeftCorner + dest_offset; core::position2d<s32> offset = dest_offset;
offset.Y += m_voffset; offset.Y += m_voffset;
if (m_text.background_type == ParsedText::BACKGROUND_COLOR) if (m_text.background_type == ParsedText::BACKGROUND_COLOR)
driver->draw2DRectangle(m_text.background_color, dest_rect); driver->draw2DRectangle(m_text.background_color, clip_rect);
for (auto &p : m_text.m_paragraphs) { for (auto &p : m_text.m_paragraphs) {
for (auto &el : p.elements) { for (auto &el : p.elements) {
core::rect<s32> rect(el.pos + offset, el.dim); core::rect<s32> rect(el.pos + offset, el.dim);
if (!rect.isRectCollided(dest_rect)) if (!rect.isRectCollided(clip_rect))
continue; continue;
switch (el.type) { switch (el.type) {
@ -947,7 +947,7 @@ void TextDrawer::draw(const core::rect<s32> &dest_rect,
if (el.type == ParsedText::ELEMENT_TEXT) if (el.type == ParsedText::ELEMENT_TEXT)
el.font->draw(el.text, rect, color, false, true, el.font->draw(el.text, rect, color, false, true,
&dest_rect); &clip_rect);
if (el.underline && el.drawwidth) { if (el.underline && el.drawwidth) {
s32 linepos = el.pos.Y + offset.Y + s32 linepos = el.pos.Y + offset.Y +
@ -958,7 +958,7 @@ void TextDrawer::draw(const core::rect<s32> &dest_rect,
el.pos.X + offset.X + el.drawwidth, el.pos.X + offset.X + el.drawwidth,
linepos + (el.baseline >> 3)); linepos + (el.baseline >> 3));
driver->draw2DRectangle(color, linerect, &dest_rect); driver->draw2DRectangle(color, linerect, &clip_rect);
} }
} break; } break;
@ -972,7 +972,7 @@ void TextDrawer::draw(const core::rect<s32> &dest_rect,
irr::core::rect<s32>( irr::core::rect<s32>(
core::position2d<s32>(0, 0), core::position2d<s32>(0, 0),
texture->getOriginalSize()), texture->getOriginalSize()),
&dest_rect, 0, true); &clip_rect, 0, true);
} break; } break;
case ParsedText::ELEMENT_ITEM: { case ParsedText::ELEMENT_ITEM: {
@ -982,7 +982,7 @@ void TextDrawer::draw(const core::rect<s32> &dest_rect,
drawItemStack( drawItemStack(
m_environment->getVideoDriver(), m_environment->getVideoDriver(),
g_fontengine->getFont(), item, rect, &dest_rect, g_fontengine->getFont(), item, rect, &clip_rect,
m_client, IT_ROT_OTHER, el.angle, el.rotation m_client, IT_ROT_OTHER, el.angle, el.rotation
); );
} break; } break;
@ -1094,6 +1094,7 @@ bool GUIHyperText::OnEvent(const SEvent &event)
m_text_scrollpos.Y = -m_vscrollbar->getPos(); m_text_scrollpos.Y = -m_vscrollbar->getPos();
m_drawer.draw(m_display_text_rect, m_text_scrollpos); m_drawer.draw(m_display_text_rect, m_text_scrollpos);
checkHover(event.MouseInput.X, event.MouseInput.Y); checkHover(event.MouseInput.X, event.MouseInput.Y);
return true;
} else if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { } else if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
ParsedText::Element *element = getElementAt( ParsedText::Element *element = getElementAt(
@ -1151,7 +1152,8 @@ void GUIHyperText::draw()
m_vscrollbar->setPos(0); m_vscrollbar->setPos(0);
m_vscrollbar->setVisible(false); m_vscrollbar->setVisible(false);
} }
m_drawer.draw(m_display_text_rect, m_text_scrollpos); m_drawer.draw(AbsoluteClippingRect,
m_display_text_rect.UpperLeftCorner + m_text_scrollpos);
// draw children // draw children
IGUIElement::draw(); IGUIElement::draw();

@ -174,7 +174,7 @@ public:
void place(const core::rect<s32> &dest_rect); void place(const core::rect<s32> &dest_rect);
inline s32 getHeight() { return m_height; }; inline s32 getHeight() { return m_height; };
void draw(const core::rect<s32> &dest_rect, void draw(const core::rect<s32> &clip_rect,
const core::position2d<s32> &dest_offset); const core::position2d<s32> &dest_offset);
ParsedText::Element *getElementAt(core::position2d<s32> pos); ParsedText::Element *getElementAt(core::position2d<s32> pos);
ParsedText::Tag *m_hovertag; ParsedText::Tag *m_hovertag;

@ -0,0 +1,70 @@
/*
Minetest
Copyright (C) 2020 DS
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "guiScrollContainer.h"
GUIScrollContainer::GUIScrollContainer(gui::IGUIEnvironment *env,
gui::IGUIElement *parent, s32 id, const core::rect<s32> &rectangle,
const std::string &orientation, f32 scrollfactor) :
gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
m_scrollbar(nullptr), m_scrollfactor(scrollfactor)
{
if (orientation == "vertical")
m_orientation = VERTICAL;
else if (orientation == "horizontal")
m_orientation = HORIZONTAL;
else
m_orientation = UNDEFINED;
}
bool GUIScrollContainer::OnEvent(const SEvent &event)
{
if (event.EventType == EET_MOUSE_INPUT_EVENT &&
event.MouseInput.Event == EMIE_MOUSE_WHEEL &&
!event.MouseInput.isLeftPressed() && m_scrollbar) {
Environment->setFocus(m_scrollbar);
bool retval = m_scrollbar->OnEvent(event);
// a hacky fix for updating the hovering and co.
IGUIElement *hovered_elem = getElementFromPoint(core::position2d<s32>(
event.MouseInput.X, event.MouseInput.Y));
SEvent mov_event = event;
mov_event.MouseInput.Event = EMIE_MOUSE_MOVED;
Environment->postEventFromUser(mov_event);
if (hovered_elem)
hovered_elem->OnEvent(mov_event);
return retval;
}
return IGUIElement::OnEvent(event);
}
void GUIScrollContainer::updateScrolling()
{
s32 pos = m_scrollbar->getPos();
core::rect<s32> rect = getRelativePosition();
if (m_orientation == VERTICAL)
rect.UpperLeftCorner.Y = pos * m_scrollfactor;
else if (m_orientation == HORIZONTAL)
rect.UpperLeftCorner.X = pos * m_scrollfactor;
setRelativePosition(rect);
}

@ -0,0 +1,56 @@
/*
Minetest
Copyright (C) 2020 DS
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "irrlichttypes_extrabloated.h"
#include "util/string.h"
#include "guiScrollBar.h"
class GUIScrollContainer : public gui::IGUIElement
{
public:
GUIScrollContainer(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
const core::rect<s32> &rectangle, const std::string &orientation,
f32 scrollfactor);
virtual bool OnEvent(const SEvent &event) override;
inline void onScrollEvent(gui::IGUIElement *caller)
{
if (caller == m_scrollbar)
updateScrolling();
}
inline void setScrollBar(GUIScrollBar *scrollbar) { m_scrollbar = scrollbar; }
private:
enum OrientationEnum
{
VERTICAL,
HORIZONTAL,
UNDEFINED
};
GUIScrollBar *m_scrollbar;
OrientationEnum m_orientation;
f32 m_scrollfactor;
void updateScrolling();
};

@ -237,6 +237,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Formspec elements are drawn in the order of definition Formspec elements are drawn in the order of definition
bgcolor[]: use 3 parameters (bgcolor, formspec (now an enum), fbgcolor) bgcolor[]: use 3 parameters (bgcolor, formspec (now an enum), fbgcolor)
box[] and image[] elements enable clipping by default box[] and image[] elements enable clipping by default
new element: scroll_container[]
*/ */
#define FORMSPEC_API_VERSION 3 #define FORMSPEC_API_VERSION 3