Browse Source

Formspec: add hypertext element

Pierre-Yves Rollo 4 months ago
parent
commit
72416a6a1f

+ 25 - 13
builtin/settingtypes.txt

@@ -851,14 +851,9 @@ tooltip_append_itemname (Append item name) bool false
 #    If disabled, bitmap and XML vectors fonts are used instead.
 freetype (FreeType fonts) bool true
 
-#    Path to the default font.
-#    If “freetype” setting is enabled: Must be a TrueType font.
-#    If “freetype” setting is disabled: Must be a bitmap or XML vectors font.
-#    The fallback font will be used if the font cannot be loaded.
-font_path (Font path) filepath fonts/liberationsans.ttf
+font_bold (Font bold by default) bool false
 
-#    Font size of the default font in point (pt).
-font_size (Font size) int 16 1
+font_italic (Font italic by default) bool false
 
 #    Shadow offset (in pixels) of the default font. If 0, then shadow will not be drawn.
 font_shadow (Font shadow) int 1
@@ -866,20 +861,31 @@ font_shadow (Font shadow) int 1
 #    Opaqueness (alpha) of the shadow behind the default font, between 0 and 255.
 font_shadow_alpha (Font shadow alpha) int 127 0 255
 
-#    Path to the monospace font.
+#    Font size of the default font in point (pt).
+font_size (Font size) int 16 1
+
+#    Path to the default font.
 #    If “freetype” setting is enabled: Must be a TrueType font.
 #    If “freetype” setting is disabled: Must be a bitmap or XML vectors font.
-#    This font is used for e.g. the console and profiler screen.
-mono_font_path (Monospace font path) filepath fonts/liberationmono.ttf
+#    The fallback font will be used if the font cannot be loaded.
+font_path (Regular font path) filepath fonts/Arimo-Regular.ttf
+
+font_path_bold (Bold font path) filepath fonts/Arimo-Bold.ttf
+font_path_italic (Italic font path) filepath fonts/Arimo-Italic.ttf
+font_path_bolditalic (Bold and italic font path) filepath fonts/Arimo-BoldItalic.ttf
 
 #    Font size of the monospace font in point (pt).
 mono_font_size (Monospace font size) int 15 1
 
-#    Path of the fallback font.
+#    Path to the monospace font.
 #    If “freetype” setting is enabled: Must be a TrueType font.
 #    If “freetype” setting is disabled: Must be a bitmap or XML vectors font.
-#    This font will be used for certain languages or if the default font is unavailable.
-fallback_font_path (Fallback font path) filepath fonts/DroidSansFallbackFull.ttf
+#    This font is used for e.g. the console and profiler screen.
+mono_font_path (Monospace font path) filepath fonts/Cousine-Regular.ttf
+
+mono_font_path_bold (Bold monospace font path) filepath fonts/Cousine-Bold.ttf
+mono_font_path_italic (Italic monospace font path) filepath fonts/Cousine-Italic.ttf
+mono_font_path_bolditalic (Bold and italic monospace font path) filepath fonts/Cousine-BoldItalic.ttf
 
 #    Font size of the fallback font in point (pt).
 fallback_font_size (Fallback font size) int 15 1
@@ -890,6 +896,12 @@ fallback_font_shadow (Fallback font shadow) int 1
 #    Opaqueness (alpha) of the shadow behind the fallback font, between 0 and 255.
 fallback_font_shadow_alpha (Fallback font shadow alpha) int 128 0 255
 
+#    Path of the fallback font.
+#    If “freetype” setting is enabled: Must be a TrueType font.
+#    If “freetype” setting is disabled: Must be a bitmap or XML vectors font.
+#    This font will be used for certain languages or if the default font is unavailable.
+fallback_font_path (Fallback font path) filepath fonts/DroidSansFallbackFull.ttf
+
 #    Path to save screenshots at.
 screenshot_path (Screenshot folder) path
 

+ 110 - 2
doc/lua_api.txt

@@ -2189,8 +2189,13 @@ Elements
   half a coordinate.  With the old system, newlines are spaced 2/5 of
   an inventory slot.
 
-### `vertlabel[<X>,<Y>;<label>]`
+### `hypertext[<X>,<Y>;<W>,<H>;<name>;<text>]`
+* Displays a static formated text with hyperlinks.
+* `x`, `y`, `w` and `h` work as per field
+* `name` is the name of the field as returned in fields to `on_receive_fields` in case of action in text.
+* `text` is the formatted text using `markup language` described below.
 
+### `vertlabel[<X>,<Y>;<label>]`
 * Textual label drawn vertically
 * `label` is the text on the label
 * **Note**: If the new coordinate system is enabled, vertlabels are
@@ -2534,6 +2539,110 @@ Some types may inherit styles from parent types.
     * noclip - boolean, set to true to allow the element to exceed formspec bounds.
     * textcolor - color. Default white.
 
+Markup language
+---------------
+
+Markup language used in `hypertext[]` elements uses tag that look like HTML tags. Some
+tags can enclose text, they open with `<tagname>` and close with `</tagname>`.
+Tags can have attributes, in that case, attributes are in the opening tag in
+form of a key/value separated with equal signs. Attribute values should not be quoted.
+
+These are the technically basic tags but see below for usual tags. Base tags are:
+
+`<style color=... font=... size=...>...</style>`
+
+Changes the style of the text.
+
+* `color`: Text color. Given color is a `colorspec`.
+* `size`: Text size.
+* `font`: Text font (`mono` or `normal`).
+
+`<global background=... margin=... valign=... color=... hovercolor=... size=... font=... halign=... >`
+
+Sets global style.
+
+Global only styles:
+* `background`: Text background, a `colorspec` or `none`.
+* `margin`: Page margins in pixel.
+* `valign`: Text vertical alignment (`top`, `middle`, `bottom`).
+
+Inheriting styles (affects child elements):
+* `color`: Default text color. Given color is a `colorspec`.
+* `hovercolor`: Color of <action> tags when mouse is over.
+* `size`: Default text size.
+* `font`: Default text font (`mono` or `normal`).
+* `halign`: Default text horizontal alignment (`left`, `right`, `center`, `justify`).
+
+This tag needs to be placed only once as it changes the global settings of the
+text. Anyway, if several tags are placed, each changed will be made in the order
+tags appear.
+
+`<tag name=... color=... hovercolor=... font=... size=...>`
+
+Defines or redefines tag style. This can be used to define new tags.
+* `name`: Name of the tag to define or change.
+* `color`: Text color. Given color is a `colorspec`.
+* `hovercolor`: Text color when element hovered (only for `action` tags). Given color is a `colorspec`.
+* `size`: Text size.
+* `font`: Text font (`mono` or `normal`).
+
+Following tags are the usual tags for text layout. They are defined by default.
+Other tags can be added using `<tag ...>` tag.
+
+`<normal>...</normal>`: Normal size text
+
+`<big>...</big>`: Big text
+
+`<bigger>...</bigger>`: Bigger text
+
+`<center>...</center>`: Centered text
+
+`<left>...</left>`: Left-aligned text
+
+`<right>...</right>`: Right-aligned text
+
+`<justify>...</justify>`: Justified text
+
+`<mono>...</mono>`: Monospaced font
+
+`<b>...</b>`, `<i>...</i>`, `<u>...</u>`: Bold, italic, underline styles.
+
+`<action name=...>...</action>`
+
+Make that text a clickable text triggering an action.
+
+* `name`: Name of the action (mandatory).
+
+When clicked, the formspec is send to the server. The value of the text field
+sent to `on_player_receive_fields` will be "action:" concatenated to the action
+name.
+
+`<img name=... float=... width=... height=...>`
+
+Draws an image which is present in the client media cache.
+
+* `name`: Name of the texture (mandatory).
+* `float`: If present, makes the image floating (`left` or `right`).
+* `width`: Force image width instead of taking texture width.
+* `height`: Force image height instead of taking texture height.
+
+If only width or height given, texture aspect is kept.
+
+`<item name=... float=... width=... height=... rotate=...>`
+
+Draws an item image.
+
+* `name`: Item string of the item to draw (mandatory).
+* `float`: If present, makes the image floating (`left` or `right`).
+* `width`: Item image width.
+* `height`: Item image height.
+* `rotate`: Rotate item image if set to `yes` or `X,Y,Z`. X, Y and Z being
+rotation speeds in percent of standard speed (-1000 to 1000). Works only if
+`inventory_items_animations` is set to true.
+* `angle`: Angle in which the item image is shown. Value has `X,Y,Z` form.
+X, Y and Z being angles around each three axes. Works only if
+`inventory_items_animations` is set to true.
+
 Inventory
 =========
 
@@ -2557,7 +2666,6 @@ Player Inventory lists
     * Is not created automatically, use `InvRef:set_size`
     * Is only used to enhance the empty hand's tool capabilities
 
-
 Colors
 ======
 

BIN
fonts/Arimo-Bold.ttf


BIN
fonts/Arimo-BoldItalic.ttf


BIN
fonts/Arimo-Italic.ttf


BIN
fonts/Cousine-Bold.ttf


BIN
fonts/Cousine-BoldItalic.ttf


BIN
fonts/Cousine-Italic.ttf


+ 53 - 17
src/client/fontengine.cpp

@@ -41,6 +41,11 @@ static void font_setting_changed(const std::string &name, void *userdata)
 	g_fontengine->readSettings();
 }
 
+unsigned int get_font_cache_index(FontMode mode, bool bold = false, bool italic = false)
+{
+	return (mode << 2) | (bold << 1) | italic;
+}
+
 /******************************************************************************/
 FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) :
 	m_settings(main_settings),
@@ -59,7 +64,12 @@ FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) :
 
 	if (m_currentMode == FM_Standard) {
 		m_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
+		m_settings->registerChangedCallback("font_bold", font_setting_changed, NULL);
+		m_settings->registerChangedCallback("font_italic", font_setting_changed, NULL);
 		m_settings->registerChangedCallback("font_path", font_setting_changed, NULL);
+		m_settings->registerChangedCallback("font_path_bold", font_setting_changed, NULL);
+		m_settings->registerChangedCallback("font_path_italic", font_setting_changed, NULL);
+		m_settings->registerChangedCallback("font_path_bolditalic", font_setting_changed, NULL);
 		m_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL);
 		m_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL);
 	}
@@ -96,7 +106,8 @@ void FontEngine::cleanCache()
 }
 
 /******************************************************************************/
-irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode)
+irr::gui::IGUIFont *FontEngine::getFont(unsigned int font_size, FontMode mode,
+	bool bold, bool italic)
 {
 	if (mode == FM_Unspecified) {
 		mode = m_currentMode;
@@ -110,22 +121,30 @@ irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode)
 	if (font_size == FONT_SIZE_UNSPECIFIED)
 		font_size = m_default_size[mode];
 
-	const auto &cache = m_font_cache[mode];
+	unsigned int cache_index = get_font_cache_index(mode, bold, italic);
+
+	const auto &cache = m_font_cache[cache_index];
+
 	if (cache.find(font_size) == cache.end()) {
 		if (mode == FM_Simple || mode == FM_SimpleMono)
 			initSimpleFont(font_size, mode);
 		else
-			initFont(font_size, mode);
+			initFont(font_size, mode, bold, italic);
 	}
 
+	if (m_font_cache[cache_index].find(font_size) ==
+			m_font_cache[cache_index].end())
+		initFont(font_size, mode, bold, italic);
+
 	const auto &font = cache.find(font_size);
 	return font != cache.end() ? font->second : nullptr;
 }
 
 /******************************************************************************/
-unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode)
+unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode,
+	bool bold, bool italic)
 {
-	irr::gui::IGUIFont* font = getFont(font_size, mode);
+	irr::gui::IGUIFont *font = getFont(font_size, mode, bold, italic);
 
 	// use current skin font as fallback
 	if (font == NULL) {
@@ -138,9 +157,9 @@ unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode)
 
 /******************************************************************************/
 unsigned int FontEngine::getTextWidth(const std::wstring& text,
-		unsigned int font_size, FontMode mode)
+		unsigned int font_size, FontMode mode, bool bold, bool italic)
 {
-	irr::gui::IGUIFont* font = getFont(font_size, mode);
+	irr::gui::IGUIFont *font = getFont(font_size, mode, bold, italic);
 
 	// use current skin font as fallback
 	if (font == NULL) {
@@ -153,9 +172,10 @@ unsigned int FontEngine::getTextWidth(const std::wstring& text,
 
 
 /** get line height for a specific font (including empty room between lines) */
-unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode)
+unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode,
+	bool bold, bool italic)
 {
-	irr::gui::IGUIFont* font = getFont(font_size, mode);
+	irr::gui::IGUIFont *font = getFont(font_size, mode, bold, italic);
 
 	// use current skin font as fallback
 	if (font == NULL) {
@@ -183,6 +203,10 @@ void FontEngine::readSettings()
 
 		m_currentMode = is_yes(gettext("needs_fallback_font")) ?
 				FM_Fallback : FM_Standard;
+
+		m_default_bold = m_settings->getBool("font_bold");
+		m_default_italic = m_settings->getBool("font_italic");
+
 	} else {
 		m_currentMode = FM_Simple;
 	}
@@ -226,14 +250,17 @@ void FontEngine::updateFontCache()
 }
 
 /******************************************************************************/
-void FontEngine::initFont(unsigned int basesize, FontMode mode)
+void FontEngine::initFont(unsigned int basesize, FontMode mode,
+	bool bold, bool italic)
 {
 	assert(mode != FM_Unspecified);
 	assert(basesize != FONT_SIZE_UNSPECIFIED);
 
-	if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end())
-		return;
+	int cache_index = get_font_cache_index(mode, bold, italic);
 
+	if (m_font_cache[cache_index].find(basesize) !=
+			m_font_cache[cache_index].end())
+		return;
 
 	std::string setting_prefix = "";
 
@@ -249,8 +276,13 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode)
 			break;
 	}
 
+	std::string setting_suffix = (bold) ?
+			((italic) ? "_bold_italic" : "_bold") :
+			((italic) ? "_italic" : "");
+
 	u32 size = std::floor(RenderingEngine::getDisplayDensity() *
 			m_settings->getFloat("gui_scaling") * basesize);
+
 	if (size == 0) {
 		errorstream << "FontEngine: attempt to use font size 0" << std::endl;
 		errorstream << "  display density: " << RenderingEngine::getDisplayDensity() << std::endl;
@@ -260,10 +292,14 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode)
 	u16 font_shadow       = 0;
 	u16 font_shadow_alpha = 0;
 	g_settings->getU16NoEx(setting_prefix + "font_shadow", font_shadow);
-	g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha", font_shadow_alpha);
+	g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha",
+			font_shadow_alpha);
+
+	std::string wanted_font_path;
+	wanted_font_path = g_settings->get(setting_prefix + "font_path" + setting_suffix);
 
 	std::string fallback_settings[] = {
-		m_settings->get(setting_prefix + "font_path"),
+		wanted_font_path,
 		m_settings->get("fallback_font_path"),
 		m_settings->getDefault(setting_prefix + "font_path")
 	};
@@ -275,7 +311,7 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode)
 				font_shadow_alpha);
 
 		if (font) {
-			m_font_cache[mode][basesize] = font;
+			m_font_cache[cache_index][basesize] = font;
 			return;
 		}
 
@@ -340,7 +376,7 @@ void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode)
 			path.str(""); // Clear
 			path << basename << "_" << (size + offset * sign) << ext;
 
-			if (!fs::PathExists(path.str())) 
+			if (!fs::PathExists(path.str()))
 				continue;
 
 			font = m_env->getFont(path.str().c_str());
@@ -365,5 +401,5 @@ void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode)
 	}
 
 	if (font)
-		m_font_cache[mode][basesize] = font;
+		m_font_cache[get_font_cache_index(mode)][basesize] = font;
 }

+ 51 - 10
src/client/fontengine.h

@@ -48,29 +48,62 @@ public:
 	~FontEngine();
 
 	/** get Font */
-	irr::gui::IGUIFont* getFont(unsigned int font_size=FONT_SIZE_UNSPECIFIED,
-			FontMode mode=FM_Unspecified);
+	irr::gui::IGUIFont *getFont(unsigned int font_size, FontMode mode,
+			bool bold, bool italic);
+
+	irr::gui::IGUIFont *getFont(unsigned int font_size=FONT_SIZE_UNSPECIFIED,
+			FontMode mode=FM_Unspecified)
+	{
+		return getFont(font_size, mode, m_default_bold, m_default_italic);
+	}
 
 	/** get text height for a specific font */
