1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

Base accessibility API.

This commit is contained in:
Pāvels Nadtočajevs
2025-03-21 16:42:23 +02:00
parent af2c713971
commit b106dfd4f9
124 changed files with 7631 additions and 181 deletions

View File

@@ -32,8 +32,12 @@
#include "container.h"
#include "core/config/project_settings.h"
#include "core/input/input_map.h"
#include "core/math/geometry_2d.h"
#include "core/os/os.h"
#include "core/string/translation_server.h"
#include "scene/gui/label.h"
#include "scene/gui/panel.h"
#include "scene/gui/scroll_container.h"
#include "scene/main/canvas_layer.h"
#include "scene/main/window.h"
@@ -246,6 +250,27 @@ PackedStringArray Control::get_configuration_warnings() const {
return warnings;
}
PackedStringArray Control::get_accessibility_configuration_warnings() const {
ERR_READ_THREAD_GUARD_V(PackedStringArray());
PackedStringArray warnings = Node::get_accessibility_configuration_warnings();
String ac_name = get_accessibility_name().strip_edges();
if (ac_name.is_empty()) {
warnings.push_back(RTR("Accessibility Name must not be empty, or contain only spaces."));
}
if (ac_name.contains(get_class_name())) {
warnings.push_back(RTR("Accessibility Name must not include Node class name."));
}
for (int i = 0; i < ac_name.length(); i++) {
if (is_control(ac_name[i])) {
warnings.push_back(RTR("Accessibility Name must not include control character."));
break;
}
}
return warnings;
}
bool Control::is_text_field() const {
ERR_READ_THREAD_GUARD_V(false);
return false;
@@ -1534,6 +1559,7 @@ void Control::set_scale(const Vector2 &p_scale) {
}
queue_redraw();
_notify_transform();
queue_accessibility_update();
}
Vector2 Control::get_scale() const {
@@ -1550,6 +1576,7 @@ void Control::set_rotation(real_t p_radians) {
data.rotation = p_radians;
queue_redraw();
_notify_transform();
queue_accessibility_update();
}
void Control::set_rotation_degrees(real_t p_degrees) {
@@ -1576,6 +1603,7 @@ void Control::set_pivot_offset(const Vector2 &p_pivot) {
data.pivot_offset = p_pivot;
queue_redraw();
_notify_transform();
queue_accessibility_update();
}
Vector2 Control::get_pivot_offset() const {
@@ -1749,6 +1777,8 @@ void Control::_size_changed() {
if (pos_changed && !size_changed) {
_update_canvas_item_transform();
}
queue_accessibility_update();
} else if (pos_changed) {
_notify_transform();
}
@@ -2018,7 +2048,40 @@ void Control::force_drag(const Variant &p_data, Control *p_control) {
ERR_FAIL_COND(!is_inside_tree());
ERR_FAIL_COND(p_data.get_type() == Variant::NIL);
get_viewport()->_gui_force_drag(this, p_data, p_control);
Viewport *vp = get_viewport();
vp->_gui_force_drag_start();
vp->_gui_force_drag(this, p_data, p_control);
}
void Control::accessibility_drag() {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_COND(!is_inside_tree());
Viewport *vp = get_viewport();
vp->_gui_force_drag_start();
Variant dnd_data = get_drag_data(Vector2(INFINITY, INFINITY));
if (dnd_data.get_type() != Variant::NIL) {
Window *w = Window::get_from_id(get_window()->get_window_id());
if (w) {
w->accessibility_announcement(vformat(RTR("%s grabbed. Select target and use %s to drop, use %s to cancel."), vp->gui_get_drag_description(), InputMap::get_singleton()->get_action_description("ui_accessibility_drag_and_drop"), InputMap::get_singleton()->get_action_description("ui_cancel")));
}
vp->_gui_force_drag(this, dnd_data, nullptr);
queue_accessibility_update();
} else {
vp->_gui_force_drag_cancel();
}
}
void Control::accessibility_drop() {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_COND(!is_inside_tree());
ERR_FAIL_COND(!get_viewport()->gui_is_dragging());
get_viewport()->gui_perform_drop_at(Vector2(INFINITY, INFINITY), this);
queue_accessibility_update();
}
void Control::set_drag_preview(Control *p_control) {
@@ -2037,7 +2100,7 @@ bool Control::is_drag_successful() const {
void Control::set_focus_mode(FocusMode p_focus_mode) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_INDEX((int)p_focus_mode, 3);
ERR_FAIL_INDEX((int)p_focus_mode, 4);
if (is_inside_tree() && p_focus_mode == FOCUS_NONE && data.focus_mode != FOCUS_NONE && has_focus()) {
release_focus();
@@ -2163,6 +2226,10 @@ Control *Control::find_next_valid_focus() const {
}
Control *from = const_cast<Control *>(this);
bool ac_enabled = get_tree() && get_tree()->is_accessibility_enabled();
// Index of the current `Control` subtree within the containing `Window`.
int window_next = -1;
while (true) {
// Find next child.
@@ -2194,6 +2261,25 @@ Control *Control::find_next_valid_focus() const {
}
next_child = next_child->data.parent_control;
}
Window *win = next_child == nullptr ? nullptr : next_child->data.parent_window;
if (win) { // Cycle through `Control` subtrees of the parent window
if (window_next == -1) {
window_next = next_child->get_index();
ERR_FAIL_INDEX_V(window_next, win->get_child_count(), nullptr);
}
for (int i = 1; i < win->get_child_count() + 1; i++) {
int next = Math::wrapi(window_next + i, 0, win->get_child_count());
Control *c = Object::cast_to<Control>(win->get_child(next));
if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) {
continue;
}
window_next = next;
next_child = c;
break;
}
}
}
}
@@ -2201,7 +2287,7 @@ Control *Control::find_next_valid_focus() const {
break;
}
if (next_child->get_focus_mode_with_recursive() == FOCUS_ALL) {
if ((next_child->get_focus_mode_with_recursive() == FOCUS_ALL) || (ac_enabled && next_child->get_focus_mode_with_recursive() == FOCUS_ACCESSIBILITY)) {
return next_child;
}
@@ -2244,6 +2330,10 @@ Control *Control::find_prev_valid_focus() const {
}
Control *from = const_cast<Control *>(this);
bool ac_enabled = get_tree() && get_tree()->is_accessibility_enabled();
// Index of the current `Control` subtree within the containing `Window`.
int window_prev = -1;
while (true) {
// Find prev child.
@@ -2253,7 +2343,28 @@ Control *Control::find_prev_valid_focus() const {
if (from->is_set_as_top_level() || !from->data.parent_control) {
// Find last of the children.
prev_child = _prev_control(from); // Wrap start here.
Window *win = from->data.parent_window;
if (win) { // Cycle through `Control` subtrees of the parent window
if (window_prev == -1) {
window_prev = from->get_index();
ERR_FAIL_INDEX_V(window_prev, win->get_child_count(), nullptr);
}
for (int i = 1; i < win->get_child_count() + 1; i++) {
int prev = Math::wrapi(window_prev - i, 0, win->get_child_count());
Control *c = Object::cast_to<Control>(win->get_child(prev));
if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) {
continue;
}
window_prev = prev;
prev_child = _prev_control(c);
break;
}
}
if (!prev_child) {
prev_child = _prev_control(from); // Wrap start here.
}
} else {
for (int i = (from->get_index() - 1); i >= 0; i--) {
@@ -2274,7 +2385,7 @@ Control *Control::find_prev_valid_focus() const {
}
}
if (prev_child->get_focus_mode_with_recursive() == FOCUS_ALL) {
if ((prev_child->get_focus_mode_with_recursive() == FOCUS_ALL) || (ac_enabled && prev_child->get_focus_mode_with_recursive() == FOCUS_ACCESSIBILITY)) {
return prev_child;
}
@@ -2509,11 +2620,13 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons
return; // Bye.
}
bool ac_enabled = get_tree() && get_tree()->is_accessibility_enabled();
Control *c = Object::cast_to<Control>(p_at);
Container *container = Object::cast_to<Container>(p_at);
bool in_container = container ? container->is_ancestor_of(this) : false;
if (c && c != this && c->get_focus_mode_with_recursive() == FOCUS_ALL && !in_container && p_clamp.intersects(c->get_global_rect())) {
if (c && c != this && ((c->get_focus_mode_with_recursive() == FOCUS_ALL) || (ac_enabled && c->get_focus_mode_with_recursive() == FOCUS_ACCESSIBILITY)) && !in_container && p_clamp.intersects(c->get_global_rect())) {
Rect2 r_c = c->get_global_rect();
r_c = r_c.intersection(p_clamp);
real_t begin_d = p_dir.dot(r_c.get_position());
@@ -3419,6 +3532,13 @@ String Control::get_tooltip(const Point2 &p_pos) const {
return data.tooltip;
}
String Control::accessibility_get_contextual_info() const {
ERR_READ_THREAD_GUARD_V(String());
String ret;
GDVIRTUAL_CALL(_accessibility_get_contextual_info, ret);
return ret;
}
Control *Control::make_custom_tooltip(const String &p_text) const {
ERR_READ_THREAD_GUARD_V(nullptr);
Object *ret = nullptr;
@@ -3428,6 +3548,35 @@ Control *Control::make_custom_tooltip(const String &p_text) const {
// Base object overrides.
void Control::_accessibility_action_foucs(const Variant &p_data) {
grab_focus();
}
void Control::_accessibility_action_blur(const Variant &p_data) {
release_focus();
}
void Control::_accessibility_action_show_tooltip(const Variant &p_data) {
Viewport *vp = get_viewport();
if (vp) {
vp->show_tooltip(this);
}
}
void Control::_accessibility_action_hide_tooltip(const Variant &p_data) {
Viewport *vp = get_viewport();
if (vp) {
vp->cancel_tooltip();
}
}
void Control::_accessibility_action_scroll_into_view(const Variant &p_data) {
ScrollContainer *sc = Object::cast_to<ScrollContainer>(get_parent());
if (sc) {
sc->ensure_control_visible(this);
}
}
void Control::_notification(int p_notification) {
ERR_MAIN_THREAD_GUARD;
switch (p_notification) {
@@ -3439,6 +3588,31 @@ void Control::_notification(int p_notification) {
saving = false;
} break;
#endif // TOOLS_ENABLED
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_transform(ae, get_transform());
DisplayServer::get_singleton()->accessibility_update_set_bounds(ae, Rect2(Vector2(), data.size_cache));
DisplayServer::get_singleton()->accessibility_update_set_tooltip(ae, data.tooltip);
DisplayServer::get_singleton()->accessibility_update_set_flag(ae, DisplayServer::AccessibilityFlags::FLAG_CLIPS_CHILDREN, data.clip_contents);
DisplayServer::get_singleton()->accessibility_update_set_flag(ae, DisplayServer::AccessibilityFlags::FLAG_TOUCH_PASSTHROUGH, data.mouse_filter == MOUSE_FILTER_PASS);
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_FOCUS, callable_mp(this, &Control::_accessibility_action_foucs));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_BLUR, callable_mp(this, &Control::_accessibility_action_blur));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SHOW_TOOLTIP, callable_mp(this, &Control::_accessibility_action_show_tooltip));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_HIDE_TOOLTIP, callable_mp(this, &Control::_accessibility_action_hide_tooltip));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_INTO_VIEW, callable_mp(this, &Control::_accessibility_action_scroll_into_view));
if (is_inside_tree() && get_viewport()->gui_is_dragging()) {
if (can_drop_data(Vector2(INFINITY, INFINITY), get_viewport()->gui_get_drag_data())) {
DisplayServer::get_singleton()->accessibility_update_set_extra_info(ae, vformat(RTR("%s can be dropped here. Use %s to drop, use %s to cancel."), get_viewport()->gui_get_drag_description(), InputMap::get_singleton()->get_action_description("ui_accessibility_drag_and_drop"), InputMap::get_singleton()->get_action_description("ui_cancel")));
} else {
DisplayServer::get_singleton()->accessibility_update_set_extra_info(ae, vformat(RTR("%s can not be dropped here. Use %s to cancel."), get_viewport()->gui_get_drag_description(), InputMap::get_singleton()->get_action_description("ui_cancel")));
}
}
} break;
case NOTIFICATION_POSTINITIALIZE: {
data.initialized = true;
@@ -3773,6 +3947,9 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("force_drag", "data", "preview"), &Control::force_drag);
ClassDB::bind_method(D_METHOD("accessibility_drag"), &Control::accessibility_drag);
ClassDB::bind_method(D_METHOD("accessibility_drop"), &Control::accessibility_drop);
ClassDB::bind_method(D_METHOD("set_mouse_filter", "filter"), &Control::set_mouse_filter);
ClassDB::bind_method(D_METHOD("get_mouse_filter"), &Control::get_mouse_filter);
ClassDB::bind_method(D_METHOD("get_mouse_filter_with_recursive"), &Control::get_mouse_filter_with_recursive);
@@ -3874,7 +4051,7 @@ void Control::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::NODE_PATH, "focus_neighbor_bottom", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbor", "get_focus_neighbor", SIDE_BOTTOM);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "focus_next", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_next", "get_focus_next");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "focus_previous", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_previous", "get_focus_previous");
ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All,Accessibility"), "set_focus_mode", "get_focus_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_recursive_behavior", PROPERTY_HINT_ENUM, "Inherited,Disabled,Enabled"), "set_focus_recursive_behavior", "get_focus_recursive_behavior");
ADD_GROUP("Mouse", "mouse_");
@@ -3893,6 +4070,7 @@ void Control::_bind_methods() {
BIND_ENUM_CONSTANT(FOCUS_NONE);
BIND_ENUM_CONSTANT(FOCUS_CLICK);
BIND_ENUM_CONSTANT(FOCUS_ALL);
BIND_ENUM_CONSTANT(FOCUS_ACCESSIBILITY);
BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_INHERITED);
BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_DISABLED);
@@ -4003,6 +4181,8 @@ void Control::_bind_methods() {
GDVIRTUAL_BIND(_drop_data, "at_position", "data");
GDVIRTUAL_BIND(_make_custom_tooltip, "for_text");
GDVIRTUAL_BIND(_accessibility_get_contextual_info);
GDVIRTUAL_BIND(_gui_input, "event");
}