Browse Source

Merge pull request #58394 from bruvzg/rtl_hint

Rémi Verschelde 3 years ago
parent
commit
19950076b1

+ 11 - 0
doc/classes/CanvasItem.xml

@@ -80,6 +80,17 @@
 				Draws a colored polygon of any amount of points, convex or concave. Unlike [method draw_polygon], a single color must be specified for the whole polygon.
 			</description>
 		</method>
+		<method name="draw_dashed_line">
+			<return type="void" />
+			<argument index="0" name="from" type="Vector2" />
+			<argument index="1" name="to" type="Vector2" />
+			<argument index="2" name="color" type="Color" />
+			<argument index="3" name="width" type="float" default="1.0" />
+			<argument index="4" name="dash" type="float" default="2.0" />
+			<description>
+				Draws a dashed line from a 2D point to another, with a given color and width. See also [method draw_multiline] and [method draw_polyline].
+			</description>
+		</method>
 		<method name="draw_end_animation">
 			<return type="void" />
 			<description>

+ 14 - 2
doc/classes/RichTextLabel.xml

@@ -256,6 +256,13 @@
 				Adds a [code][font_size][/code] tag to the tag stack. Overrides default font size for its duration.
 			</description>
 		</method>
+		<method name="push_hint">
+			<return type="void" />
+			<argument index="0" name="description" type="String" />
+			<description>
+				Adds a [code][hint][/code] tag to the tag stack. Same as BBCode [code][hint=something]{text}[/hint][/code].
+			</description>
+		</method>
 		<method name="push_indent">
 			<return type="void" />
 			<argument index="0" name="level" type="int" />
@@ -424,6 +431,9 @@
 			If [code]true[/code], the label's height will be automatically updated to fit its content.
 			[b]Note:[/b] This property is used as a workaround to fix issues with [RichTextLabel] in [Container]s, but it's unreliable in some cases and will be removed in future versions.
 		</member>
+		<member name="hint_underlined" type="bool" setter="set_hint_underline" getter="is_hint_underlined" default="true">
+			If [code]true[/code], the label underlines hint tags such as [code][hint=description]{text}[/hint][/code].
+		</member>
 		<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
 			Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
 		</member>
@@ -563,9 +573,11 @@
 		</constant>
 		<constant name="ITEM_META" value="23" enum="ItemType">
 		</constant>
-		<constant name="ITEM_DROPCAP" value="24" enum="ItemType">
+		<constant name="ITEM_HINT" value="24" enum="ItemType">
+		</constant>
+		<constant name="ITEM_DROPCAP" value="25" enum="ItemType">
 		</constant>
-		<constant name="ITEM_CUSTOMFX" value="25" enum="ItemType">
+		<constant name="ITEM_CUSTOMFX" value="26" enum="ItemType">
 		</constant>
 		<constant name="VC_CHARS_BEFORE_SHAPING" value="0" enum="VisibleCharactersBehavior">
 			Trims text before the shaping. e.g, increasing [member visible_characters] value is visually identical to typing the text.

+ 122 - 8
scene/gui/rich_text_label.cpp

@@ -699,7 +699,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 
 	Item *it_from = l.from;
 	Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
-	Variant meta;
 
 	if (it_from == nullptr) {
 		return 0;
@@ -1070,23 +1069,60 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 			}
 		}
 
+		Vector2 ul_start;
+		bool ul_started = false;
+		Color ul_color;
+
+		Vector2 dot_ul_start;
+		bool dot_ul_started = false;
+		Color dot_ul_color;
+
+		Vector2 st_start;
+		bool st_started = false;
+		Color st_color;
+
 		for (int i = 0; i < gl_size; i++) {
 			bool selected = selection.active && (sel_start != -1) && (glyphs[i].start >= sel_start) && (glyphs[i].end <= sel_end);
 			Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start);
 			Color font_color = _find_color(it, p_base_color);
-			if (_find_underline(it) || (_find_meta(it, &meta) && underline_meta)) {
-				Color uc = font_color;
-				uc.a *= 0.5;
+			if (_find_underline(it) || (_find_meta(it, nullptr) && underline_meta)) {
+				if (!ul_started) {
+					ul_started = true;
+					ul_start = p_ofs + Vector2(off.x, off.y);
+					ul_color = font_color;
+					ul_color.a *= 0.5;
+				}
+			} else if (ul_started) {
+				ul_started = false;
 				float y_off = TS->shaped_text_get_underline_position(rid);
 				float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale();
-				draw_line(p_ofs + Vector2(off.x, off.y + y_off), p_ofs + Vector2(off.x + glyphs[i].advance * glyphs[i].repeat, off.y + y_off), uc, underline_width);
+				draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
+			}
+			if (_find_hint(it, nullptr) && underline_hint) {
+				if (!dot_ul_started) {
+					dot_ul_started = true;
+					dot_ul_start = p_ofs + Vector2(off.x, off.y);
+					dot_ul_color = font_color;
+					dot_ul_color.a *= 0.5;
+				}
+			} else if (dot_ul_started) {
+				dot_ul_started = false;
+				float y_off = TS->shaped_text_get_underline_position(rid);
+				float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale();
+				draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, underline_width * 2);
 			}
 			if (_find_strikethrough(it)) {
-				Color uc = font_color;
-				uc.a *= 0.5;
+				if (!st_started) {
+					st_started = true;
+					st_start = p_ofs + Vector2(off.x, off.y);
+					st_color = font_color;
+					st_color.a *= 0.5;
+				}
+			} else if (st_started) {
+				st_started = false;
 				float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
 				float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale();
-				draw_line(p_ofs + Vector2(off.x, off.y + y_off), p_ofs + Vector2(off.x + glyphs[i].advance * glyphs[i].repeat, off.y + y_off), uc, underline_width);
+				draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
 			}
 
 			// Get FX.
@@ -1222,6 +1258,24 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 				off.x += glyphs[i].advance;
 			}
 		}
+		if (ul_started) {
+			ul_started = false;
+			float y_off = TS->shaped_text_get_underline_position(rid);
+			float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale();
+			draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
+		}
+		if (dot_ul_started) {
+			dot_ul_started = false;
+			float y_off = TS->shaped_text_get_underline_position(rid);
+			float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale();
+			draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, underline_width * 2);
+		}
+		if (st_started) {
+			st_started = false;
+			float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
+			float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale();
+			draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
+		}
 		// Draw foreground color box
 		_draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 1);
 
@@ -1907,6 +1961,20 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
 	}
 }
 
+String RichTextLabel::get_tooltip(const Point2 &p_pos) const {
+	Item *c_item = nullptr;
+	bool outside;
+
+	const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &c_item, nullptr, &outside);
+
+	String description;
+	if (c_item && !outside && const_cast<RichTextLabel *>(this)->_find_hint(c_item, &description)) {
+		return description;
+	} else {
+		return Control::get_tooltip(p_pos);
+	}
+}
+
 void RichTextLabel::_find_frame(Item *p_item, ItemFrame **r_frame, int *r_line) {
 	if (r_frame != nullptr) {
 		*r_frame = nullptr;
@@ -2244,6 +2312,24 @@ bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item)
 	return false;
 }
 
+bool RichTextLabel::_find_hint(Item *p_item, String *r_description) {
+	Item *item = p_item;
+
+	while (item) {
+		if (item->type == ITEM_HINT) {
+			ItemHint *hint = static_cast<ItemHint *>(item);
+			if (r_description) {
+				*r_description = hint->description;
+			}
+			return true;
+		}
+
+		item = item->parent;
+	}
+
+	return false;
+}
+
 Color RichTextLabel::_find_bgcolor(Item *p_item) {
 	Item *item = p_item;
 
@@ -2736,6 +2822,14 @@ void RichTextLabel::push_meta(const Variant &p_meta) {
 	_add_item(item, true);
 }
 
+void RichTextLabel::push_hint(const String &p_string) {
+	ERR_FAIL_COND(current->type == ITEM_TABLE);
+	ItemHint *item = memnew(ItemHint);
+
+	item->description = p_string;
+	_add_item(item, true);
+}
+
 void RichTextLabel::push_table(int p_columns, InlineAlignment p_alignment) {
 	ERR_FAIL_COND(p_columns < 1);
 	ItemTable *item = memnew(ItemTable);
@@ -2930,6 +3024,15 @@ bool RichTextLabel::is_meta_underlined() const {
 	return underline_meta;
 }
 
+void RichTextLabel::set_hint_underline(bool p_underline) {
+	underline_hint = p_underline;
+	update();
+}
+
+bool RichTextLabel::is_hint_underlined() const {
+	return underline_hint;
+}
+
 void RichTextLabel::set_override_selected_font_color(bool p_override_selected_font_color) {
 	override_selected_font_color = p_override_selected_font_color;
 }
@@ -3367,6 +3470,11 @@ void RichTextLabel::append_text(const String &p_bbcode) {
 			push_meta(url);
 			pos = brk_end + 1;
 			tag_stack.push_front("url");
+		} else if (tag.begins_with("hint=")) {
+			String description = tag.substr(5, tag.length());
+			push_hint(description);
+			pos = brk_end + 1;
+			tag_stack.push_front("hint");
 		} else if (tag.begins_with("dropcap")) {
 			Vector<String> subtag = tag.substr(5, tag.length()).split(" ");
 			Ref<Font> f = get_theme_font(SNAME("normal_font"));
@@ -4287,6 +4395,7 @@ void RichTextLabel::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("push_indent", "level"), &RichTextLabel::push_indent);
 	ClassDB::bind_method(D_METHOD("push_list", "level", "type", "capitalize"), &RichTextLabel::push_list);
 	ClassDB::bind_method(D_METHOD("push_meta", "data"), &RichTextLabel::push_meta);
+	ClassDB::bind_method(D_METHOD("push_hint", "description"), &RichTextLabel::push_hint);
 	ClassDB::bind_method(D_METHOD("push_underline"), &RichTextLabel::push_underline);
 	ClassDB::bind_method(D_METHOD("push_strikethrough"), &RichTextLabel::push_strikethrough);
 	ClassDB::bind_method(D_METHOD("push_table", "columns", "inline_align"), &RichTextLabel::push_table, DEFVAL(INLINE_ALIGNMENT_TOP));
@@ -4318,6 +4427,9 @@ void RichTextLabel::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_meta_underline", "enable"), &RichTextLabel::set_meta_underline);
 	ClassDB::bind_method(D_METHOD("is_meta_underlined"), &RichTextLabel::is_meta_underlined);
 
+	ClassDB::bind_method(D_METHOD("set_hint_underline", "enable"), &RichTextLabel::set_hint_underline);
+	ClassDB::bind_method(D_METHOD("is_hint_underlined"), &RichTextLabel::is_hint_underlined);
+
 	ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &RichTextLabel::set_override_selected_font_color);
 	ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &RichTextLabel::is_overriding_selected_font_color);
 
@@ -4401,6 +4513,7 @@ void RichTextLabel::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hint_underlined"), "set_hint_underline", "is_hint_underlined");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode");
 
 	// Note: "visible_characters" and "percent_visible" should be set after "text" to be correctly applied.
@@ -4454,6 +4567,7 @@ void RichTextLabel::_bind_methods() {
 	BIND_ENUM_CONSTANT(ITEM_BGCOLOR);
 	BIND_ENUM_CONSTANT(ITEM_FGCOLOR);
 	BIND_ENUM_CONSTANT(ITEM_META);
+	BIND_ENUM_CONSTANT(ITEM_HINT);
 	BIND_ENUM_CONSTANT(ITEM_DROPCAP);
 	BIND_ENUM_CONSTANT(ITEM_CUSTOMFX);
 

+ 13 - 0
scene/gui/rich_text_label.h

@@ -78,6 +78,7 @@ public:
 		ITEM_BGCOLOR,
 		ITEM_FGCOLOR,
 		ITEM_META,
+		ITEM_HINT,
 		ITEM_DROPCAP,
 		ITEM_CUSTOMFX
 	};
@@ -218,6 +219,11 @@ private:
 		ItemMeta() { type = ITEM_META; }
 	};
 
