Browse Source

Add support for multiline strings to `Tree`

Danil Alexeev 2 years ago
parent
commit
005937b576
3 changed files with 151 additions and 48 deletions
  1. 21 5
      doc/classes/TreeItem.xml
  2. 114 34
      scene/gui/tree.cpp
  3. 16 9
      scene/gui/tree.h

+ 21 - 5
doc/classes/TreeItem.xml

@@ -350,6 +350,13 @@
 			<description>
 			</description>
 		</method>
+		<method name="is_edit_multiline" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="column" type="int" />
+			<description>
+				Returns [code]true[/code] if the given [param column] is multiline editable.
+			</description>
+		</method>
 		<method name="is_editable">
 			<return type="bool" />
 			<param index="0" name="column" type="int" />
@@ -456,7 +463,7 @@
 			<param index="0" name="column" type="int" />
 			<param index="1" name="checked" type="bool" />
 			<description>
-				If [code]true[/code], the given [param column] is checked. Clears column's indeterminate status.
+				If [param checked] is [code]true[/code], the given [param column] is checked. Clears column's indeterminate status.
 			</description>
 		</method>
 		<method name="set_collapsed_recursive">
@@ -516,12 +523,21 @@
 				Sets custom font size used to draw text in the given [param column].
 			</description>
 		</method>
+		<method name="set_edit_multiline">
+			<return type="void" />
+			<param index="0" name="column" type="int" />
+			<param index="1" name="multiline" type="bool" />
+			<description>
+				If [param multiline] is [code]true[/code], the given [param column] is multiline editable.
+				[b]Note:[/b] This option only affects the type of control ([LineEdit] or [TextEdit]) that appears when editing the column. You can set multiline values with [method set_text] even if the column is not multiline editable.
+			</description>
+		</method>
 		<method name="set_editable">
 			<return type="void" />
 			<param index="0" name="column" type="int" />
 			<param index="1" name="enabled" type="bool" />
 			<description>
-				If [code]true[/code], the given [param column] is editable.
+				If [param enabled] is [code]true[/code], the given [param column] is editable.
 			</description>
 		</method>
 		<method name="set_expand_right">
@@ -529,7 +545,7 @@
 			<param index="0" name="column" type="int" />
 			<param index="1" name="enable" type="bool" />
 			<description>
-				If [code]true[/code], the given [param column] is expanded to the right.
+				If [param enable] is [code]true[/code], the given [param column] is expanded to the right.
 			</description>
 		</method>
 		<method name="set_icon">
@@ -569,7 +585,7 @@
 			<param index="0" name="column" type="int" />
 			<param index="1" name="indeterminate" type="bool" />
 			<description>
-				If [code]true[/code], the given [param column] is marked [param indeterminate].
+				If [param indeterminate] is [code]true[/code], the given [param column] is marked indeterminate.
 				[b]Note:[/b] If set [code]true[/code] from [code]false[/code], then column is cleared of checked status.
 			</description>
 		</method>
@@ -614,7 +630,7 @@
 			<param index="0" name="column" type="int" />
 			<param index="1" name="selectable" type="bool" />
 			<description>
-				If [code]true[/code], the given column is selectable.
+				If [param selectable] is [code]true[/code], the given [param column] is selectable.
 			</description>
 		</method>
 		<method name="set_structured_text_bidi_override">

+ 114 - 34
scene/gui/tree.cpp

@@ -38,6 +38,7 @@
 #include "core/string/print_string.h"
 #include "core/string/translation.h"
 #include "scene/gui/box_container.h"
+#include "scene/gui/text_edit.h"
 #include "scene/main/window.h"
 
 #include <limits.h>
@@ -166,6 +167,18 @@ TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const {
 	return cells[p_column].mode;
 }
 
