[TextEdit] Add support for optional wrapped line indentation.
This commit is contained in:
parent
81f3d43cc1
commit
dad4aae386
|
@ -992,6 +992,9 @@
|
|||
<member name="text_editor/behavior/indent/auto_indent" type="bool" setter="" getter="">
|
||||
If [code]true[/code], automatically indents code when pressing the [kbd]Enter[/kbd] key based on blocks above the new line.
|
||||
</member>
|
||||
<member name="text_editor/behavior/indent/indent_wrapped_lines" type="bool" setter="" getter="">
|
||||
If [code]true[/code], all wrapped lines are indented to the same amount as the unwrapped line.
|
||||
</member>
|
||||
<member name="text_editor/behavior/indent/size" type="int" setter="" getter="">
|
||||
When using tab indentation, determines the length of each tab. When using space indentation, determines how many spaces are inserted when pressing [kbd]Tab[/kbd] and when automatic indentation is performed.
|
||||
</member>
|
||||
|
|
|
@ -1154,6 +1154,9 @@
|
|||
<member name="highlight_current_line" type="bool" setter="set_highlight_current_line" getter="is_highlight_current_line_enabled" default="false">
|
||||
If [code]true[/code], the line containing the cursor is highlighted.
|
||||
</member>
|
||||
<member name="indent_wrapped_lines" type="bool" setter="set_indent_wrapped_lines" getter="is_indent_wrapped_lines" default="false">
|
||||
If [code]true[/code], all wrapped lines are indented to the same amount as the unwrapped line.
|
||||
</member>
|
||||
<member name="language" type="String" setter="set_language" getter="get_language" default="""">
|
||||
Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
|
||||
</member>
|
||||
|
|
|
@ -1837,6 +1837,9 @@
|
|||
<constant name="BREAK_TRIM_EDGE_SPACES" value="16" enum="LineBreakFlag" is_bitfield="true">
|
||||
Remove edge spaces from the broken line segments.
|
||||
</constant>
|
||||
<constant name="BREAK_TRIM_INDENT" value="32" enum="LineBreakFlag" is_bitfield="true">
|
||||
Subtract first line indentation width from all lines after the first one.
|
||||
</constant>
|
||||
<constant name="VC_CHARS_BEFORE_SHAPING" value="0" enum="VisibleCharactersBehavior">
|
||||
Trims text before the shaping. e.g, increasing [member Label.visible_characters] or [member RichTextLabel.visible_characters] value is visually identical to typing the text.
|
||||
</constant>
|
||||
|
|
|
@ -1039,6 +1039,7 @@ void CodeTextEditor::update_editor_settings() {
|
|||
set_indent_using_spaces(EDITOR_GET("text_editor/behavior/indent/type"));
|
||||
text_editor->set_indent_size(EDITOR_GET("text_editor/behavior/indent/size"));
|
||||
text_editor->set_auto_indent_enabled(EDITOR_GET("text_editor/behavior/indent/auto_indent"));
|
||||
text_editor->set_indent_wrapped_lines(EDITOR_GET("text_editor/behavior/indent/indent_wrapped_lines"));
|
||||
|
||||
// Completion
|
||||
text_editor->set_auto_brace_completion_enabled(EDITOR_GET("text_editor/completion/auto_brace_complete"));
|
||||
|
|
|
@ -631,6 +631,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
|
|||
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/behavior/indent/type", 0, "Tabs,Spaces")
|
||||
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "text_editor/behavior/indent/size", 4, "1,64,1") // size of 0 crashes.
|
||||
_initial_set("text_editor/behavior/indent/auto_indent", true);
|
||||
_initial_set("text_editor/behavior/indent/indent_wrapped_lines", true);
|
||||
|
||||
// Behavior: Files
|
||||
_initial_set("text_editor/behavior/files/trim_trailing_whitespace_on_save", false);
|
||||
|
|
|
@ -74,6 +74,18 @@ int TextEdit::Text::get_tab_size() const {
|
|||
return tab_size;
|
||||
}
|
||||
|
||||
void TextEdit::Text::set_indent_wrapped_lines(bool p_enabled) {
|
||||
if (indent_wrapped_lines == p_enabled) {
|
||||
return;
|
||||
}
|
||||
indent_wrapped_lines = p_enabled;
|
||||
tab_size_dirty = true;
|
||||
}
|
||||
|
||||
bool TextEdit::Text::is_indent_wrapped_lines() const {
|
||||
return indent_wrapped_lines;
|
||||
}
|
||||
|
||||
void TextEdit::Text::set_direction_and_language(TextServer::Direction p_direction, const String &p_language) {
|
||||
if (direction == p_direction && language == p_language) {
|
||||
return;
|
||||
|
@ -185,9 +197,14 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan
|
|||
text.write[p_line].data_buf->clear();
|
||||
}
|
||||
|
||||
BitField<TextServer::LineBreakFlag> flags = brk_flags;
|
||||
if (indent_wrapped_lines) {
|
||||
flags.set_flag(TextServer::BREAK_TRIM_INDENT);
|
||||
}
|
||||
|
||||
text.write[p_line].data_buf->set_width(width);
|
||||
text.write[p_line].data_buf->set_direction((TextServer::Direction)direction);
|
||||
text.write[p_line].data_buf->set_break_flags(brk_flags);
|
||||
text.write[p_line].data_buf->set_break_flags(flags);
|
||||
text.write[p_line].data_buf->set_preserve_control(draw_control_chars);
|
||||
if (p_ime_text.length() > 0) {
|
||||
if (p_text_changed) {
|
||||
|
@ -251,8 +268,12 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan
|
|||
|
||||
void TextEdit::Text::invalidate_all_lines() {
|
||||
for (int i = 0; i < text.size(); i++) {
|
||||
BitField<TextServer::LineBreakFlag> flags = brk_flags;
|
||||
if (indent_wrapped_lines) {
|
||||
flags.set_flag(TextServer::BREAK_TRIM_INDENT);
|
||||
}
|
||||
text.write[i].data_buf->set_width(width);
|
||||
text.write[i].data_buf->set_break_flags(brk_flags);
|
||||
text.write[i].data_buf->set_break_flags(flags);
|
||||
if (tab_size_dirty) {
|
||||
if (tab_size > 0) {
|
||||
Vector<float> tabs;
|
||||
|
@ -1075,9 +1096,12 @@ void TextEdit::_notification(int p_what) {
|
|||
// Draw line.
|
||||
RID rid = ldata->get_line_rid(line_wrap_index);
|
||||
float text_height = TS->shaped_text_get_size(rid).y;
|
||||
float wrap_indent = (text.is_indent_wrapped_lines() && line_wrap_index > 0) ? get_indent_level(line) * theme_cache.font->get_char_size(' ', theme_cache.font_size).width : 0.0;
|
||||
|
||||
if (rtl) {
|
||||
char_margin = size.width - char_margin - TS->shaped_text_get_size(rid).x;
|
||||
char_margin = size.width - char_margin - (TS->shaped_text_get_size(rid).x + wrap_indent);
|
||||
} else {
|
||||
char_margin += wrap_indent;
|
||||
}
|
||||
|
||||
// Draw selections.
|
||||
|
@ -2935,7 +2959,11 @@ void TextEdit::_update_placeholder() {
|
|||
// Placeholder is generally smaller then text documents, and updates less so this should be fast enough for now.
|
||||
placeholder_data_buf->clear();
|
||||
placeholder_data_buf->set_width(text.get_width());
|
||||
placeholder_data_buf->set_break_flags(text.get_brk_flags());
|
||||
BitField<TextServer::LineBreakFlag> flags = text.get_brk_flags();
|
||||
if (text.is_indent_wrapped_lines()) {
|
||||
flags.set_flag(TextServer::BREAK_TRIM_INDENT);
|
||||
}
|
||||
placeholder_data_buf->set_break_flags(flags);
|
||||
if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
|
||||
placeholder_data_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
|
||||
} else {
|
||||
|
@ -3331,6 +3359,20 @@ int TextEdit::get_tab_size() const {
|
|||
return text.get_tab_size();
|
||||
}
|
||||
|
||||
void TextEdit::set_indent_wrapped_lines(bool p_enabled) {
|
||||
if (text.is_indent_wrapped_lines() == p_enabled) {
|
||||
return;
|
||||
}
|
||||
text.set_indent_wrapped_lines(p_enabled);
|
||||
text.invalidate_all_lines();
|
||||
_update_placeholder();
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
bool TextEdit::is_indent_wrapped_lines() const {
|
||||
return text.is_indent_wrapped_lines();
|
||||
}
|
||||
|
||||
// User controls
|
||||
void TextEdit::set_overtype_mode_enabled(const bool p_enabled) {
|
||||
if (overtype_mode == p_enabled) {
|
||||
|
@ -4336,8 +4378,11 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_
|
|||
}
|
||||
|
||||
RID text_rid = text.get_line_data(row)->get_line_rid(wrap_index);
|
||||
float wrap_indent = (text.is_indent_wrapped_lines() && wrap_index > 0) ? get_indent_level(row) * theme_cache.font->get_char_size(' ', theme_cache.font_size).width : 0.0;
|
||||
if (is_layout_rtl()) {
|
||||
colx = TS->shaped_text_get_size(text_rid).x - colx;
|
||||
colx = TS->shaped_text_get_size(text_rid).x - colx + wrap_indent;
|
||||
} else {
|
||||
colx -= wrap_indent;
|
||||
}
|
||||
col = TS->shaped_text_hit_test_position(text_rid, colx);
|
||||
if (!caret_mid_grapheme_enabled) {
|
||||
|
@ -6073,6 +6118,9 @@ void TextEdit::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_tab_size", "size"), &TextEdit::set_tab_size);
|
||||
ClassDB::bind_method(D_METHOD("get_tab_size"), &TextEdit::get_tab_size);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_indent_wrapped_lines", "enabled"), &TextEdit::set_indent_wrapped_lines);
|
||||
ClassDB::bind_method(D_METHOD("is_indent_wrapped_lines"), &TextEdit::is_indent_wrapped_lines);
|
||||
|
||||
// User controls
|
||||
ClassDB::bind_method(D_METHOD("set_overtype_mode_enabled", "enabled"), &TextEdit::set_overtype_mode_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_overtype_mode_enabled"), &TextEdit::is_overtype_mode_enabled);
|
||||
|
@ -6451,6 +6499,7 @@ void TextEdit::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Arbitrary:1,Word:2,Word (Smart):3"), "set_autowrap_mode", "get_autowrap_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_wrapped_lines"), "set_indent_wrapped_lines", "is_indent_wrapped_lines");
|
||||
|
||||
ADD_GROUP("Scroll", "scroll_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_smooth"), "set_smooth_scroll_enabled", "is_smooth_scroll_enabled");
|
||||
|
@ -7075,8 +7124,11 @@ int TextEdit::_get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) con
|
|||
p_wrap_index = MIN(p_wrap_index, text.get_line_data(p_line)->get_line_count() - 1);
|
||||
|
||||
RID text_rid = text.get_line_data(p_line)->get_line_rid(p_wrap_index);
|
||||
float wrap_indent = (text.is_indent_wrapped_lines() && p_wrap_index > 0) ? get_indent_level(p_line) * theme_cache.font->get_char_size(' ', theme_cache.font_size).width : 0.0;
|
||||
if (is_layout_rtl()) {
|
||||
p_px = TS->shaped_text_get_size(text_rid).x - p_px;
|
||||
p_px = TS->shaped_text_get_size(text_rid).x - p_px + wrap_indent;
|
||||
} else {
|
||||
p_px -= wrap_indent;
|
||||
}
|
||||
int ofs = TS->shaped_text_hit_test_position(text_rid, p_px);
|
||||
if (!caret_mid_grapheme_enabled) {
|
||||
|
@ -7125,11 +7177,12 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line, int p_column
|
|||
}
|
||||
|
||||
RID text_rid = text.get_line_data(p_line)->get_line_rid(row);
|
||||
float wrap_indent = (text.is_indent_wrapped_lines() && row > 0) ? get_indent_level(p_line) * theme_cache.font->get_char_size(' ', theme_cache.font_size).width : 0.0;
|
||||
CaretInfo ts_caret = TS->shaped_text_get_carets(text_rid, p_column);
|
||||
if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) {
|
||||
return ts_caret.l_caret.position.x;
|
||||
return ts_caret.l_caret.position.x + (is_layout_rtl() ? -wrap_indent : wrap_indent);
|
||||
} else {
|
||||
return ts_caret.t_caret.position.x;
|
||||
return ts_caret.t_caret.position.x + (is_layout_rtl() ? -wrap_indent : wrap_indent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -181,6 +181,7 @@ private:
|
|||
|
||||
int tab_size = 4;
|
||||
int gutter_count = 0;
|
||||
bool indent_wrapped_lines = false;
|
||||
|
||||
void _calculate_line_height();
|
||||
void _calculate_max_line_width();
|
||||
|
@ -188,6 +189,9 @@ private:
|
|||
public:
|
||||
void set_tab_size(int p_tab_size);
|
||||
int get_tab_size() const;
|
||||
void set_indent_wrapped_lines(bool p_enabled);
|
||||
bool is_indent_wrapped_lines() const;
|
||||
|
||||
void set_font(const Ref<Font> &p_font);
|
||||
void set_font_size(int p_font_size);
|
||||
void set_direction_and_language(TextServer::Direction p_direction, const String &p_language);
|
||||
|
@ -720,6 +724,9 @@ public:
|
|||
void set_tab_size(const int p_size);
|
||||
int get_tab_size() const;
|
||||
|
||||
void set_indent_wrapped_lines(bool p_enabled);
|
||||
bool is_indent_wrapped_lines() const;
|
||||
|
||||
// User controls
|
||||
void set_overtype_mode_enabled(const bool p_enabled);
|
||||
bool is_overtype_mode_enabled() const;
|
||||
|
|
|
@ -535,6 +535,7 @@ void TextServer::_bind_methods() {
|
|||
BIND_BITFIELD_FLAG(BREAK_GRAPHEME_BOUND);
|
||||
BIND_BITFIELD_FLAG(BREAK_ADAPTIVE);
|
||||
BIND_BITFIELD_FLAG(BREAK_TRIM_EDGE_SPACES);
|
||||
BIND_BITFIELD_FLAG(BREAK_TRIM_INDENT);
|
||||
|
||||
/* VisibleCharactersBehavior */
|
||||
BIND_ENUM_CONSTANT(VC_CHARS_BEFORE_SHAPING);
|
||||
|
@ -750,13 +751,28 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped
|
|||
int l_size = shaped_text_get_glyph_count(p_shaped);
|
||||
const Glyph *l_gl = const_cast<TextServer *>(this)->shaped_text_sort_logical(p_shaped);
|
||||
|
||||
double indent = 0.0;
|
||||
if (p_break_flags.has_flag(BREAK_TRIM_INDENT)) {
|
||||
for (int i = 0; i < l_size; i++) {
|
||||
if ((l_gl[i].flags & GRAPHEME_IS_TAB) == GRAPHEME_IS_TAB || (l_gl[i].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
|
||||
indent += l_gl[i].advance * l_gl[i].repeat;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < l_size; i++) {
|
||||
double l_width = p_width[chunk];
|
||||
if (l_width > indent) {
|
||||
l_width -= indent;
|
||||
}
|
||||
if (l_gl[i].start < p_start) {
|
||||
prev_safe_break = i + 1;
|
||||
continue;
|
||||
}
|
||||
if (l_gl[i].count > 0) {
|
||||
if ((p_width[chunk] > 0) && (width + l_gl[i].advance > p_width[chunk]) && (last_safe_break >= 0)) {
|
||||
if ((l_width > 0) && (width + l_gl[i].advance > l_width) && (last_safe_break >= 0)) {
|
||||
if (p_break_flags.has_flag(BREAK_TRIM_EDGE_SPACES)) {
|
||||
int start_pos = prev_safe_break;
|
||||
int end_pos = last_safe_break;
|
||||
|
@ -891,13 +907,25 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
|
|||
int l_size = shaped_text_get_glyph_count(p_shaped);
|
||||
const Glyph *l_gl = const_cast<TextServer *>(this)->shaped_text_sort_logical(p_shaped);
|
||||
|
||||
double indent = 0.0;
|
||||
if (p_break_flags.has_flag(BREAK_TRIM_INDENT)) {
|
||||
for (int i = 0; i < l_size; i++) {
|
||||
if ((l_gl[i].flags & GRAPHEME_IS_TAB) == GRAPHEME_IS_TAB || (l_gl[i].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
|
||||
indent += l_gl[i].advance * l_gl[i].repeat;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double l_width = p_width;
|
||||
for (int i = 0; i < l_size; i++) {
|
||||
if (l_gl[i].start < p_start) {
|
||||
prev_safe_break = i + 1;
|
||||
continue;
|
||||
}
|
||||
if (l_gl[i].count > 0) {
|
||||
if ((p_width > 0) && (width + l_gl[i].advance * l_gl[i].repeat > p_width) && (last_safe_break >= 0)) {
|
||||
if ((l_width > 0) && (width + l_gl[i].advance * l_gl[i].repeat > l_width) && (last_safe_break >= 0)) {
|
||||
if (p_break_flags.has_flag(BREAK_TRIM_EDGE_SPACES)) {
|
||||
int start_pos = prev_safe_break;
|
||||
int end_pos = last_safe_break;
|
||||
|
@ -910,6 +938,9 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
|
|||
if (last_end <= l_gl[start_pos].start) {
|
||||
lines.push_back(l_gl[start_pos].start);
|
||||
lines.push_back(l_gl[end_pos].end);
|
||||
if (p_width > indent) {
|
||||
l_width = p_width - indent;
|
||||
}
|
||||
last_end = l_gl[end_pos].end;
|
||||
}
|
||||
trim_next = true;
|
||||
|
@ -917,6 +948,9 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
|
|||
if (last_end <= line_start) {
|
||||
lines.push_back(line_start);
|
||||
lines.push_back(l_gl[last_safe_break].end);
|
||||
if (p_width > indent) {
|
||||
l_width = p_width - indent;
|
||||
}
|
||||
last_end = l_gl[last_safe_break].end;
|
||||
}
|
||||
}
|
||||
|
@ -943,12 +977,18 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
|
|||
if (last_end <= l_gl[start_pos].start) {
|
||||
lines.push_back(l_gl[start_pos].start);
|
||||
lines.push_back(l_gl[end_pos].end);
|
||||
if (p_width > indent) {
|
||||
l_width = p_width - indent;
|
||||
}
|
||||
last_end = l_gl[end_pos].end;
|
||||
}
|
||||
} else {
|
||||
if (last_end <= line_start) {
|
||||
lines.push_back(line_start);
|
||||
lines.push_back(l_gl[i].end);
|
||||
if (p_width > indent) {
|
||||
l_width = p_width - indent;
|
||||
}
|
||||
last_end = l_gl[i].end;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,7 @@ public:
|
|||
BREAK_GRAPHEME_BOUND = 1 << 2,
|
||||
BREAK_ADAPTIVE = 1 << 3,
|
||||
BREAK_TRIM_EDGE_SPACES = 1 << 4,
|
||||
BREAK_TRIM_INDENT = 1 << 5,
|
||||
};
|
||||
|
||||
enum OverrunBehavior {
|
||||
|
|
Loading…
Reference in New Issue