Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add drag and drop to TextEdit #55294

Merged
merged 1 commit into from
Dec 2, 2021

Conversation

ConteZero
Copy link
Contributor

@ConteZero ConteZero commented Nov 24, 2021

I've implemented the drag and drop operation on TextEdit.

Update: I've added support for copy when CTRL is pressed in both TextEdit and LineEdit as suggested by @KoBeWi

@ConteZero ConteZero requested a review from a team as a code owner November 24, 2021 21:27
@ConteZero ConteZero force-pushed the text_edit_drag_and_drop branch from 3e901a5 to bdddf2d Compare November 24, 2021 21:41
@KoBeWi KoBeWi added this to the 4.0 milestone Nov 24, 2021
@ConteZero
Copy link
Contributor Author

This commit should solve godotengine/godot-proposals#3488

@KoBeWi
Copy link
Member

KoBeWi commented Nov 24, 2021

Nice! This wasn't apparent with the previous PRs, but it would be useful if holding Ctrl would make the text being copied instead of moved.

@ConteZero ConteZero force-pushed the text_edit_drag_and_drop branch from bdddf2d to 48e652e Compare November 25, 2021 10:17
@ConteZero
Copy link
Contributor Author

Nice! This wasn't apparent with the previous PRs, but it would be useful if holding Ctrl would make the text being copied instead of moved.

I've added support for copy when CTRL is pressed in both TextEdit and LineEdit

@KoBeWi
Copy link
Member

KoBeWi commented Nov 26, 2021

When moving a text within one TextEdit, it's registered as 2 operations (remove -> add), so you need to undo twice to revert moving. This should be merged into one undo step (check "complex operation" stuff in TextEdit).

@ConteZero ConteZero force-pushed the text_edit_drag_and_drop branch from 48e652e to 314d091 Compare November 26, 2021 16:40
@ConteZero
Copy link
Contributor Author

When moving a text within one TextEdit, it's registered as 2 operations (remove -> add), so you need to undo twice to revert moving. This should be merged into one undo step (check "complex operation" stuff in TextEdit).

Fixed

@KoBeWi
Copy link
Member

KoBeWi commented Nov 26, 2021

This is not very important, but it's not possible to copy-drop at the end of selection:
wkGh1eLN8L

While expected is this:
W3UHV6gwIv

Also for some reason, when I create a TextEdit and run the project, drag and drop doesn't work.

@ConteZero
Copy link
Contributor Author

ConteZero commented Nov 26, 2021

This is not very important, but it's not possible to copy-drop at the end of selection:

I can try to fix it, but I suspect that this is tricky to implement.

Also for some reason, when I create a TextEdit and run the project, drag and drop doesn't work.

Yes, TextEdit has many problems when you run the project, but it is not caused by my changes.
I think it is related to #54955

@KoBeWi
Copy link
Member

KoBeWi commented Nov 26, 2021

I can try to fix it, but I suspect that this is tricky to implement.

Well, if it's too difficult then it's not worth it.

Copy link
Member