-	unsigned int getTextHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED,
-			FontMode mode=FM_Unspecified);
+	unsigned int getTextHeight(unsigned int font_size, FontMode mode,
+			bool bold, bool italic);
 
 	/** get text width if a text for a specific font */
-	unsigned int getTextWidth(const std::string& text,
+	unsigned int getTextHeight(
 			unsigned int font_size=FONT_SIZE_UNSPECIFIED,
 			FontMode mode=FM_Unspecified)
 	{
-		return getTextWidth(utf8_to_wide(text));
+		return getTextHeight(font_size, mode, m_default_bold, m_default_italic);
 	}
 
+	unsigned int getTextWidth(const std::wstring& text,
+			unsigned int font_size, FontMode mode, bool bold, bool italic);
+
 	/** get text width if a text for a specific font */
 	unsigned int getTextWidth(const std::wstring& text,
 			unsigned int font_size=FONT_SIZE_UNSPECIFIED,
-			FontMode mode=FM_Unspecified);
+			FontMode mode=FM_Unspecified)
+	{
+		return getTextWidth(text, font_size, mode, m_default_bold,
+				m_default_italic);
+	}
+
+	unsigned int getTextWidth(const std::string& text,
+			unsigned int font_size, FontMode mode, bool bold, bool italic)
+	{
+		return getTextWidth(utf8_to_wide(text), font_size, mode, bold, italic);
+	}
+
+	unsigned int getTextWidth(const std::string& text,
+			unsigned int font_size=FONT_SIZE_UNSPECIFIED,
+			FontMode mode=FM_Unspecified)
+	{
+		return getTextWidth(utf8_to_wide(text), font_size, mode, m_default_bold,
+				m_default_italic);
+	}
 
 	/** get line height for a specific font (including empty room between lines) */
+	unsigned int getLineHeight(unsigned int font_size, FontMode mode, bool bold,
+			bool italic);
+
 	unsigned int getLineHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED,
-			FontMode mode=FM_Unspecified);
+			FontMode mode=FM_Unspecified)
+	{
+		return getLineHeight(font_size, mode, m_default_bold, m_default_italic);
+	}
 
 	/** get default font size */
 	unsigned int getDefaultFontSize();
@@ -86,7 +119,11 @@ private:
 	void updateFontCache();
 
 	/** initialize a new font */
-	void initFont(unsigned int basesize, FontMode mode=FM_Unspecified);
+	void initFont(
+		unsigned int basesize,
+		FontMode mode,
+		bool bold,
+		bool italic);
 
 	/** initialize a font without freetype */
 	void initSimpleFont(unsigned int basesize, FontMode mode);
@@ -104,11 +141,15 @@ private:
 	gui::IGUIEnvironment* m_env = nullptr;
 
 	/** internal storage for caching fonts of different size */
-	std::map<unsigned int, irr::gui::IGUIFont*> m_font_cache[FM_MaxMode];
+	std::map<unsigned int, irr::gui::IGUIFont*> m_font_cache[FM_MaxMode << 2];
 
 	/** default font size to use */
 	unsigned int m_default_size[FM_MaxMode];
 
+	/** default bold and italic */
+	bool m_default_bold;
+	bool m_default_italic;
+
 	/** current font engine mode */
 	FontMode m_currentMode = FM_Standard;
 

+ 47 - 23
src/client/hud.cpp

