mirror of
https://github.com/godotengine/godot.git
synced 2025-12-07 13:49:54 +00:00
Accessibility: Fix text field character count and line navigation
Two fixes for text field accessibility: 1. Fix character count being off by one due to trailing newline always being appended to the last line. Add `is_last_line` parameter to `accessibility_create_sub_text_edit_elements()` to control this. 2. Link adjacent TextRuns via `previous_on_line`/`next_on_line` so screen readers can properly navigate lines. Without these links, AccessKit treats each TextRun as a separate line, causing incorrect announcements when arrowing through multi-line text.
This commit is contained in:
parent
7a228b4b91
commit
6304e9f876
10 changed files with 50 additions and 28 deletions
|
|
@ -34,8 +34,10 @@
|
||||||
<param index="1" name="shaped_text" type="RID" />
|
<param index="1" name="shaped_text" type="RID" />
|
||||||
<param index="2" name="min_height" type="float" />
|
<param index="2" name="min_height" type="float" />
|
||||||
<param index="3" name="insert_pos" type="int" default="-1" />
|
<param index="3" name="insert_pos" type="int" default="-1" />
|
||||||
|
<param index="4" name="is_last_line" type="bool" default="false" />
|
||||||
<description>
|
<description>
|
||||||
Creates a new, empty accessibility sub-element from the shaped text buffer. Sub-elements are freed automatically when the parent element is freed, or can be freed early using the [method accessibility_free_element] method.
|
Creates a new, empty accessibility sub-element from the shaped text buffer. Sub-elements are freed automatically when the parent element is freed, or can be freed early using the [method accessibility_free_element] method.
|
||||||
|
If [param is_last_line] is [code]true[/code], no trailing newline is appended to the text content. Set to [code]true[/code] for the last line in multi-line text fields and for single-line text fields.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="accessibility_element_get_meta" qualifiers="const">
|
<method name="accessibility_element_get_meta" qualifiers="const">
|
||||||
|
|
|
||||||
|
|
@ -250,7 +250,7 @@ RID AccessibilityDriverAccessKit::accessibility_create_sub_element(const RID &p_
|
||||||
return rid;
|
return rid;
|
||||||
}
|
}
|
||||||
|
|
||||||
RID AccessibilityDriverAccessKit::accessibility_create_sub_text_edit_elements(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos) {
|
RID AccessibilityDriverAccessKit::accessibility_create_sub_text_edit_elements(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos, bool p_is_last_line) {
|
||||||
AccessibilityElement *parent_ae = rid_owner.get_or_null(p_parent_rid);
|
AccessibilityElement *parent_ae = rid_owner.get_or_null(p_parent_rid);
|
||||||
ERR_FAIL_NULL_V(parent_ae, RID());
|
ERR_FAIL_NULL_V(parent_ae, RID());
|
||||||
|
|
||||||
|
|
@ -421,7 +421,7 @@ RID AccessibilityDriverAccessKit::accessibility_create_sub_text_edit_elements(co
|
||||||
|
|
||||||
run_off_x += size_x;
|
run_off_x += size_x;
|
||||||
}
|
}
|
||||||
{
|
if (!p_is_last_line || text_elements.is_empty()) {
|
||||||
// Add "\n" at the end.
|
// Add "\n" at the end.
|
||||||
AccessibilityElement *ae = memnew(AccessibilityElement);
|
AccessibilityElement *ae = memnew(AccessibilityElement);
|
||||||
ae->role = ACCESSKIT_ROLE_TEXT_RUN;
|
ae->role = ACCESSKIT_ROLE_TEXT_RUN;
|
||||||
|
|
@ -432,18 +432,22 @@ RID AccessibilityDriverAccessKit::accessibility_create_sub_text_edit_elements(co
|
||||||
|
|
||||||
text_elements.push_back(ae);
|
text_elements.push_back(ae);
|
||||||
|
|
||||||
Vector<uint8_t> char_lengths;
|
if (!p_is_last_line) {
|
||||||
char_lengths.push_back(1);
|
accesskit_node_set_value(ae->node, "\n");
|
||||||
accesskit_node_set_value(ae->node, "\n");
|
|
||||||
accesskit_node_set_character_lengths(ae->node, char_lengths.size(), char_lengths.ptr());
|
|
||||||
|
|
||||||
Vector<float> char_positions;
|
Vector<uint8_t> char_lengths;
|
||||||
Vector<float> char_widths;
|
Vector<float> char_positions;
|
||||||
char_positions.push_back(0.0);
|
Vector<float> char_widths;
|
||||||
char_widths.push_back(1.0);
|
char_lengths.push_back(1);
|
||||||
|
char_positions.push_back(0.0);
|
||||||
|
char_widths.push_back(1.0);
|
||||||
|
|
||||||
accesskit_node_set_character_positions(ae->node, char_positions.size(), char_positions.ptr());
|
accesskit_node_set_character_lengths(ae->node, char_lengths.size(), char_lengths.ptr());
|
||||||
accesskit_node_set_character_widths(ae->node, char_widths.size(), char_widths.ptr());
|
accesskit_node_set_character_positions(ae->node, char_positions.size(), char_positions.ptr());
|
||||||
|
accesskit_node_set_character_widths(ae->node, char_widths.size(), char_widths.ptr());
|
||||||
|
} else {
|
||||||
|
accesskit_node_set_value(ae->node, "");
|
||||||
|
}
|
||||||
accesskit_node_set_text_direction(ae->node, ACCESSKIT_TEXT_DIRECTION_LEFT_TO_RIGHT);
|
accesskit_node_set_text_direction(ae->node, ACCESSKIT_TEXT_DIRECTION_LEFT_TO_RIGHT);
|
||||||
|
|
||||||
accesskit_rect rect;
|
accesskit_rect rect;
|
||||||
|
|
@ -461,10 +465,18 @@ RID AccessibilityDriverAccessKit::accessibility_create_sub_text_edit_elements(co
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
text_elements.sort_custom<RunCompare>();
|
text_elements.sort_custom<RunCompare>();
|
||||||
for (AccessibilityElement *text_element : text_elements) {
|
for (int i = 0; i < text_elements.size(); i++) {
|
||||||
RID rid = rid_owner.make_rid(text_element);
|
RID rid = rid_owner.make_rid(text_elements[i]);
|
||||||
root_ae->children.push_back(rid);
|
root_ae->children.push_back(rid);
|
||||||
wd->update.insert(rid);
|
wd->update.insert(rid);
|
||||||
|
|
||||||
|
// Link adjacent TextRuns on the same line.
|
||||||
|
if (i > 0) {
|
||||||
|
RID prev_rid = root_ae->children[i - 1];
|
||||||
|
AccessibilityElement *prev_ae = rid_owner.get_or_null(prev_rid);
|
||||||
|
accesskit_node_set_previous_on_line(text_elements[i]->node, (accesskit_node_id)prev_rid.get_id());
|
||||||
|
accesskit_node_set_next_on_line(prev_ae->node, (accesskit_node_id)rid.get_id());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return root_rid;
|
return root_rid;
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ public:
|
||||||
|
|
||||||
RID accessibility_create_element(DisplayServer::WindowID p_window_id, DisplayServer::AccessibilityRole p_role) override;
|
RID accessibility_create_element(DisplayServer::WindowID p_window_id, DisplayServer::AccessibilityRole p_role) override;
|
||||||
RID accessibility_create_sub_element(const RID &p_parent_rid, DisplayServer::AccessibilityRole p_role, int p_insert_pos = -1) override;
|
RID accessibility_create_sub_element(const RID &p_parent_rid, DisplayServer::AccessibilityRole p_role, int p_insert_pos = -1) override;
|
||||||
virtual RID accessibility_create_sub_text_edit_elements(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos = -1) override;
|
virtual RID accessibility_create_sub_text_edit_elements(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos = -1, bool p_is_last_line = false) override;
|
||||||
bool accessibility_has_element(const RID &p_id) const override;
|
bool accessibility_has_element(const RID &p_id) const override;
|
||||||
void accessibility_free_element(const RID &p_id) override;
|
void accessibility_free_element(const RID &p_id) override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -233,3 +233,10 @@ Validate extension JSON: Error: Field 'classes/EditorExportPreset/methods/get_sc
|
||||||
Validate extension JSON: Error: Field 'classes/EditorExportPreset/methods/get_script_export_mode/return_value': type changed value in new API, from "int" to "enum::EditorExportPreset.ScriptExportMode".
|
Validate extension JSON: Error: Field 'classes/EditorExportPreset/methods/get_script_export_mode/return_value': type changed value in new API, from "int" to "enum::EditorExportPreset.ScriptExportMode".
|
||||||
|
|
||||||
Change return type from `int` to `EditorExportPreset.ScriptExportMode` enum. Compatibility method registered.
|
Change return type from `int` to `EditorExportPreset.ScriptExportMode` enum. Compatibility method registered.
|
||||||
|
|
||||||
|
|
||||||
|
GH-113459
|
||||||
|
---------
|
||||||
|
Validate extension JSON: Error: Field 'classes/DisplayServer/methods/accessibility_create_sub_text_edit_elements/arguments': size changed value in new API, from 4 to 5.
|
||||||
|
|
||||||
|
Optional argument added. Compatibility method registered.
|
||||||
|
|
|
||||||
|
|
@ -1311,7 +1311,7 @@ void LineEdit::_notification(int p_what) {
|
||||||
float text_off_x = x_ofs + scroll_offset;
|
float text_off_x = x_ofs + scroll_offset;
|
||||||
|
|
||||||
if (accessibility_text_root_element.is_null()) {
|
if (accessibility_text_root_element.is_null()) {
|
||||||
accessibility_text_root_element = DisplayServer::get_singleton()->accessibility_create_sub_text_edit_elements(ae, using_placeholder ? RID() : text_rid, text_height);
|
accessibility_text_root_element = DisplayServer::get_singleton()->accessibility_create_sub_text_edit_elements(ae, using_placeholder ? RID() : text_rid, text_height, -1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Transform2D text_xform;
|
Transform2D text_xform;
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,8 @@ void TextEdit::Text::update_accessibility(int p_line, RID p_root) {
|
||||||
Line &l = text.write[p_line];
|
Line &l = text.write[p_line];
|
||||||
if (l.accessibility_text_root_element.is_empty()) {
|
if (l.accessibility_text_root_element.is_empty()) {
|
||||||
for (int i = 0; i < l.data_buf->get_line_count(); i++) {
|
for (int i = 0; i < l.data_buf->get_line_count(); i++) {
|
||||||
RID rid = DisplayServer::get_singleton()->accessibility_create_sub_text_edit_elements(p_root, l.data_buf->get_line_rid(i), max_line_height, p_line);
|
bool is_last_line = (p_line == text.size() - 1) && (i == l.data_buf->get_line_count() - 1);
|
||||||
|
RID rid = DisplayServer::get_singleton()->accessibility_create_sub_text_edit_elements(p_root, l.data_buf->get_line_rid(i), max_line_height, p_line, is_last_line);
|
||||||
l.accessibility_text_root_element.push_back(rid);
|
l.accessibility_text_root_element.push_back(rid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -738,7 +739,6 @@ void TextEdit::_notification(int p_what) {
|
||||||
case NOTIFICATION_EXIT_TREE:
|
case NOTIFICATION_EXIT_TREE:
|
||||||
case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
|
case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
|
||||||
text.clear_accessibility();
|
text.clear_accessibility();
|
||||||
accessibility_text_root_element_nl = RID();
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
|
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
|
||||||
|
|
@ -807,9 +807,6 @@ void TextEdit::_notification(int p_what) {
|
||||||
}
|
}
|
||||||
lines_drawn += ac_buf->get_line_count();
|
lines_drawn += ac_buf->get_line_count();
|
||||||
}
|
}
|
||||||
if (accessibility_text_root_element_nl.is_null()) {
|
|
||||||
accessibility_text_root_element_nl = DisplayServer::get_singleton()->accessibility_create_sub_text_edit_elements(ae, RID(), get_line_height());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Selection.
|
// Selection.
|
||||||
if (carets.size() > 0) {
|
if (carets.size() > 0) {
|
||||||
|
|
|
||||||
|
|
@ -659,8 +659,6 @@ private:
|
||||||
bool draw_tabs = false;
|
bool draw_tabs = false;
|
||||||
bool draw_spaces = false;
|
bool draw_spaces = false;
|
||||||
|
|
||||||
RID accessibility_text_root_element_nl;
|
|
||||||
|
|
||||||
// FIXME: Helper method to draw unfilled rects, should be moved to RenderingServer.
|
// FIXME: Helper method to draw unfilled rects, should be moved to RenderingServer.
|
||||||
void _draw_rect_unfilled(RID p_canvas_item, const Rect2 &p_rect, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false) const;
|
void _draw_rect_unfilled(RID p_canvas_item, const Rect2 &p_rect, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false) const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,14 @@ Error DisplayServer::_file_dialog_with_options_show_bind_compat_98194(const Stri
|
||||||
return file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, MAIN_WINDOW_ID);
|
return file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, MAIN_WINDOW_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RID DisplayServer::_accessibility_create_sub_text_edit_elements_bind_compat_113459(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos) {
|
||||||
|
return accessibility_create_sub_text_edit_elements(p_parent_rid, p_shaped_text, p_min_height, p_insert_pos, false);
|
||||||
|
}
|
||||||
|
|
||||||
void DisplayServer::_bind_compatibility_methods() {
|
void DisplayServer::_bind_compatibility_methods() {
|
||||||
ClassDB::bind_compatibility_method(D_METHOD("file_dialog_show", "title", "current_directory", "filename", "show_hidden", "mode", "filters", "callback"), &DisplayServer::_file_dialog_show_bind_compat_98194);
|
ClassDB::bind_compatibility_method(D_METHOD("file_dialog_show", "title", "current_directory", "filename", "show_hidden", "mode", "filters", "callback"), &DisplayServer::_file_dialog_show_bind_compat_98194);
|
||||||
ClassDB::bind_compatibility_method(D_METHOD("file_dialog_with_options_show", "title", "current_directory", "root", "filename", "show_hidden", "mode", "filters", "options", "callback"), &DisplayServer::_file_dialog_with_options_show_bind_compat_98194);
|
ClassDB::bind_compatibility_method(D_METHOD("file_dialog_with_options_show", "title", "current_directory", "root", "filename", "show_hidden", "mode", "filters", "options", "callback"), &DisplayServer::_file_dialog_with_options_show_bind_compat_98194);
|
||||||
|
ClassDB::bind_compatibility_method(D_METHOD("accessibility_create_sub_text_edit_elements", "parent_rid", "shaped_text", "min_height", "insert_pos"), &DisplayServer::_accessibility_create_sub_text_edit_elements_bind_compat_113459, DEFVAL(-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -648,9 +648,9 @@ RID DisplayServer::accessibility_create_sub_element(const RID &p_parent_rid, Dis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RID DisplayServer::accessibility_create_sub_text_edit_elements(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos) {
|
RID DisplayServer::accessibility_create_sub_text_edit_elements(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos, bool p_is_last_line) {
|
||||||
if (accessibility_driver) {
|
if (accessibility_driver) {
|
||||||
return accessibility_driver->accessibility_create_sub_text_edit_elements(p_parent_rid, p_shaped_text, p_min_height, p_insert_pos);
|
return accessibility_driver->accessibility_create_sub_text_edit_elements(p_parent_rid, p_shaped_text, p_min_height, p_insert_pos, p_is_last_line);
|
||||||
} else {
|
} else {
|
||||||
return RID();
|
return RID();
|
||||||
}
|
}
|
||||||
|
|
@ -1485,7 +1485,7 @@ void DisplayServer::_bind_methods() {
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("accessibility_create_element", "window_id", "role"), &DisplayServer::accessibility_create_element);
|
ClassDB::bind_method(D_METHOD("accessibility_create_element", "window_id", "role"), &DisplayServer::accessibility_create_element);
|
||||||
ClassDB::bind_method(D_METHOD("accessibility_create_sub_element", "parent_rid", "role", "insert_pos"), &DisplayServer::accessibility_create_sub_element, DEFVAL(-1));
|
ClassDB::bind_method(D_METHOD("accessibility_create_sub_element", "parent_rid", "role", "insert_pos"), &DisplayServer::accessibility_create_sub_element, DEFVAL(-1));
|
||||||
ClassDB::bind_method(D_METHOD("accessibility_create_sub_text_edit_elements", "parent_rid", "shaped_text", "min_height", "insert_pos"), &DisplayServer::accessibility_create_sub_text_edit_elements, DEFVAL(-1));
|
ClassDB::bind_method(D_METHOD("accessibility_create_sub_text_edit_elements", "parent_rid", "shaped_text", "min_height", "insert_pos", "is_last_line"), &DisplayServer::accessibility_create_sub_text_edit_elements, DEFVAL(-1), DEFVAL(false));
|
||||||
ClassDB::bind_method(D_METHOD("accessibility_has_element", "id"), &DisplayServer::accessibility_has_element);
|
ClassDB::bind_method(D_METHOD("accessibility_has_element", "id"), &DisplayServer::accessibility_has_element);
|
||||||
ClassDB::bind_method(D_METHOD("accessibility_free_element", "id"), &DisplayServer::accessibility_free_element);
|
ClassDB::bind_method(D_METHOD("accessibility_free_element", "id"), &DisplayServer::accessibility_free_element);
|
||||||
ClassDB::bind_method(D_METHOD("accessibility_element_set_meta", "id", "meta"), &DisplayServer::accessibility_element_set_meta);
|
ClassDB::bind_method(D_METHOD("accessibility_element_set_meta", "id", "meta"), &DisplayServer::accessibility_element_set_meta);
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ class DisplayServer : public Object {
|
||||||
mutable HashMap<String, RID> menu_names;
|
mutable HashMap<String, RID> menu_names;
|
||||||
|
|
||||||
RID _get_rid_from_name(NativeMenu *p_nmenu, const String &p_menu_root) const;
|
RID _get_rid_from_name(NativeMenu *p_nmenu, const String &p_menu_root) const;
|
||||||
|
RID _accessibility_create_sub_text_edit_elements_bind_compat_113459(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos = -1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
LocalVector<ObjectID> additional_outputs;
|
LocalVector<ObjectID> additional_outputs;
|
||||||
|
|
@ -678,7 +679,7 @@ public:
|
||||||
|
|
||||||
virtual RID accessibility_create_element(WindowID p_window_id, DisplayServer::AccessibilityRole p_role);
|
virtual RID accessibility_create_element(WindowID p_window_id, DisplayServer::AccessibilityRole p_role);
|
||||||
virtual RID accessibility_create_sub_element(const RID &p_parent_rid, DisplayServer::AccessibilityRole p_role, int p_insert_pos = -1);
|
virtual RID accessibility_create_sub_element(const RID &p_parent_rid, DisplayServer::AccessibilityRole p_role, int p_insert_pos = -1);
|
||||||
virtual RID accessibility_create_sub_text_edit_elements(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos = -1);
|
virtual RID accessibility_create_sub_text_edit_elements(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos = -1, bool p_is_last_line = false);
|
||||||
virtual bool accessibility_has_element(const RID &p_id) const;
|
virtual bool accessibility_has_element(const RID &p_id) const;
|
||||||
virtual void accessibility_free_element(const RID &p_id);
|
virtual void accessibility_free_element(const RID &p_id);
|
||||||
|
|
||||||
|
|
@ -910,7 +911,7 @@ public:
|
||||||
|
|
||||||
virtual RID accessibility_create_element(DisplayServer::WindowID p_window_id, DisplayServer::AccessibilityRole p_role) = 0;
|
virtual RID accessibility_create_element(DisplayServer::WindowID p_window_id, DisplayServer::AccessibilityRole p_role) = 0;
|
||||||
virtual RID accessibility_create_sub_element(const RID &p_parent_rid, DisplayServer::AccessibilityRole p_role, int p_insert_pos = -1) = 0;
|
virtual RID accessibility_create_sub_element(const RID &p_parent_rid, DisplayServer::AccessibilityRole p_role, int p_insert_pos = -1) = 0;
|
||||||
virtual RID accessibility_create_sub_text_edit_elements(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos = -1) = 0;
|
virtual RID accessibility_create_sub_text_edit_elements(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos = -1, bool p_is_last_line = false) = 0;
|
||||||
virtual bool accessibility_has_element(const RID &p_id) const = 0;
|
virtual bool accessibility_has_element(const RID &p_id) const = 0;
|
||||||
virtual void accessibility_free_element(const RID &p_id) = 0;
|
virtual void accessibility_free_element(const RID &p_id) = 0;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue