Add scroll_container formspec element (redo) (#9101)
authorDS <vorunbekannt75@web.de>
Mon, 13 Apr 2020 08:50:07 +0000 (10:50 +0200)
committerGitHub <noreply@github.com>
Mon, 13 Apr 2020 08:50:07 +0000 (10:50 +0200)
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.

build/android/jni/Android.mk
doc/lua_api.txt
games/minimal/mods/test/formspec.lua
src/gui/CMakeLists.txt
src/gui/guiFormSpecMenu.cpp
src/gui/guiFormSpecMenu.h
src/gui/guiHyperText.cpp
src/gui/guiHyperText.h
src/gui/guiScrollContainer.cpp [new file with mode: 0644]
src/gui/guiScrollContainer.h [new file with mode: 0644]
src/network/networkprotocol.h

index fdbfba9bc61cf51c0bafc50a4944ccbc24000993..b67322d794ed68b5745c02a617cbae21232395fa 100644 (file)
@@ -194,6 +194,7 @@ LOCAL_SRC_FILES := \
                jni/src/gui/guiPasswordChange.cpp         \
                jni/src/gui/guiPathSelectMenu.cpp         \
                jni/src/gui/guiScrollBar.cpp              \
+               jni/src/gui/guiScrollContainer.cpp        \
                jni/src/gui/guiSkin.cpp                   \
                jni/src/gui/guiTable.cpp                  \
                jni/src/gui/guiVolumeChange.cpp           \
index dff6c728ea39e1a1e00cdc8f2b689441832c8484..f43987cd84a1593e8538fefa8675af34d4094d90 100644 (file)
@@ -2102,6 +2102,26 @@ Elements
 * End of a container, following elements are no longer relative to this
   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>;]`
 
 * Show an inventory list if it has been sent to the client. Nothing will
index 14886aad2e8c1bb76bbd5f4bbfb4a5da7c9b7c6f..53e92b243e48fc0b946fa5c723e210749af3698e 100644 (file)
@@ -1,5 +1,35 @@
 local color = minetest.colorize\r
 \r
+local hypertext = minetest.formspec_escape([[\r
+       <global halign=justify valign=center background=#CCF color=#444 actioncolor=darkblue margin=10>\r
+       <center><bigger>Furnace</bigger></center>\r
+       <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.\r
+       <item name=default:furnace float=right width=128 rotate=yes>\r
+       <style color=black>Type:</style> Solid block\r
+       <style color=black>Drops:</style> Itself\r
+       <style color=black>Physics:</style> No\r
+       <style color=black>Luminance:</style> Inactive:No Active:Yes (8)\r
+       <style color=black>Flammable:</style> No\r
+       <style color=black>Generated:</style> No\r
+       <style color=black>Renewable:</style> Yes\r
+       <style color=black>Stackable:</style> Yes (99)\r
+       <style color=black>Itemstring:</style> default:furnace default:furnace_active\r
+       <big>Usage</big>\r
+       The furnace menu can be accessed by <action name="using">using</action> the furnace.\r
+       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.\r
+       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.\r
+       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.\r
+       <big>Renewing</big>\r
+       Furnaces can be crafted from e.g. <action name="default:cobblestone">cobblestone</action>, a renewable resource.\r
+       <big>Crafting</big>\r
+       Sorry no way to display crafting yet in formspec pages.\r
+       <big>Fuel</big>\r
+       See <action name="smelting">Smelting</action> for a list of furnace fuels.\r
+       <big>Recipes</big>\r
+       See the <action name="smelting">Smelting</action> page.\r
+]])\r
+\r
+\r
 local clip_fs = [[\r
        style_type[label,button,image_button,item_image_button,\r
                        tabheader,scrollbar,table,animated_image\r
@@ -188,13 +218,48 @@ Number]
                        animated_image[3,4.25;1,1;;test_animation.png;4;0;3]\r
                        animated_image[5.5,0.5;5,2;;test_animation.png;4;100]\r
                        animated_image[5.5,2.75;5,2;;test_animation.jpg;4;100]\r
-               ]]\r
+               ]],\r
+\r
+               "formspec_version[3]"..\r
+               "size[12,12]"..\r
+               "button[8.5,1;1,1;bla;Bla]"..\r
+               "box[1,1;8,6;#00aa]"..\r
+               "scroll_container[1,1;8,6;scrbar;vertical]"..\r
+                       "button[0,1;1,1;lorem;Lorem]"..\r
+                       "button[0,10;1,1;ipsum;Ipsum]"..\r
+                       "pwdfield[2,2;1,1;lorem2;Lorem]"..\r
+                       "list[current_player;main;4,4;1,5;]"..\r
+                       "box[2,5;3,2;#ffff00]"..\r
+                       "image[1,10;3,2;bubble.png]"..\r
+                       "image[3,1;bubble.png]"..\r
+                       "item_image[2,6;3,2;default:mese]"..\r
+                       "label[2,15;bla Bli\nfoo bar]"..\r
+                       "item_image_button[2,3;1,1;default:dirt_with_grass;itemimagebutton;ItemImageButton]"..\r
+                       "tooltip[0,11;3,2;Buz;#f00;#000]"..\r
+                       "box[0,11;3,2;#00ff00]"..\r
+                       "hypertext[3,13;3,3;;" .. hypertext .. "]" ..\r
+                       "container[0,18]"..\r
+                               "box[1,2;3,2;#0a0a]"..\r
+                               "scroll_container[1,2;3,2;scrbar2;horizontal;0.06]"..\r
+                                       "button[0,0;6,1;butnest;Nest]"..\r
+                                       "label[10,0.5;nest]"..\r
+                               "scroll_container_end[]"..\r
+                               "scrollbar[1,0;3.5,0.3;horizontal;scrbar2;0]"..\r
+                       "container_end[]"..\r
+                       "dropdown[0,6;2;hmdrpdwn;apple,bulb;1]"..\r
+                       "image_button[0,4;2,2;bubble.png;bubblebutton;bbbbtt;false;true;heart.png]"..\r
+                       "box[1,22.5;4,1;#a00a]"..\r
+               "scroll_container_end[]"..\r
+               "scrollbaroptions[max=170]".. -- lowest seen pos is: 0.1*170+6=23 (factor*max+height)\r
+               "scrollbar[7.5,0;0.3,4;vertical;scrbar;0]"..\r
+               "scrollbar[8,0;0.3,4;vertical;scrbarhmmm;0]"..\r
+               "dropdown[0,6;2;hmdrpdwnnn;apple,bulb;1]",\r
 }\r
 \r
 local function show_test_formspec(pname, page_id)\r
        page_id = page_id or 2\r
 \r
-       local fs = pages[page_id] .. "tabheader[0,0;6,0.65;maintabs;Real Coord,Styles,Noclip,MiscEle;" .. page_id .. ";false;false]"\r
+       local fs = pages[page_id] .. "tabheader[0,0;8,0.65;maintabs;Real Coord,Styles,Noclip,MiscEle,Scroll Container;" .. page_id .. ";false;false]"\r
 \r
        minetest.show_formspec(pname, "test:formspec", fs)\r
 end\r
index 110a0059581cd4f9cd8b0a909da41351fc5c378d..147f445f4dcbc418216974be6f686d9872a7bd9a 100644 (file)
@@ -16,6 +16,7 @@ set(gui_SRCS
        ${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollContainer.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiHyperText.cpp
index f3bf04275a145ac68d58c80d01a9ae693c2db61d..aac039ad6164922f63440a5923cd53c2390f2d8c 100644 (file)
@@ -65,6 +65,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "guiInventoryList.h"
 #include "guiItemImage.h"
 #include "guiScrollBar.h"
+#include "guiScrollContainer.h"
 #include "guiTable.h"
 #include "intlGUIEditBox.h"
 #include "guiHyperText.h"
@@ -143,6 +144,8 @@ GUIFormSpecMenu::~GUIFormSpecMenu()
                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;
@@ -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)
 {
        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.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);
@@ -550,8 +649,8 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element
 
                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 = getDefaultStyleForElement("checkbox", name);
                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.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 = getDefaultStyleForElement("scrollbar", name);
                e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
@@ -737,7 +836,8 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
                        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 = getDefaultStyleForElement("image", spec.fname);
@@ -774,8 +874,8 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
                        L"",
                        258 + m_fields.size()
                );
-               gui::IGUIImage *e = Environment->addImage(texture, pos, true, this,
-                               spec.fid, 0);
+               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);
@@ -886,7 +986,7 @@ void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &elemen
                );
                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 = getDefaultStyleForElement("item_image", spec.fname);
                e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
@@ -949,8 +1049,8 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
                if(type == "button_exit")
                        spec.is_exit = true;
 
-               GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc, 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->setStyles(style);
@@ -1141,7 +1241,8 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
                }
 
                //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) {
                        Environment->setFocus(e);
@@ -1217,7 +1318,8 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element
                }
 
                //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) {
                        Environment->setFocus(e);
@@ -1293,7 +1395,8 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element
                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) {
                        Environment->setFocus(e);
@@ -1379,7 +1482,8 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
                        );
 
                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) {
                        Environment->setFocus(e);
@@ -1390,7 +1494,7 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
                        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'*');
@@ -1425,7 +1529,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
        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, 0);
+                               data->current_parent, 0);
                return;
        }
 
@@ -1443,14 +1547,14 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
 
        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();
                }
        }
@@ -1491,7 +1595,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
                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));
@@ -1671,8 +1775,8 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen
        );
 
        spec.ftype = f_HyperText;
-       GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, this,
-                       spec.fid, rect, m_client, m_tsrc);
+       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);
@@ -1750,7 +1854,8 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
                                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 = getDefaultStyleForElement("label", spec.fname);
@@ -1830,7 +1935,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen
                        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 = getDefaultStyleForElement("vertlabel", spec.fname, "label");
@@ -1904,7 +2009,7 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
                        spec.is_exit = true;
 
                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) {
                        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,
                                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);
@@ -2046,7 +2151,6 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
 
 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;
@@ -2106,7 +2210,7 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &
                );
 
                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);
 
                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);
 
-                       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);
                        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);
 
                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);
@@ -2775,6 +2880,16 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
                return;
        }
 
+       if (type == "scroll_container") {
+               parseScrollContainer(data, description);
+               return;
+       }
+
+       if (type == "scroll_container_end") {
+               parseScrollContainerEnd(data);
+               return;
+       }
+
        // Ignore others
        infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
                        << std::endl;
@@ -2831,8 +2946,10 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
                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);
@@ -2841,6 +2958,9 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
        // 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();
@@ -2851,6 +2971,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
        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();
@@ -3117,11 +3238,24 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
                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) {
@@ -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.Caller->getID() > 257) {
                                bool close_on_enter = true;
index dc22e8b54f4e362abffe599529caa42936f58913..28088416d9c33d827e15ce9eac3aebe14f79b1f0 100644 (file)
@@ -39,6 +39,7 @@ class ISimpleTextureSource;
 class Client;
 class GUIScrollBar;
 class TexturePool;
+class GUIScrollContainer;
 
 typedef enum {
        f_Button,
@@ -310,6 +311,7 @@ protected:
        std::vector<std::pair<FieldSpec, GUIScrollBar *>> m_scrollbars;
        std::vector<std::pair<FieldSpec, std::vector<std::string>>> m_dropdowns;
        std::vector<gui::IGUIElement *> m_clickthrough_elements;
+       std::vector<std::pair<std::string, GUIScrollContainer *>> m_scroll_containers;
 
        GUIInventoryList::ItemSpec *m_selected_item = nullptr;
        u16 m_selected_amount = 0;
@@ -359,6 +361,7 @@ private:
                std::string focused_fieldname;
                GUITable::TableOptions table_options;
                GUITable::TableColumns table_columns;
+               gui::IGUIElement *current_parent = nullptr;
 
                GUIInventoryList::Options inventorylist_options;
 
@@ -391,6 +394,8 @@ private:
        void parseSize(parserData* data, const std::string &element);
        void parseContainer(parserData* data, const std::string &element);
        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 parseListRing(parserData* data, const std::string &element);
        void parseCheckbox(parserData* data, const std::string &element);
index e107b5a3ebc824ff9135fb277076269ef7dbbff5..88931cdf9e172105f5e2519edc440f951f1a3420 100644 (file)
@@ -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
 // 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)
 {
        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;
 
        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 &el : p.elements) {
                        core::rect<s32> rect(el.pos + offset, el.dim);
-                       if (!rect.isRectCollided(dest_rect))
+                       if (!rect.isRectCollided(clip_rect))
                                continue;
 
                        switch (el.type) {
@@ -947,7 +947,7 @@ void TextDrawer::draw(const core::rect<s32> &dest_rect,
 
                                if (el.type == ParsedText::ELEMENT_TEXT)
                                        el.font->draw(el.text, rect, color, false, true,
-                                                       &dest_rect);
+                                                       &clip_rect);
 
                                if (el.underline &&  el.drawwidth) {
                                        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,
                                                        linepos + (el.baseline >> 3));
 
-                                       driver->draw2DRectangle(color, linerect, &dest_rect);
+                                       driver->draw2DRectangle(color, linerect, &clip_rect);
                                }
                        } break;
 
@@ -972,7 +972,7 @@ void TextDrawer::draw(const core::rect<s32> &dest_rect,
                                                        irr::core::rect<s32>(
                                                                        core::position2d<s32>(0, 0),
                                                                        texture->getOriginalSize()),
-                                                       &dest_rect, 0, true);
+                                                       &clip_rect, 0, true);
                        } break;
 
                        case ParsedText::ELEMENT_ITEM: {
@@ -982,7 +982,7 @@ void TextDrawer::draw(const core::rect<s32> &dest_rect,
 
                                drawItemStack(
                                                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
                                );
                        } break;
@@ -1094,6 +1094,7 @@ bool GUIHyperText::OnEvent(const SEvent &event)
                        m_text_scrollpos.Y = -m_vscrollbar->getPos();
                        m_drawer.draw(m_display_text_rect, m_text_scrollpos);
                        checkHover(event.MouseInput.X, event.MouseInput.Y);
+                       return true;
 
                } else if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
                        ParsedText::Element *element = getElementAt(
@@ -1151,7 +1152,8 @@ void GUIHyperText::draw()
                m_vscrollbar->setPos(0);
                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
        IGUIElement::draw();
index c55f8a70597e69241c2f3e7cd82257771686b4e6..5b936262e88ab4aa256836fff75a31c4ef05518d 100644 (file)
@@ -174,7 +174,7 @@ public:
 
        void place(const core::rect<s32> &dest_rect);
        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);
        ParsedText::Element *getElementAt(core::position2d<s32> pos);
        ParsedText::Tag *m_hovertag;
diff --git a/src/gui/guiScrollContainer.cpp b/src/gui/guiScrollContainer.cpp
new file mode 100644 (file)
index 0000000..88cdc70
--- /dev/null
@@ -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);
+}
diff --git a/src/gui/guiScrollContainer.h b/src/gui/guiScrollContainer.h
new file mode 100644 (file)
index 0000000..9eaa880
--- /dev/null
@@ -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();
+};
index 7223ce05c48e7190829112fab67718c6ef140a87..4b7345b15a1d16b29557892b3a24eb53dd861b4d 100644 (file)
@@ -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
                bgcolor[]: use 3 parameters (bgcolor, formspec (now an enum), fbgcolor)
                box[] and image[] elements enable clipping by default
+               new element: scroll_container[]
 */
 #define FORMSPEC_API_VERSION 3