+/* multiline editable */
+void TreeItem::set_edit_multiline(int p_column, bool p_multiline) {
+	ERR_FAIL_INDEX(p_column, cells.size());
+	cells.write[p_column].edit_multiline = p_multiline;
+	_changed_notify(p_column);
+}
+
+bool TreeItem::is_edit_multiline(int p_column) const {
+	ERR_FAIL_INDEX_V(p_column, cells.size(), false);
+	return cells[p_column].edit_multiline;
+}
+
 /* check mode */
 void TreeItem::set_checked(int p_column, bool p_checked) {
 	ERR_FAIL_INDEX(p_column, cells.size());
@@ -1404,6 +1417,9 @@ void TreeItem::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_cell_mode", "column", "mode"), &TreeItem::set_cell_mode);
 	ClassDB::bind_method(D_METHOD("get_cell_mode", "column"), &TreeItem::get_cell_mode);
 
+	ClassDB::bind_method(D_METHOD("set_edit_multiline", "column", "multiline"), &TreeItem::set_edit_multiline);
+	ClassDB::bind_method(D_METHOD("is_edit_multiline", "column"), &TreeItem::is_edit_multiline);
+
 	ClassDB::bind_method(D_METHOD("set_checked", "column", "checked"), &TreeItem::set_checked);
 	ClassDB::bind_method(D_METHOD("set_indeterminate", "column", "indeterminate"), &TreeItem::set_indeterminate);
 	ClassDB::bind_method(D_METHOD("is_checked", "column"), &TreeItem::is_checked);
@@ -1726,7 +1742,7 @@ int Tree::compute_item_height(TreeItem *p_item) const {
 			}
 		}
 	}
-	int item_min_height = p_item->get_custom_minimum_height();
+	int item_min_height = MAX(theme_cache.font->get_height(theme_cache.font_size), p_item->get_custom_minimum_height());
 	if (height < item_min_height) {
 		height = item_min_height;
 	}
@@ -1757,7 +1773,7 @@ int Tree::get_item_height(TreeItem *p_item) const {
 	return height;
 }
 
