Add Caret Insert Below and Above shortcuts to TextEdit
This commit is contained in:
parent
39534a7aec
commit
e5354cacd0
6 changed files with 202 additions and 1 deletions
|
@ -331,6 +331,10 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = {
|
|||
{ "ui_text_caret_document_start.macos", TTRC("Caret Document Start") },
|
||||
{ "ui_text_caret_document_end", TTRC("Caret Document End") },
|
||||
{ "ui_text_caret_document_end.macos", TTRC("Caret Document End") },
|
||||
{ "ui_text_caret_add_below", TTRC("Caret Add Below") },
|
||||
{ "ui_text_caret_add_below.macos", TTRC("Caret Add Below") },
|
||||
{ "ui_text_caret_add_above", TTRC("Caret Add Above") },
|
||||
{ "ui_text_caret_add_above.macos", TTRC("Caret Add Above") },
|
||||
{ "ui_text_scroll_up", TTRC("Scroll Up") },
|
||||
{ "ui_text_scroll_up.macos", TTRC("Scroll Up") },
|
||||
{ "ui_text_scroll_down", TTRC("Scroll Down") },
|
||||
|
@ -616,6 +620,24 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
|
|||
inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_document_end.macos", inputs);
|
||||
|
||||
// Text Caret Addition Below/Above
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_add_below", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::L | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_add_below.macos", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::UP | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_add_above", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::O | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_add_above.macos", inputs);
|
||||
|
||||
// Text Scrolling
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
|
|
|
@ -838,6 +838,18 @@
|
|||
<member name="input/ui_text_backspace_word.macos" type="Dictionary" setter="" getter="">
|
||||
macOS specific override for the shortcut to delete a word.
|
||||
</member>
|
||||
<member name="input/ui_text_caret_add_above" type="Dictionary" setter="" getter="">
|
||||
Default [InputEventAction] to add an additional caret above every caret of a text
|
||||
</member>
|
||||
<member name="input/ui_text_caret_add_above.macos" type="Dictionary" setter="" getter="">
|
||||
macOS specific override for the shortcut to add a caret above every caret
|
||||
</member>
|
||||
<member name="input/ui_text_caret_add_below" type="Dictionary" setter="" getter="">
|
||||
Default [InputEventAction] to add an additional caret below every caret of a text
|
||||
</member>
|
||||
<member name="input/ui_text_caret_add_below.macos" type="Dictionary" setter="" getter="">
|
||||
macOS specific override for the shortcut to add a caret below every caret
|
||||
</member>
|
||||
<member name="input/ui_text_caret_document_end" type="Dictionary" setter="" getter="">
|
||||
Default [InputEventAction] to move the text cursor the the end of the text.
|
||||
[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
|
||||
|
|
|
@ -63,6 +63,13 @@
|
|||
Adds a new caret at the given location. Returns the index of the new caret, or [code]-1[/code] if the location is invalid.
|
||||
</description>
|
||||
</method>
|
||||
<method name="add_caret_at_carets">
|
||||
<return type="void" />
|
||||
<param index="0" name="below" type="bool" />
|
||||
<description>
|
||||
Adds an additional caret above or below every caret. If [param below] is true the new caret will be added below and above otherwise.
|
||||
</description>
|
||||
</method>
|
||||
<method name="add_gutter">
|
||||
<return type="void" />
|
||||
<param index="0" name="at" type="int" default="-1" />
|
||||
|
|
|
@ -2089,6 +2089,17 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
|
|||
accept_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if (k->is_action("ui_text_caret_add_below", true)) {
|
||||
add_caret_at_carets(true);
|
||||
accept_event();
|
||||
return;
|
||||
}
|
||||
if (k->is_action("ui_text_caret_add_above", true)) {
|
||||
add_caret_at_carets(false);
|
||||
accept_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// MISC.
|
||||
|
@ -2803,6 +2814,51 @@ void TextEdit::_move_caret_document_end(bool p_select) {
|
|||
}
|
||||
}
|
||||
|
||||
void TextEdit::_get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x) const {
|
||||
if (p_last_fit_x == -1) {
|
||||
p_last_fit_x = _get_column_x_offset_for_line(p_old_column, p_old_line, p_old_column);
|
||||
}
|
||||
|
||||
// Calculate the new line and wrap index
|
||||
p_new_line = p_old_line;
|
||||
int caret_wrap_index = p_old_wrap_index;
|
||||
if (p_below) {
|
||||
if (caret_wrap_index < get_line_wrap_count(p_new_line)) {
|
||||
caret_wrap_index++;
|
||||
} else {
|
||||
p_new_line++;
|
||||
caret_wrap_index = 0;
|
||||
}
|
||||
} else {
|
||||
if (caret_wrap_index == 0) {
|
||||
p_new_line--;
|
||||
caret_wrap_index = get_line_wrap_count(p_new_line);
|
||||
} else {
|
||||
caret_wrap_index--;
|
||||
}
|
||||
}
|
||||
|
||||
// Boundary checks
|
||||
if (p_new_line < 0) {
|
||||
p_new_line = 0;
|
||||
}
|
||||
if (p_new_line >= text.size()) {
|
||||
p_new_line = text.size() - 1;
|
||||
}
|
||||
|
||||
p_new_column = _get_char_pos_for_line(p_last_fit_x, p_new_line, caret_wrap_index);
|
||||
if (p_new_column != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && caret_wrap_index < get_line_wrap_count(p_new_line)) {
|
||||
Vector<String> rows = get_line_wrapped_text(p_new_line);
|
||||
int row_end_col = 0;
|
||||
for (int i = 0; i < caret_wrap_index + 1; i++) {
|
||||
row_end_col += rows[i].length();
|
||||
}
|
||||
if (p_new_column >= row_end_col) {
|
||||
p_new_column -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextEdit::_update_placeholder() {
|
||||
if (font.is_null() || font_size <= 0) {
|
||||
return; // Not in tree?
|
||||
|
@ -4504,6 +4560,68 @@ int TextEdit::get_caret_count() const {
|
|||
return carets.size();
|
||||
}
|
||||
|
||||
void TextEdit::add_caret_at_carets(bool p_below) {
|
||||
Vector<int> caret_edit_order = get_caret_index_edit_order();
|
||||
for (const int &caret_index : caret_edit_order) {
|
||||
const int caret_line = get_caret_line(caret_index);
|
||||
const int caret_column = get_caret_column(caret_index);
|
||||
|
||||
// The last fit x will be cleared if the caret has a selection,
|
||||
// but if it does not have a selection the last fit x will be
|
||||
// transferred to the new caret
|
||||
int caret_from_column = 0, caret_to_column = 0, caret_last_fit_x = carets[caret_index].last_fit_x;
|
||||
if (has_selection(caret_index)) {
|
||||
// If the selection goes over multiple lines, deselect it.
|
||||
if (get_selection_from_line(caret_index) != get_selection_to_line(caret_index)) {
|
||||
deselect(caret_index);
|
||||
} else {
|
||||
caret_from_column = get_selection_from_column(caret_index);
|
||||
caret_to_column = get_selection_to_column(caret_index);
|
||||
caret_last_fit_x = -1;
|
||||
carets.write[caret_index].last_fit_x = _get_column_x_offset_for_line(caret_column, caret_line, caret_column);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the line and column of the new caret as if you would move the caret by pressing the arrow keys
|
||||
int new_caret_line, new_caret_column, new_caret_from_column = 0, new_caret_to_column = 0;
|
||||
_get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_column, p_below, new_caret_line, new_caret_column, caret_last_fit_x);
|
||||
|
||||
// If the caret does have a selection calculate the new from and to columns
|
||||
if (caret_from_column != caret_to_column) {
|
||||
// We only need to calculate the selection columns if the column of the caret changed
|
||||
if (caret_column != new_caret_column) {
|
||||
int _; // unused placeholder for p_new_line
|
||||
_get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_from_column, p_below, _, new_caret_from_column);
|
||||
_get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_to_column, p_below, _, new_caret_to_column);
|
||||
} else {
|
||||
new_caret_from_column = caret_from_column;
|
||||
new_caret_to_column = caret_to_column;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new caret
|
||||
const int new_caret_index = add_caret(new_caret_line, new_caret_column);
|
||||
|
||||
if (new_caret_index == -1) {
|
||||
continue;
|
||||
}
|
||||
// Also add the selection if there should be one
|
||||
if (new_caret_from_column != new_caret_to_column) {
|
||||
select(new_caret_line, new_caret_from_column, new_caret_line, new_caret_to_column, new_caret_index);
|
||||
// Necessary to properly modify the selection after adding the new caret
|
||||
carets.write[new_caret_index].selection.selecting_line = new_caret_line;
|
||||
carets.write[new_caret_index].selection.selecting_column = new_caret_column == new_caret_from_column ? new_caret_to_column : new_caret_from_column;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Copy the last fit x over
|
||||
carets.write[new_caret_index].last_fit_x = carets[caret_index].last_fit_x;
|
||||
}
|
||||
|
||||
merge_overlapping_carets();
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
Vector<int> TextEdit::get_caret_index_edit_order() {
|
||||
if (!caret_index_edit_dirty) {
|
||||
return caret_index_edit_order;
|
||||
|
@ -5959,6 +6077,7 @@ void TextEdit::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("remove_secondary_carets"), &TextEdit::remove_secondary_carets);
|
||||
ClassDB::bind_method(D_METHOD("merge_overlapping_carets"), &TextEdit::merge_overlapping_carets);
|
||||
ClassDB::bind_method(D_METHOD("get_caret_count"), &TextEdit::get_caret_count);
|
||||
ClassDB::bind_method(D_METHOD("add_caret_at_carets", "below"), &TextEdit::add_caret_at_carets);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_caret_index_edit_order"), &TextEdit::get_caret_index_edit_order);
|
||||
ClassDB::bind_method(D_METHOD("adjust_carets_after_edit", "caret", "from_line", "from_col", "to_line", "to_col"), &TextEdit::adjust_carets_after_edit);
|
||||
|
|
|
@ -598,6 +598,9 @@ private:
|
|||
void _move_caret_document_start(bool p_select);
|
||||
void _move_caret_document_end(bool p_select);
|
||||
|
||||
// Used in add_caret_at_carets
|
||||
void _get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x = -1) const;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
|
@ -816,6 +819,7 @@ public:
|
|||
void remove_secondary_carets();
|
||||
void merge_overlapping_carets();
|
||||
int get_caret_count() const;
|
||||
void add_caret_at_carets(bool p_below);
|
||||
|
||||
Vector<int> get_caret_index_edit_order();
|
||||
void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col);
|
||||
|
|
|
@ -3313,7 +3313,7 @@ TEST_CASE("[SceneTree][TextEdit] caret") {
|
|||
memdelete(text_edit);
|
||||
}
|
||||
|
||||
TEST_CASE("[SceneTree][TextEdit] muiticaret") {
|
||||
TEST_CASE("[SceneTree][TextEdit] multicaret") {
|
||||
TextEdit *text_edit = memnew(TextEdit);
|
||||
SceneTree::get_singleton()->get_root()->add_child(text_edit);
|
||||
text_edit->set_multiple_carets_enabled(true);
|
||||
|
@ -3403,6 +3403,43 @@ TEST_CASE("[SceneTree][TextEdit] muiticaret") {
|
|||
CHECK(text_edit->get_caret_index_edit_order() == caret_index_get_order);
|
||||
}
|
||||
|
||||
SUBCASE("[TextEdit] add caret at carets") {
|
||||
text_edit->remove_secondary_carets();
|
||||
text_edit->set_caret_line(1);
|
||||
text_edit->set_caret_column(9);
|
||||
|
||||
text_edit->add_caret_at_carets(true);
|
||||
CHECK(text_edit->get_caret_count() == 2);
|
||||
CHECK(text_edit->get_caret_line(1) == 2);
|
||||
CHECK(text_edit->get_caret_column(1) == 4);
|
||||
|
||||
text_edit->add_caret_at_carets(true);
|
||||
CHECK(text_edit->get_caret_count() == 2);
|
||||
|
||||
text_edit->add_caret_at_carets(false);
|
||||
CHECK(text_edit->get_caret_count() == 3);
|
||||
CHECK(text_edit->get_caret_line(2) == 0);
|
||||
CHECK(text_edit->get_caret_column(2) == 7);
|
||||
|
||||
text_edit->remove_secondary_carets();
|
||||
text_edit->set_caret_line(0);
|
||||
text_edit->set_caret_column(4);
|
||||
text_edit->select(0, 0, 0, 4);
|
||||
text_edit->add_caret_at_carets(true);
|
||||
CHECK(text_edit->get_caret_count() == 2);
|
||||
CHECK(text_edit->get_selection_from_line(1) == 1);
|
||||
CHECK(text_edit->get_selection_to_line(1) == 1);
|
||||
CHECK(text_edit->get_selection_from_column(1) == 0);
|
||||
CHECK(text_edit->get_selection_to_column(1) == 3);
|
||||
|
||||
text_edit->add_caret_at_carets(true);
|
||||
CHECK(text_edit->get_caret_count() == 3);
|
||||
CHECK(text_edit->get_selection_from_line(2) == 2);
|
||||
CHECK(text_edit->get_selection_to_line(2) == 2);
|
||||
CHECK(text_edit->get_selection_from_column(2) == 0);
|
||||
CHECK(text_edit->get_selection_to_column(2) == 4);
|
||||
}
|
||||
|
||||
memdelete(text_edit);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue