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 select, get_character_rect and hit_test to RichTextLabel #84715

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions doc/classes/RichTextLabel.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@
[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
</description>
</method>
<method name="get_character_rect">
<return type="Rect2" />
<param index="0" name="character" type="int" />
<description>
Returns the bounding box of the character position provided.
[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
Comment on lines +88 to +89
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Returns the bounding box of the character position provided.
[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
Returns the bounding box for the character at the given index.
[b]Note:[/b] If [member threaded] is set to [code]true[/code], this method returns a value for the loaded part of the document. If you need to make sure the document is fully loaded, use [method is_ready] or the [signal finished] signal.

</description>
</method>
<method name="get_content_height" qualifiers="const">
<return type="int" />
<description>
Expand Down Expand Up @@ -221,6 +229,13 @@
[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
</description>
</method>
<method name="hit_test" qualifiers="const">
<return type="int" />
<param index="0" name="coords" type="Vector2" />
<description>
Returns caret character offset at the specified coordinates.
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume it means this?
I believe it's a good idea to say "index" here, even if it is often called "position" in Strings. It's reasonably confusing in 2D space where position is a Vector2.

Suggested change
Returns caret character offset at the specified coordinates.
Returns the character index at the given position in local space.

</description>
</method>
<method name="install_effect">
<return type="void" />
<param index="0" name="effect" type="Variant" />
Expand Down Expand Up @@ -498,6 +513,15 @@
Scrolls to the beginning of the current selection.
</description>
</method>
<method name="select">
<return type="void" />
<param index="0" name="from" type="int" />
<param index="1" name="to" type="int" />
<description>
Selects characters between [param from] and [param to].
If [member selection_enabled] is [code]false[/code], no selection will occur.
Comment on lines +521 to +522
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Selects characters between [param from] and [param to].
If [member selection_enabled] is [code]false[/code], no selection will occur.
Selects the characters between the [param from] and [param to] positions. Does nothing if [member selection_enabled] is [code]false[/code].

</description>
</method>
<method name="select_all">
<return type="void" />
<description>
Expand Down
175 changes: 175 additions & 0 deletions scene/gui/rich_text_label.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5486,6 +5486,63 @@ void RichTextLabel::selection_copy() {
}
}

void RichTextLabel::select(int p_from, int p_to) {
_validate_line_caches();

if (!selection.enabled) {
return;
}

int max_char = get_total_character_count() - 1;
p_from = CLAMP(p_from, 0, max_char);
p_to = CLAMP(p_to, p_from, max_char);
if (p_to < p_from) {
return;
}

int from_line = -1;
int to_line = -1;

for (int i = 0; i < main->first_invalid_line.load(); i++) {
MutexLock lock(main->lines[i].text_buf->get_mutex());
int char_offset = main->lines[i].char_offset;
int char_count = main->lines[i].char_count;
if (from_line == -1) {
if (char_offset <= p_from && p_from <= char_offset + char_count) {
from_line = i;
}
}
if (to_line == -1) {
if (char_offset <= p_to && p_to <= char_offset + char_count) {
to_line = i;
break;
}
}
}
if (from_line == -1 || to_line == -1) {
return;
}

Item *from_item = _get_item_at_pos(main->lines[from_line].from, nullptr, p_from - main->lines[from_line].char_offset);
Item *to_item = _get_item_at_pos(main->lines[to_line].from, nullptr, p_to - main->lines[to_line].char_offset);
ItemFrame *from_frame;
ItemFrame *to_frame;
_find_frame(from_item, &from_frame, nullptr);
_find_frame(to_item, &to_frame, nullptr);

selection.from_frame = from_frame;
selection.from_line = from_line;
selection.from_char = p_from - from_frame->lines[from_line].char_offset;
selection.from_item = from_item;
selection.to_frame = to_frame;
selection.to_line = to_line;
selection.to_char = p_to - to_frame->lines[to_line].char_offset + 1;
selection.to_item = to_item;

selection.active = true;
queue_redraw();
}

void RichTextLabel::select_all() {
_validate_line_caches();

Expand Down Expand Up @@ -5888,6 +5945,7 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_selection_from"), &RichTextLabel::get_selection_from);
ClassDB::bind_method(D_METHOD("get_selection_to"), &RichTextLabel::get_selection_to);

ClassDB::bind_method(D_METHOD("select", "from", "to"), &RichTextLabel::select);
ClassDB::bind_method(D_METHOD("select_all"), &RichTextLabel::select_all);
ClassDB::bind_method(D_METHOD("get_selected_text"), &RichTextLabel::get_selected_text);
ClassDB::bind_method(D_METHOD("deselect"), &RichTextLabel::deselect);
Expand All @@ -5914,10 +5972,13 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_visible_ratio", "ratio"), &RichTextLabel::set_visible_ratio);
ClassDB::bind_method(D_METHOD("get_visible_ratio"), &RichTextLabel::get_visible_ratio);

ClassDB::bind_method(D_METHOD("get_character_rect", "character"), &RichTextLabel::get_character_rect);
ClassDB::bind_method(D_METHOD("get_character_line", "character"), &RichTextLabel::get_character_line);
ClassDB::bind_method(D_METHOD("get_character_paragraph", "character"), &RichTextLabel::get_character_paragraph);
ClassDB::bind_method(D_METHOD("get_total_character_count"), &RichTextLabel::get_total_character_count);

ClassDB::bind_method(D_METHOD("hit_test", "coords"), &RichTextLabel::hit_test);

ClassDB::bind_method(D_METHOD("set_use_bbcode", "enable"), &RichTextLabel::set_use_bbcode);
ClassDB::bind_method(D_METHOD("is_using_bbcode"), &RichTextLabel::is_using_bbcode);

Expand Down Expand Up @@ -6091,6 +6152,106 @@ int RichTextLabel::get_visible_characters() const {
return visible_characters;
}

Rect2 RichTextLabel::get_character_rect(int p_char) {
_validate_line_caches();

Vector2 offs;
int line = -1;
int char_index = -1;

// Find paragraph
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Find paragraph
// Find paragraph.

RID p_line;
for (int i = 0; i < main->first_invalid_line.load(); i++) {
MutexLock lock(main->lines[i].text_buf->get_mutex());
int char_offset = main->lines[i].char_offset;
int char_count = main->lines[i].char_count;
if (char_offset <= p_char && p_char <= char_offset + char_count) {
line = i;
break;
}
}
if (line == -1) {
return Rect2();
}

Line &l = main->lines[line];
offs = l.offset;
offs.y -= vscroll->get_value();
float line_width = l.text_buf->get_width();
float line_length = 0.0;
for (int i = 0; i < l.text_buf->get_line_count(); i++) {
Vector2i range = l.text_buf->get_line_range(i);
Vector2 size = l.text_buf->get_line_size(i);
if (l.char_offset + range.x <= p_char && p_char <= l.char_offset + range.y) {
p_line = l.text_buf->get_line_rid(i);
char_index = p_char - l.char_offset;
line_length = l.text_buf->get_line_size(i).x;
break;
}
offs.y += size.y;
}
if (char_index == -1) {
return Rect2();
}

bool rtl = (l.text_buf->get_direction() == TextServer::DIRECTION_RTL);
switch (l.text_buf->get_alignment()) {
case HORIZONTAL_ALIGNMENT_FILL: {
TS->shaped_text_fit_to_width(p_line, line_width);
} break;
case HORIZONTAL_ALIGNMENT_LEFT: {
if (rtl) {
offs.x += line_width - line_length;
}
} break;
case HORIZONTAL_ALIGNMENT_CENTER: {
offs.x += Math::floor((line_width - line_length) / 2.0);
} break;
case HORIZONTAL_ALIGNMENT_RIGHT: {
if (!rtl) {
offs.x += line_width - line_length;
}
} break;
}

// Handle tables
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Handle tables
// Handle tables.

Array objects = TS->shaped_text_get_objects(p_line);
for (int i = 0; i < objects.size(); i++) {
Item *it = reinterpret_cast<Item *>((uint64_t)objects[i]);
if (it == nullptr) {
continue;
}
if (it->type == ITEM_TABLE) {
Rect2 table_rect = TS->shaped_text_get_object_rect(p_line, objects[i]);
ItemTable *table = static_cast<ItemTable *>(it);

bool found = false;
for (Item *subitem : table->subitems) {
ItemFrame *cell = static_cast<ItemFrame *>(subitem);
for (int j = 0; j < (int)cell->lines.size(); j++) {
if (cell->lines[j].char_offset <= p_char && p_char <= cell->lines[j].char_offset + cell->lines[j].char_count) {
found = true;
offs.x += table_rect.position.x;
offs += cell->padding.position;
offs += cell->lines[j].offset;
p_line = cell->lines[j].text_buf->get_rid();
char_index = p_char - cell->lines[j].char_offset;
break;
}
}
if (found) {
break;
}
}
}
}

Vector2 bounds = TS->shaped_text_get_grapheme_bounds(p_line, char_index);
Vector2 pos = Vector2(offs.x + bounds[0], offs.y);
Vector2 size = Vector2(bounds[1] - bounds[0], TS->shaped_text_get_size(p_line).y);
return Rect2(pos, size);
}

int RichTextLabel::get_character_line(int p_char) {
_validate_line_caches();

Expand Down Expand Up @@ -6173,6 +6334,20 @@ int RichTextLabel::get_total_glyph_count() const {
return tg;
}

int RichTextLabel::hit_test(const Point2 &coords) const {
ItemFrame *c_frame = nullptr;
Item *c_item = nullptr;
int c_line = 0;
int c_index = 0;
bool outside;
Copy link
Contributor

Choose a reason for hiding this comment

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

Explicit may be good here

Suggested change
bool outside;
bool outside = false;


const_cast<RichTextLabel *>(this)->_find_click(main, coords, &c_frame, &c_line, &c_item, &c_index, &outside, false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Something is exceptionally off about this line but I can't quite put my finger on it.

if (c_item == nullptr) {
return -1;
}
return c_frame->lines[c_line].char_offset + c_index;
}

Size2 RichTextLabel::get_minimum_size() const {
Size2 sb_min_size = theme_cache.normal_style->get_minimum_size();
Size2 min_size;
Expand Down
4 changes: 4 additions & 0 deletions scene/gui/rich_text_label.h
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ class RichTextLabel : public Control {
int get_selection_from() const;
int get_selection_to() const;
String get_selected_text() const;
void select(int p_from, int p_to);
void select_all();
void selection_copy();

Expand Down Expand Up @@ -780,11 +781,14 @@ class RichTextLabel : public Control {

void set_visible_characters(int p_visible);
int get_visible_characters() const;
Rect2 get_character_rect(int p_char);
int get_character_line(int p_char);
int get_character_paragraph(int p_char);
int get_total_character_count() const;
int get_total_glyph_count() const;

int hit_test(const Point2 &coords) const;

void set_visible_ratio(float p_ratio);
float get_visible_ratio() const;

Expand Down
Loading