@@ -608,23 +608,24 @@ void Hud::resizeHotbar() {
 
 struct MeshTimeInfo {
 	u64 time;
-	scene::IMesh *mesh;
+	scene::IMesh *mesh = nullptr;
 };
 
-void drawItemStack(video::IVideoDriver *driver,
+void drawItemStack(
+		video::IVideoDriver *driver,
 		gui::IGUIFont *font,
 		const ItemStack &item,
 		const core::rect<s32> &rect,
 		const core::rect<s32> *clip,
 		Client *client,
-		ItemRotationKind rotation_kind)
+		ItemRotationKind rotation_kind,
+		const v3s16 &angle,
+		const v3s16 &rotation_speed)
 {
 	static MeshTimeInfo rotation_time_infos[IT_ROT_NONE];
-	static thread_local bool enable_animations =
-		g_settings->getBool("inventory_items_animations");
 
 	if (item.empty()) {
-		if (rotation_kind < IT_ROT_NONE) {
+		if (rotation_kind < IT_ROT_NONE && rotation_kind != IT_ROT_OTHER) {
 			rotation_time_infos[rotation_kind].mesh = NULL;
 		}
 		return;
@@ -639,7 +640,7 @@ void drawItemStack(video::IVideoDriver *driver,
 		s32 delta = 0;
 		if (rotation_kind < IT_ROT_NONE) {
 			MeshTimeInfo &ti = rotation_time_infos[rotation_kind];
-			if (mesh != ti.mesh) {
+			if (mesh != ti.mesh && rotation_kind != IT_ROT_OTHER) {
 				ti.mesh = mesh;
 				ti.time = porting::getTimeMs();
 			} else {
@@ -677,9 +678,16 @@ void drawItemStack(video::IVideoDriver *driver,
 		core::matrix4 matrix;
 		matrix.makeIdentity();
 
+		static thread_local bool enable_animations =
+			g_settings->getBool("inventory_items_animations");
+
 		if (enable_animations) {
-			float timer_f = (float) delta / 5000.0;
-			matrix.setRotationDegrees(core::vector3df(0, 360 * timer_f, 0));
+			float timer_f = (float) delta / 5000.f;
+			matrix.setRotationDegrees(v3f(
+				angle.X + rotation_speed.X * 3.60f * timer_f,
+				angle.Y + rotation_speed.Y * 3.60f * timer_f,
+				angle.Z + rotation_speed.Z * 3.60f * timer_f)
+			);
 		}
 
 		driver->setTransform(video::ETS_WORLD, matrix);
@@ -695,15 +703,18 @@ void drawItemStack(video::IVideoDriver *driver,
 			// because these meshes are not buffered.
 			assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER);
 			video::SColor c = basecolor;
+
 			if (imesh->buffer_colors.size() > j) {
 				ItemPartColor *p = &imesh->buffer_colors[j];
 				if (p->override_base)
 					c = p->color;
 			}
+
 			if (imesh->needs_shading)
 				colorizeMeshBuffer(buf, &c);
 			else
 				setMeshBufferColor(buf, c);
+
 			video::SMaterial &material = buf->getMaterial();
 			material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
 			material.Lighting = false;
@@ -726,12 +737,12 @@ void drawItemStack(video::IVideoDriver *driver,
 		}
 	}
 
-	if(def.type == ITEM_TOOL && item.wear != 0)
-	{
+	if (def.type == ITEM_TOOL && item.wear != 0) {
 		// Draw a progressbar
-		float barheight = rect.getHeight()/16;
-		float barpad_x = rect.getWidth()/16;
-		float barpad_y = rect.getHeight()/16;
+		float barheight = rect.getHeight() / 16;
+		float barpad_x = rect.getWidth() / 16;
+		float barpad_y = rect.getHeight() / 16;
+
 		core::rect<s32> progressrect(
 			rect.UpperLeftCorner.X + barpad_x,
 			rect.LowerRightCorner.Y - barpad_y - barheight,
@@ -739,18 +750,19 @@ void drawItemStack(video::IVideoDriver *driver,
 			rect.LowerRightCorner.Y - barpad_y);
 
 		// Shrink progressrect by amount of tool damage
-		float wear = item.wear / 65535.0;
+		float wear = item.wear / 65535.0f;
 		int progressmid =
 			wear * progressrect.UpperLeftCorner.X +
-			(1-wear) * progressrect.LowerRightCorner.X;
+			(1 - wear) * progressrect.LowerRightCorner.X;
 
 		// Compute progressbar color
 		//   wear = 0.0: green
 		//   wear = 0.5: yellow
 		//   wear = 1.0: red
-		video::SColor color(255,255,255,255);
+		video::SColor color(255, 255, 255, 255);
 		int wear_i = MYMIN(std::floor(wear * 600), 511);
 		wear_i = MYMIN(wear_i + 10, 511);
+
 		if (wear_i <= 255)
 			color.set(255, wear_i, 255, 0);
 		else
@@ -760,18 +772,17 @@ void drawItemStack(video::IVideoDriver *driver,
 		progressrect2.LowerRightCorner.X = progressmid;
 		driver->draw2DRectangle(color, progressrect2, clip);
 
-		color = video::SColor(255,0,0,0);
+		color = video::SColor(255, 0, 0, 0);
 		progressrect2 = progressrect;
 		progressrect2.UpperLeftCorner.X = progressmid;
 		driver->draw2DRectangle(color, progressrect2, clip);
 	}
 
-	if(font != NULL && item.count >= 2)
-	{
+	if (font != NULL && item.count >= 2) {
 		// Get the item count as a string
 		std::string text = itos(item.count);
 		v2u32 dim = font->getDimension(utf8_to_wide(text).c_str());
-		v2s32 sdim(dim.X,dim.Y);
+		v2s32 sdim(dim.X, dim.Y);
 
 		core::rect<s32> rect2(
 			/*rect.UpperLeftCorner,
@@ -780,10 +791,23 @@ void drawItemStack(video::IVideoDriver *driver,
 			sdim
 		);
 
-		video::SColor bgcolor(128,0,0,0);
+		video::SColor bgcolor(128, 0, 0, 0);
 		driver->draw2DRectangle(bgcolor, rect2, clip);
 
-		video::SColor color(255,255,255,255);
+		video::SColor color(255, 255, 255, 255);
 		font->draw(text.c_str(), rect2, color, false, false, clip);
 	}
 }
+
+void drawItemStack(
+		video::IVideoDriver *driver,
+		gui::IGUIFont *font,
+		const ItemStack &item,
+		const core::rect<s32> &rect,
+		const core::rect<s32> *clip,
+		Client *client,
+		ItemRotationKind rotation_kind)
+{
+	drawItemStack(driver, font, item, rect, clip, client, rotation_kind,
+		v3s16(0, 0, 0), v3s16(0, 100, 0));
+}

+ 12 - 0
src/client/hud.h

@@ -122,6 +122,7 @@ enum ItemRotationKind
 	IT_ROT_SELECTED,
 	IT_ROT_HOVERED,
 	IT_ROT_DRAGGED,
+	IT_ROT_OTHER,
 	IT_ROT_NONE, // Must be last, also serves as number
 };
 
@@ -133,4 +134,15 @@ void drawItemStack(video::IVideoDriver *driver,
 		Client *client,
 		ItemRotationKind rotation_kind);
 
+void drawItemStack(
+		video::IVideoDriver *driver,
+		gui::IGUIFont *font,
+		const ItemStack &item,
+		const core::rect<s32> &rect,
+		const core::rect<s32> *clip,
+		Client *client,
+		ItemRotationKind rotation_kind,
+		const v3s16 &angle,
+		const v3s16 &rotation_speed);
+
 #endif

+ 8 - 0
src/defaultsettings.cpp

@@ -292,9 +292,17 @@ void set_default_settings(Settings *settings)
 #if USE_FREETYPE
 	settings->setDefault("freetype", "true");
 	settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "Arimo-Regular.ttf"));
+	settings->setDefault("font_path_italic", porting::getDataPath("fonts" DIR_DELIM "Arimo-Italic.ttf"));
+	settings->setDefault("font_path_bold", porting::getDataPath("fonts" DIR_DELIM "Arimo-Bold.ttf"));
+	settings->setDefault("font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Arimo-BoldItalic.ttf"));
+	settings->setDefault("font_bold", "false");
+	settings->setDefault("font_italic", "false");
 	settings->setDefault("font_shadow", "1");
 	settings->setDefault("font_shadow_alpha", "127");
 	settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "Cousine-Regular.ttf"));
+	settings->setDefault("mono_font_path_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-Italic.ttf"));
+	settings->setDefault("mono_font_path_bold", porting::getDataPath("fonts" DIR_DELIM "Cousine-Bold.ttf"));
+	settings->setDefault("mono_font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-BoldItalic.ttf"));
 	settings->setDefault("fallback_font_path", porting::getDataPath("fonts" DIR_DELIM "DroidSansFallbackFull.ttf"));
 
 	settings->setDefault("fallback_font_shadow", "1");

+ 1 - 0
src/gui/CMakeLists.txt

@@ -11,6 +11,7 @@ set(gui_SRCS
 	${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/guiHyperText.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/intlGUIEditBox.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/modalMenu.cpp

+ 77 - 14
src/gui/guiFormSpecMenu.cpp

@@ -57,6 +57,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "client/guiscalingfilter.h"
 #include "guiEditBoxWithScrollbar.h"
 #include "intlGUIEditBox.h"
+#include "guiHyperText.h"
 
 #define MY_CHECKPOS(a,b)													\
 	if (v_pos.size() != 2) {												\
@@ -155,16 +156,15 @@ void GUIFormSpecMenu::removeChildren()
 {
 	const core::list<gui::IGUIElement*> &children = getChildren();
 
-	while(!children.empty()) {
+	while (!children.empty()) {
 		(*children.getLast())->remove();
 	}
 
-	if(m_tooltip_element) {
+	if (m_tooltip_element) {
 		m_tooltip_element->remove();
 		m_tooltip_element->drop();
-		m_tooltip_element = NULL;
+		m_tooltip_element = nullptr;
 	}
-
 }
 
 void GUIFormSpecMenu::setInitialFocus()
@@ -1318,7 +1318,6 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data,
 void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& parts,
 		const std::string &type)
 {
-
 	std::vector<std::string> v_pos = split(parts[0],',');
 	std::vector<std::string> v_geom = split(parts[1],',');
 	std::string name = parts[2];
@@ -1402,6 +1401,59 @@ void GUIFormSpecMenu::parseField(parserData* data, const std::string &element,
 	errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'"  << std::endl;
 }
 
+void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &element)
+{
+	std::vector<std::string> parts = split(element, ';');
+
+	if (parts.size() != 4 && m_formspec_version < FORMSPEC_API_VERSION) {
+		errorstream << "Invalid text element(" << parts.size() << "): '" << element << "'"  << std::endl;
+		return;
+	}
+
+	std::vector<std::string> v_pos = split(parts[0], ',');
+	std::vector<std::string> v_geom = split(parts[1], ',');
+	std::string name = parts[2];
+	std::string text = parts[3];
+
+	MY_CHECKPOS("hypertext", 0);
+	MY_CHECKGEOM("hypertext", 1);
+
+	v2s32 pos;
+	v2s32 geom;
+
+	if (data->real_coordinates) {
+		pos = getRealCoordinateBasePos(false, v_pos);
+		geom = getRealCoordinateGeometry(v_geom);
+	} else {
+		pos = getElementBasePos(false, &v_pos);
+		pos -= padding;
+
+		pos.X += stof(v_pos[0]) * spacing.X;
+		pos.Y += stof(v_pos[1]) * spacing.Y + (m_btn_height * 2);
+
+		geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
+		geom.Y = (stof(v_geom[1]) * imgsize.Y) - (spacing.Y - imgsize.Y);
+	}
+
+	core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y);
+
+	if(m_form_src)
+		text = m_form_src->resolveText(text);
+
+	FieldSpec spec(
+		name,
+		utf8_to_wide(unescape_string(text)),
+		L"",
+		258 + m_fields.size()
+	);
+
+	spec.ftype = f_Unknown;
+	new GUIHyperText(
+		spec.flabel.c_str(), Environment, this, spec.fid, rect, m_client, m_tsrc);
+
+	m_fields.push_back(spec);
+}
+
 void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
 {
 	std::vector<std::string> parts = split(element,';');
@@ -2293,6 +2345,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
 		return;
 	}
 
+	if (type == "hypertext") {
+		parseHyperText(data,description);
+		return;
+	}
+
 	if (type == "label") {
 		parseLabel(data,description);
 		return;
@@ -2879,8 +2936,8 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int layer,
 			if (!item.empty()) {
 				// Draw item stack
 				drawItemStack(driver, m_font, item,
-					rect, &AbsoluteClippingRect, m_client,
-					rotation_kind);
+					rect, &AbsoluteClippingRect, m_client, rotation_kind);
+
 				// Draw tooltip
 				if (hovering && !m_selected_item) {
 					std::string tooltip = item.getDescription(m_client->idef());
@@ -2900,8 +2957,8 @@ void GUIFormSpecMenu::drawSelectedItem()
 
 	if (!m_selected_item) {
 		drawItemStack(driver, m_font, ItemStack(),
-			core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
-			NULL, m_client, IT_ROT_DRAGGED);
+				core::rect<s32>(v2s32(0, 0), v2s32(0, 0)), NULL,
+				m_client, IT_ROT_DRAGGED);
 		return;
 	}
 
@@ -3482,9 +3539,10 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
 			}
 		}
 	}
-	// Mouse wheel events: send to hovered element instead of focused
-	if(event.EventType==EET_MOUSE_INPUT_EVENT
-			&& event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
+	// Mouse wheel and move events: send to hovered element instead of focused
+	if (event.EventType == EET_MOUSE_INPUT_EVENT &&
+			(event.MouseInput.Event == EMIE_MOUSE_WHEEL ||
+			 event.MouseInput.Event == EMIE_MOUSE_MOVED)) {
 		s32 x = event.MouseInput.X;
 		s32 y = event.MouseInput.Y;
 		gui::IGUIElement *hovered =
@@ -3492,7 +3550,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
 				core::position2d<s32>(x, y));
 		if (hovered && isMyChild(hovered)) {
 			hovered->OnEvent(event);
-			return true;
+			return event.MouseInput.Event == EMIE_MOUSE_WHEEL;
 		}
 	}
 
@@ -4041,8 +4099,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
 		}
 		m_old_pointer = m_pointer;
 	}
-	if (event.EventType == EET_GUI_EVENT) {
 
+	if (event.EventType == EET_GUI_EVENT) {
 		if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED
 				&& isVisible()) {
 			// find the element that was clicked
@@ -4128,6 +4186,11 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
 					s.fdefault = L"Changed";
 					acceptInput(quit_mode_no);
 					s.fdefault = L"";
+				} else if ((s.ftype == f_Unknown) &&
+						(s.fid == event.GUIEvent.Caller->getID())) {
+					s.send = true;
+					acceptInput();
+					s.send = false;
 				}
 			}
 		}

+ 2 - 0
src/gui/guiFormSpecMenu.h

@@ -469,6 +469,7 @@ protected:
 	video::SColor m_default_tooltip_bgcolor;
 	video::SColor m_default_tooltip_color;
 
+	
 private:
 	IFormSource        *m_form_src;
 	TextDest           *m_text_dst;
@@ -529,6 +530,7 @@ private:
 	void parseSimpleField(parserData* data,std::vector<std::string> &parts);
 	void parseTextArea(parserData* data,std::vector<std::string>& parts,
 			const std::string &type);
+	void parseHyperText(parserData *data, const std::string &element);
 	void parseLabel(parserData* data, const std::string &element);
 	void parseVertLabel(parserData* data, const std::string &element);
 	void parseImageButton(parserData* data, const std::string &element,

File diff suppressed because it is too large
+ 1137 - 0
src/gui/guiHyperText.cpp


+ 229 - 0
src/gui/guiHyperText.h

@@ -0,0 +1,229 @@
+/*
+Minetest
+Copyright (C) 2019 EvicenceBKidscode / Pierre-Yves Rollo <dev@pyrollo.com>
+
+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 "config.h" // for USE_FREETYPE
+
+using namespace irr;
+
+class ISimpleTextureSource;
+class Client;
+
+#if USE_FREETYPE
+#include "irrlicht_changes/CGUITTFont.h"
+#endif
+
+class ParsedText
+{
+public:
+	ParsedText(const wchar_t *text);
+	~ParsedText();
+
+	enum ElementType
+	{
+		ELEMENT_TEXT,
+		ELEMENT_SEPARATOR,
+		ELEMENT_IMAGE,
+		ELEMENT_ITEM
+	};
+
+	enum BackgroundType
+	{
+		BACKGROUND_NONE,
+		BACKGROUND_COLOR
+	};
+
+	enum FloatType
+	{
+		FLOAT_NONE,
+		FLOAT_RIGHT,
+		FLOAT_LEFT
+	};
+
+	enum HalignType
+	{
+		HALIGN_CENTER,
+		HALIGN_LEFT,
+		HALIGN_RIGHT,
+		HALIGN_JUSTIFY
+	};
+
+	enum ValignType
+	{
+		VALIGN_MIDDLE,
+		VALIGN_TOP,
+		VALIGN_BOTTOM
+	};
+
+	typedef std::unordered_map<std::string, std::string> StyleList;
+	typedef std::unordered_map<std::string, std::string> AttrsList;
+
+	struct Tag
+	{
+		std::string name;
+		AttrsList attrs;
+		StyleList style;
+	};
+
+	struct Element
+	{
+		std::list<Tag *> tags;
+		ElementType type;
+		core::stringw text = "";
+
+		core::dimension2d<u32> dim;
+		core::position2d<s32> pos;
+		s32 drawwidth;
+
+		FloatType floating = FLOAT_NONE;
+
+		ValignType valign;
+
+#if USE_FREETYPE
+		gui::CGUITTFont *font;
+#else
+		gui::IGUIFont *font;
+#endif
+
+		irr::video::SColor color;
+		irr::video::SColor hovercolor;
+		bool underline;
+
+		s32 baseline = 0;
+
+		// img & item specific attributes
+		std::string name;
+		v3s16 angle{0, 0, 0};
+		v3s16 rotation{0, 0, 0};
+
+		s32 margin = 10;
+
+		void setStyle(StyleList &style);
+	};
+
+	struct Paragraph
+	{
+		std::vector<Element> elements;
+		HalignType halign;
+		s32 margin = 10;
+
+		void setStyle(StyleList &style);
+	};
+
+	std::vector<Paragraph> m_paragraphs;
+
+	// Element style
+	s32 margin = 3;
+	ValignType valign = VALIGN_TOP;
+	BackgroundType background_type = BACKGROUND_NONE;
+	irr::video::SColor background_color;
+
+	Tag m_root_tag;
+
+protected:
+	// Parser functions
+	void enterElement(ElementType type);
+	void endElement();
+	void enterParagraph();
+	void endParagraph();
+	void pushChar(wchar_t c);
+	ParsedText::Tag *newTag(const std::string &name, const AttrsList &attrs);
+	ParsedText::Tag *openTag(const std::string &name, const AttrsList &attrs);
+	bool closeTag(const std::string &name);
+	void parseGenericStyleAttr(const std::string &name, const std::string &value,
+			StyleList &style);
+	void parseStyles(const AttrsList &attrs, StyleList &style);
+	void globalTag(const ParsedText::AttrsList &attrs);
+	u32 parseTag(const wchar_t *text, u32 cursor);
+	void parse(const wchar_t *text);
+
+	std::unordered_map<std::string, StyleList> m_elementtags;
+	std::unordered_map<std::string, StyleList> m_paragraphtags;
+
+	std::vector<Tag *> m_tags;
+	std::list<Tag *> m_active_tags;
+
+	// Current values
+	StyleList m_style;
+	Element *m_element;
+	Paragraph *m_paragraph;
+};
+
+class TextDrawer
+{
+public:
+	TextDrawer(const wchar_t *text, Client *client, gui::IGUIEnvironment *environment,
+			ISimpleTextureSource *tsrc);
+
+	void place(const core::rect<s32> &dest_rect);
+	inline s32 getHeight() { return m_height; };
+	void draw(const core::rect<s32> &dest_rect,
+			const core::position2d<s32> &dest_offset);
+	ParsedText::Element *getElementAt(core::position2d<s32> pos);
+	ParsedText::Tag *m_hovertag;
+
+protected:
+	struct RectWithMargin
+	{
+		core::rect<s32> rect;
+		s32 margin;
+	};
+
+	ParsedText m_text;
+	Client *m_client;
+	gui::IGUIEnvironment *m_environment;
+	s32 m_height;
+	s32 m_voffset;
+	std::vector<RectWithMargin> m_floating;
+};
+
+class GUIHyperText : public gui::IGUIElement
+{
+public:
+	//! constructor
+	GUIHyperText(const wchar_t *text, gui::IGUIEnvironment *environment,
+			gui::IGUIElement *parent, s32 id,
+			const core::rect<s32> &rectangle, Client *client,
+			ISimpleTextureSource *tsrc);
+
+	//! destructor
+	virtual ~GUIHyperText();
+
+	//! draws the element and its children
+	virtual void draw();
+
+	core::dimension2du getTextDimension();
+
+	bool OnEvent(const SEvent &event);
+
+protected:
+	// GUI members
+	Client *m_client;
+	GUIScrollBar *m_vscrollbar;
+	TextDrawer m_drawer;
+
+	// Positioning
+	u32 m_scrollbar_width;
+	core::rect<s32> m_display_text_rect;
+	core::position2d<s32> m_text_scrollpos;
+
+	ParsedText::Element *getElementAt(s32 X, s32 Y);
+	void checkHover(s32 X, s32 Y);
+};

+ 2 - 0
src/irrlicht_changes/CGUITTFont.h

@@ -327,6 +327,8 @@ namespace gui
 				(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent = 0,
 				 const video::SColor& color = video::SColor(255, 0, 0, 0), bool center = false );
 
+			inline s32 getAscender() const { return font_metrics.ascender; }
+
 		protected:
 			bool use_monochrome;
 			bool use_transparency;

+ 25 - 0
src/util/string.cpp

@@ -947,3 +947,28 @@ std::wstring translate_string(const std::wstring &s) {
 	translate_all(s, i, res);
 	return res;
 }
+
+/**
+ * Create a std::string from a irr::core:stringw.
+ */
+std::string strwtostr(const irr::core::stringw &str)
+{
+	std::string text = core::stringc(str.c_str()).c_str();
+	return text;
+}
+
+/**
+ * Create a irr::core:stringw from a std::string.
+ */
+irr::core::stringw strtostrw(const std::string &str)
+{
+	size_t size = str.size();
+	// s.size() doesn't include NULL terminator
+	wchar_t *text = new wchar_t[size + sizeof(wchar_t)];
+	const char *data = &str[0];
+
+	mbsrtowcs(text, &data, size, NULL);
+
+	text[size] = L'\0';
+	return text;
+}

+ 11 - 0
src/util/string.h

@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #pragma once
 
 #include "irrlichttypes_bloated.h"
+#include "irrString.h"
 #include <cstdlib>
 #include <string>
 #include <cstring>
@@ -723,3 +724,13 @@ inline std::string str_join(const std::vector<std::string> &list,
 	}
 	return oss.str();
 }
+
+/**
+ * Create a std::string from a irr::core::stringw.
+ */
+std::string strwtostr(const irr::core::stringw &str);
+
+/**
+ * Create a irr::core:stringw from a std::string.
+ */
+irr::core::stringw strtostrw(const std::string &str);

+ 2 - 0
util/travis/clang-format-whitelist.txt

@@ -167,6 +167,8 @@ src/gui/guiEngine.h
 src/gui/guiFormSpecMenu.cpp
 src/gui/guiFormSpecMenu.h
 src/gui/guiKeyChangeMenu.cpp
+src/gui/guiHyperText.cpp
+src/gui/guiHyperText.h
 src/gui/guiMainMenu.h
 src/gui/guiPasswordChange.cpp
 src/gui/guiPathSelectMenu.cpp