+	struct ItemHint : public Item {
+		String description;
+		ItemHint() { type = ITEM_HINT; }
+	};
+
 	struct ItemParagraph : public Item {
 		HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
 		String language;
@@ -369,6 +375,7 @@ private:
 
 	int tab_size = 4;
 	bool underline_meta = true;
+	bool underline_hint = true;
 	bool override_selected_font_color = false;
 
 	HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT;
@@ -452,6 +459,7 @@ private:
 	bool _find_underline(Item *p_item);
 	bool _find_strikethrough(Item *p_item);
 	bool _find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item = nullptr);
+	bool _find_hint(Item *p_item, String *r_description);
 	Color _find_bgcolor(Item *p_item);
 	Color _find_fgcolor(Item *p_item);
 	bool _find_layout_subitem(Item *from, Item *to);
@@ -462,6 +470,7 @@ private:
 	void _scroll_changed(double);
 
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
+	virtual String get_tooltip(const Point2 &p_pos) const override;
 	Item *_get_next_item(Item *p_item, bool p_free = false) const;
 	Item *_get_prev_item(Item *p_item, bool p_free = false) const;
 
@@ -505,6 +514,7 @@ public:
 	void push_indent(int p_level);
 	void push_list(int p_level, ListType p_list, bool p_capitalize);
 	void push_meta(const Variant &p_meta);
+	void push_hint(const String &p_string);
 	void push_table(int p_columns, InlineAlignment p_alignment = INLINE_ALIGNMENT_TOP);
 	void push_fade(int p_start_index, int p_length);
 	void push_shake(int p_strength, float p_rate);
@@ -530,6 +540,9 @@ public:
 	void set_meta_underline(bool p_underline);
 	bool is_meta_underlined() const;
 
+	void set_hint_underline(bool p_underline);
+	bool is_hint_underlined() const;
+
 	void set_override_selected_font_color(bool p_override_selected_font_color);
 	bool is_overriding_selected_font_color() const;
 

+ 20 - 0
scene/main/canvas_item.cpp

@@ -444,6 +444,25 @@ void CanvasItem::item_rect_changed(bool p_size_changed) {
 	emit_signal(SceneStringNames::get_singleton()->item_rect_changed);
 }
 
+void CanvasItem::draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, real_t p_dash) {
+	ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal.");
+
+	float length = (p_to - p_from).length();
+	if (length < p_dash) {
+		RenderingServer::get_singleton()->canvas_item_add_line(canvas_item, p_from, p_to, p_color, p_width);
+		return;
+	}
+
+	Point2 off = p_from;
+	Vector2 step = p_dash * (p_to - p_from).normalized();
+	int steps = length / p_dash / 2;
+	for (int i = 0; i < steps; i++) {
+		RenderingServer::get_singleton()->canvas_item_add_line(canvas_item, off, (off + step), p_color, p_width);
+		off += 2 * step;
+	}
+	RenderingServer::get_singleton()->canvas_item_add_line(canvas_item, off, p_to, p_color, p_width);
+}
+
 void CanvasItem::draw_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width) {
 	ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal.");
 
@@ -868,6 +887,7 @@ void CanvasItem::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_is_on_top"), &CanvasItem::_is_on_top);
 
 	ClassDB::bind_method(D_METHOD("draw_line", "from", "to", "color", "width"), &CanvasItem::draw_line, DEFVAL(1.0));
+	ClassDB::bind_method(D_METHOD("draw_dashed_line", "from", "to", "color", "width", "dash"), &CanvasItem::draw_dashed_line, DEFVAL(1.0), DEFVAL(2.0));
 	ClassDB::bind_method(D_METHOD("draw_polyline", "points", "color", "width", "antialiased"), &CanvasItem::draw_polyline, DEFVAL(1.0), DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("draw_polyline_colors", "points", "colors", "width", "antialiased"), &CanvasItem::draw_polyline_colors, DEFVAL(1.0), DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("draw_arc", "center", "radius", "start_angle", "end_angle", "point_count", "color", "width", "antialiased"), &CanvasItem::draw_arc, DEFVAL(1.0), DEFVAL(false));

+ 1 - 0
scene/main/canvas_item.h

@@ -217,6 +217,7 @@ public:
 
 	/* DRAWING API */
 
+	void draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = 1.0, real_t p_dash = 2.0);
 	void draw_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = 1.0);
 	void draw_polyline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = 1.0, bool p_antialiased = false);
 	void draw_polyline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = 1.0, bool p_antialiased = false);