You've already forked godot
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:
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user