-void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color) {
+void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Point2 &p_draw_ofs, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color) {
 	ERR_FAIL_COND(theme_cache.font.is_null());
 
 	Rect2i rect = p_rect;
@@ -1795,7 +1811,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co
 
 	if (rtl && rect.size.width > 0) {
 		Point2 draw_pos = rect.position;
-		draw_pos.y += Math::floor((rect.size.y - p_cell.text_buf->get_size().y) / 2.0);
+		draw_pos.y += Math::floor(p_draw_ofs.y) - _get_title_button_height();
 		p_cell.text_buf->set_width(rect.size.width);
 		if (p_ol_size > 0 && p_ol_color.a > 0) {
 			p_cell.text_buf->draw_outline(ci, draw_pos, p_ol_size, p_ol_color);
@@ -1815,7 +1831,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co
 
 	if (!rtl && rect.size.width > 0) {
 		Point2 draw_pos = rect.position;
-		draw_pos.y += Math::floor((rect.size.y - p_cell.text_buf->get_size().y) / 2.0);
+		draw_pos.y += Math::floor(p_draw_ofs.y) - _get_title_button_height();
 		p_cell.text_buf->set_width(rect.size.width);
 		if (p_ol_size > 0 && p_ol_color.a > 0) {
 			p_cell.text_buf->draw_outline(ci, draw_pos, p_ol_size, p_ol_color);
@@ -2108,12 +2124,12 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
 			}
 
 			Point2i text_pos = item_rect.position;
-			text_pos.y += Math::floor((item_rect.size.y - p_item->cells[i].text_buf->get_size().y) / 2);
+			text_pos.y += Math::floor(p_draw_ofs.y) - _get_title_button_height();
 			int text_width = p_item->cells[i].text_buf->get_size().x;
 
 			switch (p_item->cells[i].mode) {
 				case TreeItem::CELL_MODE_STRING: {
-					draw_item_rect(p_item->cells.write[i], item_rect, cell_color, icon_col, outline_size, font_outline_color);
+					draw_item_rect(p_item->cells.write[i], item_rect, p_draw_ofs, cell_color, icon_col, outline_size, font_outline_color);
 				} break;
 				case TreeItem::CELL_MODE_CHECK: {
 					Ref<Texture2D> checked = theme_cache.checked;
@@ -2137,7 +2153,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
 					item_rect.size.x -= check_w;
 					item_rect.position.x += check_w;
 
-					draw_item_rect(p_item->cells.write[i], item_rect, cell_color, icon_col, outline_size, font_outline_color);
+					draw_item_rect(p_item->cells.write[i], item_rect, p_draw_ofs, cell_color, icon_col, outline_size, font_outline_color);
 
 				} break;
 				case TreeItem::CELL_MODE_RANGE: {
@@ -2216,7 +2232,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
 					}
 
 					if (!p_item->cells[i].editable) {
-						draw_item_rect(p_item->cells.write[i], item_rect, cell_color, icon_col, outline_size, font_outline_color);
+						draw_item_rect(p_item->cells.write[i], item_rect, p_draw_ofs, cell_color, icon_col, outline_size, font_outline_color);
 						break;
 					}
 
@@ -2244,7 +2260,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
 						ir.position += theme_cache.custom_button->get_offset();
 					}
 
-					draw_item_rect(p_item->cells.write[i], ir, cell_color, icon_col, outline_size, font_outline_color);
+					draw_item_rect(p_item->cells.write[i], ir, p_draw_ofs, cell_color, icon_col, outline_size, font_outline_color);
 
 					downarrow->draw(ci, arrow_pos);
 
@@ -2975,7 +2991,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
 	return item_h; // nothing found
 }
 
-void Tree::_text_editor_modal_close() {
+void Tree::_text_editor_popup_modal_close() {
 	if (Input::get_singleton()->is_key_pressed(Key::ESCAPE) ||
 			Input::get_singleton()->is_key_pressed(Key::KP_ENTER) ||
 			Input::get_singleton()->is_key_pressed(Key::ENTER)) {
@@ -2986,10 +3002,51 @@ void Tree::_text_editor_modal_close() {
 		return;
 	}
 
-	_text_editor_submit(text_editor->get_text());
+	if (!popup_edited_item) {
+		return;
+	}
+
+	if (popup_edited_item->is_edit_multiline(popup_edited_item_col) && popup_edited_item->get_cell_mode(popup_edited_item_col) == TreeItem::CELL_MODE_STRING) {
+		_apply_multiline_edit();
+	} else {
+		_line_editor_submit(line_editor->get_text());
+	}
 }
 
-void Tree::_text_editor_submit(String p_text) {
+void Tree::_text_editor_gui_input(const Ref<InputEvent> &p_event) {
+	if (p_event->is_action_pressed("ui_text_newline_blank", true)) {
+		accept_event();
+	} else if (p_event->is_action_pressed("ui_text_newline")) {
+		popup_editor->hide();
+		_apply_multiline_edit();
+		accept_event();
+	}
+}
+
+void Tree::_apply_multiline_edit() {
+	if (!popup_edited_item) {
+		return;
+	}
+
+	if (popup_edited_item_col < 0 || popup_edited_item_col > columns.size()) {
+		return;
+	}
+
+	TreeItem::Cell &c = popup_edited_item->cells.write[popup_edited_item_col];
+	switch (c.mode) {
+		case TreeItem::CELL_MODE_STRING: {
+			c.text = text_editor->get_text();
+		} break;
+		default: {
+			ERR_FAIL();
+		}
+	}
+
+	item_edited(popup_edited_item_col, popup_edited_item);
+	queue_redraw();
+}
+
+void Tree::_line_editor_submit(String p_text) {
 	popup_editor->hide();
 
 	if (!popup_edited_item) {
@@ -3830,18 +3887,16 @@ bool Tree::edit_selected() {
 		popup_menu->popup();
 		popup_edited_item = s;
 		popup_edited_item_col = col;
-		return true;
 
-	} else if (c.mode == TreeItem::CELL_MODE_STRING || c.mode == TreeItem::CELL_MODE_RANGE) {
+		return true;
+	} else if ((c.mode == TreeItem::CELL_MODE_STRING && !c.edit_multiline) || c.mode == TreeItem::CELL_MODE_RANGE) {
 		Rect2 popup_rect;
 
 		int value_editor_height = c.mode == TreeItem::CELL_MODE_RANGE ? value_editor->get_minimum_size().height : 0;
 		// "floor()" centers vertically.
-		Vector2 ofs(0, Math::floor((MAX(text_editor->get_minimum_size().height, rect.size.height - value_editor_height) - rect.size.height) / 2));
+		Vector2 ofs(0, Math::floor((MAX(line_editor->get_minimum_size().height, rect.size.height - value_editor_height) - rect.size.height) / 2));
 
-		Point2i textedpos = get_screen_position() + rect.position - ofs;
-		cache.text_editor_position = textedpos;
-		popup_rect.position = textedpos;
+		popup_rect.position = get_screen_position() + rect.position - ofs;
 		popup_rect.size = rect.size;
 
 		// Account for icon.
@@ -3849,9 +3904,12 @@ bool Tree::edit_selected() {
 		popup_rect.position.x += icon_size.x;
 		popup_rect.size.x -= icon_size.x;
 
-		text_editor->clear();
-		text_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::range_step_decimals(c.step)));
-		text_editor->select_all();
+		line_editor->clear();
+		line_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::range_step_decimals(c.step)));
+		line_editor->select_all();
+		line_editor->show();
+
+		text_editor->hide();
 
 		if (c.mode == TreeItem::CELL_MODE_RANGE) {
 			popup_rect.size.y += value_editor_height;
@@ -3873,6 +3931,22 @@ bool Tree::edit_selected() {
 		popup_editor->popup();
 		popup_editor->child_controls_changed();
 
+		line_editor->grab_focus();
+
+		return true;
+	} else if (c.mode == TreeItem::CELL_MODE_STRING && c.edit_multiline) {
+		line_editor->hide();
+
+		text_editor->clear();
+		text_editor->set_text(c.text);
+		text_editor->select_all();
+		text_editor->show();
+
+		popup_editor->set_position(get_screen_position() + rect.position);
+		popup_editor->set_size(rect.size);
+		popup_editor->popup();
+		popup_editor->child_controls_changed();
+
 		text_editor->grab_focus();
 
 		return true;
@@ -4144,14 +4218,10 @@ void Tree::_notification(int p_what) {
 		case NOTIFICATION_TRANSFORM_CHANGED: {
 			if (popup_edited_item != nullptr) {
 				Rect2 rect = popup_edited_item->get_meta("__focus_rect");
-				Vector2 ofs(0, (text_editor->get_size().height - rect.size.height) / 2);
-				Point2i textedpos = get_global_position() + rect.position - ofs;
 
-				if (cache.text_editor_position != textedpos) {
-					cache.text_editor_position = textedpos;
-					text_editor->set_position(textedpos);
-					value_editor->set_position(textedpos + Point2i(0, text_editor->get_size().height));
-				}
+				popup_editor->set_position(get_global_position() + rect.position);
+				popup_editor->set_size(rect.size);
+				popup_editor->child_controls_changed();
 			}
 		} break;
 	}
@@ -5386,17 +5456,26 @@ Tree::Tree() {
 
 	popup_editor = memnew(Popup);
 	add_child(popup_editor, false, INTERNAL_MODE_FRONT);
+
 	popup_editor_vb = memnew(VBoxContainer);
-	popup_editor->add_child(popup_editor_vb);
 	popup_editor_vb->add_theme_constant_override("separation", 0);
 	popup_editor_vb->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
-	text_editor = memnew(LineEdit);
-	popup_editor_vb->add_child(text_editor);
+	popup_editor->add_child(popup_editor_vb);
+
+	line_editor = memnew(LineEdit);
+	line_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+	line_editor->hide();
+	popup_editor_vb->add_child(line_editor);
+
+	text_editor = memnew(TextEdit);
 	text_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+	text_editor->hide();
+	popup_editor_vb->add_child(text_editor);
+
 	value_editor = memnew(HSlider);
-	popup_editor_vb->add_child(value_editor);
 	value_editor->set_v_size_flags(SIZE_EXPAND_FILL);
 	value_editor->hide();
+	popup_editor_vb->add_child(value_editor);
 
 	h_scroll = memnew(HScrollBar);
 	v_scroll = memnew(VScrollBar);
@@ -5410,8 +5489,9 @@ Tree::Tree() {
 
 	h_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
 	v_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
-	text_editor->connect("text_submitted", callable_mp(this, &Tree::_text_editor_submit));
-	popup_editor->connect("popup_hide", callable_mp(this, &Tree::_text_editor_modal_close));
+	line_editor->connect("text_submitted", callable_mp(this, &Tree::_line_editor_submit));
+	text_editor->connect("gui_input", callable_mp(this, &Tree::_text_editor_gui_input));
+	popup_editor->connect("popup_hide", callable_mp(this, &Tree::_text_editor_popup_modal_close));
 	popup_menu->connect("id_pressed", callable_mp(this, &Tree::popup_select));
 	value_editor->connect("value_changed", callable_mp(this, &Tree::value_editor_changed));
 

+ 16 - 9
scene/gui/tree.h

@@ -36,8 +36,9 @@
 #include "scene/gui/popup_menu.h"
 #include "scene/gui/scroll_bar.h"
 #include "scene/gui/slider.h"
-#include "scene/resources/text_line.h"
+#include "scene/resources/text_paragraph.h"
 
+class TextEdit;
 class Tree;
 
 class TreeItem : public Object {
@@ -61,8 +62,9 @@ private:
 		Ref<Texture2D> icon;
 		Rect2i icon_region;
 		String text;
+		bool edit_multiline = false;
 		String suffix;
-		Ref<TextLine> text_buf;
+		Ref<TextParagraph> text_buf;
 		String language;
 		TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
 		Array st_args;
@@ -198,6 +200,10 @@ public:
 	void set_cell_mode(int p_column, TreeCellMode p_mode);
 	TreeCellMode get_cell_mode(int p_column) const;
 
+	/* multiline editable */
+	void set_edit_multiline(int p_column, bool p_multiline);
+	bool is_edit_multiline(int p_column) const;
+
 	/* check mode */
 	void set_checked(int p_column, bool p_checked);
 	void set_indeterminate(int p_column, bool p_indeterminate);
@@ -436,7 +442,7 @@ private:
 		bool clip_content = false;
 		String title;
 		HorizontalAlignment title_alignment = HORIZONTAL_ALIGNMENT_CENTER;
-		Ref<TextLine> text_buf;
+		Ref<TextParagraph> text_buf;
 		String language;
 		Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED;
 		ColumnInfo() {
@@ -449,7 +455,8 @@ private:
 	VBoxContainer *popup_editor_vb = nullptr;
 
 	Popup *popup_editor = nullptr;
-	LineEdit *text_editor = nullptr;
+	LineEdit *line_editor = nullptr;
+	TextEdit *text_editor = nullptr;
 	HSlider *value_editor = nullptr;
 	bool updating_value_editor = false;
 	uint64_t focus_in_id = 0;
@@ -469,12 +476,14 @@ private:
 	void update_item_cell(TreeItem *p_item, int p_col);
 	void update_item_cache(TreeItem *p_item);
 	//void draw_item_text(String p_text,const Ref<Texture2D>& p_icon,int p_icon_max_w,bool p_tool,Rect2i p_rect,const Color& p_color);
-	void draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color);
+	void draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Point2 &p_draw_ofs, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color);
 	int draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item);
 	void select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_col, TreeItem *p_prev = nullptr, bool *r_in_range = nullptr, bool p_force_deselect = false);
 	int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod);
-	void _text_editor_submit(String p_text);
-	void _text_editor_modal_close();
+	void _line_editor_submit(String p_text);
+	void _apply_multiline_edit();
+	void _text_editor_popup_modal_close();
+	void _text_editor_gui_input(const Ref<InputEvent> &p_event);
 	void value_editor_changed(double p_value);
 
 	void popup_select(int p_option);
@@ -578,8 +587,6 @@ private:
 		TreeItem *hover_item = nullptr;
 		int hover_cell = -1;
 
-		Point2i text_editor_position;
-
 		bool rtl = false;
 	} cache;