@KoBeWi KoBeWi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested and it works fine (except the runtime bug, but it's unrelated issue). The code is similar to LineEdit changes, so it's likely good to, but @Paulb23 might want to take a look too.

@ConteZero ConteZero force-pushed the text_edit_drag_and_drop branch from 314d091 to b351576 Compare November 26, 2021 20:06
@ConteZero
Copy link
Contributor Author

I can try to fix it, but I suspect that this is tricky to implement.

Well, if it's too difficult then it's not worth it.

I've done some small changes to handle the copy-drop at the beginning and at the end of selection.

@ConteZero ConteZero force-pushed the text_edit_drag_and_drop branch from b351576 to 31892eb Compare November 26, 2021 20:16
Comment on lines 2533 to 2538
if (!text_changed_dirty) {
if (is_inside_tree()) {
MessageQueue::get_singleton()->push_call(this, "_text_changed");
}
text_changed_dirty = true;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to call the MessageQueue in TextEdit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -5691,6 +5825,43 @@ void TextEdit::_post_shift_selection() {
selection.selecting_text = true;
}

bool TextEdit::_is_caret_inside_selection(bool p_edge) const {
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check the selection here rather then in external ifs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -5691,6 +5825,43 @@ void TextEdit::_post_shift_selection() {
selection.selecting_text = true;
}

bool TextEdit::_is_caret_inside_selection(bool p_edge) const {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checks the mouse not not the caret, would be better called is_mouse_over_selection or is_cursor_over_selection, also would be nice to expose to the API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -1538,6 +1564,9 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {

update();
}
} else if (is_editable() && has_selection() && _is_caret_inside_selection()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should allow dragging from a TextEdit even if it's not editable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 1593 to 1595
if (is_editable() && has_selection() && _is_caret_inside_selection()) {
selection.drag_attempt = true;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure on the use case this is solving, as it should be caught in the above statement, should be safe to remove this?
Testing, locally, it seems to prevent word and line selection modes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed when you do a double or triple click and you keep the left mouse button pressed on your last click to start dragging.
The code here is really tricky, but I've added the drag and drop feature trying to avoid dangerous refactoring of the actual code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed when you do a double or triple click and you keep the left mouse button pressed on your last click to start dragging.

We should't start dragging in this use case, it should continue to select text in the given mode.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior has been kept from original LineEdit before my changes, this is the discussion #54339 (review)
I can change it in both LineEdit and TextEdit (and perhaps also in my commit for RichTextLabel), I've implemented it only because of KoBeWi comment.
Let me know what should be the expected behavior in these cases in Godot.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking around most editors have text dragging as a separate action, so I would expect Godot to do the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only hope that users do not complain about that change in the previous behavior (I'm referring to LineEdit)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the drag start in case of double or triple click from TextEdit and LineEdit

@@ -2406,6 +2460,86 @@ bool TextEdit::is_text_field() const {
return true;
}

Variant TextEdit::get_drag_data(const Point2 &p_point) {
if (selection.drag_attempt && selection.active) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Can invert this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
Control::drop_data(p_point, p_data);

if (p_data.get_type() == Variant::STRING && is_editable()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can invert this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

virtual String get_tooltip(const Point2 &p_pos) const override;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accidental new line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
text_changed_dirty = true;
}
update();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to call update here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
}
}
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems overly complex, would something like the following work?

if (p_edge) {
  if ((row == selection.from_line && col == selection.from_column) || (row == selection.to_line && col == selection.to_column)) {
    return true
  }
}
return (row > selection.from_line && row <= selection.to_line && (row > selection.from_line || col >= selection.from_column) && (row < selection.to_line || col < selection.to_column))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've used the first part of your code to simplify the current code.
But the second part

return (row > selection.from_line && row <= selection.to_line && (row > selection.from_line || col >= selection.from_column) && (row < selection.to_line || col < selection.to_column))

is not correct, it does not handle all the possible cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What use cases are missing from that statement?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest I didn't thoroughly analyze that line of code, I stopped at
row > selection.from_line &&
that exclude the first line of selection, so I assumed you misunderstood the meaning of the p_edge parameter and used my code.

Looking back at your code I now understand what you wanted to do, but to make it work some changes are needed:

row > selection.from_line --> row >= selection.from_line

and

col >= selection.from_column --> col > selection.from_column

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@ConteZero ConteZero force-pushed the text_edit_drag_and_drop branch from 31892eb to ec142a2 Compare November 28, 2021 22:10
@ConteZero ConteZero requested a review from a team as a code owner November 28, 2021 22:10
@ConteZero ConteZero force-pushed the text_edit_drag_and_drop branch from ec142a2 to 6fddac9 Compare November 29, 2021 06:49
@ConteZero
Copy link
Contributor Author

@Paulb23 as we discussed here #54339 (comment)
I've submitted this pull request #55411

@ConteZero ConteZero force-pushed the text_edit_drag_and_drop branch 3 times, most recently from e3085c0 to b7ad3dd Compare December 1, 2021 21:46
Comment on lines 1734 to 1738
if (pos.y <= get_first_visible_line()) {
_scroll_lines_up();
} else if (pos.y >= get_last_full_visible_line()) {
_scroll_lines_down();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to do this, set_caret_column should adjust the viewport for us.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, in theory it shouldn't be necessary, but in practice the scroll won't work without this code.
I have the possibility to test only on Linux, so I don't know if the problem is limited to this platform, but without the code I added the scroll only works if you move the selection from top to bottom, if you move from the bottom upward the scroll does not work.
I can add a comment at the beginning of the code to indicate that this is a temporary workaround, if you remove that code the drag functionality would be almost unusable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would still remove it for this PR, not a keen on adding temporary workarounds as they quickly become permanent. The base issue should be fixed instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
Control::drop_data(p_point, p_data);

if (is_editable() && p_data.get_type() == Variant::STRING) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Can invert this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

set_caret_line(caret_row_tmp, true, false);
set_caret_column(caret_column_tmp);
insert_text_at_caret(p_data);
end_complex_operation();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to call delete_selection here as insert_text_at_caret will handle that. This should also remove the need to make this a complex operation. Similar for the else if below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the second delete_selection after the else if
The first one instead is needed, because is true that insert_text_at_caret call delete_selection, but delete_selection reset the caret position at the beginning of the selection and this break the code for the drag and drop.

@ConteZero ConteZero force-pushed the text_edit_drag_and_drop branch from b7ad3dd to 0699941 Compare December 2, 2021 21:01
@akien-mga akien-mga merged commit 543462e into godotengine:master Dec 2, 2021
@akien-mga
Copy link
Member

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants