From 65b8b524c02df853163fc1284a00a684a046d67c Mon Sep 17 00:00:00 2001
From: sapier <Sapier at GMX dot net>
Date: Thu, 19 Jun 2014 18:17:35 +0200
Subject: [PATCH] Add srollbar formspec element

---
 builtin/common/misc_helpers.lua   |  57 ++++++++------
 builtin/mainmenu/tab_settings.lua |  66 ++++++++++------
 doc/lua_api.txt                   |  18 +++++
 src/guiFormSpecMenu.cpp           | 126 +++++++++++++++++++++++++++++-
 src/guiFormSpecMenu.h             |   5 +-
 5 files changed, 222 insertions(+), 50 deletions(-)

diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua
index e53ca373f..f57efc138 100644
--- a/builtin/common/misc_helpers.lua
+++ b/builtin/common/misc_helpers.lua
@@ -140,58 +140,58 @@ end
 --------------------------------------------------------------------------------
 function get_last_folder(text,count)
 	local parts = text:split(DIR_DELIM)
-	
+
 	if count == nil then
 		return parts[#parts]
 	end
-	
+
 	local retval = ""
 	for i=1,count,1 do
 		retval = retval .. parts[#parts - (count-i)] .. DIR_DELIM
 	end
-	
+
 	return retval
 end
 
 --------------------------------------------------------------------------------
 function cleanup_path(temppath)
-	
+
 	local parts = temppath:split("-")
-	temppath = ""	
+	temppath = ""
 	for i=1,#parts,1 do
 		if temppath ~= "" then
 			temppath = temppath .. "_"
 		end
 		temppath = temppath .. parts[i]
 	end
-	
+
 	parts = temppath:split(".")
-	temppath = ""	
+	temppath = ""
 	for i=1,#parts,1 do
 		if temppath ~= "" then
 			temppath = temppath .. "_"
 		end
 		temppath = temppath .. parts[i]
 	end
-	
+
 	parts = temppath:split("'")
-	temppath = ""	
+	temppath = ""
 	for i=1,#parts,1 do
 		if temppath ~= "" then
 			temppath = temppath .. ""
 		end
 		temppath = temppath .. parts[i]
 	end
-	
+
 	parts = temppath:split(" ")
-	temppath = ""	
+	temppath = ""
 	for i=1,#parts,1 do
 		if temppath ~= "" then
 			temppath = temppath
 		end
 		temppath = temppath .. parts[i]
 	end
-	
+
 	return temppath
 end
 
@@ -211,7 +211,7 @@ function core.splittext(text,charlimit)
 	local retval = {}
 
 	local current_idx = 1
-	
+
 	local start,stop = string.find(text," ",current_idx)
 	local nl_start,nl_stop = string.find(text,"\n",current_idx)
 	local gotnewline = false
@@ -226,30 +226,30 @@ function core.splittext(text,charlimit)
 			table.insert(retval,last_line)
 			last_line = ""
 		end
-		
+
 		if last_line ~= "" then
 			last_line = last_line .. " "
 		end
-		
+
 		last_line = last_line .. string.sub(text,current_idx,stop -1)
-		
+
 		if gotnewline then
 			table.insert(retval,last_line)
 			last_line = ""
 			gotnewline = false
 		end
 		current_idx = stop+1
-		
+
 		start,stop = string.find(text," ",current_idx)
 		nl_start,nl_stop = string.find(text,"\n",current_idx)
-	
+
 		if nl_start ~= nil and (start == nil or nl_start < start) then
 			start = nl_start
 			stop = nl_stop
 			gotnewline = true
 		end
 	end
-	
+
 	--add last part of text
 	if string.len(last_line) + (string.len(text) - current_idx) > charlimit then
 			table.insert(retval,last_line)
@@ -258,7 +258,7 @@ function core.splittext(text,charlimit)
 		last_line = last_line .. " " .. string.sub(text,current_idx)
 		table.insert(retval,last_line)
 	end
-	
+
 	return retval
 end
 
@@ -400,6 +400,17 @@ function core.explode_textlist_event(evt)
 	return {type="INV", index=0}
 end
 
+--------------------------------------------------------------------------------
+function core.explode_scrollbar_event(evt)
+	local retval = core.explode_textlist_event(evt)
+
+	retval.value = retval.index
+	retval.index = nil
+
+	return retval
+end
+
+--------------------------------------------------------------------------------
 function core.pos_to_string(pos)
 	return "(" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")"
 end
@@ -410,14 +421,14 @@ end
 if INIT == "mainmenu" then
 	function core.get_game(index)
 		local games = game.get_games()
-		
+
 		if index > 0 and index <= #games then
 			return games[index]
 		end
-		
+
 		return nil
 	end
-	
+
 	function fgettext(text, ...)
 		text = core.gettext(text)
 		local arg = {n=select('#', ...), ...}
diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua
index dec28a961..2311978e0 100644
--- a/builtin/mainmenu/tab_settings.lua
+++ b/builtin/mainmenu/tab_settings.lua
@@ -75,29 +75,38 @@ local function showconfirm_reset(tabview)
 	new_dlg:show()
 end
 
-local function gui_scale_index()
+local function gui_scale_to_scrollbar()
 
 	local current_value = tonumber(core.setting_get("gui_scaling"))
 
-	if (current_value == nil) then
+	if (current_value == nil) or current_value < 0.25 then
 		return 0
-	elseif current_value <= 0.5 then
-		return 1
-	elseif current_value <= 0.625 then
-		return 2
-	elseif current_value <= 0.75 then
-		return 3
-	elseif current_value <= 0.875 then
-		return 4
-	elseif current_value <= 1.0 then
-		return 5
-	elseif current_value <= 1.25 then
-		return 6
-	elseif current_value <= 1.5 then
-		return 7
-	else
-		return 8
 	end
+
+	if current_value <= 1.25 then
+		return ((current_value - 0.25)/ 1.0) * 700
+	end
+
+	if current_value <= 6 then
+		return ((current_value -1.25) * 100) + 700
+	end
+
+	return 1000
+end
+
+local function scrollbar_to_gui_scale(value)
+
+	value = tonumber(value)
+
+	if (value <= 700) then
+		return ((value / 700) * 1.0) + 0.25
+	end
+
+	if (value <=1000) then
+		return ((value - 700) / 100) + 1.25
+	end
+
+	return 1
 end
 
 local function formspec(tabview, name, tabdata)
@@ -138,8 +147,11 @@ local function formspec(tabview, name, tabdata)
 	tab_string = tab_string ..
 		"box[0.75,4.25;3.25,1.25;#999999]" ..
 		"label[1,4.25;" .. fgettext("GUI scale factor") .. "]" ..
-		"dropdown[1,4.75;3.0;dd_gui_scaling;0.5,0.625,0.75,0.875,1.0,1.25,1.5,2.0;"
-			.. gui_scale_index() .. "]"
+		"scrollbar[1,4.75;2.75,0.4;sb_gui_scaling;horizontal;" ..
+		 gui_scale_to_scrollbar() .. "]" ..
+		"tooltip[sb_gui_scaling;" ..
+			fgettext("Scaling factor applied to menu elements: ") ..
+			dump(core.setting_get("gui_scaling")) .. "]"
 
 	if ANDROID then
 		tab_string = tab_string ..
@@ -257,6 +269,16 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
 		core.show_keys_menu()
 		return true
 	end
+
+	if fields["sb_gui_scaling"] then
+		local event = core.explode_scrollbar_event(fields["sb_gui_scaling"])
+
+		if event.type == "CHG" then
+			local tosave = string.format("%.2f",scrollbar_to_gui_scale(event.value))
+			core.setting_set("gui_scaling",tosave)
+			return true
+		end
+	end
 	if fields["cb_touchscreen_target"] then
 		core.setting_set("touchtarget", fields["cb_touchscreen_target"])
 		return true
@@ -272,10 +294,6 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
 		core.setting_set("touchscreen_threshold",fields["dd_touchthreshold"])
 		ddhandled = true
 	end
-	if fields["dd_gui_scaling"] then
-		core.setting_set("gui_scaling",fields["dd_gui_scaling"])
-		ddhandled = true
-	end
 	
 	return ddhandled
 end
diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index 81cdb3258..28509a267 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -1061,6 +1061,9 @@ box[<X>,<Y>;<W>,<H>;<color>]
 
 dropdown[<X>,<Y>;<W>;<name>;<item 1>,<item 2>, ...,<item n>;<selected idx>]
 ^ show a dropdown field
+^ IMPORTANT NOTE: There are two different operation modes:
+^     1) handle directly on change (only changed dropdown is submitted)
+^     2) read the value on pressing a button (all dropdown values are available)
 ^ x and y position of dropdown
 ^ width of dropdown
 ^ fieldname data is transfered to Lua
@@ -1075,6 +1078,18 @@ checkbox[<X>,<Y>;<name>;<label>;<selected>;<tooltip>]
 ^ selected (optional) true/false
 ^ tooltip (optional)
 
+scrollbar[<X>,<Y>;<W>,<H>;<orientation>;<name>;<value>]
+^ show a scrollbar
+^ there are two ways to use it:
+^     1) handle the changed event (only changed scrollbar is available)
+^     2) read the value on pressing a button (all scrollbars are available)
+^ x and y position of trackbar
+^ width and height
+^ orientation vertical/horizontal
+^ fieldname data is transfered to lua
+^ value this trackbar is set to (0-1000)
+^ see also minetest.explode_scrollbar_event (main menu: engine.explode_scrollbar_event)
+
 table[<X>,<Y>;<W>,<H>;<name>;<cell 1>,<cell 2>,...,<cell n>;<selected idx>]
 ^ show scrollable table using options defined by the previous tableoptions[]
 ^ displays cells as defined by the previous tablecolumns[]
@@ -1472,6 +1487,9 @@ minetest.explode_table_event(string) -> table
 minetest.explode_textlist_event(string) -> table
 ^ returns e.g. {type="CHG", index=1}
 ^ type: "INV" (no row selected), "CHG" (selected) or "DCL" (double-click)
+minetest.explode_scrollbar_event(string) -> table
+^ returns e.g. {type="CHG", value=500}
+^ type: "INV" (something failed), "CHG" (has been changed) or "VAL" (not changed)
 
 Item handling:
 minetest.inventorycube(img1, img2, img3)
diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp
index 7ba3305bc..910a0a6b7 100644
--- a/src/guiFormSpecMenu.cpp
+++ b/src/guiFormSpecMenu.cpp
@@ -453,6 +453,7 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data,std::string element)
 			);
 
 		spec.ftype = f_CheckBox;
+
 		gui::IGUICheckBox* e = Environment->addCheckBox(fselected, rect, this,
 					spec.fid, spec.flabel.c_str());
 
@@ -467,6 +468,94 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data,std::string element)
 	errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'"  << std::endl;
 }
 
+void GUIFormSpecMenu::parseScrollBar(parserData* data, std::string element)
+{
+	std::vector<std::string> parts = split(element,';');
+
+	if (parts.size() >= 5) {
+		std::vector<std::string> v_pos = split(parts[0],',');
+		std::vector<std::string> v_dim = split(parts[1],',');
+		std::string name = parts[2];
+		std::string value = parts[4];
+
+		MY_CHECKPOS("scrollbar",0);
+
+		v2s32 pos = padding;
+		pos.X += stof(v_pos[0]) * (float) spacing.X;
+		pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+		if (v_dim.size() != 2) {
+			errorstream<< "Invalid size for element " << "scrollbar"
+				<< "specified: \"" << parts[1] << "\"" << std::endl;
+			return;
+		}
+
+		v2s32 dim;
+		dim.X = stof(v_dim[0]) * (float) spacing.X;
+		dim.Y = stof(v_dim[1]) * (float) spacing.Y;
+
+		core::rect<s32> rect =
+				core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y);
+
+		FieldSpec spec(
+				narrow_to_wide(name.c_str()),
+				L"",
+				L"",
+				258+m_fields.size()
+			);
+
+		bool is_horizontal = true;
+
+		if (parts[2] == "vertical")
+			is_horizontal = false;
+
+		spec.ftype = f_ScrollBar;
+		spec.send  = true;
+		gui::IGUIScrollBar* e =
+				Environment->addScrollBar(is_horizontal,rect,this,spec.fid);
+
+		e->setMax(1000);
+		e->setMin(0);
+		e->setPos(stoi(parts[4]));
+		e->setSmallStep(10);
+		e->setLargeStep(100);
+
+		if (!m_lock) {
+			core::rect<s32> relative_rect = e->getRelativePosition();
+
+			if (!is_horizontal) {
+				s32 original_width = relative_rect.getWidth();
+				s32 width = (original_width/(2.0/3.0))
+						* porting::getDisplayDensity()
+						* g_settings->getFloat("gui_scaling");
+				e->setRelativePosition(core::rect<s32>(
+						relative_rect.UpperLeftCorner.X,
+						relative_rect.UpperLeftCorner.Y,
+						relative_rect.LowerRightCorner.X + (width - original_width),
+						relative_rect.LowerRightCorner.Y
+					));
+			}
+			else  {
+				s32 original_height = relative_rect.getHeight();
+				s32 height = (original_height/(2.0/3.0))
+						* porting::getDisplayDensity()
+						* g_settings->getFloat("gui_scaling");
+				e->setRelativePosition(core::rect<s32>(
+						relative_rect.UpperLeftCorner.X,
+						relative_rect.UpperLeftCorner.Y,
+						relative_rect.LowerRightCorner.X,
+						relative_rect.LowerRightCorner.Y + (height - original_height)
+					));
+			}
+		}
+
+		m_scrollbars.push_back(std::pair<FieldSpec,gui::IGUIScrollBar*>(spec,e));
+		m_fields.push_back(spec);
+		return;
+	}
+	errorstream<< "Invalid scrollbar element(" << parts.size() << "): '" << element << "'"  << std::endl;
+}
+
 void GUIFormSpecMenu::parseImage(parserData* data,std::string element)
 {
 	std::vector<std::string> parts = split(element,';');
@@ -1731,6 +1820,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, std::string element)
 		return;
 	}
 
+	if (type == "scrollbar") {
+		parseScrollBar(data, description);
+		return;
+	}
+
 	// Ignore others
 	infostream
 		<< "Unknown DrawSpec: type="<<type<<", data=\""<<description<<"\""
@@ -1798,6 +1892,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 	m_itemimages.clear();
 	m_tables.clear();
 	m_checkboxes.clear();
+	m_scrollbars.clear();
 	m_fields.clear();
 	m_boxes.clear();
 	m_tooltips.clear();
@@ -2513,6 +2608,24 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
 							fields[name] = "false";
 					}
 				}
+				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);
+					gui::IGUIScrollBar *e = NULL;
+					if ((element) && (element->getType() == gui::EGUIET_SCROLL_BAR)) {
+						e = static_cast<gui::IGUIScrollBar*>(element);
+					}
+
+					if (e != 0) {
+						std::stringstream os;
+						os << e->getPos();
+						if (s.fdefault == L"Changed")
+							fields[name] = "CHG:" + os.str();
+						else
+							fields[name] = "VAL:" + os.str();
+ 					}
+				}
 				else
 				{
 					IGUIElement* e = getElementFromId(s.fid);
@@ -3120,7 +3233,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
 		}
 		if((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) ||
 				(event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
-				(event.GUIEvent.EventType == gui::EGET_COMBO_BOX_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();
 
 			if (btn_id == 257) {
@@ -3157,7 +3271,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
 						return true;
 					}
 				}
-				if ((s.ftype == f_DropDown) &&
+				else if ((s.ftype == f_DropDown) &&
 						(s.fid == event.GUIEvent.Caller->getID())) {
 					// only send the changed dropdown
 					for(u32 i=0; i<m_fields.size(); i++) {
@@ -3179,8 +3293,16 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
 					}
 					return true;
 				}
+				else if ((s.ftype == f_ScrollBar) &&
+					(s.fid == event.GUIEvent.Caller->getID()))
+				{
+					s.fdefault = L"Changed";
+					acceptInput(quit_mode_no);
+					s.fdefault = L"";
+				}
 			}
 		}
+
 		if(event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
 			if(event.GUIEvent.Caller->getID() > 257) {
 
diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h
index 72a188bc5..15bc628d1 100644
--- a/src/guiFormSpecMenu.h
+++ b/src/guiFormSpecMenu.h
@@ -40,6 +40,7 @@ typedef enum {
 	f_TabHeader,
 	f_CheckBox,
 	f_DropDown,
+	f_ScrollBar,
 	f_Unknown
 } FormspecFieldType;
 
@@ -306,7 +307,8 @@ class GUIFormSpecMenu : public GUIModalMenu
 	std::vector<std::pair<FieldSpec,GUITable*> > m_tables;
 	std::vector<std::pair<FieldSpec,gui::IGUICheckBox*> > m_checkboxes;
 	std::map<std::wstring, TooltipSpec> m_tooltips;
-	
+	std::vector<std::pair<FieldSpec,gui::IGUIScrollBar*> > m_scrollbars;
+
 	ItemSpec *m_selected_item;
 	u32 m_selected_amount;
 	bool m_selected_dragging;
@@ -397,6 +399,7 @@ class GUIFormSpecMenu : public GUIModalMenu
 	void parseListColors(parserData* data,std::string element);
 	void parseTooltip(parserData* data,std::string element);
 	bool parseVersionDirect(std::string data);
+	void parseScrollBar(parserData* data, std::string element);
 
 	/**
 	 * check if event is part of a double click
-- 
GitLab