feat: modules moved and engine moved to submodule

This commit is contained in:
Jan van der Weide 2025-04-12 18:40:44 +02:00
parent dfb5e645cd
commit c33d2130cc
5136 changed files with 225275 additions and 64485 deletions

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef ASPECT_RATIO_CONTAINER_H
#define ASPECT_RATIO_CONTAINER_H
#pragma once
#include "scene/gui/container.h"
@ -79,5 +78,3 @@ public:
VARIANT_ENUM_CAST(AspectRatioContainer::StretchMode);
VARIANT_ENUM_CAST(AspectRatioContainer::AlignmentMode);
#endif // ASPECT_RATIO_CONTAINER_H

View file

@ -41,6 +41,7 @@ void BaseButton::_unpress_group() {
if (toggle_mode && !button_group->is_allow_unpress()) {
status.pressed = true;
queue_accessibility_update();
}
for (BaseButton *E : button_group->buttons) {
@ -83,15 +84,66 @@ void BaseButton::gui_input(const Ref<InputEvent> &p_event) {
}
}
void BaseButton::_accessibility_action_click(const Variant &p_data) {
if (toggle_mode) {
status.pressed = !status.pressed;
if (status.pressed) {
_unpress_group();
if (button_group.is_valid()) {
button_group->emit_signal(SceneStringName(pressed), this);
}
}
_toggled(status.pressed);
_pressed();
} else {
_pressed();
}
queue_accessibility_update();
queue_redraw();
}
void BaseButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_CLICK, callable_mp(this, &BaseButton::_accessibility_action_click));
DisplayServer::get_singleton()->accessibility_update_set_flag(ae, DisplayServer::AccessibilityFlags::FLAG_DISABLED, status.disabled);
if (toggle_mode) {
DisplayServer::get_singleton()->accessibility_update_set_checked(ae, status.pressed);
}
if (button_group.is_valid()) {
for (const BaseButton *btn : button_group->buttons) {
if (btn->is_part_of_edited_scene()) {
continue;
}
DisplayServer::get_singleton()->accessibility_update_add_related_radio_group(ae, btn->get_accessibility_element());
}
}
if (shortcut_in_tooltip && shortcut.is_valid() && shortcut->has_valid_event()) {
String text = atr(shortcut->get_name()) + " (" + shortcut->get_as_text() + ")";
String tooltip = get_tooltip_text();
if (!tooltip.is_empty() && shortcut->get_name().nocasecmp_to(tooltip) != 0) {
text += "\n" + atr(tooltip);
}
DisplayServer::get_singleton()->accessibility_update_set_tooltip(ae, text);
}
} break;
case NOTIFICATION_MOUSE_ENTER: {
status.hovering = true;
queue_accessibility_update();
queue_redraw();
} break;
case NOTIFICATION_MOUSE_EXIT: {
status.hovering = false;
queue_accessibility_update();
queue_redraw();
} break;
@ -175,6 +227,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) {
}
_toggled(status.pressed);
_pressed();
queue_accessibility_update();
}
} else {
if ((p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_PRESS) || (!p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_RELEASE)) {
@ -214,6 +267,7 @@ void BaseButton::set_disabled(bool p_disabled) {
status.press_attempt = false;
status.pressing_inside = false;
}
queue_accessibility_update();
queue_redraw();
update_minimum_size();
}
@ -247,7 +301,7 @@ void BaseButton::set_pressed_no_signal(bool p_pressed) {
return;
}
status.pressed = p_pressed;
queue_accessibility_update();
queue_redraw();
}
@ -303,6 +357,7 @@ void BaseButton::set_toggle_mode(bool p_on) {
if (!p_on) {
set_pressed(false);
}
queue_accessibility_update();
toggle_mode = p_on;
update_configuration_warnings();
@ -313,7 +368,10 @@ bool BaseButton::is_toggle_mode() const {
}
void BaseButton::set_shortcut_in_tooltip(bool p_on) {
shortcut_in_tooltip = p_on;
if (shortcut_in_tooltip != p_on) {
shortcut_in_tooltip = p_on;
queue_accessibility_update();
}
}
bool BaseButton::is_shortcut_in_tooltip_enabled() const {
@ -353,8 +411,11 @@ bool BaseButton::is_shortcut_feedback() const {
}
void BaseButton::set_shortcut(const Ref<Shortcut> &p_shortcut) {
shortcut = p_shortcut;
set_process_shortcut_input(shortcut.is_valid());
if (shortcut != p_shortcut) {
shortcut = p_shortcut;
set_process_shortcut_input(shortcut.is_valid());
queue_accessibility_update();
}
}
Ref<Shortcut> BaseButton::get_shortcut() const {
@ -380,7 +441,7 @@ void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) {
_toggled(status.pressed);
_pressed();
queue_accessibility_update();
} else {
_pressed();
}
@ -440,6 +501,7 @@ void BaseButton::set_button_group(const Ref<ButtonGroup> &p_group) {
button_group->buttons.insert(this);
}
queue_accessibility_update();
queue_redraw(); //checkbox changes to radio if set a buttongroup
update_configuration_warnings();
}

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef BASE_BUTTON_H
#define BASE_BUTTON_H
#pragma once
#include "core/input/shortcut.h"
#include "scene/gui/control.h"
@ -87,6 +86,7 @@ protected:
void _notification(int p_what);
bool _was_pressed_by_mouse() const;
void _accessibility_action_click(const Variant &p_data);
GDVIRTUAL0(_pressed)
GDVIRTUAL1(_toggled, bool)
@ -165,5 +165,3 @@ public:
bool is_allow_unpress();
ButtonGroup();
};
#endif // BASE_BUTTON_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef BOX_CONTAINER_H
#define BOX_CONTAINER_H
#pragma once
#include "scene/gui/container.h"
@ -97,5 +96,3 @@ public:
};
VARIANT_ENUM_CAST(BoxContainer::AlignmentMode);
#endif // BOX_CONTAINER_H

View file

@ -30,6 +30,8 @@
#include "button.h"
#include "scene/gui/dialogs.h"
#include "scene/theme/theme_db.h"
Size2 Button::get_minimum_size() const {
@ -48,6 +50,10 @@ void Button::_set_internal_margin(Side p_side, float p_value) {
void Button::_queue_update_size_cache() {
}
String Button::_get_translated_text(const String &p_text) const {
return atr(p_text);
}
void Button::_update_theme_item_cache() {
Control::_update_theme_item_cache();
@ -181,15 +187,31 @@ Ref<StyleBox> Button::_get_current_stylebox() const {
void Button::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
if (!xl_text.is_empty() && get_accessibility_name().is_empty()) {
DisplayServer::get_singleton()->accessibility_update_set_name(ae, xl_text);
} else if (!xl_text.is_empty() && !get_accessibility_name().is_empty() && get_accessibility_name() != xl_text) {
DisplayServer::get_singleton()->accessibility_update_set_name(ae, get_accessibility_name() + ": " + xl_text);
}
AcceptDialog *dlg = Object::cast_to<AcceptDialog>(get_parent());
if (dlg && dlg->get_ok_button() == this) {
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_DEFAULT_BUTTON);
}
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
queue_redraw();
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
xl_text = atr(text);
xl_text = _get_translated_text(text);
_shape();
update_minimum_size();
queue_accessibility_update();
queue_redraw();
} break;
@ -566,7 +588,7 @@ void Button::_shape(Ref<TextParagraph> p_paragraph, String p_text) const {
case TextServer::AUTOWRAP_OFF:
break;
}
autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES;
autowrap_flags = autowrap_flags | autowrap_flags_trim;
p_paragraph->set_break_flags(autowrap_flags);
p_paragraph->set_line_spacing(theme_cache.line_spacing);
@ -598,14 +620,17 @@ TextServer::OverrunBehavior Button::get_text_overrun_behavior() const {
}
void Button::set_text(const String &p_text) {
if (text != p_text) {
text = p_text;
xl_text = atr(text);
_shape();
queue_redraw();
update_minimum_size();
const String translated_text = _get_translated_text(p_text);
if (text == p_text && xl_text == translated_text) {
return;
}
text = p_text;
xl_text = translated_text;
_shape();
queue_accessibility_update();
queue_redraw();
update_minimum_size();
}
String Button::get_text() const {
@ -625,11 +650,25 @@ TextServer::AutowrapMode Button::get_autowrap_mode() const {
return autowrap_mode;
}
void Button::set_autowrap_trim_flags(BitField<TextServer::LineBreakFlag> p_flags) {
if (autowrap_flags_trim != (p_flags & TextServer::BREAK_TRIM_MASK)) {
autowrap_flags_trim = p_flags & TextServer::BREAK_TRIM_MASK;
_shape();
queue_redraw();
update_minimum_size();
}
}
BitField<TextServer::LineBreakFlag> Button::get_autowrap_trim_flags() const {
return autowrap_flags_trim;
}
void Button::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
_shape();
queue_accessibility_update();
queue_redraw();
}
}
@ -642,6 +681,7 @@ void Button::set_language(const String &p_language) {
if (language != p_language) {
language = p_language;
_shape();
queue_accessibility_update();
queue_redraw();
}
}
@ -719,6 +759,7 @@ bool Button::get_clip_text() const {
void Button::set_text_alignment(HorizontalAlignment p_alignment) {
if (alignment != p_alignment) {
alignment = p_alignment;
queue_accessibility_update();
queue_redraw();
}
}
@ -766,6 +807,8 @@ void Button::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &Button::get_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &Button::set_autowrap_mode);
ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &Button::get_autowrap_mode);
ClassDB::bind_method(D_METHOD("set_autowrap_trim_flags", "autowrap_trim_flags"), &Button::set_autowrap_trim_flags);
ClassDB::bind_method(D_METHOD("get_autowrap_trim_flags"), &Button::get_autowrap_trim_flags);
ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &Button::set_text_direction);
ClassDB::bind_method(D_METHOD("get_text_direction"), &Button::get_text_direction);
ClassDB::bind_method(D_METHOD("set_language", "language"), &Button::set_language);
@ -791,8 +834,9 @@ void Button::_bind_methods() {
ADD_GROUP("Text Behavior", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_text_alignment", "get_text_alignment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis (6+ Characters),Word Ellipsis (6+ Characters),Ellipsis (Always),Word Ellipsis (Always)"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_trim_flags", PROPERTY_HINT_FLAGS, vformat("Trim Spaces After Break:%d,Trim Spaces Before Break:%d", TextServer::BREAK_TRIM_START_EDGE_SPACES, TextServer::BREAK_TRIM_END_EDGE_SPACES)), "set_autowrap_trim_flags", "get_autowrap_trim_flags");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "get_clip_text");
ADD_GROUP("Icon Behavior", "");
@ -846,7 +890,7 @@ void Button::_bind_methods() {
Button::Button(const String &p_text) {
text_buf.instantiate();
text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_TRIM_EDGE_SPACES);
text_buf->set_break_flags(TextServer::BREAK_MANDATORY | autowrap_flags_trim);
set_mouse_filter(MOUSE_FILTER_STOP);
set_text(p_text);

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef BUTTON_H
#define BUTTON_H
#pragma once
#include "scene/gui/base_button.h"
#include "scene/resources/text_paragraph.h"
@ -46,6 +45,7 @@ private:
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF;
BitField<TextServer::LineBreakFlag> autowrap_flags_trim = TextServer::BREAK_TRIM_END_EDGE_SPACES;
TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING;
Ref<Texture2D> icon;
@ -111,6 +111,7 @@ protected:
void _set_internal_margin(Side p_side, float p_value);
virtual void _queue_update_size_cache();
virtual String _get_translated_text(const String &p_text) const;
Size2 _fit_icon_size(const Size2 &p_size) const;
Ref<StyleBox> _get_current_stylebox() const;
@ -132,6 +133,9 @@ public:
void set_autowrap_mode(TextServer::AutowrapMode p_mode);
TextServer::AutowrapMode get_autowrap_mode() const;
void set_autowrap_trim_flags(BitField<TextServer::LineBreakFlag> p_flags);
BitField<TextServer::LineBreakFlag> get_autowrap_trim_flags() const;
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
@ -161,5 +165,3 @@ public:
Button(const String &p_text = String());
~Button();
};
#endif // BUTTON_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef CENTER_CONTAINER_H
#define CENTER_CONTAINER_H
#pragma once
#include "scene/gui/container.h"
@ -53,5 +52,3 @@ public:
CenterContainer();
};
#endif // CENTER_CONTAINER_H

View file

@ -81,6 +81,17 @@ Size2 CheckBox::get_minimum_size() const {
void CheckBox::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
if (is_radio()) {
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_RADIO_BUTTON);
} else {
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_CHECK_BOX);
}
} break;
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
@ -126,15 +137,15 @@ void CheckBox::_notification(int p_what) {
ofs.y = int((get_size().height - get_icon_size().height) / 2) + theme_cache.check_v_offset;
if (is_pressed()) {
on_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(on_tex->get_size())));
on_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(on_tex->get_size())), false, theme_cache.checkbox_checked_color);
} else {
off_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(off_tex->get_size())));
off_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(off_tex->get_size())), false, theme_cache.checkbox_unchecked_color);
}
} break;
}
}
bool CheckBox::is_radio() {
bool CheckBox::is_radio() const {
return get_button_group().is_valid();
}
@ -151,6 +162,9 @@ void CheckBox::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckBox, unchecked_disabled);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckBox, radio_checked_disabled);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckBox, radio_unchecked_disabled);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CheckBox, checkbox_checked_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CheckBox, checkbox_unchecked_color);
}
CheckBox::CheckBox(const String &p_text) :

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef CHECK_BOX_H
#define CHECK_BOX_H
#pragma once
#include "scene/gui/button.h"
@ -49,6 +48,9 @@ class CheckBox : public Button {
Ref<Texture2D> unchecked_disabled;
Ref<Texture2D> radio_checked_disabled;
Ref<Texture2D> radio_unchecked_disabled;
Color checkbox_checked_color;
Color checkbox_unchecked_color;
} theme_cache;
protected:
@ -58,11 +60,9 @@ protected:
void _notification(int p_what);
static void _bind_methods();
bool is_radio();
bool is_radio() const;
public:
CheckBox(const String &p_text = String());
~CheckBox();
};
#endif // CHECK_BOX_H

View file

@ -85,6 +85,13 @@ Size2 CheckButton::get_minimum_size() const {
void CheckButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_CHECK_BUTTON);
} break;
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
@ -133,9 +140,9 @@ void CheckButton::_notification(int p_what) {
ofs.y = (get_size().height - tex_size.height) / 2 + theme_cache.check_v_offset;
if (is_pressed()) {
on_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(on_tex->get_size())));
on_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(on_tex->get_size())), false, theme_cache.button_checked_color);
} else {
off_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(off_tex->get_size())));
off_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(off_tex->get_size())), false, theme_cache.button_unchecked_color);
}
} break;
}
@ -154,6 +161,9 @@ void CheckButton::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckButton, unchecked_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckButton, checked_disabled_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckButton, unchecked_disabled_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CheckButton, button_checked_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CheckButton, button_unchecked_color);
}
CheckButton::CheckButton(const String &p_text) :

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef CHECK_BUTTON_H
#define CHECK_BUTTON_H
#pragma once
#include "scene/gui/button.h"
@ -49,6 +48,9 @@ class CheckButton : public Button {
Ref<Texture2D> unchecked_mirrored;
Ref<Texture2D> checked_disabled_mirrored;
Ref<Texture2D> unchecked_disabled_mirrored;
Color button_checked_color;
Color button_unchecked_color;
} theme_cache;
protected:
@ -62,5 +64,3 @@ public:
CheckButton(const String &p_text = String());
~CheckButton();
};
#endif // CHECK_BUTTON_H

View file

@ -137,7 +137,7 @@ void CodeEdit::_notification(int p_what) {
Point2 round_ofs = hint_ofs + theme_cache.code_hint_style->get_offset() + Vector2(0, theme_cache.font->get_ascent(theme_cache.font_size) + font_height * i + yofs);
round_ofs = round_ofs.round();
draw_string(theme_cache.font, round_ofs, line.replace(String::chr(0xFFFF), ""), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size, theme_cache.code_hint_color);
draw_string(theme_cache.font, round_ofs, line.remove_char(0xFFFF), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size, theme_cache.code_hint_color);
if (end > 0) {
// Draw an underline for the currently edited function parameter.
const Vector2 b = hint_ofs + theme_cache.code_hint_style->get_offset() + Vector2(begin, font_height + font_height * i + yofs);
@ -963,7 +963,7 @@ void CodeEdit::indent_lines() {
for (Point2i line_range : line_ranges) {
for (int i = line_range.x; i <= line_range.y; i++) {
const String line_text = get_line(i);
if (line_text.size() == 0) {
if (line_text.is_empty()) {
// Ignore empty lines.
continue;
}
@ -1247,9 +1247,8 @@ void CodeEdit::add_auto_brace_completion_pair(const String &p_open_key, const St
void CodeEdit::set_auto_brace_completion_pairs(const Dictionary &p_auto_brace_completion_pairs) {
auto_brace_completion_pairs.clear();
Array keys = p_auto_brace_completion_pairs.keys();
for (int i = 0; i < keys.size(); i++) {
add_auto_brace_completion_pair(keys[i], p_auto_brace_completion_pairs[keys[i]]);
for (const KeyValue<Variant, Variant> &kv : p_auto_brace_completion_pairs) {
add_auto_brace_completion_pair(kv.key, kv.value);
}
}
@ -1599,7 +1598,7 @@ bool CodeEdit::can_fold_line(int p_line) const {
return false;
}
if (p_line + 1 >= get_line_count() || get_line(p_line).strip_edges().size() == 0) {
if (p_line + 1 >= get_line_count() || get_line(p_line).strip_edges().is_empty()) {
return false;
}
@ -1645,12 +1644,12 @@ bool CodeEdit::can_fold_line(int p_line) const {
if (delimiter_end_line == p_line) {
/* Check we are the start of the block. */
if (p_line - 1 >= 0) {
if ((in_string != -1 && is_in_string(p_line - 1) != -1) || (in_comment != -1 && is_in_comment(p_line - 1) != -1)) {
if ((in_string != -1 && is_in_string(p_line - 1) != -1) || (in_comment != -1 && is_in_comment(p_line - 1) != -1 && !is_line_code_region_start(p_line - 1) && !is_line_code_region_end(p_line - 1))) {
return false;
}
}
/* Check it continues for at least one line. */
return ((in_string != -1 && is_in_string(p_line + 1) != -1) || (in_comment != -1 && is_in_comment(p_line + 1) != -1));
return ((in_string != -1 && is_in_string(p_line + 1) != -1) || (in_comment != -1 && is_in_comment(p_line + 1) != -1 && !is_line_code_region_start(p_line + 1) && !is_line_code_region_end(p_line + 1)));
}
return ((in_string != -1 && is_in_string(delimiter_end_line) != -1) || (in_comment != -1 && is_in_comment(delimiter_end_line) != -1));
}
@ -1658,7 +1657,7 @@ bool CodeEdit::can_fold_line(int p_line) const {
/* Otherwise check indent levels. */
int start_indent = get_indent_level(p_line);
for (int i = p_line + 1; i < get_line_count(); i++) {
if (is_in_string(i) != -1 || is_in_comment(i) != -1 || get_line(i).strip_edges().size() == 0) {
if (is_in_string(i) != -1 || is_in_comment(i) != -1 || get_line(i).strip_edges().is_empty()) {
continue;
}
return (get_indent_level(i) > start_indent);
@ -1705,13 +1704,17 @@ void CodeEdit::fold_line(int p_line) {
if ((in_string != -1 && is_in_string(i) == -1) || (in_comment != -1 && is_in_comment(i) == -1)) {
break;
}
if (in_comment != -1 && (is_line_code_region_start(i) || is_line_code_region_end(i))) {
// A code region tag should split a comment block, ending it early.
break;
}
end_line = i;
}
}
} else {
int start_indent = get_indent_level(p_line);
for (int i = p_line + 1; i <= line_count; i++) {
if (get_line(i).strip_edges().size() == 0) {
if (get_line(i).strip_edges().is_empty()) {
continue;
}
if (get_indent_level(i) > start_indent) {
@ -1872,7 +1875,8 @@ bool CodeEdit::is_line_code_region_start(int p_line) const {
if (is_in_string(p_line) != -1) {
return false;
}
return get_line(p_line).strip_edges().begins_with(code_region_start_string);
Vector<String> split = get_line(p_line).strip_edges().split_spaces(1);
return split.size() > 0 && split[0] == code_region_start_string;
}
bool CodeEdit::is_line_code_region_end(int p_line) const {
@ -1883,7 +1887,8 @@ bool CodeEdit::is_line_code_region_end(int p_line) const {
if (is_in_string(p_line) != -1) {
return false;
}
return get_line(p_line).strip_edges().begins_with(code_region_end_string);
Vector<String> split = get_line(p_line).strip_edges().split_spaces(1);
return split.size() > 0 && split[0] == code_region_end_string;
}
/* Delimiters */
@ -1956,7 +1961,7 @@ String CodeEdit::get_delimiter_end_key(int p_delimiter_idx) const {
}
Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const {
if (delimiters.size() == 0) {
if (delimiters.is_empty()) {
return Point2(-1, -1);
}
ERR_FAIL_INDEX_V(p_line, get_line_count(), Point2(-1, -1));
@ -2007,7 +2012,7 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const {
}
Point2 CodeEdit::get_delimiter_end_position(int p_line, int p_column) const {
if (delimiters.size() == 0) {
if (delimiters.is_empty()) {
return Point2(-1, -1);
}
ERR_FAIL_INDEX_V(p_line, get_line_count(), Point2(-1, -1));
@ -2112,7 +2117,7 @@ String CodeEdit::get_text_for_code_completion() const {
completion_text += line.substr(0, get_caret_column());
/* Not unicode, represents the caret. */
completion_text += String::chr(0xFFFF);
completion_text += line.substr(get_caret_column(), line.size());
completion_text += line.substr(get_caret_column());
} else {
completion_text += line;
}
@ -2411,7 +2416,7 @@ String CodeEdit::get_text_with_cursor_char(int p_line, int p_column) const {
result += line_text.substr(0, p_column);
/* Not unicode, represents the cursor. */
result += String::chr(0xFFFF);
result += line_text.substr(p_column, line_text.size());
result += line_text.substr(p_column);
} else {
result += line_text;
}
@ -3058,7 +3063,7 @@ void CodeEdit::_update_code_region_tags() {
/* Delimiters */
void CodeEdit::_update_delimiter_cache(int p_from_line, int p_to_line) {
if (delimiters.size() == 0) {
if (delimiters.is_empty()) {
return;
}
@ -3208,7 +3213,7 @@ void CodeEdit::_update_delimiter_cache(int p_from_line, int p_to_line) {
}
int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const {
if (delimiters.size() == 0 || p_line >= delimiter_cache.size()) {
if (delimiters.is_empty() || p_line >= delimiter_cache.size()) {
return -1;
}
ERR_FAIL_INDEX_V(p_line, get_line_count(), 0);
@ -3348,8 +3353,8 @@ void CodeEdit::_set_delimiters(const TypedArray<String> &p_delimiters, Delimiter
continue;
}
const String start_key = key.get_slice(" ", 0);
const String end_key = key.get_slice_count(" ") > 1 ? key.get_slice(" ", 1) : String();
const String start_key = key.get_slicec(' ', 0);
const String end_key = key.get_slice_count(" ") > 1 ? key.get_slicec(' ', 1) : String();
_add_delimiter(start_key, end_key, end_key.is_empty(), p_type);
}
@ -3422,7 +3427,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
GDVIRTUAL_CALL(_filter_code_completion_candidates, completion_options_sources, completion_options);
/* No options to complete, cancel. */
if (completion_options.size() == 0) {
if (completion_options.is_empty()) {
cancel_code_completion();
return;
}
@ -3654,7 +3659,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
}
/* No options to complete, cancel. */
if (code_completion_options_new.size() == 0) {
if (code_completion_options_new.is_empty()) {
cancel_code_completion();
return;
}

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef CODE_EDIT_H
#define CODE_EDIT_H
#pragma once
#include "core/object/script_language.h"
#include "scene/gui/text_edit.h"
@ -527,5 +526,3 @@ VARIANT_ENUM_CAST(CodeEdit::CodeCompletionLocation);
struct CodeCompletionOptionCompare {
_FORCE_INLINE_ bool operator()(const ScriptLanguage::CodeCompletionOption &l, const ScriptLanguage::CodeCompletionOption &r) const;
};
#endif // CODE_EDIT_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef COLOR_MODE_H
#define COLOR_MODE_H
#pragma once
#include "scene/gui/color_picker.h"
@ -54,7 +53,6 @@ public:
virtual void slider_draw(int p_which) = 0;
virtual bool apply_theme() const { return false; }
virtual ColorPicker::PickerShapeType get_shape_override() const { return ColorPicker::SHAPE_MAX; }
ColorMode(ColorPicker *p_color_picker);
virtual ~ColorMode() {}
@ -145,12 +143,7 @@ public:
virtual void _value_changed() override;
virtual void slider_draw(int p_which) override;
virtual ColorPicker::PickerShapeType get_shape_override() const override { return ColorPicker::SHAPE_OKHSL_CIRCLE; }
ColorModeOKHSL(ColorPicker *p_color_picker) :
ColorMode(p_color_picker) {}
~ColorModeOKHSL() {}
};
#endif // COLOR_MODE_H

File diff suppressed because it is too large Load diff

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef COLOR_PICKER_H
#define COLOR_PICKER_H
#pragma once
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
@ -58,6 +57,7 @@ class ColorPresetButton : public BaseButton {
struct ThemeCache {
Ref<StyleBox> foreground_style;
Ref<StyleBox> focus_style;
Ref<Texture2D> background_icon;
Ref<Texture2D> overbright_indicator;
@ -79,6 +79,13 @@ class ColorPicker : public VBoxContainer {
GDCLASS(ColorPicker, VBoxContainer);
// These classes poke into theme items for their internal logic.
friend class ColorPickerShape;
friend class ColorPickerShapeRectangle;
friend class ColorPickerShapeWheel;
friend class ColorPickerShapeCircle;
friend class ColorPickerShapeVHSCircle;
friend class ColorPickerShapeOKHSLCircle;
friend class ColorModeRGB;
friend class ColorModeHSV;
friend class ColorModeRAW;
@ -126,12 +133,18 @@ private:
#endif
int current_slider_count = SLIDER_COUNT;
static const int MODE_BUTTON_COUNT = 3;
const float WHEEL_RADIUS = 0.42;
Vector2i circle_keyboard_joypad_picker_cursor_position;
const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 1.0 / 2;
const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 30;
float gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS;
static constexpr int MODE_BUTTON_COUNT = 3;
bool slider_theme_modified = true;
Vector<ColorMode *> modes;
LocalVector<ColorMode *> modes;
LocalVector<ColorPickerShape *> shapes;
Popup *picker_window = nullptr;
TextureRect *picker_texture_zoom = nullptr;
@ -148,14 +161,7 @@ private:
PopupMenu *options_menu = nullptr;
MarginContainer *internal_margin = nullptr;
Control *uv_edit = nullptr;
Control *w_edit = nullptr;
AspectRatioContainer *wheel_edit = nullptr;
MarginContainer *wheel_margin = nullptr;
Ref<ShaderMaterial> wheel_mat;
Ref<ShaderMaterial> circle_mat;
Control *wheel = nullptr;
Control *wheel_uv = nullptr;
HBoxContainer *shape_container = nullptr;
TextureRect *sample = nullptr;
VBoxContainer *swatches_vbc = nullptr;
GridContainer *preset_container = nullptr;
@ -179,6 +185,7 @@ private:
ColorPresetButton *selected_recent_preset = nullptr;
Ref<ButtonGroup> preset_group;
Ref<ButtonGroup> recent_preset_group;
#ifdef TOOLS_ENABLED
Callable quick_open_callback;
Callable palette_saved_callback;
@ -251,6 +258,9 @@ private:
bool center_slider_grabbers = true;
Ref<StyleBox> picker_focus_rectangle;
Ref<StyleBox> picker_focus_circle;
Color focused_not_editing_cursor_color;
Ref<Texture2D> menu_option;
Ref<Texture2D> screen_picker;
Ref<Texture2D> expanded_arrow;
@ -264,6 +274,7 @@ private:
Ref<Texture2D> bar_arrow;
Ref<Texture2D> sample_bg;
Ref<Texture2D> sample_revert;
Ref<StyleBox> sample_focus;
Ref<Texture2D> overbright_indicator;
Ref<Texture2D> picker_cursor;
Ref<Texture2D> picker_cursor_bg;
@ -278,7 +289,6 @@ private:
void _copy_color_to_hsv();
void _copy_hsv_to_color();
PickerShapeType _get_actual_shape() const;
void create_slider(GridContainer *gc, int idx);
void _reset_sliders_theme();
void _html_submitted(const String &p_html);
@ -291,11 +301,8 @@ private:
void _text_type_toggled();
void _sample_input(const Ref<InputEvent> &p_event);
void _sample_draw();
void _hsv_draw(int p_which, Control *c);
void _slider_draw(int p_which);
void _uv_input(const Ref<InputEvent> &p_event, Control *c);
void _w_input(const Ref<InputEvent> &p_event);
void _slider_or_spin_input(const Ref<InputEvent> &p_event);
void _line_edit_input(const Ref<InputEvent> &p_event);
void _preset_input(const Ref<InputEvent> &p_event, const Color &p_color);
@ -308,6 +315,12 @@ private:
void _pick_finished();
void _update_menu_items();
void _options_menu_cbk(int p_which);
void _block_input_on_popup_show();
void _enable_input_on_popup_hide();
// Native color picking.
void _pick_button_pressed_native();
void _native_cb(bool p_status, const Color &p_color);
// Legacy color picking.
void _pick_button_pressed_legacy();
@ -347,6 +360,7 @@ public:
static void finish_shaders();
void add_mode(ColorMode *p_mode);
void add_shape(ColorPickerShape *p_shape);
void set_edit_alpha(bool p_show);
bool is_editing_alpha() const;
@ -405,6 +419,7 @@ public:
bool is_hex_visible() const;
void set_focus_on_line_edit();
void set_focus_on_picker_shape();
ColorPicker();
~ColorPicker();
@ -460,5 +475,3 @@ public:
VARIANT_ENUM_CAST(ColorPicker::PickerShapeType);
VARIANT_ENUM_CAST(ColorPicker::ColorModeType);
#endif // COLOR_PICKER_H

View file

@ -0,0 +1,771 @@
/**************************************************************************/
/* color_picker_shape.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "color_picker_shape.h"
#include "scene/gui/margin_container.h"
void ColorPickerShape::_emit_color_changed() {
color_picker->emit_signal(SNAME("color_changed"), color_picker->color);
}
bool ColorPickerShape::can_handle(const Ref<InputEvent> &p_event, Vector2 &r_position, bool *r_is_click) {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
if (mb->get_button_index() != MouseButton::LEFT) {
return false;
}
if (r_is_click) {
*r_is_click = true;
}
if (mb->is_pressed()) {
is_dragging = true;
r_position = mb->get_position();
return true;
} else {
_emit_color_changed();
color_picker->add_recent_preset(color_picker->color);
is_dragging = false;
return false;
}
}
Ref<InputEventMouseMotion> mm = p_event;
if (is_dragging && mm.is_valid()) {
r_position = mm->get_position();
return true;
}
return false;
}
void ColorPickerShape::apply_color() {
color_picker->_copy_hsv_to_color();
color_picker->last_color = color_picker->color;
color_picker->set_pick_color(color_picker->color);
if (!color_picker->deferred_mode_enabled) {
_emit_color_changed();
}
}
void ColorPickerShape::cancel_event() {
is_dragging = false;
}
void ColorPickerShape::draw_focus_rect(Control *p_control, const Rect2 &p_rect) {
if (!p_control->has_focus()) {
return;
}
Rect2 focus_rect;
if (p_rect.has_area()) {
focus_rect = p_rect;
} else {
focus_rect = Rect2(Vector2(), p_control->get_size());
}
const RID ci = p_control->get_canvas_item();
if (!cursor_editing) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, focus_rect, color_picker->theme_cache.focused_not_editing_cursor_color);
}
color_picker->theme_cache.picker_focus_rectangle->draw(ci, focus_rect);
}
void ColorPickerShape::draw_focus_circle(Control *p_control) {
if (!p_control->has_focus()) {
return;
}
const Rect2 focus_rect(Vector2(), p_control->get_size());
const RID ci = p_control->get_canvas_item();
if (!cursor_editing) {
RenderingServer::get_singleton()->canvas_item_add_circle(ci, focus_rect.get_center(), focus_rect.get_size().y * 0.5, color_picker->theme_cache.focused_not_editing_cursor_color);
}
color_picker->theme_cache.picker_focus_circle->draw(ci, focus_rect);
}
void ColorPickerShape::draw_sv_square(Control *p_control, const Rect2 &p_square, bool p_draw_focus) {
const Vector2 end = p_square.get_end();
PackedVector2Array points = {
p_square.position,
Vector2(end.x, p_square.position.y),
end,
Vector2(p_square.position.x, end.y),
};
Color color1 = color_picker->color;
color1.set_hsv(color_picker->h, 1, 1);
Color color2 = color1;
color2.set_hsv(color_picker->h, 1, 0);
PackedColorArray colors = {
Color(1, 1, 1, 1),
Color(1, 1, 1, 1),
Color(0, 0, 0, 1),
Color(0, 0, 0, 1)
};
p_control->draw_polygon(points, colors);
colors = {
Color(color1, 0),
Color(color1, 1),
Color(color2, 1),
Color(color2, 0)
};
p_control->draw_polygon(points, colors);
Vector2 cursor_pos;
cursor_pos.x = CLAMP(p_square.position.x + p_square.size.x * color_picker->s, p_square.position.x, end.x);
cursor_pos.y = CLAMP(p_square.position.y + p_square.size.y * (1.0 - color_picker->v), p_square.position.y, end.y);
if (p_draw_focus) {
draw_focus_rect(p_control, p_square);
}
draw_cursor(p_control, cursor_pos);
}
void ColorPickerShape::draw_cursor(Control *p_control, const Vector2 &p_center, bool p_draw_bg) {
const Vector2 position = p_center - color_picker->theme_cache.picker_cursor->get_size() * 0.5;
if (p_draw_bg) {
p_control->draw_texture(color_picker->theme_cache.picker_cursor_bg, position, Color(color_picker->color, 1.0));
}
p_control->draw_texture(color_picker->theme_cache.picker_cursor, position);
}
void ColorPickerShape::draw_circle_cursor(Control *p_control, float p_hue) {
const Vector2 center = p_control->get_size() * 0.5;
const Vector2 cursor_pos(
center.x + (center.x * Math::cos(p_hue * Math::TAU) * color_picker->s),
center.y + (center.y * Math::sin(p_hue * Math::TAU) * color_picker->s));
draw_cursor(p_control, cursor_pos);
}
void ColorPickerShape::connect_shape_focus(Control *p_shape) {
p_shape->set_focus_mode(Control::FOCUS_ALL);
p_shape->connect(SceneStringName(focus_entered), callable_mp(this, &ColorPickerShape::shape_focus_entered));
p_shape->connect(SceneStringName(focus_exited), callable_mp(this, &ColorPickerShape::shape_focus_exited));
}
void ColorPickerShape::shape_focus_entered() {
Input *input = Input::get_singleton();
if (!(input->is_action_pressed("ui_up") || input->is_action_pressed("ui_down") || input->is_action_pressed("ui_left") || input->is_action_pressed("ui_right"))) {
cursor_editing = true;
}
}
void ColorPickerShape::shape_focus_exited() {
cursor_editing = false;
}
void ColorPickerShape::handle_cursor_editing(const Ref<InputEvent> &p_event, Control *p_control) {
if (p_event->is_action_pressed("ui_accept", false, true)) {
cursor_editing = !cursor_editing;
p_control->queue_redraw();
color_picker->accept_event();
}
if (cursor_editing && p_event->is_action_pressed("ui_cancel", false, true)) {
cursor_editing = false;
p_control->queue_redraw();
color_picker->accept_event();
}
if (!cursor_editing) {
return;
}
Input *input = Input::get_singleton();
bool is_joypad_event = Object::cast_to<InputEventJoypadMotion>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr());
if (p_event->is_action_pressed("ui_left", true) || p_event->is_action_pressed("ui_right", true) || p_event->is_action_pressed("ui_up", true) || p_event->is_action_pressed("ui_down", true)) {
if (is_joypad_event) {
if (color_picker->is_processing_internal()) {
color_picker->accept_event();
return;
}
color_picker->set_process_internal(true);
}
Vector2 color_change_vector = Vector2(
input->is_action_pressed("ui_right") - input->is_action_pressed("ui_left"),
input->is_action_pressed("ui_down") - input->is_action_pressed("ui_up"));
update_cursor(color_change_vector, p_event->is_echo());
color_picker->accept_event();
}
}
int ColorPickerShape::get_edge_h_change(const Vector2 &p_color_change_vector) {
int h_change = 0;
if (color_picker->h > 0 && color_picker->h < 0.5) {
h_change -= p_color_change_vector.x;
} else if (color_picker->h > 0.5 && color_picker->h < 1) {
h_change += p_color_change_vector.x;
}
if (color_picker->h > 0.25 && color_picker->h < 0.75) {
h_change -= p_color_change_vector.y;
} else if (color_picker->h < 0.25 || color_picker->h > 0.75) {
h_change += p_color_change_vector.y;
}
return h_change;
}
float ColorPickerShape::get_h_on_circle_edge(const Vector2 &p_color_change_vector) {
int h_change = get_edge_h_change(p_color_change_vector);
float target_h = Math::wrapf(color_picker->h + h_change / 360.0, 0, 1);
int current_quarter = color_picker->h * 4;
int future_quarter = target_h * 4;
if (p_color_change_vector.y > 0 && ((future_quarter == 0 && current_quarter == 1) || (future_quarter == 1 && current_quarter == 0))) {
target_h = 0.25f;
} else if (p_color_change_vector.y < 0 && ((future_quarter == 2 && current_quarter == 3) || (future_quarter == 3 && current_quarter == 2))) {
target_h = 0.75f;
} else if (p_color_change_vector.x < 0 && ((future_quarter == 1 && current_quarter == 2) || (future_quarter == 2 && current_quarter == 1))) {
target_h = 0.5f;
} else if (p_color_change_vector.x > 0 && ((future_quarter == 3 && current_quarter == 0) || (future_quarter == 0 && current_quarter == 3))) {
target_h = 0;
}
return target_h;
}
void ColorPickerShape::initialize_controls() {
_initialize_controls();
update_theme();
is_initialized = true;
}
void ColorPickerShape::update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) {
if (p_color_change_vector.is_zero_approx()) {
echo_multiplier = 1.0;
} else {
echo_multiplier = p_is_echo ? CLAMP(echo_multiplier * 1.1, 1, 25) : 1;
_update_cursor(p_color_change_vector * echo_multiplier, p_is_echo);
apply_color();
}
}
ColorPickerShape::ColorPickerShape(ColorPicker *p_color_picker) {
color_picker = p_color_picker;
}
void ColorPickerShapeRectangle::_sv_square_input(const Ref<InputEvent> &p_event) {
handle_cursor_editing(p_event, sv_square);
Vector2 event_position;
if (!can_handle(p_event, event_position)) {
return;
}
event_position = (event_position / sv_square->get_size()).clampf(0.0, 1.0);
color_picker->s = event_position.x;
color_picker->v = 1.0 - event_position.y;
apply_color();
}
void ColorPickerShapeRectangle::_hue_slider_input(const Ref<InputEvent> &p_event) {
handle_cursor_editing(p_event, hue_slider);
Vector2 event_position;
if (!can_handle(p_event, event_position)) {
return;
}
color_picker->h = CLAMP(event_position.y / hue_slider->get_size().y, 0.0, 1.0);
apply_color();
}
void ColorPickerShapeRectangle::_sv_square_draw() {
draw_sv_square(sv_square, Rect2(Vector2(), sv_square->get_size()));
}
void ColorPickerShapeRectangle::_hue_slider_draw() {
const Vector2 size = hue_slider->get_size();
hue_slider->draw_texture_rect(color_picker->theme_cache.color_hue, Rect2(0, 0, -size.y, size.x), false, Color(1, 1, 1), true);
draw_focus_rect(hue_slider);
int y = size.y * color_picker->h;
const Color color = Color::from_hsv(color_picker->h, 1, 1);
hue_slider->draw_line(Vector2(0, y), Vector2(size.x, y), color.inverted());
}
void ColorPickerShapeRectangle::_initialize_controls() {
sv_square = memnew(Control);
color_picker->shape_container->add_child(sv_square);
sv_square->connect(SceneStringName(gui_input), callable_mp(this, &ColorPickerShapeRectangle::_sv_square_input));
sv_square->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeRectangle::_sv_square_draw));
connect_shape_focus(sv_square);
hue_slider = memnew(Control);
color_picker->shape_container->add_child(hue_slider);
hue_slider->connect(SceneStringName(gui_input), callable_mp(this, &ColorPickerShapeRectangle::_hue_slider_input));
hue_slider->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeRectangle::_hue_slider_draw));
connect_shape_focus(hue_slider);
controls.append(sv_square);
controls.append(hue_slider);
}
void ColorPickerShapeRectangle::_update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) {
if (sv_square->has_focus()) {
color_picker->s = CLAMP(color_picker->s + p_color_change_vector.x / 100.0, 0, 1);
color_picker->v = CLAMP(color_picker->v - p_color_change_vector.y / 100.0, 0, 1);
} else if (hue_slider->has_focus()) {
color_picker->h = CLAMP(color_picker->h + p_color_change_vector.y * echo_multiplier / 360.0, 0, 1);
}
}
void ColorPickerShapeRectangle::update_theme() {
const ColorPicker::ThemeCache &theme_cache = color_picker->theme_cache;
sv_square->set_custom_minimum_size(Size2(theme_cache.sv_width, theme_cache.sv_height));
hue_slider->set_custom_minimum_size(Size2(theme_cache.h_width, 0));
}
void ColorPickerShapeRectangle::grab_focus() {
hue_slider->grab_focus();
}
float ColorPickerShapeWheel::_get_h_on_wheel(const Vector2 &p_color_change_vector) {
int h_change = get_edge_h_change(p_color_change_vector);
float target_h = Math::wrapf(color_picker->h + h_change / 360.0, 0, 1);
int current_quarter = color_picker->h * 4;
int future_quarter = target_h * 4;
if (p_color_change_vector.y > 0 && ((future_quarter == 0 && current_quarter == 1) || (future_quarter == 1 && current_quarter == 0))) {
rotate_next_echo_event = !rotate_next_echo_event;
} else if (p_color_change_vector.y < 0 && ((future_quarter == 2 && current_quarter == 3) || (future_quarter == 3 && current_quarter == 2))) {
rotate_next_echo_event = !rotate_next_echo_event;
} else if (p_color_change_vector.x < 0 && ((future_quarter == 1 && current_quarter == 2) || (future_quarter == 2 && current_quarter == 1))) {
rotate_next_echo_event = !rotate_next_echo_event;
} else if (p_color_change_vector.x > 0 && ((future_quarter == 3 && current_quarter == 0) || (future_quarter == 0 && current_quarter == 3))) {
rotate_next_echo_event = !rotate_next_echo_event;
}
return target_h;
}
void ColorPickerShapeWheel::_reset_wheel_focus() {
wheel_focused = true;
}
void ColorPickerShapeWheel::_wheel_input(const Ref<InputEvent> &p_event) {
handle_cursor_editing(p_event, wheel_uv);
if (!cursor_editing) {
// Wheel and inner square are the same control, so focus has to be moved manually.
if (!wheel_focused && p_event->is_action_pressed("ui_down", true)) {
wheel_focused = true;
wheel_uv->queue_redraw();
color_picker->accept_event();
return;
} else if (wheel_focused && p_event->is_action_pressed("ui_up", true)) {
wheel_focused = false;
wheel_uv->queue_redraw();
color_picker->accept_event();
return;
}
}
Vector2 event_position;
bool is_click = false;
if (!can_handle(p_event, event_position, &is_click)) {
if (is_click) {
// Released mouse button while dragging wheel.
spinning = false;
}
return;
}
const Vector2 uv_size = wheel_uv->get_size();
const Vector2 ring_radius = uv_size * Math::SQRT12 * WHEEL_RADIUS;
const Vector2 center = uv_size * 0.5;
if (is_click && !spinning) {
real_t dist = center.distance_to(event_position);
if (dist >= center.x * WHEEL_RADIUS * 2.0 && dist <= center.x) {
spinning = true;
if (!wheel_focused) {
cursor_editing = true;
wheel_focused = true;
}
} else if (dist > center.x) {
// Clicked outside the wheel.
cancel_event();
return;
}
};
if (spinning) {
real_t rad = center.angle_to_point(event_position);
color_picker->h = ((rad >= 0) ? rad : (Math::TAU + rad)) / Math::TAU;
apply_color();
return;
}
const Rect2 uv_rect(center - ring_radius, ring_radius * 2.0);
event_position -= uv_rect.position;
event_position /= uv_rect.size;
if (is_click && (event_position.x < 0 || event_position.x > 1 || event_position.y < 0 || event_position.y > 1)) {
// Clicked inside the wheel, but outside the square.
cancel_event();
return;
}
event_position = event_position.clampf(0.0, 1.0);
color_picker->s = event_position.x;
color_picker->v = 1.0 - event_position.y;
if (wheel_focused) {
cursor_editing = true;
wheel_focused = false;
}
apply_color();
}
void ColorPickerShapeWheel::_wheel_draw() {
wheel->draw_rect(Rect2(Point2(), wheel->get_size()), Color(1, 1, 1));
}
void ColorPickerShapeWheel::_wheel_uv_draw() {
const Vector2 uv_size = wheel_uv->get_size();
const Vector2 ring_radius = uv_size * Math::SQRT12 * WHEEL_RADIUS;
const Vector2 center = uv_size * 0.5;
const Rect2 uv_rect(center - ring_radius, ring_radius * 2.0);
draw_sv_square(wheel_uv, uv_rect, !wheel_focused);
if (wheel_focused) {
draw_focus_circle(wheel_uv);
}
float radius = WHEEL_RADIUS * 2.0;
radius += (1.0 - radius) * 0.5;
const Vector2 cursor_pos = center +
Vector2(center.x * Math::cos(color_picker->h * Math::TAU) * radius,
center.y * Math::sin(color_picker->h * Math::TAU) * radius);
draw_cursor(wheel_uv, cursor_pos, false);
}
void ColorPickerShapeWheel::_initialize_controls() {
wheel_margin = memnew(MarginContainer);
color_picker->shape_container->add_child(wheel_margin);
Ref<ShaderMaterial> material;
material.instantiate();
material->set_shader(ColorPicker::wheel_shader);
material->set_shader_parameter("wheel_radius", WHEEL_RADIUS);
wheel = memnew(Control);
wheel->set_material(material);
wheel_margin->add_child(wheel);
wheel->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeWheel::_wheel_draw));
wheel_uv = memnew(Control);
wheel_margin->add_child(wheel_uv);
wheel_uv->connect(SceneStringName(focus_entered), callable_mp(this, &ColorPickerShapeWheel::_reset_wheel_focus));
wheel_uv->connect(SceneStringName(gui_input), callable_mp(this, &ColorPickerShapeWheel::_wheel_input));
wheel_uv->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeWheel::_wheel_uv_draw));
connect_shape_focus(wheel_uv);
controls.append(wheel_margin);
controls.append(wheel);
controls.append(wheel_uv);
}
void ColorPickerShapeWheel::_update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) {
if (wheel_focused) {
if (p_is_echo && rotate_next_echo_event) {
color_picker->h = _get_h_on_wheel(-p_color_change_vector);
} else {
rotate_next_echo_event = false;
color_picker->h = _get_h_on_wheel(p_color_change_vector);
}
} else {
color_picker->s = CLAMP(color_picker->s + p_color_change_vector.x / 100.0, 0, 1);
color_picker->v = CLAMP(color_picker->v - p_color_change_vector.y / 100.0, 0, 1);
}
}
void ColorPickerShapeWheel::update_theme() {
const ColorPicker::ThemeCache &theme_cache = color_picker->theme_cache;
wheel_margin->set_custom_minimum_size(Size2(theme_cache.sv_width, theme_cache.sv_height));
wheel_margin->add_theme_constant_override(SNAME("margin_bottom"), 8 * theme_cache.base_scale);
}
void ColorPickerShapeWheel::grab_focus() {
wheel_uv->grab_focus();
}
void ColorPickerShapeCircle::update_circle_cursor(const Vector2 &p_color_change_vector, const Vector2 &p_center, const Vector2 &p_hue_offset) {
if (circle_keyboard_joypad_picker_cursor_position == Vector2i()) {
circle_keyboard_joypad_picker_cursor_position = p_center + p_hue_offset;
}
Vector2i potential_cursor_position = circle_keyboard_joypad_picker_cursor_position + p_color_change_vector;
real_t potential_new_cursor_distance = p_center.distance_to(potential_cursor_position);
real_t dist_pre = p_center.distance_to(circle_keyboard_joypad_picker_cursor_position);
if (color_picker->s < 1 || potential_new_cursor_distance < dist_pre) {
circle_keyboard_joypad_picker_cursor_position += p_color_change_vector;
real_t dist = p_center.distance_to(circle_keyboard_joypad_picker_cursor_position);
real_t rad = p_center.angle_to_point(circle_keyboard_joypad_picker_cursor_position);
color_picker->h = ((rad >= 0) ? rad : (Math::TAU + rad)) / Math::TAU;
color_picker->s = CLAMP(dist / p_center.x, 0, 1);
} else {
color_picker->h = get_h_on_circle_edge(p_color_change_vector);
circle_keyboard_joypad_picker_cursor_position = Vector2i();
}
}
void ColorPickerShapeCircle::_initialize_controls() {
circle_margin = memnew(MarginContainer);
color_picker->shape_container->add_child(circle_margin);
Ref<ShaderMaterial> material;
material.instantiate();
material->set_shader(_get_shader());
circle = memnew(Control);
circle->set_material(material);
circle_margin->add_child(circle);
circle->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeCircle::_circle_draw));
circle_overlay = memnew(Control);
circle_overlay->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
circle->add_child(circle_overlay);
circle_overlay->connect(SceneStringName(gui_input), callable_mp(this, &ColorPickerShapeCircle::_circle_input));
circle_overlay->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeCircle::_circle_overlay_draw));
connect_shape_focus(circle_overlay);
value_slider = memnew(Control);
color_picker->shape_container->add_child(value_slider);
value_slider->connect(SceneStringName(gui_input), callable_mp(this, &ColorPickerShapeCircle::_value_slider_input));
value_slider->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeCircle::_value_slider_draw));
connect_shape_focus(value_slider);
controls.append(circle_margin);
controls.append(circle);
controls.append(circle_overlay);
controls.append(value_slider);
}
void ColorPickerShapeCircle::update_theme() {
const ColorPicker::ThemeCache &theme_cache = color_picker->theme_cache;
circle_margin->set_custom_minimum_size(Size2(theme_cache.sv_width, theme_cache.sv_height));
circle_margin->add_theme_constant_override(SNAME("margin_bottom"), 8 * theme_cache.base_scale);
value_slider->set_custom_minimum_size(Size2(theme_cache.h_width, 0));
}
void ColorPickerShapeCircle::grab_focus() {
circle_overlay->grab_focus();
}
void ColorPickerShapeVHSCircle::_circle_input(const Ref<InputEvent> &p_event) {
handle_cursor_editing(p_event, circle_overlay);
Vector2 event_position;
bool is_click = false;
if (!can_handle(p_event, event_position, &is_click)) {
return;
}
Vector2 center = circle->get_size() * 0.5;
real_t dist = center.distance_to(event_position);
if (is_click && dist > center.x) {
// Clicked outside the circle.
cancel_event();
return;
}
real_t rad = center.angle_to_point(event_position);
color_picker->h = ((rad >= 0) ? rad : (Math::TAU + rad)) / Math::TAU;
color_picker->s = CLAMP(dist / center.x, 0, 1);
color_picker->ok_hsl_h = color_picker->h;
color_picker->ok_hsl_s = color_picker->s;
circle_keyboard_joypad_picker_cursor_position = Vector2i();
apply_color();
}
void ColorPickerShapeVHSCircle::_value_slider_input(const Ref<InputEvent> &p_event) {
handle_cursor_editing(p_event, value_slider);
Vector2 event_position;
if (!can_handle(p_event, event_position)) {
return;
}
color_picker->v = 1.0 - CLAMP(event_position.y / value_slider->get_size().y, 0.0, 1.0);
apply_color();
}
void ColorPickerShapeVHSCircle::_circle_draw() {
Ref<ShaderMaterial> material = circle->get_material();
material->set_shader_parameter(SNAME("v"), color_picker->v);
circle->draw_rect(Rect2(Point2(), circle->get_size()), Color(1, 1, 1));
}
void ColorPickerShapeVHSCircle::_circle_overlay_draw() {
draw_focus_circle(circle_overlay);
draw_circle_cursor(circle_overlay, color_picker->h);
}
void ColorPickerShapeVHSCircle::_value_slider_draw() {
const Vector2 size = value_slider->get_size();
PackedVector2Array points{
Vector2(),
Vector2(size.x, 0),
size,
Vector2(0, size.y)
};
Color color = Color::from_hsv(color_picker->h, color_picker->s, 1);
PackedColorArray colors = {
color,
color,
Color(),
Color()
};
value_slider->draw_polygon(points, colors);
draw_focus_rect(value_slider);
int y = size.y * (1 - CLAMP(color_picker->v, 0, 1));
color.set_hsv(color_picker->h, 1, color_picker->v);
value_slider->draw_line(Vector2(0, y), Vector2(size.x, y), color.inverted());
}
void ColorPickerShapeVHSCircle::_update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) {
if (circle_overlay->has_focus()) {
const Vector2 center = circle_overlay->get_size() / 2.0;
const Vector2 hue_offset = center * Vector2(Math::cos(color_picker->h * Math::TAU), Math::sin(color_picker->h * Math::TAU)) * color_picker->s;
update_circle_cursor(p_color_change_vector, center, hue_offset);
} else if (value_slider->has_focus()) {
color_picker->v = CLAMP(color_picker->v - p_color_change_vector.y * echo_multiplier / 100.0, 0, 1);
}
}
void ColorPickerShapeOKHSLCircle::_circle_input(const Ref<InputEvent> &p_event) {
handle_cursor_editing(p_event, circle_overlay);
Vector2 event_position;
bool is_click = false;
if (!can_handle(p_event, event_position, &is_click)) {
return;
}
const Vector2 center = circle->get_size() * 0.5;
real_t dist = center.distance_to(event_position);
if (is_click && dist > center.x) {
// Clicked outside the circle.
cancel_event();
return;
}
real_t rad = center.angle_to_point(event_position);
color_picker->h = ((rad >= 0) ? rad : (Math::TAU + rad)) / Math::TAU;
color_picker->s = CLAMP(dist / center.x, 0, 1);
color_picker->ok_hsl_h = color_picker->h;
color_picker->ok_hsl_s = color_picker->s;
circle_keyboard_joypad_picker_cursor_position = Vector2i();
apply_color();
}
void ColorPickerShapeOKHSLCircle::_value_slider_input(const Ref<InputEvent> &p_event) {
handle_cursor_editing(p_event, value_slider);
Vector2 event_position;
if (!can_handle(p_event, event_position)) {
return;
}
color_picker->ok_hsl_l = 1.0 - CLAMP(event_position.y / value_slider->get_size().y, 0.0, 1.0);
apply_color();
}
void ColorPickerShapeOKHSLCircle::_circle_draw() {
Ref<ShaderMaterial> material = circle->get_material();
material->set_shader_parameter(SNAME("ok_hsl_l"), color_picker->ok_hsl_l);
circle->draw_rect(Rect2(Point2(), circle->get_size()), Color(1, 1, 1));
}
void ColorPickerShapeOKHSLCircle::_circle_overlay_draw() {
draw_focus_circle(circle_overlay);
draw_circle_cursor(circle_overlay, color_picker->ok_hsl_h);
}
void ColorPickerShapeOKHSLCircle::_value_slider_draw() {
const float ok_hsl_h = color_picker->ok_hsl_h;
const float ok_hsl_s = color_picker->ok_hsl_s;
const float ok_hsl_l = color_picker->ok_hsl_l;
const Vector2 size = value_slider->get_size();
PackedVector2Array points{
Vector2(size.x, 0),
Vector2(size.x, size.y * 0.5),
size,
Vector2(0, size.y),
Vector2(0, size.y * 0.5),
Vector2()
};
Color color1 = Color::from_ok_hsl(ok_hsl_h, ok_hsl_s, 1);
Color color2 = Color::from_ok_hsl(ok_hsl_h, ok_hsl_s, 0.5);
Color color3 = Color::from_ok_hsl(ok_hsl_h, ok_hsl_s, 0);
PackedColorArray colors = {
color1,
color2,
color3,
color3,
color2,
color1,
};
value_slider->draw_polygon(points, colors);
draw_focus_rect(value_slider);
int y = size.y * (1 - CLAMP(ok_hsl_l, 0, 1));
value_slider->draw_line(Vector2(0, y), Vector2(size.x, y), Color::from_hsv(ok_hsl_h, 1, ok_hsl_l).inverted());
}
void ColorPickerShapeOKHSLCircle::_update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) {
if (circle_overlay->has_focus()) {
const Vector2 center = circle_overlay->get_size() / 2.0;
const Vector2 hue_offset = center * Vector2(Math::cos(color_picker->ok_hsl_h * Math::TAU), Math::sin(color_picker->ok_hsl_h * Math::TAU)) * color_picker->ok_hsl_s;
update_circle_cursor(p_color_change_vector, center, hue_offset);
color_picker->ok_hsl_h = color_picker->h;
color_picker->ok_hsl_s = color_picker->s;
} else if (value_slider->has_focus()) {
color_picker->ok_hsl_l = CLAMP(color_picker->ok_hsl_l - p_color_change_vector.y * echo_multiplier / 100.0, 0, 1);
}
}

View file

@ -0,0 +1,215 @@
/**************************************************************************/
/* color_picker_shape.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/color_picker.h"
class ColorPickerShape : public Object {
GDCLASS(ColorPickerShape, Object);
void _emit_color_changed();
protected:
ColorPicker *color_picker = nullptr;
bool is_dragging = false;
virtual void _initialize_controls() = 0;
virtual void _update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) = 0;
bool can_handle(const Ref<InputEvent> &p_event, Vector2 &r_position, bool *r_is_click = nullptr);
void apply_color();
void cancel_event();
void draw_focus_rect(Control *p_control, const Rect2 &p_rect = Rect2());
void draw_focus_circle(Control *p_control);
void draw_sv_square(Control *p_control, const Rect2 &p_square, bool p_draw_focus = true);
void draw_cursor(Control *p_control, const Vector2 &p_center, bool p_draw_bg = true);
void draw_circle_cursor(Control *p_control, float p_hue);
void connect_shape_focus(Control *p_shape);
void shape_focus_entered();
void shape_focus_exited();
void handle_cursor_editing(const Ref<InputEvent> &p_event, Control *p_control);
int get_edge_h_change(const Vector2 &p_color_change_vector);
float get_h_on_circle_edge(const Vector2 &p_color_change_vector);
public:
Vector<Control *> controls;
bool is_initialized = false;
bool cursor_editing = false;
float echo_multiplier = 1;
virtual String get_name() const = 0;
virtual Ref<Texture2D> get_icon() const = 0;
virtual bool is_ok_hsl() const { return false; }
void initialize_controls();
virtual void update_theme() = 0;
void update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo);
virtual void grab_focus() = 0;
ColorPickerShape(ColorPicker *p_color_picker);
};
class ColorPickerShapeRectangle : public ColorPickerShape {
Control *sv_square = nullptr;
Control *hue_slider = nullptr;
void _sv_square_input(const Ref<InputEvent> &p_event);
void _hue_slider_input(const Ref<InputEvent> &p_event);
void _sv_square_draw();
void _hue_slider_draw();
protected:
virtual void _initialize_controls() override;
virtual void _update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) override;
public:
virtual String get_name() const override { return ETR("HSV Rectangle"); }
virtual Ref<Texture2D> get_icon() const override { return color_picker->theme_cache.shape_rect; }
virtual void update_theme() override;
virtual void grab_focus() override;
ColorPickerShapeRectangle(ColorPicker *p_color_picker) :
ColorPickerShape(p_color_picker) {}
};
class ColorPickerShapeWheel : public ColorPickerShape {
GDCLASS(ColorPickerShapeWheel, ColorPickerShape);
inline static constexpr float WHEEL_RADIUS = 0.42;
MarginContainer *wheel_margin = nullptr;
Control *wheel = nullptr;
Control *wheel_uv = nullptr;
bool wheel_focused = true;
bool spinning = false;
bool rotate_next_echo_event = false;
float _get_h_on_wheel(const Vector2 &p_color_change_vector);
void _reset_wheel_focus();
void _wheel_input(const Ref<InputEvent> &p_event);
void _wheel_draw();
void _wheel_uv_draw();
protected:
virtual void _initialize_controls() override;
virtual void _update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) override;
public:
virtual String get_name() const override { return ETR("HSV Wheel"); }
virtual Ref<Texture2D> get_icon() const override { return color_picker->theme_cache.shape_rect_wheel; }
virtual void update_theme() override;
virtual void grab_focus() override;
ColorPickerShapeWheel(ColorPicker *p_color_picker) :
ColorPickerShape(p_color_picker) {}
};
class ColorPickerShapeCircle : public ColorPickerShape {
GDCLASS(ColorPickerShapeCircle, ColorPickerShape);
protected:
MarginContainer *circle_margin = nullptr;
Control *circle = nullptr;
Control *circle_overlay = nullptr;
Control *value_slider = nullptr;
Vector2i circle_keyboard_joypad_picker_cursor_position;
virtual void _circle_input(const Ref<InputEvent> &p_event) = 0;
virtual void _value_slider_input(const Ref<InputEvent> &p_event) = 0;
virtual void _circle_draw() = 0;
virtual void _circle_overlay_draw() = 0;
virtual void _value_slider_draw() = 0;
void update_circle_cursor(const Vector2 &p_color_change_vector, const Vector2 &p_center, const Vector2 &p_hue_offset);
public:
virtual Ref<Shader> _get_shader() const = 0;
virtual void _initialize_controls() override;
virtual Ref<Texture2D> get_icon() const override { return color_picker->theme_cache.shape_circle; }
virtual void update_theme() override;
virtual void grab_focus() override;
ColorPickerShapeCircle(ColorPicker *p_color_picker) :
ColorPickerShape(p_color_picker) {}
};
class ColorPickerShapeVHSCircle : public ColorPickerShapeCircle {
GDCLASS(ColorPickerShapeVHSCircle, ColorPickerShapeCircle);
protected:
virtual void _update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) override;
virtual Ref<Shader> _get_shader() const override { return ColorPicker::circle_shader; }
virtual void _circle_input(const Ref<InputEvent> &p_event) override;
virtual void _value_slider_input(const Ref<InputEvent> &p_event) override;
virtual void _circle_draw() override;
virtual void _circle_overlay_draw() override;
virtual void _value_slider_draw() override;
public:
virtual String get_name() const override { return ETR("VHS Circle"); }
ColorPickerShapeVHSCircle(ColorPicker *p_color_picker) :
ColorPickerShapeCircle(p_color_picker) {}
};
class ColorPickerShapeOKHSLCircle : public ColorPickerShapeCircle {
GDCLASS(ColorPickerShapeOKHSLCircle, ColorPickerShapeCircle);
protected:
virtual void _update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) override;
virtual Ref<Shader> _get_shader() const override { return ColorPicker::circle_ok_color_shader; }
virtual void _circle_input(const Ref<InputEvent> &p_event) override;
virtual void _value_slider_input(const Ref<InputEvent> &p_event) override;
virtual void _circle_draw() override;
virtual void _circle_overlay_draw() override;
virtual void _value_slider_draw() override;
public:
virtual String get_name() const override { return ETR("OKHSL Circle"); }
virtual bool is_ok_hsl() const override { return true; }
ColorPickerShapeOKHSLCircle(ColorPicker *p_color_picker) :
ColorPickerShapeCircle(p_color_picker) {}
};

View file

@ -35,6 +35,7 @@ void ColorRect::set_color(const Color &p_color) {
return;
}
color = p_color;
queue_accessibility_update();
queue_redraw();
}
@ -44,6 +45,13 @@ Color ColorRect::get_color() const {
void ColorRect::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_color_value(ae, color);
} break;
case NOTIFICATION_DRAW: {
draw_rect(Rect2(Point2(), get_size()), color);
} break;

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef COLOR_RECT_H
#define COLOR_RECT_H
#pragma once
#include "scene/gui/control.h"
@ -46,5 +45,3 @@ public:
void set_color(const Color &p_color);
Color get_color() const;
};
#endif // COLOR_RECT_H

View file

@ -184,6 +184,13 @@ Vector<int> Container::get_allowed_size_flags_vertical() const {
void Container::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_CONTAINER);
} break;
case NOTIFICATION_RESIZED:
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_ENTER_TREE: {

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef CONTAINER_H
#define CONTAINER_H
#pragma once
#include "scene/gui/control.h"
@ -75,5 +74,3 @@ public:
Container();
};
#endif // CONTAINER_H

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"
@ -56,18 +60,10 @@ Dictionary Control::_edit_get_state() const {
s["scale"] = get_scale();
s["pivot"] = get_pivot_offset();
Array anchors;
anchors.push_back(get_anchor(SIDE_LEFT));
anchors.push_back(get_anchor(SIDE_TOP));
anchors.push_back(get_anchor(SIDE_RIGHT));
anchors.push_back(get_anchor(SIDE_BOTTOM));
Array anchors = { get_anchor(SIDE_LEFT), get_anchor(SIDE_TOP), get_anchor(SIDE_RIGHT), get_anchor(SIDE_BOTTOM) };
s["anchors"] = anchors;
Array offsets;
offsets.push_back(get_offset(SIDE_LEFT));
offsets.push_back(get_offset(SIDE_TOP));
offsets.push_back(get_offset(SIDE_RIGHT));
offsets.push_back(get_offset(SIDE_BOTTOM));
Array offsets = { get_offset(SIDE_LEFT), get_offset(SIDE_TOP), get_offset(SIDE_RIGHT), get_offset(SIDE_BOTTOM) };
s["offsets"] = offsets;
s["layout_mode"] = _get_layout_mode();
@ -254,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;
@ -481,9 +498,9 @@ void Control::_validate_property(PropertyInfo &p_property) const {
}
// Validate which positioning properties should be displayed depending on the parent and the layout mode.
Node *parent_node = get_parent_control();
if (!parent_node) {
// If there is no parent, display both anchor and container options.
Control *parent_control = get_parent_control();
if (!parent_control) {
// If there is no parent control, display both anchor and container options.
// Set the layout mode to be disabled with the proper value.
if (p_property.name == "layout_mode") {
@ -496,7 +513,7 @@ void Control::_validate_property(PropertyInfo &p_property) const {
if (!use_custom_anchors && (p_property.name.begins_with("anchor_") || p_property.name.begins_with("offset_") || p_property.name.begins_with("grow_"))) {
p_property.usage ^= PROPERTY_USAGE_EDITOR;
}
} else if (Object::cast_to<Container>(parent_node)) {
} else if (Object::cast_to<Container>(parent_control)) {
// If the parent is a container, display only container-related properties.
if (p_property.name.begins_with("anchor_") || p_property.name.begins_with("offset_") || p_property.name.begins_with("grow_") || p_property.name == "anchors_preset") {
p_property.usage ^= PROPERTY_USAGE_DEFAULT;
@ -508,7 +525,7 @@ void Control::_validate_property(PropertyInfo &p_property) const {
p_property.usage |= PROPERTY_USAGE_READ_ONLY;
} else if (p_property.name == "size_flags_horizontal" || p_property.name == "size_flags_vertical") {
// Filter allowed size flags based on the parent container configuration.
Container *parent_container = Object::cast_to<Container>(parent_node);
Container *parent_container = Object::cast_to<Container>(parent_control);
Vector<int> size_flags;
if (p_property.name == "size_flags_horizontal") {
size_flags = parent_container->get_allowed_size_flags_horizontal();
@ -548,7 +565,7 @@ void Control::_validate_property(PropertyInfo &p_property) const {
}
}
} else {
// If the parent is NOT a container or not a control at all, display only anchoring-related properties.
// If the parent is a non-container control, display only anchoring-related properties.
if (p_property.name.begins_with("size_flags_")) {
p_property.usage ^= PROPERTY_USAGE_EDITOR;
@ -570,7 +587,7 @@ void Control::_validate_property(PropertyInfo &p_property) const {
}
// Disable the property if it's managed by the parent container.
if (!Object::cast_to<Container>(parent_node)) {
if (!Object::cast_to<Container>(parent_control)) {
return;
}
bool property_is_managed_by_container = false;
@ -607,11 +624,6 @@ bool Control::_property_get_revert(const StringName &p_name, Variant &r_property
// Global relations.
bool Control::is_top_level_control() const {
ERR_READ_THREAD_GUARD_V(false);
return is_inside_tree() && (!data.parent_canvas_item && !data.RI && is_set_as_top_level());
}
Control *Control::get_parent_control() const {
ERR_READ_THREAD_GUARD_V(nullptr);
return data.parent_control;
@ -631,10 +643,6 @@ Control *Control::get_root_parent_control() const {
const Control *c = Object::cast_to<Control>(ci);
if (c) {
root = c;
if (c->data.RI || c->is_top_level_control()) {
break;
}
}
ci = ci->get_parent_item();
@ -875,6 +883,13 @@ void Control::_compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t (
r_offsets[3] = p_rect.position.y + p_rect.size.y - (p_anchors[3] * parent_rect_size.y);
}
void Control::_compute_edge_positions(Rect2 p_rect, real_t (&r_edge_positions)[4]) {
for (int i = 0; i < 4; i++) {
real_t area = p_rect.size[i & 1];
r_edge_positions[i] = data.offset[i] + (data.anchor[i] * area);
}
}
/// Presets and layout modes.
void Control::_set_layout_mode(LayoutMode p_mode) {
@ -905,11 +920,11 @@ void Control::_update_layout_mode() {
}
Control::LayoutMode Control::_get_layout_mode() const {
Node *parent_node = get_parent_control();
Control *parent_control = get_parent_control();
// In these modes the property is read-only.
if (!parent_node) {
if (!parent_control) {
return LayoutMode::LAYOUT_MODE_UNCONTROLLED;
} else if (Object::cast_to<Container>(parent_node)) {
} else if (Object::cast_to<Container>(parent_control)) {
return LayoutMode::LAYOUT_MODE_CONTAINER;
}
@ -918,20 +933,25 @@ Control::LayoutMode Control::_get_layout_mode() const {
return LayoutMode::LAYOUT_MODE_ANCHORS;
}
// Otherwise fallback on what's stored.
return data.stored_layout_mode;
// Only position/anchors modes are valid for non-container control parent.
if (data.stored_layout_mode == LayoutMode::LAYOUT_MODE_POSITION || data.stored_layout_mode == LayoutMode::LAYOUT_MODE_ANCHORS) {
return data.stored_layout_mode;
}
// Otherwise fallback to position mode.
return LayoutMode::LAYOUT_MODE_POSITION;
}
Control::LayoutMode Control::_get_default_layout_mode() const {
Node *parent_node = get_parent_control();
Control *parent_control = get_parent_control();
// In these modes the property is read-only.
if (!parent_node) {
if (!parent_control) {
return LayoutMode::LAYOUT_MODE_UNCONTROLLED;
} else if (Object::cast_to<Container>(parent_node)) {
} else if (Object::cast_to<Container>(parent_control)) {
return LayoutMode::LAYOUT_MODE_CONTAINER;
}
// Otherwise fallback on the position mode.
// Otherwise fallback to the position mode.
return LayoutMode::LAYOUT_MODE_POSITION;
}
@ -1400,10 +1420,13 @@ void Control::set_position(const Point2 &p_point, bool p_keep_offsets) {
}
#endif // TOOLS_ENABLED
real_t edge_pos[4];
_compute_edge_positions(get_parent_anchorable_rect(), edge_pos);
Size2 offset_size(edge_pos[2] - edge_pos[0], edge_pos[3] - edge_pos[1]);
if (p_keep_offsets) {
_compute_anchors(Rect2(p_point, data.size_cache), data.offset, data.anchor);
_compute_anchors(Rect2(p_point, offset_size), data.offset, data.anchor);
} else {
_compute_offsets(Rect2(p_point, data.size_cache), data.anchor, data.offset);
_compute_offsets(Rect2(p_point, offset_size), data.anchor, data.offset);
}
_size_changed();
}
@ -1537,6 +1560,7 @@ void Control::set_scale(const Vector2 &p_scale) {
}
queue_redraw();
_notify_transform();
queue_accessibility_update();
}
Vector2 Control::get_scale() const {
@ -1553,6 +1577,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) {
@ -1579,6 +1604,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 {
@ -1690,14 +1716,8 @@ Size2 Control::get_combined_minimum_size() const {
void Control::_size_changed() {
Rect2 parent_rect = get_parent_anchorable_rect();
real_t edge_pos[4];
for (int i = 0; i < 4; i++) {
real_t area = parent_rect.size[i & 1];
edge_pos[i] = data.offset[i] + (data.anchor[i] * area);
}
_compute_edge_positions(parent_rect, edge_pos);
Point2 new_pos_cache = Point2(edge_pos[0], edge_pos[1]);
Size2 new_size_cache = Point2(edge_pos[2], edge_pos[3]) - new_pos_cache;
@ -1727,8 +1747,11 @@ void Control::_size_changed() {
new_size_cache.height = minimum_size.height;
}
bool pos_changed = !new_pos_cache.is_equal_approx(data.pos_cache);
bool size_changed = !new_size_cache.is_equal_approx(data.size_cache);
bool pos_changed = new_pos_cache != data.pos_cache;
bool size_changed = new_size_cache != data.size_cache;
// Below helps in getting rid of floating point errors for signaling resized.
bool approx_pos_changed = !new_pos_cache.is_equal_approx(data.pos_cache);
bool approx_size_changed = !new_size_cache.is_equal_approx(data.size_cache);
if (pos_changed) {
data.pos_cache = new_pos_cache;
@ -1738,13 +1761,13 @@ void Control::_size_changed() {
}
if (is_inside_tree()) {
if (pos_changed || size_changed) {
if (approx_pos_changed || approx_size_changed) {
// Ensure global transform is marked as dirty before `NOTIFICATION_RESIZED` / `item_rect_changed` signal
// so an up to date global transform could be obtained when handling these.
_notify_transform();
item_rect_changed(size_changed);
if (size_changed) {
item_rect_changed(approx_size_changed);
if (approx_size_changed) {
notification(NOTIFICATION_RESIZED);
}
}
@ -1752,6 +1775,8 @@ void Control::_size_changed() {
if (pos_changed && !size_changed) {
_update_canvas_item_transform();
}
queue_accessibility_update();
} else if (pos_changed) {
_notify_transform();
}
@ -1866,6 +1891,46 @@ Control::MouseFilter Control::get_mouse_filter() const {
return data.mouse_filter;
}
Control::MouseFilter Control::get_mouse_filter_with_recursive() const {
ERR_READ_THREAD_GUARD_V(MOUSE_FILTER_IGNORE);
if (_is_parent_mouse_disabled()) {
return MOUSE_FILTER_IGNORE;
}
return data.mouse_filter;
}
void Control::set_mouse_recursive_behavior(RecursiveBehavior p_recursive_mouse_behavior) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_INDEX((int)p_recursive_mouse_behavior, 4);
if (data.mouse_recursive_behavior == p_recursive_mouse_behavior) {
return;
}
_set_mouse_recursive_behavior_ignore_cache(p_recursive_mouse_behavior);
}
void Control::_set_mouse_recursive_behavior_ignore_cache(RecursiveBehavior p_recursive_mouse_behavior) {
data.mouse_recursive_behavior = p_recursive_mouse_behavior;
if (p_recursive_mouse_behavior == RECURSIVE_BEHAVIOR_INHERITED) {
Control *parent = get_parent_control();
if (parent) {
_propagate_mouse_behavior_recursively(parent->data.parent_mouse_recursive_behavior, false);
} else {
_propagate_mouse_behavior_recursively(RECURSIVE_BEHAVIOR_ENABLED, false);
}
} else {
_propagate_mouse_behavior_recursively(p_recursive_mouse_behavior, false);
}
if (get_viewport()) {
get_viewport()->_gui_update_mouse_over();
}
}
Control::RecursiveBehavior Control::get_mouse_recursive_behavior() const {
ERR_READ_THREAD_GUARD_V(RECURSIVE_BEHAVIOR_INHERITED);
return data.mouse_recursive_behavior;
}
void Control::set_force_pass_scroll_events(bool p_force_pass_scroll_events) {
ERR_MAIN_THREAD_GUARD;
data.force_pass_scroll_events = p_force_pass_scroll_events;
@ -1981,7 +2046,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(Math::INF, Math::INF));
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(Math::INF, Math::INF), this);
queue_accessibility_update();
}
void Control::set_drag_preview(Control *p_control) {
@ -2000,7 +2098,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();
@ -2014,6 +2112,42 @@ Control::FocusMode Control::get_focus_mode() const {
return data.focus_mode;
}
Control::FocusMode Control::get_focus_mode_with_recursive() const {
ERR_READ_THREAD_GUARD_V(FOCUS_NONE);
if (_is_focus_disabled_recursively()) {
return FOCUS_NONE;
}
return data.focus_mode;
}
void Control::set_focus_recursive_behavior(RecursiveBehavior p_recursive_focus_behavior) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_INDEX((int)p_recursive_focus_behavior, 4);
if (data.focus_recursive_behavior == p_recursive_focus_behavior) {
return;
}
_set_focus_recursive_behavior_ignore_cache(p_recursive_focus_behavior);
}
void Control::_set_focus_recursive_behavior_ignore_cache(RecursiveBehavior p_recursive_focus_behavior) {
data.focus_recursive_behavior = p_recursive_focus_behavior;
if (p_recursive_focus_behavior == RECURSIVE_BEHAVIOR_INHERITED) {
Control *parent = get_parent_control();
if (parent) {
_propagate_focus_behavior_recursively(parent->data.parent_focus_recursive_behavior, false);
} else {
_propagate_focus_behavior_recursively(RECURSIVE_BEHAVIOR_ENABLED, false);
}
} else {
_propagate_focus_behavior_recursively(p_recursive_focus_behavior, false);
}
}
Control::RecursiveBehavior Control::get_focus_recursive_behavior() const {
ERR_READ_THREAD_GUARD_V(RECURSIVE_BEHAVIOR_INHERITED);
return data.focus_recursive_behavior;
}
bool Control::has_focus() const {
ERR_READ_THREAD_GUARD_V(false);
return is_inside_tree() && get_viewport()->_gui_control_has_focus(this);
@ -2054,7 +2188,7 @@ static Control *_next_control(Control *p_from) {
return nullptr; // Can't go above.
}
Control *parent = Object::cast_to<Control>(p_from->get_parent());
Control *parent = p_from->get_parent_control();
if (!parent) {
return nullptr;
@ -2077,21 +2211,25 @@ static Control *_next_control(Control *p_from) {
Control *Control::find_next_valid_focus() const {
ERR_READ_THREAD_GUARD_V(nullptr);
// If the focus property is manually overwritten, attempt to use it.
if (!data.focus_next.is_empty()) {
Node *n = get_node_or_null(data.focus_next);
ERR_FAIL_NULL_V_MSG(n, nullptr, "Next focus node path is invalid: '" + data.focus_next + "'.");
Control *c = Object::cast_to<Control>(n);
ERR_FAIL_NULL_V_MSG(c, nullptr, "Next focus node is not a control: '" + n->get_name() + "'.");
if (c->is_visible_in_tree() && c->get_focus_mode_with_recursive() != FOCUS_NONE) {
return c;
}
}
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) {
// If the focus property is manually overwritten, attempt to use it.
if (!data.focus_next.is_empty()) {
Node *n = get_node_or_null(data.focus_next);
ERR_FAIL_NULL_V_MSG(n, nullptr, "Next focus node path is invalid: '" + data.focus_next + "'.");
Control *c = Object::cast_to<Control>(n);
ERR_FAIL_NULL_V_MSG(c, nullptr, "Next focus node is not a control: '" + n->get_name() + "'.");
if (c->is_visible() && c->get_focus_mode() != FOCUS_NONE) {
return c;
}
}
// Find next child.
Control *next_child = nullptr;
@ -2110,83 +2248,121 @@ Control *Control::find_next_valid_focus() const {
next_child = _next_control(from);
if (!next_child) { // Nothing else. Go up and find either window or subwindow.
next_child = const_cast<Control *>(this);
while (next_child && !next_child->is_set_as_top_level()) {
next_child = cast_to<Control>(next_child->get_parent());
while (next_child) {
if (next_child->is_set_as_top_level()) {
break;
}
if (next_child->data.RI) {
break;
}
next_child = next_child->data.parent_control;
}
if (!next_child) {
next_child = const_cast<Control *>(this);
while (next_child) {
if (next_child->data.RI) {
break;
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;
}
next_child = next_child->get_parent_control();
window_next = next;
next_child = c;
break;
}
}
}
}
if (next_child == from || next_child == this) { // No next control.
return (get_focus_mode() == FOCUS_ALL) ? next_child : nullptr;
}
if (next_child) {
if (next_child->get_focus_mode() == FOCUS_ALL) {
return next_child;
}
from = next_child;
} else {
if (!next_child) {
break;
}
if ((next_child->get_focus_mode_with_recursive() == FOCUS_ALL) || (ac_enabled && next_child->get_focus_mode_with_recursive() == FOCUS_ACCESSIBILITY)) {
return next_child;
}
if (next_child == from || next_child == this) {
return nullptr; // Stuck in a loop with no next control.
}
from = next_child; // Try to find the next control with focus mode FOCUS_ALL.
}
return nullptr;
}
static Control *_prev_control(Control *p_from) {
Control *child = nullptr;
for (int i = p_from->get_child_count() - 1; i >= 0; i--) {
Control *c = Object::cast_to<Control>(p_from->get_child(i));
if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) {
continue;
}
child = c;
break;
// Find the last child as prev, try the same in the last child.
return _prev_control(c);
}
if (!child) {
return p_from;
}
// No prev in parent, try the same in parent.
return _prev_control(child);
return p_from; // Not found in the children, return itself.
}
Control *Control::find_prev_valid_focus() const {
ERR_READ_THREAD_GUARD_V(nullptr);
// If the focus property is manually overwritten, attempt to use it.
if (!data.focus_prev.is_empty()) {
Node *n = get_node_or_null(data.focus_prev);
ERR_FAIL_NULL_V_MSG(n, nullptr, "Previous focus node path is invalid: '" + data.focus_prev + "'.");
Control *c = Object::cast_to<Control>(n);
ERR_FAIL_NULL_V_MSG(c, nullptr, "Previous focus node is not a control: '" + n->get_name() + "'.");
if (c->is_visible_in_tree() && c->get_focus_mode_with_recursive() != FOCUS_NONE) {
return c;
}
}
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) {
// If the focus property is manually overwritten, attempt to use it.
if (!data.focus_prev.is_empty()) {
Node *n = get_node_or_null(data.focus_prev);
ERR_FAIL_NULL_V_MSG(n, nullptr, "Previous focus node path is invalid: '" + data.focus_prev + "'.");
Control *c = Object::cast_to<Control>(n);
ERR_FAIL_NULL_V_MSG(c, nullptr, "Previous focus node is not a control: '" + n->get_name() + "'.");
if (c->is_visible() && c->get_focus_mode() != FOCUS_NONE) {
return c;
}
}
// Find prev child.
Control *prev_child = nullptr;
if (from->is_set_as_top_level() || !Object::cast_to<Control>(from->get_parent())) {
if (from->is_set_as_top_level() || !from->data.parent_control) {
// Find last of the children.
prev_child = _prev_control(from);
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--) {
@ -2201,21 +2377,21 @@ Control *Control::find_prev_valid_focus() const {
}
if (!prev_child) {
prev_child = Object::cast_to<Control>(from->get_parent());
prev_child = from->data.parent_control;
} else {
prev_child = _prev_control(prev_child);
}
}
if (prev_child == from || prev_child == this) { // No prev control.
return (get_focus_mode() == FOCUS_ALL) ? prev_child : nullptr;
}
if (prev_child->get_focus_mode() == 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;
}
from = prev_child;
if (prev_child == from || prev_child == this) {
return nullptr; // Stuck in a loop with no prev control.
}
from = prev_child; // Try to find the prev control with focus mode FOCUS_ALL.
}
return nullptr;
@ -2266,14 +2442,7 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) {
ERR_FAIL_NULL_V_MSG(n, nullptr, "Neighbor focus node path is invalid: '" + data.focus_neighbor[p_side] + "'.");
Control *c = Object::cast_to<Control>(n);
ERR_FAIL_NULL_V_MSG(c, nullptr, "Neighbor focus node is not a control: '" + n->get_name() + "'.");
bool valid = true;
if (!c->is_visible()) {
valid = false;
}
if (c->get_focus_mode() == FOCUS_NONE) {
valid = false;
}
if (valid) {
if (c->is_visible_in_tree() && c->get_focus_mode_with_recursive() != FOCUS_NONE) {
return c;
}
@ -2382,6 +2551,64 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) {
return result;
}
bool Control::_is_focus_disabled_recursively() const {
switch (data.focus_recursive_behavior) {
case RECURSIVE_BEHAVIOR_INHERITED:
return data.parent_focus_recursive_behavior == RECURSIVE_BEHAVIOR_DISABLED;
case RECURSIVE_BEHAVIOR_DISABLED:
return true;
case RECURSIVE_BEHAVIOR_ENABLED:
return false;
}
return false;
}
void Control::_propagate_focus_behavior_recursively(RecursiveBehavior p_focus_recursive_behavior, bool p_skip_non_inherited) {
if (is_inside_tree() && (data.focus_recursive_behavior == RECURSIVE_BEHAVIOR_DISABLED || (data.focus_recursive_behavior == RECURSIVE_BEHAVIOR_INHERITED && p_focus_recursive_behavior == RECURSIVE_BEHAVIOR_DISABLED)) && has_focus()) {
release_focus();
}
if (p_skip_non_inherited && data.focus_recursive_behavior != RECURSIVE_BEHAVIOR_INHERITED) {
return;
}
data.parent_focus_recursive_behavior = p_focus_recursive_behavior;
for (int i = 0; i < get_child_count(); i++) {
Control *control = Object::cast_to<Control>(get_child(i));
if (control) {
control->_propagate_focus_behavior_recursively(p_focus_recursive_behavior, true);
}
}
}
bool Control::_is_parent_mouse_disabled() const {
switch (data.mouse_recursive_behavior) {
case RECURSIVE_BEHAVIOR_INHERITED:
return data.parent_mouse_recursive_behavior == RECURSIVE_BEHAVIOR_DISABLED;
case RECURSIVE_BEHAVIOR_DISABLED:
return true;
case RECURSIVE_BEHAVIOR_ENABLED:
return false;
}
return false;
}
void Control::_propagate_mouse_behavior_recursively(RecursiveBehavior p_mouse_recursive_behavior, bool p_skip_non_inherited) {
if (p_skip_non_inherited && data.mouse_recursive_behavior != RECURSIVE_BEHAVIOR_INHERITED) {
return;
}
data.parent_mouse_recursive_behavior = p_mouse_recursive_behavior;
for (int i = 0; i < get_child_count(); i++) {
Control *control = Object::cast_to<Control>(get_child(i));
if (control) {
control->_propagate_mouse_behavior_recursively(p_mouse_recursive_behavior, true);
}
}
}
Control *Control::find_valid_focus_neighbor(Side p_side) const {
return const_cast<Control *>(this)->_get_focus_neighbor(p_side);
}
@ -2391,11 +2618,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() == 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());
@ -2808,6 +3037,44 @@ Variant Control::get_theme_item(Theme::DataType p_data_type, const StringName &p
return Variant();
}
Variant Control::get_used_theme_item(const String &p_full_name, const StringName &p_theme_type) const {
if (p_full_name.begins_with("theme_override_icons/")) {
String name = p_full_name.substr(strlen("theme_override_icons/"));
if (has_theme_icon(name)) { // Exclude cached and default ones.
return get_theme_icon(name);
}
} else if (p_full_name.begins_with("theme_override_styles/")) {
String name = p_full_name.substr(strlen("theme_override_styles/"));
if (has_theme_stylebox(name)) {
return get_theme_stylebox(name);
}
} else if (p_full_name.begins_with("theme_override_fonts/")) {
String name = p_full_name.substr(strlen("theme_override_fonts/"));
if (has_theme_font(name)) {
return get_theme_font(name);
}
} else if (p_full_name.begins_with("theme_override_font_sizes/")) {
String name = p_full_name.substr(strlen("theme_override_font_sizes/"));
if (has_theme_font_size(name)) {
return get_theme_font_size(name);
}
} else if (p_full_name.begins_with("theme_override_colors/")) {
String name = p_full_name.substr(strlen("theme_override_colors/"));
if (has_theme_color(name)) {
return get_theme_color(name);
}
} else if (p_full_name.begins_with("theme_override_constants/")) {
String name = p_full_name.substr(strlen("theme_override_constants/"));
if (has_theme_constant(name)) {
return get_theme_constant(name);
}
} else {
ERR_FAIL_V_MSG(Variant(), vformat("The property %s is not a theme item.", p_full_name));
}
return Variant();
}
#ifdef TOOLS_ENABLED
Ref<Texture2D> Control::get_editor_theme_icon(const StringName &p_name) const {
return get_theme_icon(p_name, SNAME("EditorIcons"));
@ -3263,6 +3530,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;
@ -3272,6 +3546,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) {
@ -3283,6 +3586,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(Math::INF, Math::INF), 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;
@ -3298,6 +3626,9 @@ void Control::_notification(int p_notification) {
data.theme_owner->assign_theme_on_parented(this);
_update_layout_mode();
_set_focus_recursive_behavior_ignore_cache(data.focus_recursive_behavior);
_set_mouse_recursive_behavior_ignore_cache(data.mouse_recursive_behavior);
} break;
case NOTIFICATION_UNPARENTED: {
@ -3518,6 +3849,9 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_global_rect"), &Control::get_global_rect);
ClassDB::bind_method(D_METHOD("set_focus_mode", "mode"), &Control::set_focus_mode);
ClassDB::bind_method(D_METHOD("get_focus_mode"), &Control::get_focus_mode);
ClassDB::bind_method(D_METHOD("get_focus_mode_with_recursive"), &Control::get_focus_mode_with_recursive);
ClassDB::bind_method(D_METHOD("set_focus_recursive_behavior", "focus_recursive_behavior"), &Control::set_focus_recursive_behavior);
ClassDB::bind_method(D_METHOD("get_focus_recursive_behavior"), &Control::get_focus_recursive_behavior);
ClassDB::bind_method(D_METHOD("has_focus"), &Control::has_focus);
ClassDB::bind_method(D_METHOD("grab_focus"), &Control::grab_focus);
ClassDB::bind_method(D_METHOD("release_focus"), &Control::release_focus);
@ -3611,8 +3945,15 @@ 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);
ClassDB::bind_method(D_METHOD("set_mouse_recursive_behavior", "mouse_recursive_behavior"), &Control::set_mouse_recursive_behavior);
ClassDB::bind_method(D_METHOD("get_mouse_recursive_behavior"), &Control::get_mouse_recursive_behavior);
ClassDB::bind_method(D_METHOD("set_force_pass_scroll_events", "force_pass_scroll_events"), &Control::set_force_pass_scroll_events);
ClassDB::bind_method(D_METHOD("is_force_pass_scroll_events"), &Control::is_force_pass_scroll_events);
@ -3708,10 +4049,12 @@ 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_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass (Propagate Up),Ignore"), "set_mouse_filter", "get_mouse_filter");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_recursive_behavior", PROPERTY_HINT_ENUM, "Inherited,Disabled,Enabled"), "set_mouse_recursive_behavior", "get_mouse_recursive_behavior");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape");
@ -3725,6 +4068,11 @@ 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);
BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_ENABLED);
BIND_CONSTANT(NOTIFICATION_RESIZED);
BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER);
@ -3831,6 +4179,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");
}

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef CONTROL_H
#define CONTROL_H
#pragma once
#include "core/math/transform_2d.h"
#include "core/object/gdvirtual.gen.inc"
@ -65,7 +64,14 @@ public:
enum FocusMode {
FOCUS_NONE,
FOCUS_CLICK,
FOCUS_ALL
FOCUS_ALL,
FOCUS_ACCESSIBILITY,
};
enum RecursiveBehavior {
RECURSIVE_BEHAVIOR_INHERITED,
RECURSIVE_BEHAVIOR_DISABLED,
RECURSIVE_BEHAVIOR_ENABLED,
};
enum SizeFlags {
@ -191,6 +197,8 @@ private:
real_t offset[4] = { 0.0, 0.0, 0.0, 0.0 };
real_t anchor[4] = { ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN };
FocusMode focus_mode = FOCUS_NONE;
RecursiveBehavior parent_focus_recursive_behavior = RECURSIVE_BEHAVIOR_INHERITED;
RecursiveBehavior focus_recursive_behavior = RECURSIVE_BEHAVIOR_INHERITED;
GrowDirection h_grow = GROW_DIRECTION_END;
GrowDirection v_grow = GROW_DIRECTION_END;
@ -219,6 +227,8 @@ private:
// Input events and rendering.
MouseFilter mouse_filter = MOUSE_FILTER_STOP;
RecursiveBehavior parent_mouse_recursive_behavior = RECURSIVE_BEHAVIOR_INHERITED;
RecursiveBehavior mouse_recursive_behavior = RECURSIVE_BEHAVIOR_INHERITED;
bool force_pass_scroll_events = true;
bool clip_contents = false;
@ -291,6 +301,7 @@ private:
void _compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t (&r_offsets)[4]);
void _compute_anchors(Rect2 p_rect, const real_t p_offsets[4], real_t (&r_anchors)[4]);
void _compute_edge_positions(Rect2 p_rect, real_t (&r_edge_positions)[4]);
void _set_layout_mode(LayoutMode p_mode);
void _update_layout_mode();
@ -312,10 +323,19 @@ private:
void _call_gui_input(const Ref<InputEvent> &p_event);
// Mouse Filter.
bool _is_parent_mouse_disabled() const;
// Focus.
void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Rect2 &p_rect, const Rect2 &p_clamp, real_t p_min, real_t &r_closest_dist_squared, Control **r_closest);
Control *_get_focus_neighbor(Side p_side, int p_count = 0);
bool _is_focus_disabled_recursively() const;
void _propagate_focus_behavior_recursively(RecursiveBehavior p_focus_recursive_behavior, bool p_force);
void _propagate_mouse_behavior_recursively(RecursiveBehavior p_focus_recursive_behavior, bool p_force);
void _set_mouse_recursive_behavior_ignore_cache(RecursiveBehavior p_recursive_mouse_behavior);
void _set_focus_recursive_behavior_ignore_cache(RecursiveBehavior p_recursive_mouse_behavior);
// Theming.
@ -327,8 +347,6 @@ private:
static int root_layout_direction;
String get_tooltip_text() const;
protected:
// Dynamic properties.
@ -353,6 +371,12 @@ protected:
void _notification(int p_notification);
static void _bind_methods();
void _accessibility_action_foucs(const Variant &p_data);
void _accessibility_action_blur(const Variant &p_data);
void _accessibility_action_show_tooltip(const Variant &p_data);
void _accessibility_action_hide_tooltip(const Variant &p_data);
void _accessibility_action_scroll_into_view(const Variant &p_data);
// Exposed virtual methods.
GDVIRTUAL1RC(bool, _has_point, Vector2)
@ -365,6 +389,8 @@ protected:
GDVIRTUAL2(_drop_data, Vector2, Variant)
GDVIRTUAL1RC(Object *, _make_custom_tooltip, String)
GDVIRTUAL0RC(String, _accessibility_get_contextual_info);
GDVIRTUAL1(_gui_input, Ref<InputEvent>)
public:
@ -420,6 +446,7 @@ public:
static void set_root_layout_direction(int p_root_dir);
PackedStringArray get_configuration_warnings() const override;
PackedStringArray get_accessibility_configuration_warnings() const override;
#ifdef TOOLS_ENABLED
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif //TOOLS_ENABLED
@ -428,8 +455,6 @@ public:
// Global relations.
bool is_top_level_control() const;
Control *get_parent_control() const;
Window *get_parent_window() const;
Control *get_root_parent_control() const;
@ -516,6 +541,10 @@ public:
void set_mouse_filter(MouseFilter p_filter);
MouseFilter get_mouse_filter() const;
MouseFilter get_mouse_filter_with_recursive() const;
void set_mouse_recursive_behavior(RecursiveBehavior p_recursive_mouse_behavior);
RecursiveBehavior get_mouse_recursive_behavior() const;
void set_force_pass_scroll_events(bool p_force_pass_scroll_events);
bool is_force_pass_scroll_events() const;
@ -534,12 +563,17 @@ public:
virtual void drop_data(const Point2 &p_point, const Variant &p_data);
void set_drag_preview(Control *p_control);
void force_drag(const Variant &p_data, Control *p_control);
void accessibility_drag();
void accessibility_drop();
bool is_drag_successful() const;
// Focus.
void set_focus_mode(FocusMode p_focus_mode);
FocusMode get_focus_mode() const;
FocusMode get_focus_mode_with_recursive() const;
void set_focus_recursive_behavior(RecursiveBehavior p_recursive_mouse_behavior);
RecursiveBehavior get_focus_recursive_behavior() const;
bool has_focus() const;
void grab_focus();
void grab_click_focus();
@ -607,6 +641,7 @@ public:
Color get_theme_color(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
int get_theme_constant(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
Variant get_theme_item(Theme::DataType p_data_type, const StringName &p_name, const StringName &p_theme_type = StringName()) const;
Variant get_used_theme_item(const String &p_full_name, const StringName &p_theme_type = StringName()) const;
#ifdef TOOLS_ENABLED
Ref<Texture2D> get_editor_theme_icon(const StringName &p_name) const;
#endif //TOOLS_ENABLED
@ -648,15 +683,19 @@ public:
// Extra properties.
String get_tooltip_text() const;
void set_tooltip_text(const String &text);
virtual String get_tooltip(const Point2 &p_pos) const;
virtual Control *make_custom_tooltip(const String &p_text) const;
virtual String accessibility_get_contextual_info() const;
Control();
~Control();
};
VARIANT_ENUM_CAST(Control::FocusMode);
VARIANT_ENUM_CAST(Control::RecursiveBehavior);
VARIANT_BITFIELD_CAST(Control::SizeFlags);
VARIANT_ENUM_CAST(Control::CursorShape);
VARIANT_ENUM_CAST(Control::LayoutPreset);
@ -673,5 +712,3 @@ VARIANT_ENUM_CAST(Control::TextDirection);
#define SET_DRAG_FORWARDING_CDU(from, to) from->set_drag_forwarding(Callable(), callable_mp(this, &to::_can_drop_data_fw).bind(from), callable_mp(this, &to::_drop_data_fw).bind(from));
#define SET_DRAG_FORWARDING_GCD(from, to) from->set_drag_forwarding(callable_mp(this, &to::get_drag_data_fw).bind(from), callable_mp(this, &to::can_drop_data_fw).bind(from), callable_mp(this, &to::drop_data_fw).bind(from));
#define SET_DRAG_FORWARDING_GCDU(from, to) from->set_drag_forwarding(callable_mp(this, &to::_get_drag_data_fw).bind(from), callable_mp(this, &to::_can_drop_data_fw).bind(from), callable_mp(this, &to::_drop_data_fw).bind(from));
#endif // CONTROL_H

View file

@ -30,6 +30,8 @@
#ifndef DISABLE_DEPRECATED
#include "scene/gui/line_edit.h"
void AcceptDialog::_register_text_enter_bind_compat_89419(Control *p_line_edit) {
register_text_enter(Object::cast_to<LineEdit>(p_line_edit));
}

View file

@ -51,6 +51,12 @@ void AcceptDialog::_parent_focused() {
void AcceptDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_DIALOG);
} break;
case NOTIFICATION_POST_ENTER_TREE: {
if (is_visible()) {
get_ok_button()->grab_focus();
@ -197,16 +203,12 @@ bool AcceptDialog::has_autowrap() {
}
void AcceptDialog::set_ok_button_text(String p_ok_button_text) {
ok_button->set_text(p_ok_button_text);
child_controls_changed();
if (is_visible()) {
_update_child_rects();
}
ok_text = p_ok_button_text;
_update_ok_text();
}
String AcceptDialog::get_ok_button_text() const {
return ok_button->get_text();
return ok_text;
}
void AcceptDialog::register_text_enter(LineEdit *p_line_edit) {
@ -257,6 +259,25 @@ void AcceptDialog::_update_child_rects() {
}
}
void AcceptDialog::_update_ok_text() {
String prev_text = ok_button->get_text();
String new_text = internal_ok_text;
if (!ok_text.is_empty()) {
new_text = ok_text;
}
if (new_text == prev_text) {
return;
}
ok_button->set_text(new_text);
child_controls_changed();
if (is_visible()) {
_update_child_rects();
}
}
Size2 AcceptDialog::_get_contents_minimum_size() const {
// First, we then iterate over the label and any other custom controls
// to try and find the size that encompasses all content.
@ -294,6 +315,11 @@ Size2 AcceptDialog::_get_contents_minimum_size() const {
return content_minsize;
}
void AcceptDialog::set_internal_ok_text(const String &p_text) {
internal_ok_text = p_text;
_update_ok_text();
}
void AcceptDialog::_custom_action(const String &p_action) {
emit_signal(SNAME("custom_action"), p_action);
custom_action(p_action);
@ -428,6 +454,9 @@ AcceptDialog::AcceptDialog() {
set_clamp_to_embedder(true);
set_keep_title_visible(true);
set_flag(FLAG_MINIMIZE_DISABLED, true);
set_flag(FLAG_MAXIMIZE_DISABLED, true);
bg_panel = memnew(Panel);
add_child(bg_panel, false, INTERNAL_MODE_FRONT);
@ -442,7 +471,7 @@ AcceptDialog::AcceptDialog() {
buttons_hbox->add_spacer();
ok_button = memnew(Button);
ok_button->set_text(ETR("OK"));
set_internal_ok_text(ETR("OK"));
buttons_hbox->add_child(ok_button);
buttons_hbox->add_spacer();

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef DIALOGS_H
#define DIALOGS_H
#pragma once
#include "box_container.h"
#include "scene/gui/button.h"
@ -52,6 +51,9 @@ class AcceptDialog : public Window {
Button *ok_button = nullptr;
bool popped_up = false;
String ok_text;
String internal_ok_text;
bool hide_on_ok = true;
bool close_on_escape = true;
@ -65,6 +67,7 @@ class AcceptDialog : public Window {
void _custom_action(const String &p_action);
void _custom_button_visibility_changed(Button *button);
void _update_child_rects();
void _update_ok_text();
static bool swap_cancel_ok;
@ -82,6 +85,8 @@ protected:
virtual void cancel_pressed() {}
virtual void custom_action(const String &) {}
void set_internal_ok_text(const String &p_text);
// Not private since used by derived classes signal.
void _text_submitted(const String &p_text);
void _ok_pressed();
@ -139,5 +144,3 @@ public:
ConfirmationDialog();
};
#endif // DIALOGS_H

View file

@ -68,10 +68,18 @@ void FileDialog::_native_popup() {
} else if (access == ACCESS_USERDATA) {
root = OS::get_singleton()->get_user_data_dir();
}
// Attach native file dialog to first persistent parent window.
Window *w = (is_transient() || is_transient_to_focused()) ? get_parent_visible_window() : nullptr;
while (w && w->get_flag(FLAG_POPUP) && w->get_parent_visible_window()) {
w = w->get_parent_visible_window();
}
DisplayServer::WindowID wid = w ? w->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_EXTRA)) {
DisplayServer::get_singleton()->file_dialog_with_options_show(get_translated_title(), ProjectSettings::get_singleton()->globalize_path(full_dir), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), processed_filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb_with_options));
DisplayServer::get_singleton()->file_dialog_with_options_show(get_translated_title(), ProjectSettings::get_singleton()->globalize_path(full_dir), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), processed_filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb_with_options), wid);
} else {
DisplayServer::get_singleton()->file_dialog_show(get_translated_title(), ProjectSettings::get_singleton()->globalize_path(full_dir), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), processed_filters, callable_mp(this, &FileDialog::_native_dialog_cb));
DisplayServer::get_singleton()->file_dialog_show(get_translated_title(), ProjectSettings::get_singleton()->globalize_path(full_dir), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), processed_filters, callable_mp(this, &FileDialog::_native_dialog_cb), wid);
}
}
@ -160,9 +168,9 @@ void FileDialog::_native_dialog_cb_with_options(bool p_ok, const Vector<String>
} else if (filters.size() > 1 && p_filter == 0) {
// Match all filters.
for (int i = 0; i < filters.size(); i++) {
String flt = filters[i].get_slice(";", 0);
String flt = filters[i].get_slicec(';', 0);
for (int j = 0; j < flt.get_slice_count(","); j++) {
String str = flt.get_slice(",", j).strip_edges();
String str = flt.get_slicec(',', j).strip_edges();
if (f.matchn(str)) {
valid = true;
break;
@ -178,10 +186,10 @@ void FileDialog::_native_dialog_cb_with_options(bool p_ok, const Vector<String>
idx--;
}
if (idx >= 0 && idx < filters.size()) {
String flt = filters[idx].get_slice(";", 0);
String flt = filters[idx].get_slicec(';', 0);
int filter_slice_count = flt.get_slice_count(",");
for (int j = 0; j < filter_slice_count; j++) {
String str = (flt.get_slice(",", j).strip_edges());
String str = flt.get_slicec(',', j).strip_edges();
if (f.matchn(str)) {
valid = true;
break;
@ -189,8 +197,8 @@ void FileDialog::_native_dialog_cb_with_options(bool p_ok, const Vector<String>
}
if (!valid && filter_slice_count > 0) {
String str = (flt.get_slice(",", 0).strip_edges());
f += str.substr(1, str.length() - 1);
String str = flt.get_slicec(',', 0).strip_edges();
f += str.substr(1);
file->set_text(f.get_file());
valid = true;
}
@ -202,8 +210,8 @@ void FileDialog::_native_dialog_cb_with_options(bool p_ok, const Vector<String>
// Add first extension of filter if no valid extension is found.
if (!valid) {
int idx = p_filter;
String flt = filters[idx].get_slice(";", 0);
String ext = flt.get_slice(",", 0).strip_edges().get_extension();
String flt = filters[idx].get_slicec(';', 0);
String ext = flt.get_slicec(',', 0).strip_edges().get_extension();
f += "." + ext;
}
emit_signal(SNAME("file_selected"), f);
@ -478,7 +486,7 @@ void FileDialog::_post_popup() {
void FileDialog::_push_history() {
local_history.resize(local_history_pos + 1);
String new_path = dir_access->get_current_dir();
if (local_history.size() == 0 || new_path != local_history[local_history_pos]) {
if (local_history.is_empty() || new_path != local_history[local_history_pos]) {
local_history.push_back(new_path);
local_history_pos++;
dir_prev->set_disabled(local_history_pos == 0);
@ -514,7 +522,7 @@ void FileDialog::_action_pressed() {
} else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) {
String path = dir_access->get_current_dir();
path = path.replace("\\", "/");
path = path.replace_char('\\', '/');
TreeItem *item = tree->get_selected();
if (item) {
Dictionary d = item->get_metadata(0);
@ -535,9 +543,9 @@ void FileDialog::_action_pressed() {
} else if (filters.size() > 1 && filter->get_selected() == 0) {
// Match all filters.
for (int i = 0; i < filters.size(); i++) {
String flt = filters[i].get_slice(";", 0);
String flt = filters[i].get_slicec(';', 0);
for (int j = 0; j < flt.get_slice_count(","); j++) {
String str = flt.get_slice(",", j).strip_edges();
String str = flt.get_slicec(',', j).strip_edges();
if (f.matchn(str)) {
valid = true;
break;
@ -553,10 +561,10 @@ void FileDialog::_action_pressed() {
idx--;
}
if (idx >= 0 && idx < filters.size()) {
String flt = filters[idx].get_slice(";", 0);
String flt = filters[idx].get_slicec(';', 0);
int filter_slice_count = flt.get_slice_count(",");
for (int j = 0; j < filter_slice_count; j++) {
String str = (flt.get_slice(",", j).strip_edges());
String str = (flt.get_slicec(',', j).strip_edges());
if (f.matchn(str)) {
valid = true;
break;
@ -564,8 +572,8 @@ void FileDialog::_action_pressed() {
}
if (!valid && filter_slice_count > 0) {
String str = (flt.get_slice(",", 0).strip_edges());
f += str.substr(1, str.length() - 1);
String str = flt.get_slicec(',', 0).strip_edges();
f += str.substr(1);
file->set_text(f.get_file());
valid = true;
}
@ -661,10 +669,10 @@ void FileDialog::deselect_all() {
switch (mode) {
case FILE_MODE_OPEN_FILE:
case FILE_MODE_OPEN_FILES:
set_ok_button_text(ETR("Open"));
set_internal_ok_text(ETR("Open"));
break;
case FILE_MODE_OPEN_DIR:
set_ok_button_text(ETR("Select Current Folder"));
set_internal_ok_text(ETR("Select Current Folder"));
break;
case FILE_MODE_OPEN_ANY:
set_ok_button_text(ETR("Open"));
@ -690,14 +698,14 @@ void FileDialog::_tree_selected() {
if (!d["dir"]) {
file->set_text(d["name"]);
if (mode == FILE_MODE_SAVE_FILE) {
set_ok_button_text(ETR("Save"));
set_internal_ok_text(ETR("Save"));
} else {
set_ok_button_text(ETR("Open"));
set_internal_ok_text(ETR("Open"));
}
} else if (mode == FILE_MODE_OPEN_DIR || mode == FILE_MODE_OPEN_ANY || !dir_access->file_exists(file->get_text())) {
file->set_text("");
if (mode == FILE_MODE_OPEN_DIR || mode == FILE_MODE_OPEN_ANY) {
set_ok_button_text(ETR("Select This Folder"));
set_internal_ok_text(ETR("Select This Folder"));
}
}
@ -862,9 +870,9 @@ void FileDialog::update_file_list() {
} else if (filters.size() > 1 && filter->get_selected() == 0) {
// match all filters
for (int i = 0; i < filters.size(); i++) {
String f = filters[i].get_slice(";", 0);
String f = filters[i].get_slicec(';', 0);
for (int j = 0; j < f.get_slice_count(","); j++) {
patterns.push_back(f.get_slice(",", j).strip_edges());
patterns.push_back(f.get_slicec(',', j).strip_edges());
}
}
} else {
@ -874,9 +882,9 @@ void FileDialog::update_file_list() {
}
if (idx >= 0 && idx < filters.size()) {
String f = filters[idx].get_slice(";", 0);
String f = filters[idx].get_slicec(';', 0);
for (int j = 0; j < f.get_slice_count(","); j++) {
patterns.push_back(f.get_slice(",", j).strip_edges());
patterns.push_back(f.get_slicec(',', j).strip_edges());
}
}
}
@ -1184,7 +1192,7 @@ void FileDialog::set_current_path(const String &p_path) {
set_current_file(p_path);
} else {
String path_dir = p_path.substr(0, pos);
String path_file = p_path.substr(pos + 1, p_path.length());
String path_file = p_path.substr(pos + 1);
set_current_dir(path_dir);
set_current_file(path_file);
}
@ -1227,35 +1235,35 @@ void FileDialog::set_file_mode(FileMode p_mode) {
mode = p_mode;
switch (mode) {
case FILE_MODE_OPEN_FILE:
set_ok_button_text(ETR("Open"));
set_internal_ok_text(ETR("Open"));
if (mode_overrides_title) {
set_title(ETR("Open a File"));
}
makedir->hide();
break;
case FILE_MODE_OPEN_FILES:
set_ok_button_text(ETR("Open"));
set_internal_ok_text(ETR("Open"));
if (mode_overrides_title) {
set_title(ETR("Open File(s)"));
}
makedir->hide();
break;
case FILE_MODE_OPEN_DIR:
set_ok_button_text(ETR("Select Current Folder"));
set_internal_ok_text(ETR("Select Current Folder"));
if (mode_overrides_title) {
set_title(ETR("Open a Directory"));
}
makedir->show();
break;
case FILE_MODE_OPEN_ANY:
set_ok_button_text(ETR("Open"));
set_internal_ok_text(ETR("Open"));
if (mode_overrides_title) {
set_title(ETR("Open a File or Directory"));
}
makedir->show();
break;
case FILE_MODE_SAVE_FILE:
set_ok_button_text(ETR("Save"));
set_internal_ok_text(ETR("Save"));
if (mode_overrides_title) {
set_title(ETR("Save a File"));
}
@ -1434,9 +1442,11 @@ void FileDialog::_update_option_controls() {
for (const FileDialog::Option &opt : options) {
Label *lbl = memnew(Label);
lbl->set_text(opt.name);
lbl->set_focus_mode(Control::FOCUS_NONE);
grid_options->add_child(lbl);
if (opt.values.is_empty()) {
CheckBox *cb = memnew(CheckBox);
cb->set_accessibility_name(opt.name);
cb->set_pressed(opt.default_idx);
grid_options->add_child(cb);
cb->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name));
@ -1446,6 +1456,7 @@ void FileDialog::_update_option_controls() {
for (const String &val : opt.values) {
ob->add_item(val);
}
ob->set_accessibility_name(opt.name);
ob->select(opt.default_idx);
grid_options->add_child(ob);
ob->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_option_changed_item_selected).bind(opt.name));
@ -1724,11 +1735,14 @@ FileDialog::FileDialog() {
dir_prev = memnew(Button);
dir_prev->set_theme_type_variation(SceneStringName(FlatButton));
dir_prev->set_accessibility_name(ETR("Previous"));
dir_prev->set_tooltip_text(ETR("Go to previous folder."));
dir_next = memnew(Button);
dir_next->set_accessibility_name(ETR("Next"));
dir_next->set_theme_type_variation(SceneStringName(FlatButton));
dir_next->set_tooltip_text(ETR("Go to next folder."));
dir_up = memnew(Button);
dir_up->set_accessibility_name(ETR("Parent Folder"));
dir_up->set_theme_type_variation(SceneStringName(FlatButton));
dir_up->set_tooltip_text(ETR("Go to parent folder."));
hbc->add_child(dir_prev);
@ -1738,22 +1752,27 @@ FileDialog::FileDialog() {
dir_next->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_forward));
dir_up->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_up));
hbc->add_child(memnew(Label(ETR("Path:"))));
Label *lbl_path = memnew(Label(ETR("Path:")));
lbl_path->set_focus_mode(Control::FOCUS_NONE);
hbc->add_child(lbl_path);
drives_container = memnew(HBoxContainer);
hbc->add_child(drives_container);
drives = memnew(OptionButton);
drives->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_select_drive));
drives->set_accessibility_name(ETR("Drive"));
hbc->add_child(drives);
dir = memnew(LineEdit);
dir->set_accessibility_name(ETR("Directory Path"));
dir->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
hbc->add_child(dir);
dir->set_h_size_flags(Control::SIZE_EXPAND_FILL);
refresh = memnew(Button);
refresh->set_theme_type_variation(SceneStringName(FlatButton));
refresh->set_accessibility_name(ETR("Refresh"));
refresh->set_tooltip_text(ETR("Refresh files."));
refresh->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::update_file_list));
hbc->add_child(refresh);
@ -1762,6 +1781,7 @@ FileDialog::FileDialog() {
show_hidden->set_theme_type_variation(SceneStringName(FlatButton));
show_hidden->set_toggle_mode(true);
show_hidden->set_pressed(is_showing_hidden_files());
show_hidden->set_accessibility_name(ETR("Show Hidden Files"));
show_hidden->set_tooltip_text(ETR("Toggle the visibility of hidden files."));
show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files));
hbc->add_child(show_hidden);
@ -1770,7 +1790,8 @@ FileDialog::FileDialog() {
show_filename_filter_button->set_theme_type_variation(SceneStringName(FlatButton));
show_filename_filter_button->set_toggle_mode(true);
show_filename_filter_button->set_pressed(false);
show_filename_filter_button->set_tooltip_text(RTR("Toggle the visibility of the filter for file names."));
show_filename_filter_button->set_accessibility_name(ETR("Filter File Names"));
show_filename_filter_button->set_tooltip_text(ETR("Toggle the visibility of the filter for file names."));
show_filename_filter_button->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_filename_filter));
hbc->add_child(show_filename_filter_button);
@ -1779,6 +1800,7 @@ FileDialog::FileDialog() {
makedir = memnew(Button);
makedir->set_theme_type_variation(SceneStringName(FlatButton));
makedir->set_accessibility_name(ETR("Create New Folder"));
makedir->set_tooltip_text(ETR("Create a new folder."));
makedir->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_make_dir));
hbc->add_child(makedir);
@ -1786,6 +1808,7 @@ FileDialog::FileDialog() {
tree = memnew(Tree);
tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
tree->set_accessibility_name(ETR("Directories and Files"));
tree->set_hide_root(true);
vbox->add_margin_child(ETR("Directories & Files:"), tree, true);
@ -1797,19 +1820,24 @@ FileDialog::FileDialog() {
tree->add_child(message);
filename_filter_box = memnew(HBoxContainer);
filename_filter_box->add_child(memnew(Label(RTR("Filter:"))));
filename_filter_box->add_child(memnew(Label(ETR("Filter:"))));
filename_filter = memnew(LineEdit);
filename_filter->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
filename_filter->set_stretch_ratio(4);
filename_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
filename_filter->set_clear_button_enabled(true);
filename_filter->set_accessibility_name(ETR("Filename Filter"));
filename_filter_box->add_child(filename_filter);
filename_filter_box->set_visible(false);
vbox->add_child(filename_filter_box);
file_box = memnew(HBoxContainer);
file_box->add_child(memnew(Label(ETR("File:"))));
Label *lbl_file = memnew(Label(ETR("File:")));
lbl_file->set_focus_mode(Control::FOCUS_NONE);
file_box->add_child(lbl_file);
file = memnew(LineEdit);
file->set_accessibility_name(ETR("File Name"));
file->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
file->set_stretch_ratio(4);
file->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@ -1878,6 +1906,7 @@ FileDialog::FileDialog() {
set_hide_on_ok(false);
set_size(Size2(640, 360));
set_internal_ok_text(ETR("Save")); // Default mode text.
if (register_func) {
register_func(this);

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef FILE_DIALOG_H
#define FILE_DIALOG_H
#pragma once
#include "box_container.h"
#include "core/io/dir_access.h"
@ -298,5 +297,3 @@ public:
VARIANT_ENUM_CAST(FileDialog::FileMode);
VARIANT_ENUM_CAST(FileDialog::Access);
#endif // FILE_DIALOG_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef FLOW_CONTAINER_H
#define FLOW_CONTAINER_H
#pragma once
#include "scene/gui/container.h"
@ -116,5 +115,3 @@ public:
VARIANT_ENUM_CAST(FlowContainer::AlignmentMode);
VARIANT_ENUM_CAST(FlowContainer::LastWrapAlignmentMode);
#endif // FLOW_CONTAINER_H

View file

@ -0,0 +1,652 @@
/**************************************************************************/
/* foldable_container.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "foldable_container.h"
#include "scene/resources/text_line.h"
#include "scene/theme/theme_db.h"
Size2 FoldableContainer::get_minimum_size() const {
_update_title_min_size();
if (folded) {
return title_minimum_size;
}
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
ms = ms.max(c->get_combined_minimum_size());
}
ms += theme_cache.panel_style->get_minimum_size();
return Size2(MAX(ms.width, title_minimum_size.width), ms.height + title_minimum_size.height);
}
void FoldableContainer::fold() {
set_folded(true);
emit_signal(SNAME("folding_changed"), folded);
}
void FoldableContainer::expand() {
set_folded(false);
emit_signal(SNAME("folding_changed"), folded);
}
void FoldableContainer::set_folded(bool p_folded) {
if (folded != p_folded) {
if (!changing_group && foldable_group.is_valid()) {
if (!p_folded) {
_update_group();
foldable_group->emit_signal(SNAME("expanded"), this);
} else if (!foldable_group->updating_group && foldable_group->get_expanded_container() == this && !foldable_group->is_allow_folding_all()) {
return;
}
}
folded = p_folded;
update_minimum_size();
queue_sort();
queue_redraw();
}
}
bool FoldableContainer::is_folded() const {
return folded;
}
void FoldableContainer::set_foldable_group(const Ref<FoldableGroup> &p_group) {
if (foldable_group.is_valid()) {
foldable_group->containers.erase(this);
}
foldable_group = p_group;
if (foldable_group.is_valid()) {
changing_group = true;
if (folded && !foldable_group->get_expanded_container() && !foldable_group->is_allow_folding_all()) {
set_folded(false);
} else if (!folded && foldable_group->get_expanded_container()) {
set_folded(true);
}
foldable_group->containers.insert(this);
changing_group = false;
}
queue_redraw();
}
Ref<FoldableGroup> FoldableContainer::get_foldable_group() const {
return foldable_group;
}
void FoldableContainer::set_text(const String &p_text) {
if (text == p_text) {
return;
}
text = p_text;
_shape();
update_minimum_size();
queue_redraw();
}
String FoldableContainer::get_text() const {
return text;
}
void FoldableContainer::set_text_alignment(HorizontalAlignment p_alignment) {
ERR_FAIL_INDEX((int)p_alignment, 3);
text_alignment = p_alignment;
if (_get_actual_alignment() != text_buf->get_horizontal_alignment()) {
_shape();
queue_redraw();
}
}
HorizontalAlignment FoldableContainer::get_text_alignment() const {
return text_alignment;
}
void FoldableContainer::set_language(const String &p_language) {
if (language == p_language) {
return;
}
language = p_language;
_shape();
update_minimum_size();
queue_redraw();
}
String FoldableContainer::get_language() const {
return language;
}
void FoldableContainer::set_text_direction(TextDirection p_text_direction) {
ERR_FAIL_INDEX(int(p_text_direction), 4);
if (text_direction == p_text_direction) {
return;
}
text_direction = p_text_direction;
_shape();
queue_redraw();
}
Control::TextDirection FoldableContainer::get_text_direction() const {
return text_direction;
}
void FoldableContainer::set_text_overrun_behavior(TextServer::OverrunBehavior p_overrun_behavior) {
if (overrun_behavior == p_overrun_behavior) {
return;
}
overrun_behavior = p_overrun_behavior;
_shape();
update_minimum_size();
queue_redraw();
}
TextServer::OverrunBehavior FoldableContainer::get_text_overrun_behavior() const {
return overrun_behavior;
}
void FoldableContainer::set_title_position(TitlePosition p_title_position) {
ERR_FAIL_INDEX(p_title_position, POSITION_MAX);
if (title_position == p_title_position) {
return;
}
title_position = p_title_position;
queue_redraw();
queue_sort();
}
FoldableContainer::TitlePosition FoldableContainer::get_title_position() const {
return title_position;
}
void FoldableContainer::add_title_bar_control(Control *p_control) {
ERR_FAIL_NULL(p_control);
if (p_control->get_parent()) {
p_control->get_parent()->remove_child(p_control);
ERR_FAIL_COND_MSG(p_control->get_parent() != nullptr, "Failed to remove control from parent.");
}
add_child(p_control, false, INTERNAL_MODE_FRONT);
title_controls.push_back(p_control);
}
void FoldableContainer::remove_title_bar_control(Control *p_control) {
ERR_FAIL_NULL(p_control);
int64_t index = title_controls.find(p_control);
ERR_FAIL_COND_MSG(index == -1, "Can't remove control from title bar.");
title_controls.remove_at(index);
remove_child(p_control);
}
void FoldableContainer::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
Ref<InputEventMouseMotion> m = p_event;
if (m.is_valid()) {
Rect2 title_rect = Rect2(0, (title_position == POSITION_TOP) ? 0 : get_size().height - title_minimum_size.height, get_size().width, title_minimum_size.height);
if (title_rect.has_point(m->get_position())) {
if (!is_hovering) {
is_hovering = true;
queue_redraw();
}
} else if (is_hovering) {
is_hovering = false;
queue_redraw();
}
return;
}
if (p_event->is_action_pressed(SNAME("ui_accept"), false, true)) {
set_folded(!folded);
emit_signal(SNAME("folding_changed"), folded);
accept_event();
return;
}
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
Rect2 title_rect = Rect2(0, (title_position == POSITION_TOP) ? 0 : get_size().height - title_minimum_size.height, get_size().width, title_minimum_size.height);
if (b->get_button_index() == MouseButton::LEFT && b->is_pressed() && title_rect.has_point(b->get_position())) {
set_folded(!folded);
emit_signal(SNAME("folding_changed"), folded);
accept_event();
}
}
}
String FoldableContainer::get_tooltip(const Point2 &p_pos) const {
if (Rect2(0, (title_position == POSITION_TOP) ? 0 : get_size().height - title_minimum_size.height, get_size().width, title_minimum_size.height).has_point(p_pos)) {
return Control::get_tooltip(p_pos);
}
return String();
}
void FoldableContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
Size2 size = get_size();
int h_separation = _get_h_separation();
Ref<StyleBox> title_style = _get_title_style();
Ref<Texture2D> icon = _get_title_icon();
real_t title_controls_width = _get_title_controls_width();
if (title_controls_width > 0) {
title_controls_width += h_separation;
}
Rect2 title_rect(
Point2(0, (title_position == POSITION_TOP) ? 0 : size.height - title_minimum_size.height),
Size2(size.width, title_minimum_size.height));
_draw_flippable_stylebox(title_style, title_rect);
Size2 title_ms = title_style->get_minimum_size();
int title_text_width = size.width - title_ms.width;
int title_style_ofs = (title_position == POSITION_TOP) ? title_style->get_margin(SIDE_TOP) : title_style->get_margin(SIDE_BOTTOM);
Point2 title_text_pos(title_style->get_margin(SIDE_LEFT), title_style_ofs);
title_text_pos.y += MAX((title_minimum_size.height - title_ms.height - text_buf->get_size().height) * 0.5, 0);
title_text_width -= icon->get_width() + h_separation + title_controls_width;
Point2 icon_pos(0, MAX((title_minimum_size.height - title_ms.height - icon->get_height()) * 0.5, 0) + title_style_ofs);
bool rtl = is_layout_rtl();
if (rtl) {
icon_pos.x = size.width - title_style->get_margin(SIDE_RIGHT) - icon->get_width();
title_text_pos.x += title_controls_width;
} else {
icon_pos.x = title_style->get_margin(SIDE_LEFT);
title_text_pos.x += icon->get_width() + h_separation;
}
icon->draw(ci, title_rect.position + icon_pos);
Color font_color = folded ? theme_cache.title_collapsed_font_color : theme_cache.title_font_color;
if (is_hovering) {
font_color = theme_cache.title_hovered_font_color;
}
text_buf->set_width(title_text_width);
if (title_text_width > 0) {
if (theme_cache.title_font_outline_size > 0 && theme_cache.title_font_outline_color.a > 0) {
text_buf->draw_outline(ci, title_rect.position + title_text_pos, theme_cache.title_font_outline_size, theme_cache.title_font_outline_color);
}
text_buf->draw(ci, title_rect.position + title_text_pos, font_color);
}
if (!folded) {
Rect2 panel_rect(
Point2(0, (title_position == POSITION_TOP) ? title_minimum_size.height : 0),
Size2(size.width, size.height - title_minimum_size.height));
_draw_flippable_stylebox(theme_cache.panel_style, panel_rect);
}
if (has_focus()) {
Rect2 focus_rect = folded ? title_rect : Rect2(Point2(), size);
_draw_flippable_stylebox(theme_cache.focus_style, focus_rect);
}
} break;
case NOTIFICATION_SORT_CHILDREN: {
bool rtl = is_layout_rtl();
const Vector2 size = get_size();
const Ref<StyleBox> title_style = _get_title_style();
uint32_t title_count = title_controls.size();
if (title_count > 0) {
int h_separation = MAX(theme_cache.h_separation, 0);
real_t offset = 0.0;
if (rtl) {
offset = title_style->get_margin(SIDE_LEFT);
} else {
offset = _get_title_controls_width();
offset = size.x - title_style->get_margin(SIDE_RIGHT) - offset;
}
real_t v_center = title_minimum_size.y * 0.5;
if (title_position == POSITION_BOTTOM) {
v_center = size.y - v_center + (title_style->get_margin(SIDE_BOTTOM) - title_style->get_margin(SIDE_TOP)) * 0.5;
} else {
v_center += (title_style->get_margin(SIDE_TOP) - title_style->get_margin(SIDE_BOTTOM)) * 0.5;
}
for (uint32_t i = 0; i < title_count; i++) {
Control *control = title_controls[rtl ? title_count - i - 1 : i];
if (!control->is_visible()) {
continue;
}
Rect2 rect(Vector2(), control->get_combined_minimum_size());
rect.position.x = offset;
rect.position.y = v_center - rect.size.y * 0.5;
fit_child_in_rect(control, rect);
offset += rect.size.x + h_separation;
}
}
Rect2 inner_rect;
inner_rect.position.x = rtl ? theme_cache.panel_style->get_margin(SIDE_RIGHT) : theme_cache.panel_style->get_margin(SIDE_LEFT);
inner_rect.size.x = size.x - theme_cache.panel_style->get_margin(SIDE_LEFT) - theme_cache.panel_style->get_margin(SIDE_RIGHT);
inner_rect.position.y = theme_cache.panel_style->get_margin(SIDE_TOP);
inner_rect.size.y = size.y - theme_cache.panel_style->get_margin(SIDE_TOP) - theme_cache.panel_style->get_margin(SIDE_BOTTOM) - title_minimum_size.y;
if (title_position == POSITION_TOP) {
inner_rect.position.y += title_minimum_size.y;
}
for (int i = 0; i < get_child_count(false); i++) {
Control *c = as_sortable_control(get_child(i, false), SortableVisibilityMode::IGNORE);
if (!c) {
continue;
}
c->set_visible(!folded);
if (!folded) {
fit_child_in_rect(c, inner_rect);
}
}
} break;
case NOTIFICATION_MOUSE_EXIT: {
if (is_hovering) {
is_hovering = false;
queue_redraw();
}
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
_shape();
update_minimum_size();
queue_redraw();
} break;
}
}
real_t FoldableContainer::_get_title_controls_width() const {
real_t width = 0.0;
int visible_controls = 0;
for (const Control *control : title_controls) {
if (control->is_visible()) {
width += control->get_combined_minimum_size().x;
visible_controls++;
}
}
if (visible_controls > 1) {
width += _get_h_separation() * (visible_controls - 1);
}
return width;
}
Ref<StyleBox> FoldableContainer::_get_title_style() const {
if (is_hovering) {
return folded ? theme_cache.title_collapsed_hover_style : theme_cache.title_hover_style;
}
return folded ? theme_cache.title_collapsed_style : theme_cache.title_style;
}
Ref<Texture2D> FoldableContainer::_get_title_icon() const {
if (!folded) {
return (title_position == POSITION_TOP) ? theme_cache.expanded_arrow : theme_cache.expanded_arrow_mirrored;
} else if (is_layout_rtl()) {
return theme_cache.folded_arrow_mirrored;
}
return theme_cache.folded_arrow;
}
void FoldableContainer::_update_title_min_size() const {
Ref<StyleBox> title_style = folded ? theme_cache.title_collapsed_style : theme_cache.title_style;
Ref<Texture2D> icon = _get_title_icon();
Size2 title_ms = title_style->get_minimum_size();
int h_separation = _get_h_separation();
title_minimum_size = title_ms;
title_minimum_size.width += icon->get_width();
if (!text.is_empty()) {
title_minimum_size.width += h_separation;
Size2 text_size = text_buf->get_size();
title_minimum_size.height += MAX(text_size.height, icon->get_height());
if (overrun_behavior == TextServer::OverrunBehavior::OVERRUN_NO_TRIMMING) {
title_minimum_size.width += text_size.width;
}
} else {
title_minimum_size.height += icon->get_height();
}
if (!title_controls.is_empty()) {
real_t controls_height = 0;
int visible_controls = 0;
for (const Control *control : title_controls) {
if (!control->is_visible()) {
continue;
}
Vector2 size = control->get_combined_minimum_size();
title_minimum_size.width += size.width;
controls_height = MAX(controls_height, size.height);
visible_controls++;
}
if (visible_controls > 0) {
title_minimum_size.width += h_separation * visible_controls;
}
title_minimum_size.height = MAX(title_minimum_size.height, title_ms.height + controls_height);
}
}
void FoldableContainer::_shape() {
Ref<Font> font = theme_cache.title_font;
int font_size = theme_cache.title_font_size;
if (font.is_null() || font_size == 0) {
return;
}
text_buf->clear();
text_buf->set_width(-1);
if (text_direction == TEXT_DIRECTION_INHERITED) {
text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
} else {
text_buf->set_direction((TextServer::Direction)text_direction);
}
text_buf->set_horizontal_alignment(_get_actual_alignment());
text_buf->set_text_overrun_behavior(overrun_behavior);
text_buf->add_string(atr(text), font, font_size, language);
}
HorizontalAlignment FoldableContainer::_get_actual_alignment() const {
if (is_layout_rtl()) {
if (text_alignment == HORIZONTAL_ALIGNMENT_RIGHT) {
return HORIZONTAL_ALIGNMENT_LEFT;
} else if (text_alignment == HORIZONTAL_ALIGNMENT_LEFT) {
return HORIZONTAL_ALIGNMENT_RIGHT;
}
}
return text_alignment;
}
void FoldableContainer::_update_group() {
foldable_group->updating_group = true;
for (FoldableContainer *container : foldable_group->containers) {
if (container != this) {
container->set_folded(true);
}
}
foldable_group->updating_group = false;
}
void FoldableContainer::_draw_flippable_stylebox(const Ref<StyleBox> p_stylebox, const Rect2 &p_rect) {
if (title_position == POSITION_BOTTOM) {
Rect2 rect(-p_rect.position, p_rect.size);
draw_set_transform(Point2(0.0, p_stylebox->get_draw_rect(rect).size.height), 0.0, Size2(1.0, -1.0));
p_stylebox->draw(get_canvas_item(), rect);
draw_set_transform_matrix(Transform2D());
} else {
p_stylebox->draw(get_canvas_item(), p_rect);
}
}
void FoldableContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("fold"), &FoldableContainer::fold);
ClassDB::bind_method(D_METHOD("expand"), &FoldableContainer::expand);
ClassDB::bind_method(D_METHOD("set_folded", "folded"), &FoldableContainer::set_folded);
ClassDB::bind_method(D_METHOD("is_folded"), &FoldableContainer::is_folded);
ClassDB::bind_method(D_METHOD("set_foldable_group", "button_group"), &FoldableContainer::set_foldable_group);
ClassDB::bind_method(D_METHOD("get_foldable_group"), &FoldableContainer::get_foldable_group);
ClassDB::bind_method(D_METHOD("set_text", "text"), &FoldableContainer::set_text);
ClassDB::bind_method(D_METHOD("get_text"), &FoldableContainer::get_text);
ClassDB::bind_method(D_METHOD("set_title_alignment", "alignment"), &FoldableContainer::set_text_alignment);
ClassDB::bind_method(D_METHOD("get_title_alignment"), &FoldableContainer::get_text_alignment);
ClassDB::bind_method(D_METHOD("set_language", "language"), &FoldableContainer::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &FoldableContainer::get_language);
ClassDB::bind_method(D_METHOD("set_text_direction", "text_direction"), &FoldableContainer::set_text_direction);
ClassDB::bind_method(D_METHOD("get_text_direction"), &FoldableContainer::get_text_direction);
ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &FoldableContainer::set_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &FoldableContainer::get_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("set_title_position", "title_position"), &FoldableContainer::set_title_position);
ClassDB::bind_method(D_METHOD("get_title_position"), &FoldableContainer::get_title_position);
ClassDB::bind_method(D_METHOD("add_title_bar_control", "control"), &FoldableContainer::add_title_bar_control);
ClassDB::bind_method(D_METHOD("remove_title_bar_control", "control"), &FoldableContainer::remove_title_bar_control);
ADD_SIGNAL(MethodInfo("folding_changed", PropertyInfo(Variant::BOOL, "is_folded")));
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "folded"), "set_folded", "is_folded");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "title_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_title_alignment", "get_title_alignment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "title_position", PROPERTY_HINT_ENUM, "Top,Bottom"), "set_title_position", "get_title_position");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "foldable_group", PROPERTY_HINT_RESOURCE_TYPE, "FoldableGroup"), "set_foldable_group", "get_foldable_group");
ADD_GROUP("BiDi", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID), "set_language", "get_language");
BIND_ENUM_CONSTANT(POSITION_TOP);
BIND_ENUM_CONSTANT(POSITION_BOTTOM);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, FoldableContainer, title_style, "title_panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, FoldableContainer, title_hover_style, "title_hover_panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, FoldableContainer, title_collapsed_style, "title_collapsed_panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, FoldableContainer, title_collapsed_hover_style, "title_collapsed_hover_panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, FoldableContainer, focus_style, "focus");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, FoldableContainer, panel_style, "panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_FONT, FoldableContainer, title_font, "font");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_FONT_SIZE, FoldableContainer, title_font_size, "font_size");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_CONSTANT, FoldableContainer, title_font_outline_size, "outline_size");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, FoldableContainer, title_font_color, "font_color");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, FoldableContainer, title_hovered_font_color, "hover_font_color");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, FoldableContainer, title_collapsed_font_color, "collapsed_font_color");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, FoldableContainer, title_font_outline_color, "font_outline_color");
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FoldableContainer, expanded_arrow);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FoldableContainer, expanded_arrow_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FoldableContainer, folded_arrow);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FoldableContainer, folded_arrow_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, FoldableContainer, h_separation);
}
FoldableContainer::FoldableContainer(const String &p_text) {
text_buf.instantiate();
set_text(p_text);
set_focus_mode(FOCUS_ALL);
set_mouse_filter(MOUSE_FILTER_STOP);
}
FoldableContainer::~FoldableContainer() {
if (foldable_group.is_valid()) {
foldable_group->containers.erase(this);
}
}
FoldableContainer *FoldableGroup::get_expanded_container() const {
for (FoldableContainer *container : containers) {
if (!container->is_folded()) {
return container;
}
}
return nullptr;
}
void FoldableGroup::set_allow_folding_all(bool p_enabled) {
allow_folding_all = p_enabled;
if (!allow_folding_all && !get_expanded_container() && containers.size() > 0) {
updating_group = true;
(*containers.begin())->set_folded(false);
updating_group = false;
}
}
bool FoldableGroup::is_allow_folding_all() const {
return allow_folding_all;
}
void FoldableGroup::get_containers(List<FoldableContainer *> *r_containers) const {
for (FoldableContainer *container : containers) {
r_containers->push_back(container);
}
}
TypedArray<FoldableContainer> FoldableGroup::_get_containers() const {
TypedArray<FoldableContainer> foldable_containers;
for (const FoldableContainer *container : containers) {
foldable_containers.push_back(container);
}
return foldable_containers;
}
void FoldableGroup::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_expanded_container"), &FoldableGroup::get_expanded_container);
ClassDB::bind_method(D_METHOD("get_containers"), &FoldableGroup::_get_containers);
ClassDB::bind_method(D_METHOD("set_allow_folding_all", "enabled"), &FoldableGroup::set_allow_folding_all);
ClassDB::bind_method(D_METHOD("is_allow_folding_all"), &FoldableGroup::is_allow_folding_all);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_folding_all"), "set_allow_folding_all", "is_allow_folding_all");
ADD_SIGNAL(MethodInfo("expanded", PropertyInfo(Variant::OBJECT, "container", PROPERTY_HINT_RESOURCE_TYPE, "FoldableContainer")));
}
FoldableGroup::FoldableGroup() {
set_local_to_scene(true);
}

View file

@ -0,0 +1,171 @@
/**************************************************************************/
/* foldable_container.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/container.h"
class FoldableGroup;
class TextLine;
class FoldableContainer : public Container {
GDCLASS(FoldableContainer, Container);
public:
enum TitlePosition {
POSITION_TOP,
POSITION_BOTTOM,
POSITION_MAX
};
private:
bool folded = false;
String text;
Ref<FoldableGroup> foldable_group;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
HorizontalAlignment text_alignment = HORIZONTAL_ALIGNMENT_LEFT;
TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING;
TitlePosition title_position = POSITION_TOP;
Ref<TextLine> text_buf;
bool changing_group = false;
bool is_hovering = false;
mutable Vector2 title_minimum_size;
LocalVector<Control *> title_controls;
struct ThemeCache {
Ref<StyleBox> title_style;
Ref<StyleBox> title_hover_style;
Ref<StyleBox> title_collapsed_style;
Ref<StyleBox> title_collapsed_hover_style;
Ref<StyleBox> panel_style;
Ref<StyleBox> focus_style;
Color title_font_color;
Color title_hovered_font_color;
Color title_collapsed_font_color;
Color title_font_outline_color;
Ref<Font> title_font;
int title_font_size = 0;
int title_font_outline_size = 0;
Ref<Texture2D> expanded_arrow;
Ref<Texture2D> expanded_arrow_mirrored;
Ref<Texture2D> folded_arrow;
Ref<Texture2D> folded_arrow_mirrored;
int h_separation = 0;
} theme_cache;
Ref<StyleBox> _get_title_style() const;
Ref<Texture2D> _get_title_icon() const;
int _get_h_separation() const { return MAX(theme_cache.h_separation, 0); }
real_t _get_title_controls_width() const;
void _update_title_min_size() const;
void _shape();
HorizontalAlignment _get_actual_alignment() const;
void _update_group();
void _draw_flippable_stylebox(const Ref<StyleBox> p_stylebox, const Rect2 &p_rect);
protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
virtual String get_tooltip(const Point2 &p_pos) const override;
void _notification(int p_what);
static void _bind_methods();
public:
void fold();
void expand();
void set_folded(bool p_folded);
bool is_folded() const;
void set_foldable_group(const Ref<FoldableGroup> &p_group);
Ref<FoldableGroup> get_foldable_group() const;
void set_text(const String &p_text);
String get_text() const;
void set_text_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_text_alignment() const;
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
void set_text_overrun_behavior(TextServer::OverrunBehavior p_overrun_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior() const;
void set_language(const String &p_language);
String get_language() const;
void set_title_position(TitlePosition p_title_position);
TitlePosition get_title_position() const;
void add_title_bar_control(Control *p_control);
void remove_title_bar_control(Control *p_control);
virtual Size2 get_minimum_size() const override;
virtual Vector<int> get_allowed_size_flags_horizontal() const override { return { SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END }; }
virtual Vector<int> get_allowed_size_flags_vertical() const override { return { SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END }; }
FoldableContainer(const String &p_text = String());
~FoldableContainer();
};
VARIANT_ENUM_CAST(FoldableContainer::TitlePosition);
class FoldableGroup : public Resource {
GDCLASS(FoldableGroup, Resource);
friend class FoldableContainer;
HashSet<FoldableContainer *> containers;
bool allow_folding_all = false;
bool updating_group = false;
protected:
static void _bind_methods();
public:
FoldableContainer *get_expanded_container() const;
void get_containers(List<FoldableContainer *> *r_containers) const;
TypedArray<FoldableContainer> _get_containers() const;
void set_allow_folding_all(bool p_enabled);
bool is_allow_folding_all() const;
FoldableGroup();
};

View file

@ -353,6 +353,62 @@ int GraphEdit::get_connection_count(const StringName &p_node, int p_port) {
return count;
}
GraphNode *GraphEdit::get_input_connection_target(const StringName &p_node, int p_port) {
for (const Ref<Connection> &conn : connections) {
if (conn->to_node == p_node && conn->to_port == p_port) {
GraphNode *from = Object::cast_to<GraphNode>(get_node(NodePath(conn->from_node)));
if (from) {
return from;
}
}
}
return nullptr;
}
GraphNode *GraphEdit::get_output_connection_target(const StringName &p_node, int p_port) {
for (const Ref<Connection> &conn : connections) {
if (conn->from_node == p_node && conn->from_port == p_port) {
GraphNode *to = Object::cast_to<GraphNode>(get_node(NodePath(conn->to_node)));
if (to) {
return to;
}
}
}
return nullptr;
}
String GraphEdit::get_connections_description(const StringName &p_node, int p_port) {
String out;
for (const Ref<Connection> &conn : connections) {
if (conn->from_node == p_node && conn->from_port == p_port) {
GraphNode *to = Object::cast_to<GraphNode>(get_node(NodePath(conn->to_node)));
if (to) {
if (!out.is_empty()) {
out += ", ";
}
String name = to->get_accessibility_name();
if (name.is_empty()) {
name = to->get_name();
}
out += vformat(ETR("connection to %s (%s) port %d"), name, to->get_title(), conn->to_port);
}
} else if (conn->to_node == p_node && conn->to_port == p_port) {
GraphNode *from = Object::cast_to<GraphNode>(get_node(NodePath(conn->from_node)));
if (from) {
if (!out.is_empty()) {
out += ", ";
}
String name = from->get_accessibility_name();
if (name.is_empty()) {
name = from->get_name();
}
out += vformat(ETR("connection from %s (%s) port %d"), name, from->get_title(), conn->from_port);
}
}
}
return out;
}
void GraphEdit::set_scroll_offset(const Vector2 &p_offset) {
setting_scroll_offset = true;
h_scrollbar->set_value(p_offset.x);
@ -780,6 +836,10 @@ void GraphEdit::_notification(int p_what) {
// Draw background fill.
draw_style_box(theme_cache.panel, Rect2(Point2(), get_size()));
if (has_focus()) {
draw_style_box(theme_cache.panel_focus, Rect2(Point2(), get_size()));
}
// Draw background grid.
if (show_grid) {
_draw_grid();
@ -958,9 +1018,172 @@ bool GraphEdit::_filter_input(const Point2 &p_point) {
return false;
}
void GraphEdit::start_keyboard_connecting(GraphNode *p_node, int p_in_port, int p_out_port) {
if (!p_node || p_in_port == p_out_port || (p_in_port != -1 && p_out_port != -1)) {
return;
}
connecting_valid = false;
keyboard_connecting = true;
if (p_in_port != -1) {
Vector2 pos = p_node->get_input_port_position(p_in_port) * zoom + p_node->get_position();
if (right_disconnects || valid_right_disconnect_types.has(p_node->get_input_port_type(p_in_port))) {
// Check disconnect.
for (const Ref<Connection> &conn : connection_map[p_node->get_name()]) {
if (conn->to_node == p_node->get_name() && conn->to_port == p_in_port) {
Node *fr = get_node(NodePath(conn->from_node));
if (Object::cast_to<GraphNode>(fr)) {
connecting_from_node = conn->from_node;
connecting_from_port_index = conn->from_port;
connecting_from_output = true;
connecting_type = Object::cast_to<GraphNode>(fr)->get_output_port_type(conn->from_port);
connecting_color = Object::cast_to<GraphNode>(fr)->get_output_port_color(conn->from_port);
connecting_target_valid = false;
connecting_to_point = pos;
just_disconnected = true;
if (connecting_type >= 0) {
emit_signal(SNAME("disconnection_request"), conn->from_node, conn->from_port, conn->to_node, conn->to_port);
fr = get_node(NodePath(connecting_from_node));
if (Object::cast_to<GraphNode>(fr)) {
connecting = true;
emit_signal(SNAME("connection_drag_started"), connecting_from_node, connecting_from_port_index, true);
}
}
return;
}
}
}
}
connecting_from_node = p_node->get_name();
connecting_from_port_index = p_in_port;
connecting_from_output = false;
connecting_type = p_node->get_input_port_type(p_in_port);
connecting_color = p_node->get_input_port_color(p_in_port);
connecting_target_valid = false;
connecting_to_point = pos;
if (connecting_type >= 0) {
connecting = true;
just_disconnected = false;
emit_signal(SNAME("connection_drag_started"), connecting_from_node, connecting_from_port_index, false);
}
return;
}
if (p_out_port != -1) {
Vector2 pos = p_node->get_output_port_position(p_out_port) * zoom + p_node->get_position();
if (valid_left_disconnect_types.has(p_node->get_output_port_type(p_out_port))) {
// Check disconnect.
for (const Ref<Connection> &conn : connection_map[p_node->get_name()]) {
if (conn->from_node == p_node->get_name() && conn->from_port == p_out_port) {
Node *to = get_node(NodePath(conn->to_node));
if (Object::cast_to<GraphNode>(to)) {
connecting_from_node = conn->to_node;
connecting_from_port_index = conn->to_port;
connecting_from_output = false;
connecting_type = Object::cast_to<GraphNode>(to)->get_input_port_type(conn->to_port);
connecting_color = Object::cast_to<GraphNode>(to)->get_input_port_color(conn->to_port);
connecting_target_valid = false;
connecting_to_point = pos;
if (connecting_type >= 0) {
just_disconnected = true;
emit_signal(SNAME("disconnection_request"), conn->from_node, conn->from_port, conn->to_node, conn->to_port);
to = get_node(NodePath(connecting_from_node)); // Maybe it was erased.
if (Object::cast_to<GraphNode>(to)) {
connecting = true;
emit_signal(SNAME("connection_drag_started"), connecting_from_node, connecting_from_port_index, false);
}
}
return;
}
}
}
}
connecting_from_node = p_node->get_name();
connecting_from_port_index = p_out_port;
connecting_from_output = true;
connecting_type = p_node->get_output_port_type(p_out_port);
connecting_color = p_node->get_output_port_color(p_out_port);
connecting_target_valid = false;
connecting_to_point = pos;
if (connecting_type >= 0) {
connecting = true;
just_disconnected = false;
emit_signal(SNAME("connection_drag_started"), connecting_from_node, connecting_from_port_index, true);
}
return;
}
}
void GraphEdit::end_keyboard_connecting(GraphNode *p_node, int p_in_port, int p_out_port) {
if (!p_node) {
return;
}
connecting_valid = true;
connecting_target_valid = false;
if (p_in_port != -1) {
Vector2 pos = p_node->get_input_port_position(p_in_port) * zoom + p_node->get_position();
int type = p_node->get_input_port_type(p_in_port);
if (type == connecting_type || p_node->is_ignoring_valid_connection_type() || valid_connection_types.has(ConnectionType(connecting_type, type))) {
connecting_target_valid = true;
connecting_to_point = pos;
connecting_target_node = p_node->get_name();
connecting_target_port_index = p_in_port;
}
}
if (p_out_port != -1) {
Vector2 pos = p_node->get_output_port_position(p_out_port) * zoom + p_node->get_position();
int type = p_node->get_output_port_type(p_out_port);
if (type == connecting_type || p_node->is_ignoring_valid_connection_type() || valid_connection_types.has(ConnectionType(type, connecting_type))) {
connecting_target_valid = true;
connecting_to_point = pos;
connecting_target_node = p_node->get_name();
connecting_target_port_index = p_out_port;
}
}
if (connecting_valid) {
if (connecting && connecting_target_valid) {
if (connecting_from_output) {
emit_signal(SNAME("connection_request"), connecting_from_node, connecting_from_port_index, connecting_target_node, connecting_target_port_index);
} else {
emit_signal(SNAME("connection_request"), connecting_target_node, connecting_target_port_index, connecting_from_node, connecting_from_port_index);
}
} else if (!just_disconnected) {
if (connecting_from_output) {
emit_signal(SNAME("connection_to_empty"), connecting_from_node, connecting_from_port_index, Vector2());
} else {
emit_signal(SNAME("connection_from_empty"), connecting_from_node, connecting_from_port_index, Vector2());
}
}
}
keyboard_connecting = false;
if (connecting) {
force_connection_drag_end();
}
}
Dictionary GraphEdit::get_type_names() const {
return type_names;
}
void GraphEdit::set_type_names(const Dictionary &p_names) {
type_names = p_names;
}
void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) {
Ref<InputEventMouseButton> mb = p_ev;
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
if (keyboard_connecting) {
force_connection_drag_end();
keyboard_connecting = false;
}
connecting_valid = false;
click_pos = mb->get_position() / zoom;
for (int i = get_child_count() - 1; i >= 0; i--) {
@ -1086,7 +1309,7 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) {
}
Ref<InputEventMouseMotion> mm = p_ev;
if (mm.is_valid() && connecting) {
if (mm.is_valid() && connecting && !keyboard_connecting) {
connecting_to_point = mm->get_position();
minimap->queue_redraw();
callable_mp(this, &GraphEdit::_update_top_connection_layer).call_deferred();
@ -1198,7 +1421,7 @@ bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &mpos
control_rect.size *= zoom;
control_rect.position += p_offset;
if (!control_rect.has_point(mpos) || p_control->get_mouse_filter() == MOUSE_FILTER_IGNORE) {
if (!control_rect.has_point(mpos) || p_control->get_mouse_filter_with_recursive() == MOUSE_FILTER_IGNORE) {
// Test children.
for (int i = 0; i < p_control->get_child_count(); i++) {
Control *child_rect = Object::cast_to<Control>(p_control->get_child(i));
@ -1316,7 +1539,7 @@ Ref<GraphEdit::Connection> GraphEdit::get_closest_connection_at_point(const Vect
Vector<Vector2> points = get_connection_line(conn->_cache.from_pos * zoom, conn->_cache.to_pos * zoom);
for (int i = 0; i < points.size() - 1; i++) {
float distance = Geometry2D::get_distance_to_segment(transformed_point, &points[i]);
const real_t distance = Geometry2D::get_distance_to_segment(transformed_point, points[i], points[i + 1]);
if (distance <= lines_thickness * 0.5 + p_max_distance && distance < closest_distance) {
closest_connection = conn;
closest_distance = distance;
@ -1663,7 +1886,7 @@ void GraphEdit::_draw_grid() {
for (int i = from_pos.x; i < from_pos.x + len.x; i++) {
Color color;
if (ABS(i) % GRID_MINOR_STEPS_PER_MAJOR_LINE == 0) {
if (Math::abs(i) % GRID_MINOR_STEPS_PER_MAJOR_LINE == 0) {
color = theme_cache.grid_major;
} else {
color = theme_cache.grid_minor;
@ -1676,7 +1899,7 @@ void GraphEdit::_draw_grid() {
for (int i = from_pos.y; i < from_pos.y + len.y; i++) {
Color color;
if (ABS(i) % GRID_MINOR_STEPS_PER_MAJOR_LINE == 0) {
if (Math::abs(i) % GRID_MINOR_STEPS_PER_MAJOR_LINE == 0) {
color = theme_cache.grid_major;
} else {
color = theme_cache.grid_minor;
@ -1694,7 +1917,7 @@ void GraphEdit::_draw_grid() {
if (transparent_grid_minor.a != 0) {
for (int i = from_pos.x; i < from_pos.x + len.x; i++) {
for (int j = from_pos.y; j < from_pos.y + len.y; j++) {
if (ABS(i) % GRID_MINOR_STEPS_PER_MAJOR_DOT == 0 && ABS(j) % GRID_MINOR_STEPS_PER_MAJOR_DOT == 0) {
if (Math::abs(i) % GRID_MINOR_STEPS_PER_MAJOR_DOT == 0 && Math::abs(j) % GRID_MINOR_STEPS_PER_MAJOR_DOT == 0) {
continue;
}
@ -2142,6 +2365,7 @@ void GraphEdit::force_connection_drag_end() {
connecting = false;
connecting_valid = false;
keyboard_connecting = false;
minimap->queue_redraw();
queue_redraw();
connections_layer->queue_redraw();
@ -2329,6 +2553,24 @@ TypedArray<Dictionary> GraphEdit::_get_connections_intersecting_with_rect(const
return arr;
}
TypedArray<Dictionary> GraphEdit::_get_connection_list_from_node(const StringName &p_node) const {
ERR_FAIL_COND_V(!connection_map.has(p_node), TypedArray<Dictionary>());
List<Ref<GraphEdit::Connection>> connections_from_node = connection_map.get(p_node);
TypedArray<Dictionary> connections_from_node_dict;
for (const Ref<Connection> &conn : connections_from_node) {
Dictionary d;
d["from_node"] = conn->from_node;
d["from_port"] = conn->from_port;
d["to_node"] = conn->to_node;
d["to_port"] = conn->to_port;
d["keep_alive"] = conn->keep_alive;
connections_from_node_dict.push_back(d);
}
return connections_from_node_dict;
}
void GraphEdit::_zoom_minus() {
set_zoom(zoom / zoom_step);
}
@ -2689,6 +2931,7 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_connection_list"), &GraphEdit::_get_connection_list);
ClassDB::bind_method(D_METHOD("get_connection_count", "from_node", "from_port"), &GraphEdit::get_connection_count);
ClassDB::bind_method(D_METHOD("get_closest_connection_at_point", "point", "max_distance"), &GraphEdit::_get_closest_connection_at_point, DEFVAL(4.0));
ClassDB::bind_method(D_METHOD("get_connection_list_from_node", "node"), &GraphEdit::_get_connection_list_from_node);
ClassDB::bind_method(D_METHOD("get_connections_intersecting_with_rect", "rect"), &GraphEdit::_get_connections_intersecting_with_rect);
ClassDB::bind_method(D_METHOD("clear_connections"), &GraphEdit::clear_connections);
ClassDB::bind_method(D_METHOD("force_connection_drag_end"), &GraphEdit::force_connection_drag_end);
@ -2774,6 +3017,9 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_right_disconnects", "enable"), &GraphEdit::set_right_disconnects);
ClassDB::bind_method(D_METHOD("is_right_disconnects_enabled"), &GraphEdit::is_right_disconnects_enabled);
ClassDB::bind_method(D_METHOD("set_type_names", "type_names"), &GraphEdit::set_type_names);
ClassDB::bind_method(D_METHOD("get_type_names"), &GraphEdit::get_type_names);
GDVIRTUAL_BIND(_is_in_input_hotzone, "in_node", "in_port", "mouse_position");
GDVIRTUAL_BIND(_is_in_output_hotzone, "in_node", "in_port", "mouse_position");
@ -2794,6 +3040,8 @@ void GraphEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "panning_scheme", PROPERTY_HINT_ENUM, "Scroll Zooms,Scroll Pans"), "set_panning_scheme", "get_panning_scheme");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "type_names", PROPERTY_HINT_DICTIONARY_TYPE, "int;String"), "set_type_names", "get_type_names");
ADD_GROUP("Connection Lines", "connection_lines");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_curvature"), "set_connection_lines_curvature", "get_connection_lines_curvature");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_thickness", PROPERTY_HINT_RANGE, "0,100,0.1,suffix:px"), "set_connection_lines_thickness", "get_connection_lines_thickness");
@ -2850,6 +3098,7 @@ void GraphEdit::_bind_methods() {
BIND_ENUM_CONSTANT(GRID_PATTERN_DOTS);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphEdit, panel);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphEdit, panel_focus);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, GraphEdit, grid_major);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, GraphEdit, grid_minor);
@ -2958,7 +3207,8 @@ GraphEdit::GraphEdit() {
zoom_minus_button->set_theme_type_variation(SceneStringName(FlatButton));
zoom_minus_button->set_visible(show_zoom_buttons);
zoom_minus_button->set_tooltip_text(ETR("Zoom Out"));
zoom_minus_button->set_focus_mode(FOCUS_NONE);
zoom_minus_button->set_accessibility_name(ETR("Zoom Out"));
zoom_minus_button->set_focus_mode(FOCUS_ACCESSIBILITY);
menu_hbox->add_child(zoom_minus_button);
zoom_minus_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_minus));
@ -2966,7 +3216,8 @@ GraphEdit::GraphEdit() {
zoom_reset_button->set_theme_type_variation(SceneStringName(FlatButton));
zoom_reset_button->set_visible(show_zoom_buttons);
zoom_reset_button->set_tooltip_text(ETR("Zoom Reset"));
zoom_reset_button->set_focus_mode(FOCUS_NONE);
zoom_reset_button->set_accessibility_name(ETR("Zoom Reset"));
zoom_reset_button->set_focus_mode(FOCUS_ACCESSIBILITY);
menu_hbox->add_child(zoom_reset_button);
zoom_reset_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_reset));
@ -2974,7 +3225,8 @@ GraphEdit::GraphEdit() {
zoom_plus_button->set_theme_type_variation(SceneStringName(FlatButton));
zoom_plus_button->set_visible(show_zoom_buttons);
zoom_plus_button->set_tooltip_text(ETR("Zoom In"));
zoom_plus_button->set_focus_mode(FOCUS_NONE);
zoom_plus_button->set_accessibility_name(ETR("Zoom In"));
zoom_plus_button->set_focus_mode(FOCUS_ACCESSIBILITY);
menu_hbox->add_child(zoom_plus_button);
zoom_plus_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_plus));
@ -2986,6 +3238,7 @@ GraphEdit::GraphEdit() {
toggle_grid_button->set_toggle_mode(true);
toggle_grid_button->set_pressed(true);
toggle_grid_button->set_tooltip_text(ETR("Toggle the visual grid."));
toggle_grid_button->set_accessibility_name(ETR("Grid"));
toggle_grid_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(toggle_grid_button);
toggle_grid_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_show_grid_toggled));
@ -2995,6 +3248,7 @@ GraphEdit::GraphEdit() {
toggle_snapping_button->set_visible(show_grid_buttons);
toggle_snapping_button->set_toggle_mode(true);
toggle_snapping_button->set_tooltip_text(ETR("Toggle snapping to the grid."));
toggle_snapping_button->set_accessibility_name(ETR("Snap to Grid"));
toggle_snapping_button->set_pressed(snapping_enabled);
toggle_snapping_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(toggle_snapping_button);
@ -3007,6 +3261,7 @@ GraphEdit::GraphEdit() {
snapping_distance_spinbox->set_step(1);
snapping_distance_spinbox->set_value(snapping_distance);
snapping_distance_spinbox->set_tooltip_text(ETR("Change the snapping distance."));
snapping_distance_spinbox->set_accessibility_name(ETR("Snapping Distance"));
menu_hbox->add_child(snapping_distance_spinbox);
snapping_distance_spinbox->connect(SceneStringName(value_changed), callable_mp(this, &GraphEdit::_snapping_distance_changed));
@ -3017,6 +3272,7 @@ GraphEdit::GraphEdit() {
minimap_button->set_visible(show_minimap_button);
minimap_button->set_toggle_mode(true);
minimap_button->set_tooltip_text(ETR("Toggle the graph minimap."));
minimap_button->set_accessibility_name(ETR("Minimap"));
minimap_button->set_pressed(show_grid);
minimap_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(minimap_button);
@ -3025,6 +3281,7 @@ GraphEdit::GraphEdit() {
arrange_button = memnew(Button);
arrange_button->set_theme_type_variation(SceneStringName(FlatButton));
arrange_button->set_visible(show_arrange_button);
arrange_button->set_accessibility_name(ETR("Auto Arrange"));
arrange_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::arrange_nodes));
arrange_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(arrange_button);

View file

@ -28,9 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GRAPH_EDIT_H
#define GRAPH_EDIT_H
#pragma once
#include "core/variant/typed_dictionary.h"
#include "scene/gui/box_container.h"
#include "scene/gui/graph_frame.h"
#include "scene/gui/graph_node.h"
@ -199,6 +199,7 @@ private:
bool show_grid = true;
GridPattern grid_pattern = GRID_PATTERN_LINES;
bool keyboard_connecting = false;
bool connecting = false;
StringName connecting_from_node;
bool connecting_from_output = false;
@ -270,6 +271,7 @@ private:
float base_scale = 1.0;
Ref<StyleBox> panel;
Ref<StyleBox> panel_focus;
Color grid_major;
Color grid_minor;
@ -304,6 +306,8 @@ private:
HashMap<StringName, HashSet<StringName>> frame_attached_nodes;
HashMap<StringName, StringName> linked_parent_map;
Dictionary type_names;
void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
@ -345,6 +349,7 @@ private:
TypedArray<Dictionary> _get_connection_list() const;
Dictionary _get_closest_connection_at_point(const Vector2 &p_point, float p_max_distance = 4.0) const;
TypedArray<Dictionary> _get_connections_intersecting_with_rect(const Rect2 &p_rect) const;
TypedArray<Dictionary> _get_connection_list_from_node(const StringName &p_node) const;
Rect2 _compute_shrinked_frame_rect(const GraphFrame *p_frame);
void _set_drag_frame_attached_nodes(GraphFrame *p_frame, bool p_drag);
@ -404,6 +409,9 @@ public:
Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, bool keep_alive = false);
bool is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
int get_connection_count(const StringName &p_node, int p_port);
GraphNode *get_input_connection_target(const StringName &p_node, int p_port);
GraphNode *get_output_connection_target(const StringName &p_node, int p_port);
String get_connections_description(const StringName &p_node, int p_port);
void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
void force_connection_drag_end();
@ -413,6 +421,13 @@ public:
Ref<Connection> get_closest_connection_at_point(const Vector2 &p_point, float p_max_distance = 4.0) const;
List<Ref<Connection>> get_connections_intersecting_with_rect(const Rect2 &p_rect) const;
bool is_keyboard_connecting() const { return keyboard_connecting; }
void start_keyboard_connecting(GraphNode *p_node, int p_in_port, int p_out_port);
void end_keyboard_connecting(GraphNode *p_node, int p_in_port, int p_out_port);
Dictionary get_type_names() const;
void set_type_names(const Dictionary &p_names);
virtual bool is_node_hover_valid(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
void set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity);
@ -517,5 +532,3 @@ public:
VARIANT_ENUM_CAST(GraphEdit::PanningScheme);
VARIANT_ENUM_CAST(GraphEdit::GridPattern);
#endif // GRAPH_EDIT_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GRAPH_EDIT_ARRANGER_H
#define GRAPH_EDIT_ARRANGER_H
#pragma once
#include "core/object/ref_counted.h"
#include "core/templates/hash_map.h"
@ -63,5 +62,3 @@ public:
GraphEditArranger(GraphEdit *p_graph_edit) :
graph_edit(p_graph_edit) {}
};
#endif // GRAPH_EDIT_ARRANGER_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GRAPH_ELEMENT_H
#define GRAPH_ELEMENT_H
#pragma once
#include "scene/gui/container.h"
@ -93,5 +92,3 @@ public:
GraphElement() {}
};
#endif // GRAPH_ELEMENT_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GRAPH_FRAME_H
#define GRAPH_FRAME_H
#pragma once
#include "scene/gui/graph_element.h"
@ -105,5 +104,3 @@ public:
GraphFrame();
};
#endif // GRAPH_FRAME_H

View file

@ -31,6 +31,7 @@
#include "graph_node.h"
#include "scene/gui/box_container.h"
#include "scene/gui/graph_edit.h"
#include "scene/gui/label.h"
#include "scene/theme/theme_db.h"
@ -41,8 +42,8 @@ bool GraphNode::_set(const StringName &p_name, const Variant &p_value) {
return false;
}
int idx = str.get_slice("/", 1).to_int();
String slot_property_name = str.get_slice("/", 2);
int idx = str.get_slicec('/', 1).to_int();
String slot_property_name = str.get_slicec('/', 2);
Slot slot;
if (slot_table.has(idx)) {
@ -93,8 +94,8 @@ bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const {
return false;
}
int idx = str.get_slice("/", 1).to_int();
StringName slot_property_name = str.get_slice("/", 2);
int idx = str.get_slicec('/', 1).to_int();
StringName slot_property_name = str.get_slicec('/', 2);
Slot slot;
if (slot_table.has(idx)) {
@ -196,6 +197,10 @@ void GraphNode::_resort() {
children_count++;
}
slot_count = children_count;
if (selected_slot >= slot_count) {
selected_slot = -1;
}
if (children_count == 0) {
return;
@ -279,12 +284,13 @@ void GraphNode::_resort() {
Rect2 rect(margin, from_y_pos, final_width, height);
fit_child_in_rect(child, rect);
slot_y_cache.push_back(from_y_pos - sb_panel->get_margin(SIDE_TOP) + height * 0.5);
slot_y_cache.push_back(child->get_rect().position.y + child->get_rect().size.height * 0.5);
ofs_y = to_y_pos;
valid_children_idx++;
}
queue_accessibility_update();
queue_redraw();
port_pos_dirty = true;
}
@ -306,8 +312,311 @@ void GraphNode::draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Co
port_icon->draw(get_canvas_item(), p_pos + icon_offset, p_color);
}
void GraphNode::_accessibility_action_slot(const Variant &p_data) {
CustomAccessibilityAction action = (CustomAccessibilityAction)p_data.operator int();
switch (action) {
case ACTION_CONNECT_INPUT: {
if (slot_table.has(selected_slot)) {
const Slot &slot = slot_table[selected_slot];
if (slot.enable_left) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
if (graph) {
for (int i = 0; i < left_port_cache.size(); i++) {
if (left_port_cache[i].slot_index == selected_slot) {
if (graph->is_keyboard_connecting()) {
graph->end_keyboard_connecting(this, i, -1);
} else {
graph->start_keyboard_connecting(this, i, -1);
}
queue_accessibility_update();
queue_redraw();
break;
}
}
}
}
}
} break;
case ACTION_CONNECT_OUTPUT: {
if (slot_table.has(selected_slot)) {
const Slot &slot = slot_table[selected_slot];
if (slot.enable_right) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
if (graph) {
for (int i = 0; i < right_port_cache.size(); i++) {
if (right_port_cache[i].slot_index == selected_slot) {
if (graph->is_keyboard_connecting()) {
graph->end_keyboard_connecting(this, -1, i);
} else {
graph->start_keyboard_connecting(this, -1, i);
}
queue_accessibility_update();
queue_redraw();
break;
}
}
}
}
}
} break;
case ACTION_FOLLOW_INPUT: {
if (slot_table.has(selected_slot)) {
const Slot &slot = slot_table[selected_slot];
if (slot.enable_left) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
if (graph) {
for (int i = 0; i < left_port_cache.size(); i++) {
if (left_port_cache[i].slot_index == selected_slot) {
GraphNode *target = graph->get_input_connection_target(get_name(), i);
if (target) {
target->grab_focus();
break;
}
}
}
}
}
}
} break;
case ACTION_FOLLOW_OUTPUT: {
if (slot_table.has(selected_slot)) {
const Slot &slot = slot_table[selected_slot];
if (slot.enable_right) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
if (graph) {
for (int i = 0; i < right_port_cache.size(); i++) {
if (right_port_cache[i].slot_index == selected_slot) {
GraphNode *target = graph->get_output_connection_target(get_name(), i);
if (target) {
target->grab_focus();
break;
}
}
}
}
}
}
} break;
}
}
void GraphNode::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (port_pos_dirty) {
_port_pos_update();
}
if (p_event->is_pressed() && slot_count > 0) {
if (p_event->is_action("ui_up", true)) {
selected_slot--;
if (selected_slot < 0) {
selected_slot = -1;
} else {
accept_event();
}
} else if (p_event->is_action("ui_down", true)) {
selected_slot++;
if (selected_slot >= slot_count) {
selected_slot = -1;
} else {
accept_event();
}
} else if (p_event->is_action("ui_cancel", true)) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
if (graph && graph->is_keyboard_connecting()) {
graph->force_connection_drag_end();
accept_event();
}
} else if (p_event->is_action("ui_graph_delete", true)) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
if (graph && graph->is_keyboard_connecting()) {
graph->end_keyboard_connecting(this, -1, -1);
accept_event();
}
} else if (p_event->is_action("ui_graph_follow_left", true)) {
if (slot_table.has(selected_slot)) {
const Slot &slot = slot_table[selected_slot];
if (slot.enable_left) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
if (graph) {
for (int i = 0; i < left_port_cache.size(); i++) {
if (left_port_cache[i].slot_index == selected_slot) {
GraphNode *target = graph->get_input_connection_target(get_name(), i);
if (target) {
target->grab_focus();
accept_event();
break;
}
}
}
}
}
}
} else if (p_event->is_action("ui_graph_follow_right", true)) {
if (slot_table.has(selected_slot)) {
const Slot &slot = slot_table[selected_slot];
if (slot.enable_right) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
if (graph) {
for (int i = 0; i < right_port_cache.size(); i++) {
if (right_port_cache[i].slot_index == selected_slot) {
GraphNode *target = graph->get_output_connection_target(get_name(), i);
if (target) {
target->grab_focus();
accept_event();
break;
}
}
}
}
}
}
} else if (p_event->is_action("ui_left", true)) {
if (slot_table.has(selected_slot)) {
const Slot &slot = slot_table[selected_slot];
if (slot.enable_left) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
if (graph) {
for (int i = 0; i < left_port_cache.size(); i++) {
if (left_port_cache[i].slot_index == selected_slot) {
if (graph->is_keyboard_connecting()) {
graph->end_keyboard_connecting(this, i, -1);
} else {
graph->start_keyboard_connecting(this, i, -1);
}
accept_event();
break;
}
}
}
}
}
} else if (p_event->is_action("ui_right", true)) {
if (slot_table.has(selected_slot)) {
const Slot &slot = slot_table[selected_slot];
if (slot.enable_right) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
if (graph) {
for (int i = 0; i < right_port_cache.size(); i++) {
if (right_port_cache[i].slot_index == selected_slot) {
if (graph->is_keyboard_connecting()) {
graph->end_keyboard_connecting(this, -1, i);
} else {
graph->start_keyboard_connecting(this, -1, i);
}
accept_event();
break;
}
}
}
}
}
} else if (p_event->is_action("ui_accept", true)) {
if (slot_table.has(selected_slot)) {
int idx = 0;
for (int i = 0; i < get_child_count(false); i++) {
Control *child = as_sortable_control(get_child(i, false), SortableVisibilityMode::IGNORE);
if (!child) {
continue;
}
if (idx == selected_slot) {
selected_slot = -1;
child->grab_focus();
break;
}
idx++;
}
accept_event();
}
}
queue_accessibility_update();
queue_redraw();
}
GraphElement::gui_input(p_event);
}
void GraphNode::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
String name = get_accessibility_name();
if (name.is_empty()) {
name = get_name();
}
name = vformat(ETR("graph node %s (%s)"), name, get_title());
if (slot_table.has(selected_slot)) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
Dictionary type_info;
if (graph) {
type_info = graph->get_type_names();
}
const Slot &slot = slot_table[selected_slot];
name += ", " + vformat(ETR("slot %d of %d"), selected_slot + 1, slot_count);
if (slot.enable_left) {
if (type_info.has(slot.type_left)) {
name += "," + vformat(ETR("input port, type: %s"), type_info[slot.type_left]);
} else {
name += "," + vformat(ETR("input port, type: %d"), slot.type_left);
}
if (graph) {
for (int i = 0; i < left_port_cache.size(); i++) {
if (left_port_cache[i].slot_index == selected_slot) {
String cd = graph->get_connections_description(get_name(), i);
if (cd.is_empty()) {
name += " " + ETR("no connections");
} else {
name += " " + cd;
}
break;
}
}
}
}
if (slot.enable_left) {
if (type_info.has(slot.type_right)) {
name += "," + vformat(ETR("output port, type: %s"), type_info[slot.type_right]);
} else {
name += "," + vformat(ETR("output port, type: %d"), slot.type_right);
}
if (graph) {
for (int i = 0; i < right_port_cache.size(); i++) {
if (right_port_cache[i].slot_index == selected_slot) {
String cd = graph->get_connections_description(get_name(), i);
if (cd.is_empty()) {
name += " " + ETR("no connections");
} else {
name += " " + cd;
}
break;
}
}
}
}
if (graph && graph->is_keyboard_connecting()) {
name += ", " + ETR("currently selecting target port");
}
} else {
name += ", " + vformat(ETR("has %d slots"), slot_count);
}
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_LIST);
DisplayServer::get_singleton()->accessibility_update_set_name(ae, name);
DisplayServer::get_singleton()->accessibility_update_add_custom_action(ae, CustomAccessibilityAction::ACTION_CONNECT_INPUT, ETR("Edit Input Port Connection"));
DisplayServer::get_singleton()->accessibility_update_add_custom_action(ae, CustomAccessibilityAction::ACTION_CONNECT_OUTPUT, ETR("Edit Output Port Connection"));
DisplayServer::get_singleton()->accessibility_update_add_custom_action(ae, CustomAccessibilityAction::ACTION_FOLLOW_INPUT, ETR("Follow Input Port Connection"));
DisplayServer::get_singleton()->accessibility_update_add_custom_action(ae, CustomAccessibilityAction::ACTION_FOLLOW_OUTPUT, ETR("Follow Output Port Connection"));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_CUSTOM, callable_mp(this, &GraphNode::_accessibility_action_slot));
} break;
case NOTIFICATION_FOCUS_EXIT: {
selected_slot = -1;
queue_redraw();
} break;
case NOTIFICATION_DRAW: {
// Used for layout calculations.
Ref<StyleBox> sb_panel = theme_cache.panel;
@ -317,6 +626,7 @@ void GraphNode::_notification(int p_what) {
Ref<StyleBox> sb_to_draw_titlebar = selected ? theme_cache.titlebar_selected : theme_cache.titlebar;
Ref<StyleBox> sb_slot = theme_cache.slot;
Ref<StyleBox> sb_slot_selected = theme_cache.slot_selected;
int port_h_offset = theme_cache.port_h_offset;
@ -329,6 +639,10 @@ void GraphNode::_notification(int p_what) {
// Draw body (slots area) stylebox.
draw_style_box(sb_to_draw_panel, body_rect);
if (has_focus()) {
draw_style_box(theme_cache.panel_focus, body_rect);
}
// Draw title bar stylebox above.
draw_style_box(sb_to_draw_titlebar, titlebar_rect);
@ -348,12 +662,18 @@ void GraphNode::_notification(int p_what) {
// Left port.
if (slot.enable_left) {
draw_port(slot_index, Point2i(port_h_offset, slot_y_cache[E.key] + sb_panel->get_margin(SIDE_TOP)), true, slot.color_left);
draw_port(slot_index, Point2i(port_h_offset, slot_y_cache[E.key]), true, slot.color_left);
}
// Right port.
if (slot.enable_right) {
draw_port(slot_index, Point2i(get_size().x - port_h_offset, slot_y_cache[E.key] + sb_panel->get_margin(SIDE_TOP)), false, slot.color_right);
draw_port(slot_index, Point2i(get_size().x - port_h_offset, slot_y_cache[E.key]), false, slot.color_right);
}
if (slot_index == selected_slot) {
Size2i port_sz = theme_cache.port->get_size();
draw_style_box(sb_slot_selected, Rect2i(port_h_offset - port_sz.x, slot_y_cache[E.key] + sb_panel->get_margin(SIDE_TOP) - port_sz.y, port_sz.x * 2, port_sz.y * 2));
draw_style_box(sb_slot_selected, Rect2i(get_size().x - port_h_offset - port_sz.x, slot_y_cache[E.key] + sb_panel->get_margin(SIDE_TOP) - port_sz.y, port_sz.x * 2, port_sz.y * 2));
}
// Draw slot stylebox.
@ -400,6 +720,8 @@ void GraphNode::set_slot(int p_slot_index, bool p_enable_left, int p_type_left,
slot.custom_port_icon_right = p_custom_right;
slot.draw_stylebox = p_draw_stylebox;
slot_table[p_slot_index] = slot;
queue_accessibility_update();
queue_redraw();
port_pos_dirty = true;
@ -408,12 +730,16 @@ void GraphNode::set_slot(int p_slot_index, bool p_enable_left, int p_type_left,
void GraphNode::clear_slot(int p_slot_index) {
slot_table.erase(p_slot_index);
queue_accessibility_update();
queue_redraw();
port_pos_dirty = true;
}
void GraphNode::clear_all_slots() {
slot_table.clear();
queue_accessibility_update();
queue_redraw();
port_pos_dirty = true;
}
@ -433,6 +759,8 @@ void GraphNode::set_slot_enabled_left(int p_slot_index, bool p_enable) {
}
slot_table[p_slot_index].enable_left = p_enable;
queue_accessibility_update();
queue_redraw();
port_pos_dirty = true;
@ -447,6 +775,8 @@ void GraphNode::set_slot_type_left(int p_slot_index, int p_type) {
}
slot_table[p_slot_index].type_left = p_type;
queue_accessibility_update();
queue_redraw();
port_pos_dirty = true;
@ -517,6 +847,8 @@ void GraphNode::set_slot_enabled_right(int p_slot_index, bool p_enable) {
}
slot_table[p_slot_index].enable_right = p_enable;
queue_accessibility_update();
queue_redraw();
port_pos_dirty = true;
@ -531,6 +863,8 @@ void GraphNode::set_slot_type_right(int p_slot_index, int p_type) {
}
slot_table[p_slot_index].type_right = p_type;
queue_accessibility_update();
queue_redraw();
port_pos_dirty = true;
@ -646,14 +980,9 @@ Size2 GraphNode::get_minimum_size() const {
void GraphNode::_port_pos_update() {
int edgeofs = theme_cache.port_h_offset;
int separation = theme_cache.separation;
Ref<StyleBox> sb_panel = theme_cache.panel;
Ref<StyleBox> sb_titlebar = theme_cache.titlebar;
left_port_cache.clear();
right_port_cache.clear();
int vertical_ofs = titlebar_hbox->get_size().height + sb_titlebar->get_minimum_size().height + sb_panel->get_margin(SIDE_TOP);
int slot_index = 0;
for (int i = 0; i < get_child_count(false); i++) {
@ -663,11 +992,12 @@ void GraphNode::_port_pos_update() {
}
Size2i size = child->get_rect().size;
Point2 pos = child->get_position();
if (slot_table.has(slot_index)) {
if (slot_table[slot_index].enable_left) {
PortCache port_cache;
port_cache.pos = Point2i(edgeofs, vertical_ofs + size.height / 2);
port_cache.pos = Point2i(edgeofs, pos.y + size.height / 2);
port_cache.type = slot_table[slot_index].type_left;
port_cache.color = slot_table[slot_index].color_left;
port_cache.slot_index = slot_index;
@ -675,7 +1005,7 @@ void GraphNode::_port_pos_update() {
}
if (slot_table[slot_index].enable_right) {
PortCache port_cache;
port_cache.pos = Point2i(get_size().width - edgeofs, vertical_ofs + size.height / 2);
port_cache.pos = Point2i(get_size().width - edgeofs, pos.y + size.height / 2);
port_cache.type = slot_table[slot_index].type_right;
port_cache.color = slot_table[slot_index].color_right;
port_cache.slot_index = slot_index;
@ -683,10 +1013,12 @@ void GraphNode::_port_pos_update() {
}
}
vertical_ofs += separation;
vertical_ofs += size.height;
slot_index++;
}
slot_count = slot_index;
if (selected_slot >= slot_count) {
selected_slot = -1;
}
port_pos_dirty = false;
}
@ -781,6 +1113,25 @@ int GraphNode::get_output_port_slot(int p_port_idx) {
return right_port_cache[p_port_idx].slot_index;
}
String GraphNode::get_accessibility_container_name(const Node *p_node) const {
int idx = 0;
for (int i = 0; i < get_child_count(false); i++) {
Control *child = as_sortable_control(get_child(i, false), SortableVisibilityMode::IGNORE);
if (!child) {
continue;
}
if (child == p_node) {
String name = get_accessibility_name();
if (name.is_empty()) {
name = get_name();
}
return vformat(ETR(", in slot %d of graph node %s (%s)"), idx + 1, name, get_title());
}
idx++;
}
return String();
}
void GraphNode::set_title(const String &p_title) {
if (title == p_title) {
return;
@ -890,9 +1241,11 @@ void GraphNode::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel_selected);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel_focus);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, titlebar);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, titlebar_selected);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, slot);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, slot_selected);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GraphNode, separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GraphNode, port_h_offset);
@ -910,7 +1263,9 @@ GraphNode::GraphNode() {
title_label = memnew(Label);
title_label->set_theme_type_variation("GraphNodeTitleLabel");
title_label->set_h_size_flags(SIZE_EXPAND_FILL);
title_label->set_focus_mode(Control::FOCUS_NONE);
titlebar_hbox->add_child(title_label);
set_mouse_filter(MOUSE_FILTER_STOP);
set_focus_mode(FOCUS_ACCESSIBILITY);
}

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GRAPH_NODE_H
#define GRAPH_NODE_H
#pragma once
#include "scene/gui/graph_element.h"
@ -67,6 +66,14 @@ class GraphNode : public GraphElement {
int final_size = 0;
};
enum CustomAccessibilityAction {
ACTION_CONNECT_INPUT,
ACTION_CONNECT_OUTPUT,
ACTION_FOLLOW_INPUT,
ACTION_FOLLOW_OUTPUT,
};
void _accessibility_action_slot(const Variant &p_data);
HBoxContainer *titlebar_hbox = nullptr;
Label *title_label = nullptr;
@ -78,12 +85,17 @@ class GraphNode : public GraphElement {
HashMap<int, Slot> slot_table;
Vector<int> slot_y_cache;
int slot_count = 0;
int selected_slot = -1;
struct ThemeCache {
Ref<StyleBox> panel;
Ref<StyleBox> panel_selected;
Ref<StyleBox> panel_focus;
Ref<StyleBox> titlebar;
Ref<StyleBox> titlebar_selected;
Ref<StyleBox> slot;
Ref<StyleBox> slot_selected;
int separation = 0;
int port_h_offset = 0;
@ -113,6 +125,9 @@ protected:
void _get_property_list(List<PropertyInfo> *p_list) const;
public:
virtual String get_accessibility_container_name(const Node *p_node) const override;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void set_title(const String &p_title);
String get_title() const;
@ -173,5 +188,3 @@ public:
GraphNode();
};
#endif // GRAPH_NODE_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GRID_CONTAINER_H
#define GRID_CONTAINER_H
#pragma once
#include "scene/gui/container.h"
@ -56,5 +55,3 @@ public:
GridContainer();
};
#endif // GRID_CONTAINER_H

View file

@ -45,7 +45,7 @@ void ItemList::_shape_text(int p_idx) {
}
item.text_buf->add_string(item.xl_text, theme_cache.font, theme_cache.font_size, item.language);
if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) {
item.text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES);
item.text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES);
} else {
item.text_buf->set_break_flags(TextServer::BREAK_NONE);
}
@ -64,6 +64,7 @@ int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bo
items.write[item_id].xl_text = _atr(item_id, p_item);
_shape_text(item_id);
queue_accessibility_update();
queue_redraw();
shape_changed = true;
notify_property_list_changed();
@ -77,6 +78,7 @@ int ItemList::add_icon_item(const Ref<Texture2D> &p_item, bool p_selectable) {
items.push_back(item);
int item_id = items.size() - 1;
queue_accessibility_update();
queue_redraw();
shape_changed = true;
notify_property_list_changed();
@ -96,6 +98,7 @@ void ItemList::set_item_text(int p_idx, const String &p_text) {
items.write[p_idx].text = p_text;
items.write[p_idx].xl_text = _atr(p_idx, p_text);
_shape_text(p_idx);
queue_accessibility_update();
queue_redraw();
shape_changed = true;
}
@ -114,6 +117,7 @@ void ItemList::set_item_text_direction(int p_idx, Control::TextDirection p_text_
if (items[p_idx].text_direction != p_text_direction) {
items.write[p_idx].text_direction = p_text_direction;
_shape_text(p_idx);
queue_accessibility_update();
queue_redraw();
}
}
@ -131,6 +135,7 @@ void ItemList::set_item_language(int p_idx, const String &p_language) {
if (items[p_idx].language != p_language) {
items.write[p_idx].language = p_language;
_shape_text(p_idx);
queue_accessibility_update();
queue_redraw();
}
}
@ -149,6 +154,7 @@ void ItemList::set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode)
items.write[p_idx].auto_translate_mode = p_mode;
items.write[p_idx].xl_text = _atr(p_idx, items[p_idx].text);
_shape_text(p_idx);
queue_accessibility_update();
queue_redraw();
}
}
@ -163,7 +169,11 @@ void ItemList::set_item_tooltip_enabled(int p_idx, const bool p_enabled) {
p_idx += get_item_count();
}
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].tooltip_enabled = p_enabled;
if (items[p_idx].tooltip_enabled != p_enabled) {
items.write[p_idx].tooltip_enabled = p_enabled;
items.write[p_idx].accessibility_item_dirty = true;
queue_accessibility_update();
}
}
bool ItemList::is_item_tooltip_enabled(int p_idx) const {
@ -182,6 +192,7 @@ void ItemList::set_item_tooltip(int p_idx, const String &p_tooltip) {
}
items.write[p_idx].tooltip = p_tooltip;
queue_accessibility_update();
queue_redraw();
shape_changed = true;
}
@ -348,6 +359,8 @@ void ItemList::set_item_selectable(int p_idx, bool p_selectable) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].selectable = p_selectable;
items.write[p_idx].accessibility_item_dirty = true;
queue_accessibility_update();
}
bool ItemList::is_item_selectable(int p_idx) const {
@ -366,6 +379,8 @@ void ItemList::set_item_disabled(int p_idx, bool p_disabled) {
}
items.write[p_idx].disabled = p_disabled;
items.write[p_idx].accessibility_item_dirty = true;
queue_accessibility_update();
queue_redraw();
}
@ -403,7 +418,10 @@ void ItemList::select(int p_idx, bool p_single) {
}
for (int i = 0; i < items.size(); i++) {
items.write[i].selected = p_idx == i;
if (items.write[i].selected != (p_idx == i)) {
items.write[i].selected = (p_idx == i);
items.write[i].accessibility_item_dirty = true;
}
}
current = p_idx;
@ -411,8 +429,10 @@ void ItemList::select(int p_idx, bool p_single) {
} else {
if (items[p_idx].selectable && !items[p_idx].disabled) {
items.write[p_idx].selected = true;
items.write[p_idx].accessibility_item_dirty = true;
}
}
queue_accessibility_update();
queue_redraw();
}
@ -425,18 +445,24 @@ void ItemList::deselect(int p_idx) {
} else {
items.write[p_idx].selected = false;
}
items.write[p_idx].accessibility_item_dirty = true;
queue_accessibility_update();
queue_redraw();
}
void ItemList::deselect_all() {
if (items.size() < 1) {
if (items.is_empty()) {
return;
}
for (int i = 0; i < items.size(); i++) {
items.write[i].selected = false;
if (items.write[i].selected) {
items.write[i].selected = false;
items.write[i].accessibility_item_dirty = true;
}
}
current = -1;
queue_accessibility_update();
queue_redraw();
}
@ -457,6 +483,7 @@ void ItemList::set_current(int p_current) {
select(p_current, true);
} else {
current = p_current;
queue_accessibility_update();
queue_redraw();
}
}
@ -477,6 +504,7 @@ void ItemList::move_item(int p_from_idx, int p_to_idx) {
items.remove_at(p_from_idx);
items.insert(p_to_idx, item);
queue_accessibility_update();
queue_redraw();
shape_changed = true;
notify_property_list_changed();
@ -489,7 +517,17 @@ void ItemList::set_item_count(int p_count) {
return;
}
if (items.size() > p_count) {
for (int i = p_count; i < items.size(); i++) {
if (items[i].accessibility_item_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(items.write[i].accessibility_item_element);
items.write[i].accessibility_item_element = RID();
}
}
}
items.resize(p_count);
queue_accessibility_update();
queue_redraw();
shape_changed = true;
notify_property_list_changed();
@ -502,10 +540,15 @@ int ItemList::get_item_count() const {
void ItemList::remove_item(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
if (items[p_idx].accessibility_item_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(items.write[p_idx].accessibility_item_element);
items.write[p_idx].accessibility_item_element = RID();
}
items.remove_at(p_idx);
if (current == p_idx) {
current = -1;
}
queue_accessibility_update();
queue_redraw();
shape_changed = true;
defer_select_single = -1;
@ -513,9 +556,16 @@ void ItemList::remove_item(int p_idx) {
}
void ItemList::clear() {
for (int i = 0; i < items.size(); i++) {
if (items[i].accessibility_item_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(items.write[i].accessibility_item_element);
items.write[i].accessibility_item_element = RID();
}
}
items.clear();
current = -1;
ensure_selected_visible = false;
queue_accessibility_update();
queue_redraw();
shape_changed = true;
defer_select_single = -1;
@ -558,13 +608,14 @@ void ItemList::set_max_text_lines(int p_lines) {
max_text_lines = p_lines;
for (int i = 0; i < items.size(); i++) {
if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) {
items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES);
items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES);
items.write[i].text_buf->set_max_lines_visible(p_lines);
} else {
items.write[i].text_buf->set_break_flags(TextServer::BREAK_NONE);
}
}
shape_changed = true;
queue_accessibility_update();
queue_redraw();
}
}
@ -581,6 +632,7 @@ void ItemList::set_max_columns(int p_amount) {
}
max_columns = p_amount;
queue_accessibility_update();
queue_redraw();
shape_changed = true;
}
@ -595,6 +647,7 @@ void ItemList::set_select_mode(SelectMode p_mode) {
}
select_mode = p_mode;
queue_accessibility_update();
queue_redraw();
}
@ -608,7 +661,7 @@ void ItemList::set_icon_mode(IconMode p_mode) {
icon_mode = p_mode;
for (int i = 0; i < items.size(); i++) {
if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) {
items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES);
items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES);
} else {
items.write[i].text_buf->set_break_flags(TextServer::BREAK_NONE);
}
@ -694,7 +747,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
if (mm.is_valid()) {
int closest = get_item_at_position(mm->get_position(), true);
if (closest != hovered) {
prev_hovered = hovered;
hovered = closest;
queue_accessibility_update();
queue_redraw();
}
}
@ -834,6 +889,21 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
if (p_event->is_pressed() && items.size() > 0) {
if (p_event->is_action("ui_menu", true)) {
if (current != -1 && allow_rmb_select) {
int i = current;
if (items[i].disabled) {
// Don't emit any signal or do any action with clicked item when disabled.
return;
}
emit_signal(SNAME("item_clicked"), i, get_item_rect(i).position, MouseButton::RIGHT);
accept_event();
return;
}
}
if (p_event->is_action("ui_up", true)) {
if (!search_string.is_empty()) {
uint64_t now = OS::get_singleton()->get_ticks_msec();
@ -1076,8 +1146,131 @@ static Rect2 _adjust_to_max_size(Size2 p_size, Size2 p_max_size) {
return Rect2(ofs_x, ofs_y, tex_width, tex_height);
}
RID ItemList::get_focused_accessibility_element() const {
if (current == -1) {
return get_accessibility_element();
} else {
const Item &item = items[current];
return item.accessibility_item_element;
}
}
void ItemList::_accessibility_action_scroll_set(const Variant &p_data) {
const Point2 &pos = p_data;
scroll_bar_h->set_value(pos.x);
scroll_bar_v->set_value(pos.y);
}
void ItemList::_accessibility_action_scroll_up(const Variant &p_data) {
scroll_bar_v->set_value(scroll_bar_v->get_value() - scroll_bar_v->get_page() / 4);
}
void ItemList::_accessibility_action_scroll_down(const Variant &p_data) {
scroll_bar_v->set_value(scroll_bar_v->get_value() + scroll_bar_v->get_page() / 4);
}
void ItemList::_accessibility_action_scroll_left(const Variant &p_data) {
scroll_bar_h->set_value(scroll_bar_h->get_value() - scroll_bar_h->get_page() / 4);
}
void ItemList::_accessibility_action_scroll_right(const Variant &p_data) {
scroll_bar_h->set_value(scroll_bar_h->get_value() + scroll_bar_h->get_page() / 4);
}
void ItemList::_accessibility_action_scroll_into_view(const Variant &p_data, int p_index) {
ERR_FAIL_INDEX(p_index, items.size());
Rect2 r = items[p_index].rect_cache;
int from_v = scroll_bar_v->get_value();
int to_v = from_v + scroll_bar_v->get_page();
int from_h = scroll_bar_h->get_value();
int to_h = from_h + scroll_bar_h->get_page();
if (r.position.y < from_v) {
scroll_bar_v->set_value(r.position.y);
} else if (r.position.y + r.size.y > to_v) {
scroll_bar_v->set_value(r.position.y + r.size.y - (to_v - from_v));
}
if (r.position.x < from_h) {
scroll_bar_h->set_value(r.position.x);
} else if (r.position.x + r.size.x > to_h) {
scroll_bar_h->set_value(r.position.x + r.size.x - (to_h - from_h));
}
}
void ItemList::_accessibility_action_focus(const Variant &p_data, int p_index) {
select(p_index);
}
void ItemList::_accessibility_action_blur(const Variant &p_data, int p_index) {
deselect(p_index);
}
void ItemList::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_EXIT_TREE:
case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
for (int i = 0; i < items.size(); i++) {
items.write[i].accessibility_item_element = RID();
}
accessibility_scroll_element = RID();
} break;
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
force_update_list_size();
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_LIST_BOX);
DisplayServer::get_singleton()->accessibility_update_set_list_item_count(ae, items.size());
DisplayServer::get_singleton()->accessibility_update_set_flag(ae, DisplayServer::AccessibilityFlags::FLAG_MULTISELECTABLE, select_mode == SELECT_MULTI);
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_DOWN, callable_mp(this, &ItemList::_accessibility_action_scroll_down));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_UP, callable_mp(this, &ItemList::_accessibility_action_scroll_up));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_LEFT, callable_mp(this, &ItemList::_accessibility_action_scroll_left));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_RIGHT, callable_mp(this, &ItemList::_accessibility_action_scroll_right));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_SCROLL_OFFSET, callable_mp(this, &ItemList::_accessibility_action_scroll_set));
if (accessibility_scroll_element.is_null()) {
accessibility_scroll_element = DisplayServer::get_singleton()->accessibility_create_sub_element(ae, DisplayServer::AccessibilityRole::ROLE_CONTAINER);
}
Transform2D scroll_xform;
scroll_xform.set_origin(Vector2i(-scroll_bar_h->get_value(), -scroll_bar_v->get_value()));
DisplayServer::get_singleton()->accessibility_update_set_transform(accessibility_scroll_element, scroll_xform);
DisplayServer::get_singleton()->accessibility_update_set_bounds(accessibility_scroll_element, Rect2(0, 0, scroll_bar_h->get_max(), scroll_bar_v->get_max()));
for (int i = 0; i < items.size(); i++) {
const Item &item = items.write[i];
if (item.accessibility_item_element.is_null()) {
item.accessibility_item_element = DisplayServer::get_singleton()->accessibility_create_sub_element(accessibility_scroll_element, DisplayServer::AccessibilityRole::ROLE_LIST_BOX_OPTION);
item.accessibility_item_dirty = true;
}
if (item.accessibility_item_dirty || i == hovered || i == prev_hovered) {
DisplayServer::get_singleton()->accessibility_update_add_action(item.accessibility_item_element, DisplayServer::AccessibilityAction::ACTION_SCROLL_INTO_VIEW, callable_mp(this, &ItemList::_accessibility_action_scroll_into_view).bind(i));
DisplayServer::get_singleton()->accessibility_update_add_action(item.accessibility_item_element, DisplayServer::AccessibilityAction::ACTION_FOCUS, callable_mp(this, &ItemList::_accessibility_action_focus).bind(i));
DisplayServer::get_singleton()->accessibility_update_add_action(item.accessibility_item_element, DisplayServer::AccessibilityAction::ACTION_BLUR, callable_mp(this, &ItemList::_accessibility_action_blur).bind(i));
DisplayServer::get_singleton()->accessibility_update_set_list_item_index(item.accessibility_item_element, i);
DisplayServer::get_singleton()->accessibility_update_set_list_item_level(item.accessibility_item_element, 0);
DisplayServer::get_singleton()->accessibility_update_set_list_item_selected(item.accessibility_item_element, item.selected);
DisplayServer::get_singleton()->accessibility_update_set_name(item.accessibility_item_element, item.xl_text);
DisplayServer::get_singleton()->accessibility_update_set_flag(item.accessibility_item_element, DisplayServer::AccessibilityFlags::FLAG_DISABLED, item.disabled);
if (item.tooltip_enabled) {
DisplayServer::get_singleton()->accessibility_update_set_tooltip(item.accessibility_item_element, item.tooltip);
}
Rect2 r = get_item_rect(i);
DisplayServer::get_singleton()->accessibility_update_set_bounds(item.accessibility_item_element, Rect2(r.position, r.size));
item.accessibility_item_dirty = false;
}
}
prev_hovered = -1;
} break;
case NOTIFICATION_RESIZED: {
shape_changed = true;
queue_redraw();
@ -1089,6 +1282,7 @@ void ItemList::_notification(int p_what) {
_shape_text(i);
}
shape_changed = true;
queue_accessibility_update();
queue_redraw();
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
@ -1097,6 +1291,7 @@ void ItemList::_notification(int p_what) {
_shape_text(i);
}
shape_changed = true;
queue_accessibility_update();
queue_redraw();
} break;
@ -1537,6 +1732,8 @@ void ItemList::force_update_list_size() {
items.write[i].rect_cache.size = minsize;
items.write[i].min_rect_cache.size = minsize;
items.write[i].accessibility_item_dirty = true;
}
int fit_size = size.x - theme_cache.panel_style->get_minimum_size().width;
@ -1661,7 +1858,9 @@ void ItemList::_scroll_changed(double) {
void ItemList::_mouse_exited() {
if (hovered > -1) {
prev_hovered = hovered;
hovered = -1;
queue_accessibility_update();
queue_redraw();
}
}
@ -1763,6 +1962,7 @@ String ItemList::get_tooltip(const Point2 &p_pos) const {
void ItemList::sort_items_by_text() {
items.sort();
queue_accessibility_update();
queue_redraw();
shape_changed = true;
@ -1872,6 +2072,7 @@ void ItemList::set_auto_width(bool p_enable) {
auto_width = p_enable;
shape_changed = true;
queue_accessibility_update();
queue_redraw();
}
@ -1886,6 +2087,7 @@ void ItemList::set_auto_height(bool p_enable) {
auto_height = p_enable;
shape_changed = true;
queue_accessibility_update();
queue_redraw();
}
@ -2078,7 +2280,7 @@ void ItemList::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_text_lines", PROPERTY_HINT_RANGE, "1,10,1,or_greater"), "set_max_text_lines", "get_max_text_lines");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_width"), "set_auto_width", "has_auto_width");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_height"), "set_auto_height", "has_auto_height");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis (6+ Characters),Word Ellipsis (6+ Characters),Ellipsis (Always),Word Ellipsis (Always)"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wraparound_items"), "set_wraparound_items", "has_wraparound_items");
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_");
ADD_GROUP("Columns", "");

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef ITEM_LIST_H
#define ITEM_LIST_H
#pragma once
#include "scene/gui/control.h"
#include "scene/gui/scroll_bar.h"
@ -53,6 +52,9 @@ public:
private:
struct Item {
mutable RID accessibility_item_element;
mutable bool accessibility_item_dirty = true;
Ref<Texture2D> icon;
bool icon_transposed = false;
Rect2i icon_region;
@ -88,12 +90,14 @@ private:
Item(bool p_dummy) {}
};
RID accessibility_scroll_element;
static inline PropertyListHelper base_property_helper;
PropertyListHelper property_helper;
int current = -1;
int hovered = -1;
int prev_hovered = -1;
bool shape_changed = true;
@ -181,7 +185,18 @@ protected:
bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
void _accessibility_action_scroll_set(const Variant &p_data);
void _accessibility_action_scroll_up(const Variant &p_data);
void _accessibility_action_scroll_down(const Variant &p_data);
void _accessibility_action_scroll_left(const Variant &p_data);
void _accessibility_action_scroll_right(const Variant &p_data);
void _accessibility_action_scroll_into_view(const Variant &p_data, int p_index);
void _accessibility_action_focus(const Variant &p_data, int p_index);
void _accessibility_action_blur(const Variant &p_data, int p_index);
public:
virtual RID get_focused_accessibility_element() const override;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
int add_item(const String &p_item, const Ref<Texture2D> &p_texture = Ref<Texture2D>(), bool p_selectable = true);
@ -325,5 +340,3 @@ public:
VARIANT_ENUM_CAST(ItemList::SelectMode);
VARIANT_ENUM_CAST(ItemList::IconMode);
#endif // ITEM_LIST_H

View file

@ -55,6 +55,27 @@ TextServer::AutowrapMode Label::get_autowrap_mode() const {
return autowrap_mode;
}
void Label::set_autowrap_trim_flags(BitField<TextServer::LineBreakFlag> p_flags) {
if (autowrap_flags_trim == (p_flags & TextServer::BREAK_TRIM_MASK)) {
return;
}
autowrap_flags_trim = p_flags & TextServer::BREAK_TRIM_MASK;
for (Paragraph &para : paragraphs) {
para.lines_dirty = true;
}
queue_redraw();
update_configuration_warnings();
if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) {
update_minimum_size();
}
}
BitField<TextServer::LineBreakFlag> Label::get_autowrap_trim_flags() const {
return autowrap_flags_trim;
}
void Label::set_justification_flags(BitField<TextServer::JustificationFlag> p_flags) {
if (jst_flags == p_flags) {
return;
@ -79,6 +100,7 @@ void Label::set_uppercase(bool p_uppercase) {
uppercase = p_uppercase;
text_dirty = true;
queue_accessibility_update();
queue_redraw();
}
@ -196,7 +218,7 @@ void Label::_shape() const {
case TextServer::AUTOWRAP_OFF:
break;
}
autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES;
autowrap_flags = autowrap_flags | autowrap_flags_trim;
PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(para.text_rid, width, 0, autowrap_flags);
for (int i = 0; i < line_breaks.size(); i = i + 2) {
@ -236,6 +258,17 @@ void Label::_shape() const {
if (para.lines_dirty) {
BitField<TextServer::TextOverrunFlag> overrun_flags = TextServer::OVERRUN_NO_TRIM;
switch (overrun_behavior) {
case TextServer::OVERRUN_TRIM_WORD_ELLIPSIS_FORCE: {
overrun_flags.set_flag(TextServer::OVERRUN_TRIM);
overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY);
overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS);
overrun_flags.set_flag(TextServer::OVERRUN_ENFORCE_ELLIPSIS);
} break;
case TextServer::OVERRUN_TRIM_ELLIPSIS_FORCE: {
overrun_flags.set_flag(TextServer::OVERRUN_TRIM);
overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS);
overrun_flags.set_flag(TextServer::OVERRUN_ENFORCE_ELLIPSIS);
} break;
case TextServer::OVERRUN_TRIM_WORD_ELLIPSIS:
overrun_flags.set_flag(TextServer::OVERRUN_TRIM);
overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY);
@ -392,14 +425,15 @@ inline void draw_glyph(const Glyph &p_gl, const RID &p_canvas, const Color &p_fo
}
}
inline void draw_glyph_shadow(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_shadow_color, int p_shadow_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) {
inline void draw_glyph_shadow(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_shadow_color, const Vector2 &p_ofs, const Vector2 &shadow_ofs) {
if (p_gl.font_rid != RID()) {
if (p_font_shadow_color.a > 0) {
TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color);
}
if (p_font_shadow_color.a > 0 && p_shadow_outline_size > 0) {
TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color);
}
TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color);
}
}
inline void draw_glyph_shadow_outline(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_shadow_color, int p_shadow_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) {
if (p_gl.font_rid != RID()) {
TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color);
}
}
@ -659,6 +693,15 @@ PackedStringArray Label::get_configuration_warnings() const {
void Label::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);
DisplayServer::get_singleton()->accessibility_update_set_value(ae, xl_text);
DisplayServer::get_singleton()->accessibility_update_set_text_align(ae, horizontal_alignment);
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
String new_text = atr(text);
if (new_text == xl_text) {
@ -670,6 +713,7 @@ void Label::_notification(int p_what) {
}
text_dirty = true;
queue_accessibility_update();
queue_redraw();
update_configuration_warnings();
} break;
@ -703,7 +747,6 @@ void Label::_notification(int p_what) {
bool has_settings = settings.is_valid();
Size2 string_size;
Ref<StyleBox> style = theme_cache.normal_style;
Ref<Font> font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font;
int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size;
@ -717,7 +760,11 @@ void Label::_notification(int p_what) {
int shadow_outline_size = has_settings ? settings->get_shadow_size() : theme_cache.font_shadow_outline_size;
bool rtl_layout = is_layout_rtl();
style->draw(ci, Rect2(Point2(0, 0), get_size()));
if (has_focus()) {
theme_cache.focus_style->draw(ci, Rect2(Point2(0, 0), get_size()));
} else {
theme_cache.normal_style->draw(ci, Rect2(Point2(0, 0), get_size()));
}
bool trim_chars = (visible_chars >= 0) && (visible_chars_behavior == TextServer::VC_CHARS_AFTER_SHAPING);
bool trim_glyphs_ltr = (visible_chars >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_LTR) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && !rtl_layout));
@ -768,7 +815,10 @@ void Label::_notification(int p_what) {
// Draw shadow, outline and text. Note: Do not merge this into the single loop iteration, to prevent overlaps.
int processed_glyphs_step = 0;
for (int step = DRAW_STEP_SHADOW; step < DRAW_STEP_MAX; step++) {
for (int step = DRAW_STEP_SHADOW_OUTLINE; step < DRAW_STEP_MAX; step++) {
if (step == DRAW_STEP_SHADOW_OUTLINE && (font_shadow_color.a == 0 || shadow_outline_size <= 0)) {
continue;
}
if (step == DRAW_STEP_SHADOW && (font_shadow_color.a == 0)) {
continue;
}
@ -784,8 +834,10 @@ void Label::_notification(int p_what) {
for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end + para.start > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
if (!skip) {
if (step == DRAW_STEP_SHADOW) {
draw_glyph_shadow(ellipsis_glyphs[gl_idx], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs);
if (step == DRAW_STEP_SHADOW_OUTLINE) {
draw_glyph_shadow_outline(ellipsis_glyphs[gl_idx], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs);
} else if (step == DRAW_STEP_SHADOW) {
draw_glyph_shadow(ellipsis_glyphs[gl_idx], ci, font_shadow_color, offset_step, shadow_ofs);
} else if (step == DRAW_STEP_OUTLINE) {
draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_outline_color, outline_size, offset_step);
} else if (step == DRAW_STEP_TEXT) {
@ -814,8 +866,10 @@ void Label::_notification(int p_what) {
for (int k = 0; k < glyphs[j].repeat; k++) {
bool skip = (trim_chars && glyphs[j].end + para.start > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
if (!skip) {
if (step == DRAW_STEP_SHADOW) {
draw_glyph_shadow(glyphs[j], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs);
if (step == DRAW_STEP_SHADOW_OUTLINE) {
draw_glyph_shadow_outline(glyphs[j], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs);
} else if (step == DRAW_STEP_SHADOW) {
draw_glyph_shadow(glyphs[j], ci, font_shadow_color, offset_step, shadow_ofs);
} else if (step == DRAW_STEP_OUTLINE) {
draw_glyph_outline(glyphs[j], ci, font_outline_color, outline_size, offset_step);
} else if (step == DRAW_STEP_TEXT) {
@ -832,8 +886,10 @@ void Label::_notification(int p_what) {
for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end + para.start > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
if (!skip) {
if (step == DRAW_STEP_SHADOW) {
draw_glyph_shadow(ellipsis_glyphs[gl_idx], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs);
if (step == DRAW_STEP_SHADOW_OUTLINE) {
draw_glyph_shadow_outline(ellipsis_glyphs[gl_idx], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs);
} else if (step == DRAW_STEP_SHADOW) {
draw_glyph_shadow(ellipsis_glyphs[gl_idx], ci, font_shadow_color, offset_step, shadow_ofs);
} else if (step == DRAW_STEP_OUTLINE) {
draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_outline_color, outline_size, offset_step);
} else if (step == DRAW_STEP_TEXT) {
@ -940,7 +996,7 @@ Size2 Label::get_minimum_size() const {
const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font;
int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size;
min_size.height = MAX(min_size.height, font->get_height(font_size) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM));
min_size.height = MAX(min_size.height, font->get_height(font_size));
Size2 min_style = theme_cache.normal_style->get_minimum_size();
if (autowrap_mode != TextServer::AUTOWRAP_OFF) {
@ -1029,7 +1085,7 @@ void Label::set_horizontal_alignment(HorizontalAlignment p_alignment) {
}
}
horizontal_alignment = p_alignment;
queue_accessibility_update();
queue_redraw();
}
@ -1062,6 +1118,7 @@ void Label::set_text(const String &p_string) {
if (visible_ratio < 1) {
visible_chars = get_total_character_count() * visible_ratio;
}
queue_accessibility_update();
queue_redraw();
update_minimum_size();
update_configuration_warnings();
@ -1152,6 +1209,7 @@ void Label::set_paragraph_separator(const String &p_paragraph_separator) {
if (paragraph_separator != p_paragraph_separator) {
paragraph_separator = p_paragraph_separator;
text_dirty = true;
queue_accessibility_update();
queue_redraw();
}
}
@ -1244,6 +1302,7 @@ void Label::set_visible_characters(int p_amount) {
}
if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
text_dirty = true;
queue_accessibility_update();
}
queue_redraw();
}
@ -1268,6 +1327,7 @@ void Label::set_visible_ratio(float p_ratio) {
if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
text_dirty = true;
queue_accessibility_update();
}
queue_redraw();
}
@ -1285,6 +1345,7 @@ void Label::set_visible_characters_behavior(TextServer::VisibleCharactersBehavio
if (visible_chars_behavior != p_behavior) {
if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING || p_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
text_dirty = true;
queue_accessibility_update();
}
visible_chars_behavior = p_behavior;
queue_redraw();
@ -1342,6 +1403,8 @@ void Label::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_paragraph_separator"), &Label::get_paragraph_separator);
ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &Label::set_autowrap_mode);
ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &Label::get_autowrap_mode);
ClassDB::bind_method(D_METHOD("set_autowrap_trim_flags", "autowrap_trim_flags"), &Label::set_autowrap_trim_flags);
ClassDB::bind_method(D_METHOD("get_autowrap_trim_flags"), &Label::get_autowrap_trim_flags);
ClassDB::bind_method(D_METHOD("set_justification_flags", "justification_flags"), &Label::set_justification_flags);
ClassDB::bind_method(D_METHOD("get_justification_flags"), &Label::get_justification_flags);
ClassDB::bind_method(D_METHOD("set_clip_text", "enable"), &Label::set_clip_text);
@ -1380,11 +1443,12 @@ void Label::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_vertical_alignment", "get_vertical_alignment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_trim_flags", PROPERTY_HINT_FLAGS, vformat("Trim Spaces After Break:%d,Trim Spaces Before Break:%d", TextServer::BREAK_TRIM_START_EDGE_SPACES, TextServer::BREAK_TRIM_END_EDGE_SPACES)), "set_autowrap_trim_flags", "get_autowrap_trim_flags");
ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Justify Only After Last Tab:8,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "paragraph_separator"), "set_paragraph_separator", "get_paragraph_separator");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "is_clipping_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis (6+ Characters),Word Ellipsis (6+ Characters),Ellipsis (Always),Word Ellipsis (Always)"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "ellipsis_char"), "set_ellipsis_char", "get_ellipsis_char");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "tab_stops"), "set_tab_stops", "get_tab_stops");
@ -1404,6 +1468,7 @@ void Label::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Label, normal_style, "normal");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Label, focus_style, "focus");
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Label, line_spacing);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Label, paragraph_spacing);
@ -1419,6 +1484,7 @@ void Label::_bind_methods() {
}
Label::Label(const String &p_text) {
set_focus_mode(FOCUS_ACCESSIBILITY);
set_mouse_filter(MOUSE_FILTER_IGNORE);
set_text(p_text);
set_v_size_flags(SIZE_SHRINK_CENTER);

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef LABEL_H
#define LABEL_H
#pragma once
#include "scene/gui/control.h"
#include "scene/resources/label_settings.h"
@ -39,6 +38,7 @@ class Label : public Control {
private:
enum LabelDrawStep {
DRAW_STEP_SHADOW_OUTLINE,
DRAW_STEP_SHADOW,
DRAW_STEP_OUTLINE,
DRAW_STEP_TEXT,
@ -50,6 +50,7 @@ private:
String text;
String xl_text;
TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF;
BitField<TextServer::LineBreakFlag> autowrap_flags_trim = TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES;
BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
bool clip = false;
String el_char = U"";
@ -89,6 +90,7 @@ private:
struct ThemeCache {
Ref<StyleBox> normal_style;
Ref<StyleBox> focus_style;
Ref<Font> font;
int font_size = 0;
@ -153,6 +155,9 @@ public:
void set_autowrap_mode(TextServer::AutowrapMode p_mode);
TextServer::AutowrapMode get_autowrap_mode() const;
void set_autowrap_trim_flags(BitField<TextServer::LineBreakFlag> p_flags);
BitField<TextServer::LineBreakFlag> get_autowrap_trim_flags() const;
void set_justification_flags(BitField<TextServer::JustificationFlag> p_flags);
BitField<TextServer::JustificationFlag> get_justification_flags() const;
@ -196,5 +201,3 @@ public:
Label(const String &p_text = String());
~Label();
};
#endif // LABEL_H

View file

@ -33,6 +33,7 @@
#include "core/input/input_map.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "core/string/translation_server.h"
#include "scene/gui/label.h"
#include "scene/main/window.h"
#include "scene/theme/theme_db.h"
@ -399,7 +400,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
if (context_menu_enabled) {
_update_context_menu();
menu->set_position(get_screen_position() + get_local_mouse_position());
menu->set_position(get_screen_transform().xform(get_local_mouse_position()));
menu->reset_size();
menu->popup();
}
@ -486,6 +487,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
if (!pass && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
DisplayServer::get_singleton()->clipboard_set_primary(text);
}
queue_accessibility_update();
} else if (b->is_double_click()) {
// Double-click select word.
last_dblclk = OS::get_singleton()->get_ticks_msec();
@ -500,6 +502,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
selection.creating = true;
selection.start_column = caret_column;
set_caret_column(selection.end);
queue_accessibility_update();
break;
}
}
@ -630,7 +633,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
if (k->is_action("ui_menu", true)) {
_update_context_menu();
Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2);
menu->set_position(get_screen_position() + pos);
menu->set_position(get_screen_transform().xform(pos));
menu->reset_size();
menu->popup();
menu->grab_focus();
@ -668,13 +671,14 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
return;
}
// Start Unicode input (hold).
// Start Unicode Alt input (hold).
if (k->is_alt_pressed() && k->get_keycode() == Key::KP_ADD && !alt_start && !alt_start_no_hold) {
if (selection.enabled) {
selection_delete();
}
alt_start = true;
alt_code = 0;
alt_mode = ALT_INPUT_UNICODE;
ime_text = "u";
ime_selection = Vector2i(0, -1);
_shape();
@ -690,6 +694,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
}
alt_start_no_hold = true;
alt_code = 0;
alt_mode = ALT_INPUT_UNICODE;
ime_text = "u";
ime_selection = Vector2i(0, -1);
_shape();
@ -698,37 +703,101 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
return;
}
// Start OEM Alt input (hold).
if (k->is_alt_pressed() && k->get_keycode() >= Key::KP_1 && k->get_keycode() <= Key::KP_9 && !alt_start && !alt_start_no_hold) {
if (selection.enabled) {
selection_delete();
}
alt_start = true;
alt_code = (uint32_t)(k->get_keycode() - Key::KP_0);
alt_mode = ALT_INPUT_OEM;
ime_text = vformat("o%s", String::num_int64(alt_code, 10));
ime_selection = Vector2i(0, -1);
_shape();
queue_redraw();
accept_event();
return;
}
// Start Windows Alt input (hold).
if (k->is_alt_pressed() && k->get_keycode() == Key::KP_0 && !alt_start && !alt_start_no_hold) {
if (selection.enabled) {
selection_delete();
}
alt_start = true;
alt_mode = ALT_INPUT_WIN;
alt_code = 0;
ime_text = "w";
ime_selection = Vector2i(0, -1);
_shape();
queue_redraw();
accept_event();
return;
}
// Update Unicode input.
if (k->is_pressed() && ((k->is_alt_pressed() && alt_start) || alt_start_no_hold)) {
if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
alt_code = alt_code << 4;
if (alt_mode == ALT_INPUT_UNICODE) {
alt_code = alt_code << 4;
} else {
alt_code = alt_code * 10;
}
alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
} else if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
alt_code = alt_code << 4;
if (alt_mode == ALT_INPUT_UNICODE) {
alt_code = alt_code << 4;
} else {
alt_code = alt_code * 10;
}
alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
} else if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
} else if (alt_mode == ALT_INPUT_UNICODE && k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
alt_code = alt_code << 4;
alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
} else if ((Key)k->get_unicode() >= Key::KEY_0 && (Key)k->get_unicode() <= Key::KEY_9) {
alt_code = alt_code << 4;
if (alt_mode == ALT_INPUT_UNICODE) {
alt_code = alt_code << 4;
} else {
alt_code = alt_code * 10;
}
alt_code += (uint32_t)((Key)k->get_unicode() - Key::KEY_0);
} else if ((Key)k->get_unicode() >= Key::A && (Key)k->get_unicode() <= Key::F) {
} else if (alt_mode == ALT_INPUT_UNICODE && (Key)k->get_unicode() >= Key::A && (Key)k->get_unicode() <= Key::F) {
alt_code = alt_code << 4;
alt_code += (uint32_t)((Key)k->get_unicode() - Key::A) + 10;
} else if (k->get_physical_keycode() >= Key::KEY_0 && k->get_physical_keycode() <= Key::KEY_9) {
alt_code = alt_code << 4;
if (alt_mode == ALT_INPUT_UNICODE) {
alt_code = alt_code << 4;
} else {
alt_code = alt_code * 10;
}
alt_code += (uint32_t)(k->get_physical_keycode() - Key::KEY_0);
}
if (k->get_keycode() == Key::BACKSPACE) {
alt_code = alt_code >> 4;
if (alt_mode == ALT_INPUT_UNICODE) {
alt_code = alt_code >> 4;
} else {
alt_code = alt_code / 10;
}
}
if (alt_code > 0x10ffff) {
alt_code = 0x10ffff;
}
if (alt_code > 0) {
ime_text = vformat("u%s", String::num_int64(alt_code, 16, true));
if (alt_mode == ALT_INPUT_UNICODE) {
ime_text = vformat("u%s", String::num_int64(alt_code, 16, true));
} else if (alt_mode == ALT_INPUT_OEM) {
ime_text = vformat("o%s", String::num_int64(alt_code, 10));
} else if (alt_mode == ALT_INPUT_WIN) {
ime_text = vformat("w%s", String::num_int64(alt_code, 10));
}
} else {
ime_text = "u";
if (alt_mode == ALT_INPUT_UNICODE) {
ime_text = "u";
} else if (alt_mode == ALT_INPUT_OEM) {
ime_text = "o";
} else if (alt_mode == ALT_INPUT_WIN) {
ime_text = "w";
}
}
ime_selection = Vector2i(0, -1);
_shape();
@ -741,11 +810,32 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
if ((!k->is_pressed() && alt_start && k->get_keycode() == Key::ALT) || (alt_start_no_hold && (k->is_action("ui_text_submit", true) || k->is_action("ui_accept", true)))) {
alt_start = false;
alt_start_no_hold = false;
if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff)) {
ime_text = String();
ime_selection = Vector2i();
char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
insert_text_at_caret(ucodestr);
if (alt_mode == ALT_INPUT_UNICODE) {
if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff)) {
char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
insert_text_at_caret(ucodestr);
}
} else if (alt_mode == ALT_INPUT_OEM) {
if (alt_code > 0x00 && alt_code <= 0xff) {
char32_t ucodestr[2] = { alt_code_oem437[alt_code], 0 };
insert_text_at_caret(ucodestr);
} else if ((alt_code > 0xff && alt_code < 0xd800) || (alt_code > 0xdfff)) {
char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
insert_text_at_caret(ucodestr);
}
} else if (alt_mode == ALT_INPUT_WIN) {
if (alt_code > 0x00 && alt_code <= 0xff) {
char32_t ucodestr[2] = { alt_code_cp1252[alt_code], 0 };
insert_text_at_caret(ucodestr);
} else if ((alt_code > 0xff && alt_code < 0xd800) || (alt_code > 0xdfff)) {
char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
insert_text_at_caret(ucodestr);
}
}
alt_mode = ALT_INPUT_NONE;
} else {
ime_text = String();
ime_selection = Vector2i();
@ -760,6 +850,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
if (alt_start_no_hold && k->is_action("ui_cancel", true)) {
alt_start = false;
alt_start_no_hold = false;
alt_mode = ALT_INPUT_NONE;
ime_text = String();
ime_selection = Vector2i();
_shape();
@ -973,7 +1064,9 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
if (p_data.is_string() && is_editable()) {
apply_ime();
set_caret_at_pixel_pos(p_point.x);
if (p_point != Vector2(Math::INF, Math::INF)) {
set_caret_at_pixel_pos(p_point.x);
}
int caret_column_tmp = caret_column;
bool is_inside_sel = selection.enabled && caret_column >= selection.begin && caret_column <= selection.end;
if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
@ -1009,6 +1102,7 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
}
text_changed_dirty = true;
}
queue_accessibility_update();
queue_redraw();
}
}
@ -1035,6 +1129,33 @@ void LineEdit::_update_theme_item_cache() {
theme_cache.base_scale = get_theme_default_base_scale();
}
void LineEdit::_accessibility_action_set_selection(const Variant &p_data) {
Dictionary new_selection = p_data;
int sel_start_pos = new_selection["start_char"];
int sel_end_pos = new_selection["end_char"];
select(sel_start_pos, sel_end_pos);
}
void LineEdit::_accessibility_action_replace_selected(const Variant &p_data) {
String new_text = p_data;
insert_text_at_caret(new_text);
}
void LineEdit::_accessibility_action_set_value(const Variant &p_data) {
String new_text = p_data;
set_text(new_text);
}
void LineEdit::_accessibility_action_menu(const Variant &p_data) {
_update_context_menu();
Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2);
menu->set_position(get_screen_position() + pos);
menu->reset_size();
menu->popup();
menu->grab_focus();
}
void LineEdit::_notification(int p_what) {
switch (p_what) {
#ifdef TOOLS_ENABLED
@ -1049,6 +1170,98 @@ void LineEdit::_notification(int p_what) {
}
} break;
#endif
case NOTIFICATION_EXIT_TREE:
case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
accessibility_text_root_element = RID();
} break;
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_TEXT_FIELD);
bool using_placeholder = text.is_empty() && ime_text.is_empty();
if (using_placeholder && !placeholder.is_empty()) {
DisplayServer::get_singleton()->accessibility_update_set_placeholder(ae, atr(placeholder));
}
if (!placeholder.is_empty() && get_accessibility_name().is_empty()) {
DisplayServer::get_singleton()->accessibility_update_set_name(ae, atr(placeholder));
}
DisplayServer::get_singleton()->accessibility_update_set_flag(ae, DisplayServer::AccessibilityFlags::FLAG_READONLY, !editable);
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_TEXT_SELECTION, callable_mp(this, &LineEdit::_accessibility_action_set_selection));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_REPLACE_SELECTED_TEXT, callable_mp(this, &LineEdit::_accessibility_action_replace_selected));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_VALUE, callable_mp(this, &LineEdit::_accessibility_action_set_value));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SHOW_CONTEXT_MENU, callable_mp(this, &LineEdit::_accessibility_action_menu));
if (!language.is_empty()) {
DisplayServer::get_singleton()->accessibility_update_set_language(ae, language);
} else {
DisplayServer::get_singleton()->accessibility_update_set_language(ae, TranslationServer::get_singleton()->get_tool_locale());
}
bool rtl = is_layout_rtl();
Ref<StyleBox> style = theme_cache.normal;
Ref<Font> font = theme_cache.font;
Size2 size = get_size();
int x_ofs = 0;
float text_width = TS->shaped_text_get_size(text_rid).x;
float text_height = TS->shaped_text_get_size(text_rid).y;
int y_area = size.height - style->get_minimum_size().height;
int y_ofs = style->get_offset().y + (y_area - text_height) / 2;
switch (alignment) {
case HORIZONTAL_ALIGNMENT_FILL:
case HORIZONTAL_ALIGNMENT_LEFT: {
if (rtl) {
x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - Math::ceil(style->get_margin(SIDE_RIGHT) + (text_width))));
} else {
x_ofs = style->get_offset().x;
}
} break;
case HORIZONTAL_ALIGNMENT_CENTER: {
if (!Math::is_zero_approx(scroll_offset)) {
x_ofs = style->get_offset().x;
} else {
x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - (text_width)) / 2);
}
} break;
case HORIZONTAL_ALIGNMENT_RIGHT: {
if (rtl) {
x_ofs = style->get_offset().x;
} else {
x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - Math::ceil(style->get_margin(SIDE_RIGHT) + (text_width))));
}
} break;
}
bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled;
if (right_icon.is_valid() || display_clear_icon) {
Ref<Texture2D> r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon;
if (alignment == HORIZONTAL_ALIGNMENT_CENTER) {
if (Math::is_zero_approx(scroll_offset)) {
x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2);
}
} else {
x_ofs = MAX(style->get_margin(SIDE_LEFT), x_ofs - r_icon->get_width() - style->get_margin(SIDE_RIGHT));
}
}
float text_off_x = x_ofs + scroll_offset;
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);
}
Transform2D text_xform;
text_xform.set_origin(Vector2i(text_off_x, y_ofs));
DisplayServer::get_singleton()->accessibility_update_set_transform(accessibility_text_root_element, text_xform);
if (selection.enabled) {
DisplayServer::get_singleton()->accessibility_update_set_text_selection(ae, accessibility_text_root_element, selection.begin, accessibility_text_root_element, selection.end);
} else {
DisplayServer::get_singleton()->accessibility_update_set_text_selection(ae, accessibility_text_root_element, caret_column, accessibility_text_root_element, caret_column);
}
} break;
case NOTIFICATION_RESIZED: {
_fit_to_width();
@ -1202,12 +1415,14 @@ void LineEdit::_notification(int p_what) {
if (rect.position.x < x_ofs) {
rect.size.x -= (x_ofs - rect.position.x);
rect.position.x = x_ofs;
} else if (rect.position.x + rect.size.x > ofs_max) {
}
if (rect.position.x + rect.size.x > ofs_max) {
rect.size.x = ofs_max - rect.position.x;
}
RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, selection_color);
}
}
const Glyph *glyphs = TS->shaped_text_get_glyphs(text_rid);
int gl_size = TS->shaped_text_get_glyph_count(text_rid);
@ -1733,7 +1948,7 @@ void LineEdit::_validate_caret_can_draw() {
draw_caret = true;
caret_blink_timer = 0.0;
}
caret_can_draw = editing && (window_has_focus || (menu && menu->has_focus())) && (has_focus() || caret_force_displayed);
caret_can_draw = (caret_force_displayed && !is_part_of_edited_scene()) || (editing && (window_has_focus || (menu && menu->has_focus())) && has_focus());
}
void LineEdit::delete_char() {
@ -1920,6 +2135,8 @@ void LineEdit::set_caret_column(int p_column) {
caret_column = p_column;
queue_accessibility_update();
// Fit to window.
if (!is_inside_tree()) {
@ -1988,6 +2205,7 @@ void LineEdit::set_caret_column(int p_column) {
scroll_offset = MIN(0, scroll_offset);
queue_accessibility_update();
queue_redraw();
}
@ -2016,7 +2234,7 @@ void LineEdit::insert_text_at_caret(String p_text) {
}
}
String pre = text.substr(0, caret_column);
String post = text.substr(caret_column, text.length() - caret_column);
String post = text.substr(caret_column);
text = pre + p_text + post;
_shape();
TextServer::Direction dir = TS->shaped_text_get_dominant_direction_in_range(text_rid, caret_column, caret_column + p_text.length());
@ -2082,6 +2300,7 @@ void LineEdit::deselect() {
selection.enabled = false;
selection.creating = false;
selection.double_click = false;
queue_accessibility_update();
queue_redraw();
}
@ -2140,6 +2359,7 @@ void LineEdit::selection_fill_at_caret() {
}
selection.enabled = (selection.begin != selection.end);
queue_accessibility_update();
}
void LineEdit::select_all() {
@ -2155,6 +2375,7 @@ void LineEdit::select_all() {
selection.begin = 0;
selection.end = text.length();
selection.enabled = true;
queue_accessibility_update();
queue_redraw();
}
@ -2172,6 +2393,7 @@ void LineEdit::set_editable(bool p_editable) {
_validate_caret_can_draw();
update_minimum_size();
queue_accessibility_update();
queue_redraw();
}
@ -2241,6 +2463,7 @@ void LineEdit::select(int p_from, int p_to) {
selection.end = p_to;
selection.creating = false;
selection.double_click = false;
queue_accessibility_update();
queue_redraw();
}
@ -2611,7 +2834,7 @@ void LineEdit::_shape() {
t = s.repeat(text.length() + ime_text.length());
} else {
if (!ime_text.is_empty()) {
t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length());
t = text.substr(0, caret_column) + ime_text + text.substr(caret_column);
} else {
t = text;
}
@ -2634,6 +2857,13 @@ void LineEdit::_shape() {
if ((expand_to_text_length && old_size.x != size.x) || (old_size.y != size.y)) {
update_minimum_size();
}
if (accessibility_text_root_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(accessibility_text_root_element);
accessibility_text_root_element = RID();
}
queue_accessibility_update();
}
void LineEdit::_fit_to_width() {
@ -2707,7 +2937,6 @@ Key LineEdit::_get_menu_action_accelerator(const String &p_action) {
void LineEdit::_generate_context_menu() {
menu = memnew(PopupMenu);
add_child(menu, false, INTERNAL_MODE_FRONT);
menu->force_parent_owned();
menu_dir = memnew(PopupMenu);
menu_dir->add_radio_check_item(ETR("Same as Layout Direction"), MENU_DIR_INHERITED);

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef LINE_EDIT_H
#define LINE_EDIT_H
#pragma once
#include "scene/gui/control.h"
#include "scene/gui/popup_menu.h"
@ -93,6 +92,14 @@ private:
bool pass = false;
bool text_changed_dirty = false;
enum AltInputMode {
ALT_INPUT_NONE,
ALT_INPUT_UNICODE,
ALT_INPUT_OEM,
ALT_INPUT_WIN,
};
AltInputMode alt_mode = ALT_INPUT_NONE;
bool alt_start = false;
bool alt_start_no_hold = false;
uint32_t alt_code = 0;
@ -106,6 +113,7 @@ private:
Point2 ime_selection;
RID text_rid;
RID accessibility_text_root_element;
float full_width = 0.0;
bool selecting_enabled = true;
@ -265,6 +273,11 @@ protected:
virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void _accessibility_action_set_selection(const Variant &p_data);
void _accessibility_action_replace_selected(const Variant &p_data);
void _accessibility_action_set_value(const Variant &p_data);
void _accessibility_action_menu(const Variant &p_data);
public:
void edit();
void unedit();
@ -419,5 +432,3 @@ public:
VARIANT_ENUM_CAST(LineEdit::MenuItems);
VARIANT_ENUM_CAST(LineEdit::VirtualKeyboardType);
#endif // LINE_EDIT_H

View file

@ -44,6 +44,8 @@ void LinkButton::_shape() {
}
TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, xl_text));
text_buf->add_string(xl_text, font, font_size, language);
queue_accessibility_update();
}
void LinkButton::set_text(const String &p_text) {
@ -109,7 +111,10 @@ String LinkButton::get_language() const {
}
void LinkButton::set_uri(const String &p_uri) {
uri = p_uri;
if (uri != p_uri) {
uri = p_uri;
queue_accessibility_update();
}
}
String LinkButton::get_uri() const {
@ -147,6 +152,17 @@ Size2 LinkButton::get_minimum_size() const {
void LinkButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_LINK);
if (!xl_text.is_empty() && get_accessibility_name().is_empty()) {
DisplayServer::get_singleton()->accessibility_update_set_name(ae, xl_text);
}
DisplayServer::get_singleton()->accessibility_update_set_url(ae, uri);
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
xl_text = atr(text);
_shape();
@ -288,7 +304,7 @@ void LinkButton::_bind_methods() {
LinkButton::LinkButton(const String &p_text) {
text_buf.instantiate();
set_focus_mode(FOCUS_NONE);
set_focus_mode(FOCUS_ACCESSIBILITY);
set_default_cursor_shape(CURSOR_POINTING_HAND);
set_text(p_text);

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef LINK_BUTTON_H
#define LINK_BUTTON_H
#pragma once
#include "scene/gui/base_button.h"
#include "scene/resources/text_line.h"
@ -110,5 +109,3 @@ public:
};
VARIANT_ENUM_CAST(LinkButton::UnderlineMode);
#endif // LINK_BUTTON_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef MARGIN_CONTAINER_H
#define MARGIN_CONTAINER_H
#pragma once
#include "scene/gui/container.h"
@ -57,5 +56,3 @@ public:
MarginContainer();
};
#endif // MARGIN_CONTAINER_H

View file

@ -85,6 +85,15 @@ void MenuBar::gui_input(const Ref<InputEvent> &p_event) {
_open_popup(selected_menu, true);
}
return;
} else if (p_event->is_action("ui_accept", true) && p_event->is_pressed()) {
if (focused_menu == -1) {
focused_menu = 0;
}
selected_menu = focused_menu;
if (active_menu >= 0) {
get_menu_popup(active_menu)->hide();
}
_open_popup(selected_menu, true);
}
Ref<InputEventMouseMotion> mm = p_event;
@ -219,9 +228,9 @@ void MenuBar::bind_global_menu() {
String prev_tag;
if (start_index >= 0) {
for (int i = 0; i < count; i++) {
String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slice("#", 1);
String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slicec('#', 1);
if (!tag.is_empty() && tag != prev_tag) {
MenuBar *mb = Object::cast_to<MenuBar>(ObjectDB::get_instance(ObjectID(static_cast<uint64_t>(tag.to_int()))));
MenuBar *mb = ObjectDB::get_instance<MenuBar>(ObjectID(static_cast<uint64_t>(tag.to_int())));
if (mb && mb->get_start_index() >= start_index) {
global_start_idx = i;
break;
@ -276,6 +285,12 @@ void MenuBar::unbind_global_menu() {
void MenuBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_MENU_BAR);
} break;
case NOTIFICATION_ENTER_TREE: {
if (get_menu_count() > 0) {
_refresh_menu_names();
@ -408,6 +423,10 @@ void MenuBar::_draw_menu_item(int p_index) {
bool pressed = (active_menu == p_index);
bool rtl = is_layout_rtl();
if (has_focus() && focused_menu == -1 && p_index == 0) {
hovered = true;
}
if (menu_cache[p_index].hidden) {
return;
}
@ -548,7 +567,7 @@ int MenuBar::get_menu_idx_from_control(PopupMenu *p_child) const {
}
void MenuBar::_popup_changed(ObjectID p_menu) {
PopupMenu *pm = Object::cast_to<PopupMenu>(ObjectDB::get_instance(p_menu));
PopupMenu *pm = ObjectDB::get_instance<PopupMenu>(p_menu);
if (!pm) {
return;
}
@ -950,6 +969,7 @@ String MenuBar::get_tooltip(const Point2 &p_pos) const {
}
MenuBar::MenuBar() {
set_focus_mode(FOCUS_ALL);
set_process_shortcut_input(true);
}

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef MENU_BAR_H
#define MENU_BAR_H
#pragma once
#include "scene/gui/popup_menu.h"
@ -195,5 +194,3 @@ public:
MenuBar();
~MenuBar();
};
#endif // MENU_BAR_H

View file

@ -126,6 +126,14 @@ int MenuButton::get_item_count() const {
void MenuButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);
DisplayServer::get_singleton()->accessibility_update_set_popup_type(ae, DisplayServer::AccessibilityPopupType::POPUP_MENU);
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
popup->set_layout_direction((Window::LayoutDirection)get_layout_direction());
} break;
@ -218,7 +226,7 @@ MenuButton::MenuButton(const String &p_text) :
set_toggle_mode(true);
set_disable_shortcuts(false);
set_process_shortcut_input(true);
set_focus_mode(FOCUS_NONE);
set_focus_mode(FOCUS_ACCESSIBILITY);
set_action_mode(ACTION_MODE_BUTTON_PRESS);
popup = memnew(PopupMenu);

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef MENU_BUTTON_H
#define MENU_BUTTON_H
#pragma once
#include "scene/gui/button.h"
#include "scene/gui/popup_menu.h"
@ -78,5 +77,3 @@ public:
MenuButton(const String &p_text = String());
~MenuButton();
};
#endif // MENU_BUTTON_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef NINE_PATCH_RECT_H
#define NINE_PATCH_RECT_H
#pragma once
#include "scene/gui/control.h"
@ -82,5 +81,3 @@ public:
};
VARIANT_ENUM_CAST(NinePatchRect::AxisStretchMode)
#endif // NINE_PATCH_RECT_H

View file

@ -73,6 +73,14 @@ Size2 OptionButton::get_minimum_size() const {
void OptionButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);
DisplayServer::get_singleton()->accessibility_update_set_popup_type(ae, DisplayServer::AccessibilityPopupType::POPUP_LIST);
} break;
case NOTIFICATION_POSTINITIALIZE: {
_refresh_size_cache();
if (has_theme_icon(SNAME("arrow"))) {
@ -165,7 +173,7 @@ bool OptionButton::_set(const StringName &p_name, const Variant &p_value) {
_select(index, false);
}
const String property = sname.get_slice("/", 2);
const String property = sname.get_slicec('/', 2);
if (property == "text" || property == "icon") {
_queue_update_size_cache();
}
@ -240,6 +248,21 @@ void OptionButton::set_item_tooltip(int p_idx, const String &p_tooltip) {
popup->set_item_tooltip(p_idx, p_tooltip);
}
void OptionButton::set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode) {
if (p_idx < 0) {
p_idx += get_item_count();
}
if (popup->get_item_auto_translate_mode(p_idx) == p_mode) {
return;
}
popup->set_item_auto_translate_mode(p_idx, p_mode);
if (current == p_idx) {
set_text(popup->get_item_text(p_idx));
}
_queue_update_size_cache();
}
void OptionButton::set_item_disabled(int p_idx, bool p_disabled) {
popup->set_item_disabled(p_idx, p_disabled);
}
@ -272,6 +295,10 @@ String OptionButton::get_item_tooltip(int p_idx) const {
return popup->get_item_tooltip(p_idx);
}
Node::AutoTranslateMode OptionButton::get_item_auto_translate_mode(int p_idx) const {
return popup->get_item_auto_translate_mode(p_idx);
}
bool OptionButton::is_item_disabled(int p_idx) const {
return popup->is_item_disabled(p_idx);
}
@ -431,6 +458,25 @@ void OptionButton::_queue_update_size_cache() {
callable_mp(this, &OptionButton::_refresh_size_cache).call_deferred();
}
String OptionButton::_get_translated_text(const String &p_text) const {
if (0 <= current && current < popup->get_item_count()) {
AutoTranslateMode mode = popup->get_item_auto_translate_mode(current);
switch (mode) {
case AUTO_TRANSLATE_MODE_INHERIT: {
return atr(p_text);
} break;
case AUTO_TRANSLATE_MODE_ALWAYS: {
return tr(p_text);
} break;
case AUTO_TRANSLATE_MODE_DISABLED: {
return p_text;
} break;
}
ERR_FAIL_V_MSG(atr(p_text), "Unexpected auto translate mode: " + itos(mode));
}
return atr(p_text);
}
void OptionButton::select(int p_idx) {
_select(p_idx, false);
}
@ -510,12 +556,14 @@ void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_id", "idx", "id"), &OptionButton::set_item_id);
ClassDB::bind_method(D_METHOD("set_item_metadata", "idx", "metadata"), &OptionButton::set_item_metadata);
ClassDB::bind_method(D_METHOD("set_item_tooltip", "idx", "tooltip"), &OptionButton::set_item_tooltip);
ClassDB::bind_method(D_METHOD("set_item_auto_translate_mode", "idx", "mode"), &OptionButton::set_item_auto_translate_mode);
ClassDB::bind_method(D_METHOD("get_item_text", "idx"), &OptionButton::get_item_text);
ClassDB::bind_method(D_METHOD("get_item_icon", "idx"), &OptionButton::get_item_icon);
ClassDB::bind_method(D_METHOD("get_item_id", "idx"), &OptionButton::get_item_id);
ClassDB::bind_method(D_METHOD("get_item_index", "id"), &OptionButton::get_item_index);
ClassDB::bind_method(D_METHOD("get_item_metadata", "idx"), &OptionButton::get_item_metadata);
ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &OptionButton::get_item_tooltip);
ClassDB::bind_method(D_METHOD("get_item_auto_translate_mode", "idx"), &OptionButton::get_item_auto_translate_mode);
ClassDB::bind_method(D_METHOD("is_item_disabled", "idx"), &OptionButton::is_item_disabled);
ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &OptionButton::is_item_separator);
ClassDB::bind_method(D_METHOD("add_separator", "text"), &OptionButton::add_separator, DEFVAL(String()));

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef OPTION_BUTTON_H
#define OPTION_BUTTON_H
#pragma once
#include "scene/gui/button.h"
#include "scene/gui/popup_menu.h"
@ -80,6 +79,7 @@ class OptionButton : public Button {
protected:
Size2 get_minimum_size() const override;
virtual void _queue_update_size_cache() override;
virtual String _get_translated_text(const String &p_text) const override;
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
@ -106,6 +106,7 @@ public:
void set_item_metadata(int p_idx, const Variant &p_metadata);
void set_item_disabled(int p_idx, bool p_disabled);
void set_item_tooltip(int p_idx, const String &p_tooltip);
void set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode);
String get_item_text(int p_idx) const;
Ref<Texture2D> get_item_icon(int p_idx) const;
@ -115,6 +116,7 @@ public:
bool is_item_disabled(int p_idx) const;
bool is_item_separator(int p_idx) const;
String get_item_tooltip(int p_idx) const;
AutoTranslateMode get_item_auto_translate_mode(int p_idx) const;
bool has_selectable_items() const;
int get_selectable_item(bool p_from_last = false) const;
@ -150,5 +152,3 @@ public:
OptionButton(const String &p_text = String());
~OptionButton();
};
#endif // OPTION_BUTTON_H

View file

@ -33,6 +33,13 @@
void Panel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_PANEL);
} break;
case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
theme_cache.panel_style->draw(ci, Rect2(Point2(), get_size()));

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef PANEL_H
#define PANEL_H
#pragma once
#include "scene/gui/control.h"
@ -47,5 +46,3 @@ protected:
public:
Panel();
};
#endif // PANEL_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef PANEL_CONTAINER_H
#define PANEL_CONTAINER_H
#pragma once
#include "scene/gui/container.h"
@ -52,5 +51,3 @@ public:
PanelContainer();
};
#endif // PANEL_CONTAINER_H

View file

@ -38,7 +38,7 @@
#include "scene/theme/theme_db.h"
void Popup::_input_from_window(const Ref<InputEvent> &p_event) {
if (get_flag(FLAG_POPUP) && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
if ((ac_popup || get_flag(FLAG_POPUP)) && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
hide_reason = HIDE_REASON_CANCELED; // ESC pressed, mark as canceled unconditionally.
_close_pressed();
}
@ -115,7 +115,7 @@ void Popup::_notification(int p_what) {
} break;
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
if (!is_in_edited_scene_root() && get_flag(FLAG_POPUP)) {
if (!is_in_edited_scene_root() && (get_flag(FLAG_POPUP) || ac_popup)) {
if (hide_reason == HIDE_REASON_NONE) {
hide_reason = HIDE_REASON_UNFOCUSED;
}
@ -126,7 +126,7 @@ void Popup::_notification(int p_what) {
}
void Popup::_parent_focused() {
if (popped_up && get_flag(FLAG_POPUP)) {
if (popped_up && (get_flag(FLAG_POPUP) || ac_popup)) {
if (hide_reason == HIDE_REASON_NONE) {
hide_reason = HIDE_REASON_UNFOCUSED;
}
@ -167,6 +167,11 @@ Rect2i Popup::_popup_adjust_rect() const {
Rect2i current(get_position(), get_size());
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SELF_FITTING_WINDOWS)) {
// We're fine as is, the Display Server will take care of that for us.
return current;
}
if (current.position.x + current.size.x > parent_rect.position.x + parent_rect.size.x) {
current.position.x = parent_rect.position.x + parent_rect.size.x - current.size.x;
}
@ -218,7 +223,10 @@ Popup::Popup() {
set_transient(true);
set_flag(FLAG_BORDERLESS, true);
set_flag(FLAG_RESIZE_DISABLED, true);
set_flag(FLAG_MINIMIZE_DISABLED, true);
set_flag(FLAG_MAXIMIZE_DISABLED, true);
set_flag(FLAG_POPUP, true);
set_flag(FLAG_POPUP_WM_HINT, true);
}
Popup::~Popup() {
@ -299,7 +307,7 @@ Rect2i PopupPanel::_popup_adjust_rect() const {
_update_child_rects();
if (is_layout_rtl()) {
current.position -= Vector2(ABS(panel->get_offset(SIDE_RIGHT)), panel->get_offset(SIDE_TOP)) * get_content_scale_factor();
current.position -= Vector2(Math::abs(panel->get_offset(SIDE_RIGHT)), panel->get_offset(SIDE_TOP)) * get_content_scale_factor();
} else {
current.position -= Vector2(panel->get_offset(SIDE_LEFT), panel->get_offset(SIDE_TOP)) * get_content_scale_factor();
}

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef POPUP_H
#define POPUP_H
#pragma once
#include "scene/main/window.h"
@ -41,6 +40,7 @@ class Popup : public Window {
GDCLASS(Popup, Window);
LocalVector<Window *> visible_parents;
bool ac_popup = false;
bool popped_up = false;
public:
@ -60,6 +60,7 @@ protected:
void _close_pressed();
virtual Rect2i _popup_adjust_rect() const override;
virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
void set_ac_popup() { ac_popup = true; }
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
@ -107,5 +108,3 @@ public:
PopupPanel();
};
#endif // POPUP_H

View file

@ -316,32 +316,26 @@ int PopupMenu::_get_items_total_height() const {
}
int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
// Make the item area exclude shadows and the vertical margins and scrollbar.
Rect2 item_clickable_area = panel->get_global_rect();
if (scroll_container->get_v_scroll_bar()->is_visible_in_tree()) {
const int scroll_width = scroll_container->get_v_scroll_bar()->get_size().width;
if (is_layout_rtl()) {
item_clickable_area.position.x += scroll_width;
item_clickable_area.size.width -= scroll_width;
}
item_clickable_area.size.width -= scroll_width;
}
float win_scale = get_content_scale_factor();
item_clickable_area.position.x += theme_cache.panel_style->get_margin(SIDE_LEFT);
item_clickable_area.position.y += theme_cache.panel_style->get_margin(SIDE_TOP);
item_clickable_area.position *= win_scale;
item_clickable_area.size.y -= theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM);
item_clickable_area.size *= win_scale;
// Transform to scroll_container local coordinates.
const Point2 scaled_pos = p_over / get_content_scale_factor();
const Point2 over_scroll_container =
scroll_container->get_global_transform_with_canvas().xform_inv(scaled_pos);
if (!item_clickable_area.has_point(p_over)) {
// Check if point is inside the item control as clipped by scroll_container.
const Rect2 scroll_container_rect = Rect2(Point2(), scroll_container->get_size());
const Rect2 bounding_rect = scroll_container_rect.intersection(control->get_rect());
if (!bounding_rect.has_point(over_scroll_container)) {
return -1;
}
float ofs = item_clickable_area.position.y + (float)theme_cache.v_separation * win_scale * 0.5;
// Perform item hit check in control node local space,
// so we don't need to worry about any of the container theming.
const float over_control_y = control->get_transform().xform_inv(over_scroll_container).y;
float bottom_edge = 0;
for (int i = 0; i < items.size(); i++) {
ofs += i > 0 ? (float)theme_cache.v_separation * win_scale : (float)theme_cache.v_separation * win_scale * 0.5;
ofs += _get_item_height(i) * win_scale;
if (p_over.y - control->get_position().y * win_scale < ofs) {
bottom_edge += theme_cache.v_separation;
bottom_edge += _get_item_height(i);
if (bottom_edge > over_control_y) {
return i;
}
}
@ -499,9 +493,11 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
bool match_found = false;
for (int i = search_from; i < items.size(); i++) {
if (!items[i].separator && !items[i].disabled) {
prev_mouse_over = mouse_over;
mouse_over = i;
emit_signal(SNAME("id_focused"), items[i].id);
scroll_to_item(i);
queue_accessibility_update();
control->queue_redraw();
set_input_as_handled();
match_found = true;
@ -513,9 +509,11 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
// If the last item is not selectable, try re-searching from the start.
for (int i = 0; i < search_from; i++) {
if (!items[i].separator && !items[i].disabled) {
prev_mouse_over = mouse_over;
mouse_over = i;
emit_signal(SNAME("id_focused"), items[i].id);
scroll_to_item(i);
queue_accessibility_update();
control->queue_redraw();
set_input_as_handled();
break;
@ -537,9 +535,11 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
bool match_found = false;
for (int i = search_from; i >= 0; i--) {
if (!items[i].separator && !items[i].disabled) {
prev_mouse_over = mouse_over;
mouse_over = i;
emit_signal(SNAME("id_focused"), items[i].id);
scroll_to_item(i);
queue_accessibility_update();
control->queue_redraw();
set_input_as_handled();
match_found = true;
@ -551,9 +551,11 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
// If the first item is not selectable, try re-searching from the end.
for (int i = items.size() - 1; i >= search_from; i--) {
if (!items[i].separator && !items[i].disabled) {
prev_mouse_over = mouse_over;
mouse_over = i;
emit_signal(SNAME("id_focused"), items[i].id);
scroll_to_item(i);
queue_accessibility_update();
control->queue_redraw();
set_input_as_handled();
break;
@ -738,9 +740,11 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
}
if (items[i].text.findn(search_string) == 0) {
prev_mouse_over = mouse_over;
mouse_over = i;
emit_signal(SNAME("id_focused"), items[i].id);
scroll_to_item(i);
queue_accessibility_update();
control->queue_redraw();
set_input_as_handled();
break;
@ -755,6 +759,7 @@ void PopupMenu::_mouse_over_update(const Point2 &p_over) {
if (id < 0) {
mouse_over = -1;
queue_accessibility_update();
control->queue_redraw();
return;
}
@ -766,6 +771,7 @@ void PopupMenu::_mouse_over_update(const Point2 &p_over) {
if (over != mouse_over) {
mouse_over = over;
queue_accessibility_update();
control->queue_redraw();
}
}
@ -1060,7 +1066,7 @@ Rect2i PopupMenu::_popup_adjust_rect() const {
_update_shadow_offsets();
if (is_layout_rtl()) {
current.position -= Vector2(ABS(panel->get_offset(SIDE_RIGHT)), panel->get_offset(SIDE_TOP)) * get_content_scale_factor();
current.position -= Vector2(Math::abs(panel->get_offset(SIDE_RIGHT)), panel->get_offset(SIDE_TOP)) * get_content_scale_factor();
} else {
current.position -= Vector2(panel->get_offset(SIDE_LEFT), panel->get_offset(SIDE_TOP)) * get_content_scale_factor();
}
@ -1104,8 +1110,105 @@ void PopupMenu::remove_child_notify(Node *p_child) {
_menu_changed();
}
void PopupMenu::_accessibility_action_click(const Variant &p_data, int p_idx) {
activate_item(p_idx);
}
RID PopupMenu::get_focused_accessibility_element() const {
if (mouse_over == -1) {
return get_accessibility_element();
} else {
const Item &item = items[mouse_over];
return item.accessibility_item_element;
}
}
void PopupMenu::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_EXIT_TREE: {
if (system_menu_id != NativeMenu::INVALID_MENU_ID) {
unbind_global_menu();
}
[[fallthrough]];
}
case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
for (int i = 0; i < items.size(); i++) {
items.write[i].accessibility_item_element = RID();
}
accessibility_scroll_element = RID();
} break;
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
if (has_meta("_menu_name")) {
DisplayServer::get_singleton()->accessibility_update_set_name(ae, get_meta("_menu_name", get_name()));
}
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_MENU);
DisplayServer::get_singleton()->accessibility_update_set_list_item_count(ae, items.size());
if (accessibility_scroll_element.is_null()) {
accessibility_scroll_element = DisplayServer::get_singleton()->accessibility_create_sub_element(ae, DisplayServer::AccessibilityRole::ROLE_CONTAINER);
}
Transform2D scroll_xform;
scroll_xform.set_origin(Vector2i(0, -scroll_container->get_v_scroll_bar()->get_value()));
DisplayServer::get_singleton()->accessibility_update_set_transform(accessibility_scroll_element, scroll_xform);
DisplayServer::get_singleton()->accessibility_update_set_bounds(accessibility_scroll_element, Rect2(0, 0, get_size().x, scroll_container->get_v_scroll_bar()->get_max()));
float scroll_width = scroll_container->get_v_scroll_bar()->is_visible_in_tree() ? scroll_container->get_v_scroll_bar()->get_size().width : 0;
float display_width = control->get_size().width - scroll_width;
Point2 ofs;
for (int i = 0; i < items.size(); i++) {
const Item &item = items.write[i];
ofs.y += i > 0 ? theme_cache.v_separation : (float)theme_cache.v_separation / 2;
Point2 item_ofs = ofs;
if (item.accessibility_item_element.is_null()) {
item.accessibility_item_element = DisplayServer::get_singleton()->accessibility_create_sub_element(accessibility_scroll_element, DisplayServer::AccessibilityRole::ROLE_MENU_ITEM);
item.accessibility_item_dirty = true;
}
item_ofs.x += item.indent * theme_cache.indent;
float h = _get_item_height(i);
if (item.accessibility_item_dirty || i == prev_mouse_over || i == mouse_over) {
switch (item.checkable_type) {
case Item::CHECKABLE_TYPE_NONE: {
DisplayServer::get_singleton()->accessibility_update_set_role(item.accessibility_item_element, DisplayServer::AccessibilityRole::ROLE_MENU_ITEM);
} break;
case Item::CHECKABLE_TYPE_CHECK_BOX: {
DisplayServer::get_singleton()->accessibility_update_set_role(item.accessibility_item_element, DisplayServer::AccessibilityRole::ROLE_MENU_ITEM_CHECK_BOX);
DisplayServer::get_singleton()->accessibility_update_set_checked(item.accessibility_item_element, item.checked);
} break;
case Item::CHECKABLE_TYPE_RADIO_BUTTON: {
DisplayServer::get_singleton()->accessibility_update_set_role(item.accessibility_item_element, DisplayServer::AccessibilityRole::ROLE_MENU_ITEM_RADIO);
DisplayServer::get_singleton()->accessibility_update_set_checked(item.accessibility_item_element, item.checked);
} break;
}
DisplayServer::get_singleton()->accessibility_update_add_action(item.accessibility_item_element, DisplayServer::AccessibilityAction::ACTION_CLICK, callable_mp(this, &PopupMenu::_accessibility_action_click).bind(i));
DisplayServer::get_singleton()->accessibility_update_set_list_item_index(item.accessibility_item_element, i);
DisplayServer::get_singleton()->accessibility_update_set_list_item_level(item.accessibility_item_element, 0);
DisplayServer::get_singleton()->accessibility_update_set_list_item_selected(item.accessibility_item_element, i == mouse_over);
DisplayServer::get_singleton()->accessibility_update_set_name(item.accessibility_item_element, item.xl_text);
DisplayServer::get_singleton()->accessibility_update_set_flag(item.accessibility_item_element, DisplayServer::AccessibilityFlags::FLAG_DISABLED, item.disabled);
DisplayServer::get_singleton()->accessibility_update_set_tooltip(item.accessibility_item_element, item.tooltip);
DisplayServer::get_singleton()->accessibility_update_set_bounds(item.accessibility_item_element, Rect2(item_ofs, Size2(display_width, h + theme_cache.v_separation)));
item.accessibility_item_dirty = false;
}
ofs.y += h;
}
prev_mouse_over = -1;
} break;
case NOTIFICATION_ENTER_TREE: {
PopupMenu *pm = Object::cast_to<PopupMenu>(get_parent());
if (pm) {
@ -1118,12 +1221,6 @@ void PopupMenu::_notification(int p_what) {
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (system_menu_id != NativeMenu::INVALID_MENU_ID) {
unbind_global_menu();
}
} break;
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
panel->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style);
@ -1146,12 +1243,14 @@ void PopupMenu::_notification(int p_what) {
}
for (int i = 0; i < items.size(); i++) {
Item &item = items.write[i];
item.xl_text = atr(item.text);
item.xl_text = _atr(i, item.text);
item.dirty = true;
if (is_global) {
nmenu->set_item_text(global_menu, i, item.xl_text);
}
item.accessibility_item_dirty = true;
_shape_item(i);
queue_accessibility_update();
}
child_controls_changed();
@ -1166,6 +1265,7 @@ void PopupMenu::_notification(int p_what) {
case NOTIFICATION_WM_MOUSE_EXIT: {
if (mouse_over >= 0 && (!items[mouse_over].submenu || submenu_over != -1)) {
mouse_over = -1;
queue_accessibility_update();
control->queue_redraw();
}
} break;
@ -1281,7 +1381,9 @@ void PopupMenu::_notification(int p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible()) {
if (mouse_over >= 0) {
prev_mouse_over = mouse_over;
mouse_over = -1;
queue_accessibility_update();
control->queue_redraw();
}
@ -1341,6 +1443,7 @@ void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) {
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1364,6 +1467,7 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1387,6 +1491,7 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) {
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1412,6 +1517,7 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1435,6 +1541,7 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_acce
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1460,6 +1567,7 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1485,6 +1593,7 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1522,6 +1631,7 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1551,6 +1661,7 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1580,6 +1691,7 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1611,6 +1723,7 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1640,6 +1753,7 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1671,6 +1785,7 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1697,6 +1812,7 @@ void PopupMenu::add_submenu_node_item(const String &p_label, PopupMenu *p_submen
item.text = p_label;
item.xl_text = atr(p_label);
item.id = p_id == -1 ? items.size() : p_id;
item.accessibility_item_dirty = true;
item.submenu = p_submenu;
item.submenu_name = p_submenu->get_name();
items.push_back(item);
@ -1710,6 +1826,7 @@ void PopupMenu::add_submenu_node_item(const String &p_label, PopupMenu *p_submen
}
_shape_item(items.size() - 1);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
@ -1731,15 +1848,18 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) {
return;
}
items.write[p_idx].text = p_text;
items.write[p_idx].xl_text = atr(p_text);
items.write[p_idx].xl_text = _atr(p_idx, p_text);
items.write[p_idx].dirty = true;
items.write[p_idx].accessibility_item_dirty = true;
if (global_menu.is_valid()) {
NativeMenu::get_singleton()->set_item_text(global_menu, p_idx, items[p_idx].xl_text);
}
_shape_item(p_idx);
_shape_item(p_idx);
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@ -1750,9 +1870,14 @@ void PopupMenu::set_item_text_direction(int p_idx, Control::TextDirection p_text
}
ERR_FAIL_INDEX(p_idx, items.size());
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (items[p_idx].text_direction != p_text_direction) {
items.write[p_idx].text_direction = p_text_direction;
items.write[p_idx].dirty = true;
items.write[p_idx].accessibility_item_dirty = true;
_shape_item(p_idx);
queue_accessibility_update();
control->queue_redraw();
}
}
@ -1765,10 +1890,28 @@ void PopupMenu::set_item_language(int p_idx, const String &p_language) {
if (items[p_idx].language != p_language) {
items.write[p_idx].language = p_language;
items.write[p_idx].dirty = true;
items.write[p_idx].accessibility_item_dirty = true;
_shape_item(p_idx);
queue_accessibility_update();
control->queue_redraw();
}
}
void PopupMenu::set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode) {
if (p_idx < 0) {
p_idx += get_item_count();
}
ERR_FAIL_INDEX(p_idx, items.size());
if (items[p_idx].auto_translate_mode == p_mode) {
return;
}
items.write[p_idx].auto_translate_mode = p_mode;
items.write[p_idx].xl_text = _atr(p_idx, items[p_idx].text);
items.write[p_idx].dirty = true;
control->queue_redraw();
}
void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
if (p_idx < 0) {
p_idx += get_item_count();
@ -1832,11 +1975,13 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
}
items.write[p_idx].checked = p_checked;
items.write[p_idx].accessibility_item_dirty = true;
if (global_menu.is_valid()) {
NativeMenu::get_singleton()->set_item_checked(global_menu, p_idx, p_checked);
}
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
_menu_changed();
@ -1875,11 +2020,13 @@ void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) {
items.write[p_idx].accel = p_accel;
items.write[p_idx].dirty = true;
items.write[p_idx].accessibility_item_dirty = true;
if (global_menu.is_valid()) {
NativeMenu::get_singleton()->set_item_accelerator(global_menu, p_idx, p_accel);
}
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
_menu_changed();
@ -1911,11 +2058,13 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
}
items.write[p_idx].disabled = p_disabled;
items.write[p_idx].accessibility_item_dirty = true;
if (global_menu.is_valid()) {
NativeMenu::get_singleton()->set_item_disabled(global_menu, p_idx, p_disabled);
}
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
_menu_changed();
@ -1979,11 +2128,13 @@ void PopupMenu::set_item_submenu_node(int p_idx, PopupMenu *p_submenu) {
void PopupMenu::toggle_item_checked(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].checked = !items[p_idx].checked;
items.write[p_idx].accessibility_item_dirty = true;
if (global_menu.is_valid()) {
NativeMenu::get_singleton()->set_item_checked(global_menu, p_idx, items[p_idx].checked);
}
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
_menu_changed();
@ -2009,6 +2160,11 @@ String PopupMenu::get_item_language(int p_idx) const {
return items[p_idx].language;
}
Node::AutoTranslateMode PopupMenu::get_item_auto_translate_mode(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, items.size(), AUTO_TRANSLATE_MODE_INHERIT);
return items[p_idx].auto_translate_mode;
}
int PopupMenu::get_item_idx_from_text(const String &text) const {
for (int idx = 0; idx < items.size(); idx++) {
if (items[idx].text == text) {
@ -2115,6 +2271,9 @@ void PopupMenu::set_item_as_separator(int p_idx, bool p_separator) {
}
items.write[p_idx].separator = p_separator;
items.write[p_idx].accessibility_item_dirty = true;
queue_accessibility_update();
control->queue_redraw();
}
@ -2135,11 +2294,13 @@ void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
}
items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE;
items.write[p_idx].accessibility_item_dirty = true;
if (global_menu.is_valid()) {
NativeMenu::get_singleton()->set_item_checkable(global_menu, p_idx, p_checkable);
}
queue_accessibility_update();
control->queue_redraw();
_menu_changed();
}
@ -2156,11 +2317,13 @@ void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) {
}
items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE;
items.write[p_idx].accessibility_item_dirty = true;
if (global_menu.is_valid()) {
NativeMenu::get_singleton()->set_item_radio_checkable(global_menu, p_idx, p_radio_checkable);
}
queue_accessibility_update();
control->queue_redraw();
_menu_changed();
}
@ -2176,11 +2339,13 @@ void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
}
items.write[p_idx].tooltip = p_tooltip;
items.write[p_idx].accessibility_item_dirty = true;
if (global_menu.is_valid()) {
NativeMenu::get_singleton()->set_item_tooltip(global_menu, p_idx, p_tooltip);
}
queue_accessibility_update();
control->queue_redraw();
_menu_changed();
}
@ -2280,11 +2445,13 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) {
}
items.write[p_idx].state = p_state;
items.write[p_idx].accessibility_item_dirty = true;
if (global_menu.is_valid()) {
NativeMenu::get_singleton()->set_item_state(global_menu, p_idx, p_state);
}
queue_accessibility_update();
control->queue_redraw();
_menu_changed();
}
@ -2329,11 +2496,13 @@ void PopupMenu::toggle_item_multistate(int p_idx) {
if (items.write[p_idx].max_states <= items[p_idx].state) {
items.write[p_idx].state = 0;
}
items.write[p_idx].accessibility_item_dirty = true;
if (global_menu.is_valid()) {
NativeMenu::get_singleton()->set_item_state(global_menu, p_idx, items[p_idx].state);
}
queue_accessibility_update();
control->queue_redraw();
_menu_changed();
}
@ -2367,11 +2536,12 @@ void PopupMenu::set_focused_item(int p_idx) {
return;
}
prev_mouse_over = mouse_over;
mouse_over = p_idx;
if (mouse_over != -1) {
scroll_to_item(mouse_over);
}
queue_accessibility_update();
control->queue_redraw();
}
@ -2393,6 +2563,10 @@ void PopupMenu::set_item_count(int p_count) {
if (is_global && prev_size > p_count) {
for (int i = prev_size - 1; i >= p_count; i--) {
nmenu->remove_item(global_menu, i);
if (items[i].accessibility_item_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(items.write[i].accessibility_item_element);
items.write[i].accessibility_item_element = RID();
}
}
}
@ -2573,6 +2747,10 @@ void PopupMenu::activate_item(int p_idx) {
void PopupMenu::remove_item(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
if (items[p_idx].accessibility_item_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(items.write[p_idx].accessibility_item_element);
items.write[p_idx].accessibility_item_element = RID();
}
if (items[p_idx].shortcut.is_valid()) {
_unref_shortcut(items[p_idx].shortcut);
}
@ -2592,6 +2770,7 @@ void PopupMenu::add_separator(const String &p_text, int p_id) {
Item sep;
sep.separator = true;
sep.id = p_id;
sep.accessibility_item_dirty = true;
if (!p_text.is_empty()) {
sep.text = p_text;
sep.xl_text = atr(p_text);
@ -2607,7 +2786,11 @@ void PopupMenu::add_separator(const String &p_text, int p_id) {
}
void PopupMenu::clear(bool p_free_submenus) {
for (const Item &I : items) {
for (Item &I : items) {
if (I.accessibility_item_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(I.accessibility_item_element);
I.accessibility_item_element = RID();
}
if (I.shortcut.is_valid()) {
_unref_shortcut(I.shortcut);
}
@ -2631,7 +2814,9 @@ void PopupMenu::clear(bool p_free_submenus) {
}
items.clear();
prev_mouse_over = -1;
mouse_over = -1;
queue_accessibility_update();
control->queue_redraw();
child_controls_changed();
notify_property_list_changed();
@ -2818,6 +3003,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_text", "index", "text"), &PopupMenu::set_item_text);
ClassDB::bind_method(D_METHOD("set_item_text_direction", "index", "direction"), &PopupMenu::set_item_text_direction);
ClassDB::bind_method(D_METHOD("set_item_language", "index", "language"), &PopupMenu::set_item_language);
ClassDB::bind_method(D_METHOD("set_item_auto_translate_mode", "index", "mode"), &PopupMenu::set_item_auto_translate_mode);
ClassDB::bind_method(D_METHOD("set_item_icon", "index", "icon"), &PopupMenu::set_item_icon);
ClassDB::bind_method(D_METHOD("set_item_icon_max_width", "index", "width"), &PopupMenu::set_item_icon_max_width);
ClassDB::bind_method(D_METHOD("set_item_icon_modulate", "index", "modulate"), &PopupMenu::set_item_icon_modulate);
@ -2844,6 +3030,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_item_text", "index"), &PopupMenu::get_item_text);
ClassDB::bind_method(D_METHOD("get_item_text_direction", "index"), &PopupMenu::get_item_text_direction);
ClassDB::bind_method(D_METHOD("get_item_language", "index"), &PopupMenu::get_item_language);
ClassDB::bind_method(D_METHOD("get_item_auto_translate_mode", "index"), &PopupMenu::get_item_auto_translate_mode);
ClassDB::bind_method(D_METHOD("get_item_icon", "index"), &PopupMenu::get_item_icon);
ClassDB::bind_method(D_METHOD("get_item_icon_max_width", "index"), &PopupMenu::get_item_icon_max_width);
ClassDB::bind_method(D_METHOD("get_item_icon_modulate", "index"), &PopupMenu::get_item_icon_modulate);
@ -2983,6 +3170,23 @@ void PopupMenu::_native_popup(const Rect2i &p_rect) {
NativeMenu::get_singleton()->popup(global_menu, popup_pos);
}
String PopupMenu::_atr(int p_idx, const String &p_text) const {
ERR_FAIL_INDEX_V(p_idx, items.size(), atr(p_text));
switch (items[p_idx].auto_translate_mode) {
case AUTO_TRANSLATE_MODE_INHERIT: {
return atr(p_text);
} break;
case AUTO_TRANSLATE_MODE_ALWAYS: {
return tr(p_text);
} break;
case AUTO_TRANSLATE_MODE_DISABLED: {
return p_text;
} break;
}
ERR_FAIL_V_MSG(atr(p_text), "Unexpected auto translate mode: " + itos(items[p_idx].auto_translate_mode));
}
void PopupMenu::popup(const Rect2i &p_bounds) {
bool native = global_menu.is_valid();
#ifdef TOOLS_ENABLED
@ -2994,18 +3198,30 @@ void PopupMenu::popup(const Rect2i &p_bounds) {
if (native) {
_native_popup(p_bounds != Rect2i() ? p_bounds : Rect2i(get_position(), Size2i()));
} else {
set_flag(FLAG_NO_FOCUS, !is_embedded());
if (is_inside_tree()) {
bool ac = get_tree()->is_accessibility_enabled();
// Note: Native popup menus need keyboard focus to work with screen reader.
set_flag(FLAG_POPUP, !ac);
set_flag(FLAG_NO_FOCUS, !is_embedded() && !ac);
if (ac) {
set_ac_popup();
}
}
moved = Vector2();
popup_time_msec = OS::get_singleton()->get_ticks_msec();
if (!is_embedded()) {
float win_scale = get_parent_visible_window()->get_content_scale_factor();
set_content_scale_factor(win_scale);
Size2 minsize = get_contents_minimum_size() * win_scale;
minsize.height = Math::ceil(minsize.height); // Ensures enough height at fractional content scales to prevent the v_scroll_bar from showing.
set_min_size(minsize); // `height` is truncated here by the cast to Size2i for Window.min_size.
set_size(Vector2(0, 0)); // Shrinkwraps to min size.
Size2 scale = get_parent_viewport()->get_popup_base_transform().get_scale();
CanvasItem *c = Object::cast_to<CanvasItem>(get_parent());
if (c) {
scale *= c->get_global_transform_with_canvas().get_scale();
}
real_t popup_scale = MIN(scale.x, scale.y);
set_content_scale_factor(popup_scale);
Size2 minsize = get_contents_minimum_size() * popup_scale;
minsize.height = Math::ceil(minsize.height); // Ensures enough height at fractional content scales to prevent the v_scroll_bar from showing.
set_min_size(minsize); // `height` is truncated here by the cast to Size2i for Window.min_size.
set_size(Vector2(0, 0)); // Shrinkwraps to min size.
Popup::popup(p_bounds);
}
}
@ -3023,7 +3239,15 @@ void PopupMenu::set_visible(bool p_visible) {
_native_popup(Rect2i(get_position(), get_size()));
}
} else {
set_flag(FLAG_NO_FOCUS, !is_embedded());
if (is_inside_tree()) {
bool ac = get_tree()->is_accessibility_enabled();
// Note: Native popup menus need keyboard focus to work with screen reader.
set_flag(FLAG_POPUP, !ac);
set_flag(FLAG_NO_FOCUS, !is_embedded() && !ac);
if (ac) {
set_ac_popup();
}
}
Popup::set_visible(p_visible);
}

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef POPUP_MENU_H
#define POPUP_MENU_H
#pragma once
#include "core/input/shortcut.h"
#include "scene/gui/popup.h"
@ -45,6 +44,9 @@ class PopupMenu : public Popup {
static HashMap<NativeMenu::SystemMenus, PopupMenu *> system_menus;
struct Item {
mutable RID accessibility_item_element;
mutable bool accessibility_item_dirty = true;
Ref<Texture2D> icon;
int icon_max_width = 0;
Color icon_modulate = Color(1, 1, 1, 1);
@ -55,6 +57,7 @@ class PopupMenu : public Popup {
String language;
Control::TextDirection text_direction = Control::TEXT_DIRECTION_AUTO;
AutoTranslateMode auto_translate_mode = AUTO_TRANSLATE_MODE_INHERIT;
bool checked = false;
enum {
@ -95,6 +98,7 @@ class PopupMenu : public Popup {
Item(bool p_dummy) {}
};
RID accessibility_scroll_element;
mutable Rect2i pre_popup_rect;
void _update_shadow_offsets() const;
@ -118,10 +122,11 @@ class PopupMenu : public Popup {
Timer *submenu_timer = nullptr;
List<Rect2> autohide_areas;
mutable Vector<Item> items;
BitField<MouseButtonMask> initial_button_mask;
BitField<MouseButtonMask> initial_button_mask = MouseButtonMask::NONE;
bool during_grabbed_click = false;
bool is_scrolling = false;
int mouse_over = -1;
int prev_mouse_over = -1;
int submenu_over = -1;
String _get_accel_text(const Item &p_item) const;
int _get_mouse_over(const Point2 &p_over) const;
@ -134,6 +139,8 @@ class PopupMenu : public Popup {
void _shape_item(int p_idx) const;
void _accessibility_action_click(const Variant &p_data, int p_idx);
void _activate_submenu(int p_over, bool p_by_keyboard = false);
void _submenu_timeout();
@ -216,6 +223,7 @@ class PopupMenu : public Popup {
void _set_item_checkable_type(int p_index, int p_checkable_type);
int _get_item_checkable_type(int p_index) const;
void _native_popup(const Rect2i &p_rect);
String _atr(int p_idx, const String &p_text) const;
protected:
virtual Rect2i _popup_adjust_rect() const override;
@ -248,6 +256,8 @@ public:
// this value should be updated to reflect the new size.
static const int ITEM_PROPERTY_SIZE = 10;
virtual RID get_focused_accessibility_element() const override;
virtual void _parent_focused() override;
RID bind_global_menu();
@ -280,6 +290,7 @@ public:
void set_item_text_direction(int p_idx, Control::TextDirection p_text_direction);
void set_item_language(int p_idx, const String &p_language);
void set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode);
void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon);
void set_item_icon_max_width(int p_idx, int p_width);
void set_item_icon_modulate(int p_idx, const Color &p_modulate);
@ -307,6 +318,7 @@ public:
String get_item_xl_text(int p_idx) const;
Control::TextDirection get_item_text_direction(int p_idx) const;
String get_item_language(int p_idx) const;
AutoTranslateMode get_item_auto_translate_mode(int p_idx) const;
int get_item_idx_from_text(const String &text) const;
Ref<Texture2D> get_item_icon(int p_idx) const;
int get_item_icon_max_width(int p_idx) const;
@ -385,5 +397,3 @@ public:
PopupMenu();
~PopupMenu();
};
#endif // POPUP_MENU_H

View file

@ -54,6 +54,14 @@ void ProgressBar::_notification(int p_what) {
queue_redraw();
}
} break;
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_PROGRESS_INDICATOR);
} break;
case NOTIFICATION_DRAW: {
draw_style_box(theme_cache.background_style, Rect2(Point2(), get_size()));

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef PROGRESS_BAR_H
#define PROGRESS_BAR_H
#pragma once
#include "scene/gui/range.h"
@ -90,5 +89,3 @@ private:
};
VARIANT_ENUM_CAST(ProgressBar::FillMode);
#endif // PROGRESS_BAR_H

View file

@ -43,12 +43,51 @@ PackedStringArray Range::get_configuration_warnings() const {
void Range::_value_changed(double p_value) {
GDVIRTUAL_CALL(_value_changed, p_value);
}
void Range::_value_changed_notify() {
_value_changed(shared->val);
emit_signal(SceneStringName(value_changed), shared->val);
queue_accessibility_update();
queue_redraw();
}
void Range::_accessibility_action_inc(const Variant &p_data) {
double step = ((shared->step > 0) ? shared->step : 1);
set_value(shared->val + step);
}
void Range::_accessibility_action_dec(const Variant &p_data) {
double step = ((shared->step > 0) ? shared->step : 1);
set_value(shared->val - step);
}
void Range::_accessibility_action_set_value(const Variant &p_data) {
double new_val = p_data;
set_value(new_val);
}
void Range::_notification(int p_what) {
ERR_MAIN_THREAD_GUARD;
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SPIN_BUTTON);
DisplayServer::get_singleton()->accessibility_update_set_num_value(ae, shared->val);
DisplayServer::get_singleton()->accessibility_update_set_num_range(ae, shared->min, shared->max);
if (shared->step > 0) {
DisplayServer::get_singleton()->accessibility_update_set_num_step(ae, shared->step);
} else {
DisplayServer::get_singleton()->accessibility_update_set_num_step(ae, 1);
}
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_DECREMENT, callable_mp(this, &Range::_accessibility_action_dec));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_INCREMENT, callable_mp(this, &Range::_accessibility_action_inc));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_VALUE, callable_mp(this, &Range::_accessibility_action_set_value));
} break;
}
}
void Range::Shared::emit_value_changed() {
for (Range *E : owners) {
Range *r = E;
@ -80,6 +119,7 @@ void Range::Shared::redraw_owners() {
if (!r->is_inside_tree()) {
continue;
}
r->queue_accessibility_update();
r->queue_redraw();
}
}
@ -91,6 +131,7 @@ void Range::set_value(double p_val) {
if (shared->val != prev_val) {
shared->emit_value_changed();
}
queue_accessibility_update();
}
void Range::_set_value_no_signal(double p_val) {
@ -143,6 +184,8 @@ void Range::set_min(double p_min) {
shared->emit_changed("min");
update_configuration_warnings();
queue_accessibility_update();
}
void Range::set_max(double p_max) {
@ -156,6 +199,8 @@ void Range::set_max(double p_max) {
set_value(shared->val);
shared->emit_changed("max");
queue_accessibility_update();
}
void Range::set_step(double p_step) {
@ -165,6 +210,8 @@ void Range::set_step(double p_step) {
shared->step = p_step;
shared->emit_changed("step");
queue_accessibility_update();
}
void Range::set_page(double p_page) {
@ -177,6 +224,8 @@ void Range::set_page(double p_page) {
set_value(shared->val);
shared->emit_changed("page");
queue_accessibility_update();
}
double Range::get_value() const {
@ -264,6 +313,7 @@ void Range::unshare() {
nshared->allow_lesser = shared->allow_lesser;
_unref_shared();
_ref_shared(nshared);
queue_accessibility_update();
}
void Range::_ref_shared(Shared *p_shared) {
@ -279,7 +329,7 @@ void Range::_ref_shared(Shared *p_shared) {
void Range::_unref_shared() {
if (shared) {
shared->owners.erase(this);
if (shared->owners.size() == 0) {
if (shared->owners.is_empty()) {
memdelete(shared);
shared = nullptr;
}

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef RANGE_H
#define RANGE_H
#pragma once
#include "scene/gui/control.h"
@ -65,9 +64,14 @@ class Range : public Control {
protected:
virtual void _value_changed(double p_value);
void _notify_shared_value_changed() { shared->emit_value_changed(); }
void _notification(int p_what);
static void _bind_methods();
void _accessibility_action_inc(const Variant &p_data);
void _accessibility_action_dec(const Variant &p_data);
void _accessibility_action_set_value(const Variant &p_data);
bool _rounded_values = false;
GDVIRTUAL1(_value_changed, double)
@ -108,5 +112,3 @@ public:
Range();
~Range();
};
#endif // RANGE_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef REFERENCE_RECT_H
#define REFERENCE_RECT_H
#pragma once
#include "scene/gui/control.h"
@ -54,5 +53,3 @@ public:
void set_editor_only(const bool &p_enabled);
bool get_editor_only() const;
};
#endif // REFERENCE_RECT_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef RICH_TEXT_EFFECT_H
#define RICH_TEXT_EFFECT_H
#pragma once
#include "core/io/resource.h"
#include "core/object/gdvirtual.gen.inc"
@ -113,5 +112,3 @@ public:
RichTextEffect();
};
#endif // RICH_TEXT_EFFECT_H

View file

@ -51,7 +51,15 @@ void RichTextLabel::_push_meta_bind_compat_89024(const Variant &p_meta) {
}
void RichTextLabel::_add_image_bind_compat_80410(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region) {
add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, Variant(), false, String(), false);
add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, Variant(), false, String(), false, String());
}
void RichTextLabel::_add_image_bind_compat_76829(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region, const Variant &p_key, bool p_pad, const String &p_tooltip, bool p_size_in_percent) {
add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, p_key, p_pad, p_tooltip, p_size_in_percent, String());
}
void RichTextLabel::_push_table_bind_compat_76829(int p_columns, InlineAlignment p_alignment, int p_align_to_row) {
push_table(p_columns, p_alignment, p_align_to_row, String());
}
bool RichTextLabel::_remove_paragraph_bind_compat_91098(int p_paragraph) {
@ -65,6 +73,8 @@ void RichTextLabel::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("push_meta", "data", "underline_mode"), &RichTextLabel::_push_meta_bind_compat_99481, DEFVAL(META_UNDERLINE_ALWAYS));
ClassDB::bind_compatibility_method(D_METHOD("push_meta", "data"), &RichTextLabel::_push_meta_bind_compat_89024);
ClassDB::bind_compatibility_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::_add_image_bind_compat_80410, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()));
ClassDB::bind_compatibility_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region", "key", "pad", "tooltip", "size_in_percent"), &RichTextLabel::_add_image_bind_compat_76829, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(Variant()), DEFVAL(false), DEFVAL(String()), DEFVAL(false));
ClassDB::bind_compatibility_method(D_METHOD("push_table", "columns", "inline_align", "align_to_row"), &RichTextLabel::_push_table_bind_compat_76829, DEFVAL(INLINE_ALIGNMENT_TOP), DEFVAL(-1));
ClassDB::bind_compatibility_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::_remove_paragraph_bind_compat_91098);
}

File diff suppressed because it is too large Load diff

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef RICH_TEXT_LABEL_H
#define RICH_TEXT_LABEL_H
#pragma once
#include "core/object/worker_thread_pool.h"
#include "core/templates/rid_owner.h"
@ -45,6 +44,7 @@ class RichTextLabel : public Control {
enum RTLDrawStep {
DRAW_STEP_BACKGROUND,
DRAW_STEP_SHADOW_OUTLINE,
DRAW_STEP_SHADOW,
DRAW_STEP_OUTLINE,
DRAW_STEP_TEXT,
@ -106,12 +106,12 @@ public:
};
enum DefaultFont {
NORMAL_FONT,
BOLD_FONT,
ITALICS_FONT,
BOLD_ITALICS_FONT,
MONO_FONT,
CUSTOM_FONT,
RTL_NORMAL_FONT,
RTL_BOLD_FONT,
RTL_ITALICS_FONT,
RTL_BOLD_ITALICS_FONT,
RTL_MONO_FONT,
RTL_CUSTOM_FONT,
};
enum ImageUpdateMask {
@ -137,8 +137,11 @@ protected:
void _push_meta_bind_compat_99481(const Variant &p_meta, MetaUnderline p_underline_mode);
void _push_meta_bind_compat_89024(const Variant &p_meta);
void _add_image_bind_compat_80410(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region);
void _add_image_bind_compat_76829(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region, const Variant &p_key, bool p_pad, const String &p_tooltip, bool p_size_in_percent);
void _push_table_bind_compat_76829(int p_columns, InlineAlignment p_alignment, int p_align_to_row);
bool _remove_paragraph_bind_compat_91098(int p_paragraph);
void _set_table_column_expand_bind_compat_101482(int p_column, bool p_expand, int p_ratio);
static void _bind_compatibility_methods();
#endif
@ -151,6 +154,11 @@ private:
Ref<TextLine> text_prefix;
float prefix_width = 0;
Ref<TextParagraph> text_buf;
RID accessibility_line_element;
RID accessibility_text_element;
Item *dc_item = nullptr;
Color dc_color;
int dc_ol_size = 0;
Color dc_ol_color;
@ -160,7 +168,16 @@ private:
int char_offset = 0;
int char_count = 0;
Line() { text_buf.instantiate(); }
Line() {
text_buf.instantiate();
}
~Line() {
if (accessibility_line_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(accessibility_line_element);
accessibility_line_element = RID();
accessibility_text_element = RID();
}
}
_FORCE_INLINE_ float get_height(float line_separation) const {
return offset.y + text_buf->get_size().y + text_buf->get_line_count() * line_separation;
@ -178,8 +195,10 @@ private:
int line = 0;
RID rid;
RID accessibility_item_element;
void _clear_children() {
RichTextLabel *owner_rtl = Object::cast_to<RichTextLabel>(ObjectDB::get_instance(owner));
RichTextLabel *owner_rtl = ObjectDB::get_instance<RichTextLabel>(owner);
while (subitems.size()) {
Item *subitem = subitems.front()->get();
if (subitem && subitem->rid.is_valid() && owner_rtl) {
@ -237,6 +256,7 @@ private:
struct ItemImage : public Item {
Ref<Texture2D> image;
String alt_text;
InlineAlignment inline_align = INLINE_ALIGNMENT_CENTER;
bool pad = false;
bool size_in_percent = false;
@ -249,7 +269,7 @@ private:
ItemImage() { type = ITEM_IMAGE; }
~ItemImage() {
if (image.is_valid()) {
RichTextLabel *owner_rtl = Object::cast_to<RichTextLabel>(ObjectDB::get_instance(owner));
RichTextLabel *owner_rtl = ObjectDB::get_instance<RichTextLabel>(owner);
if (owner_rtl) {
image->disconnect_changed(callable_mp(owner_rtl, &RichTextLabel::_texture_changed));
}
@ -258,7 +278,7 @@ private:
};
struct ItemFont : public Item {
DefaultFont def_font = CUSTOM_FONT;
DefaultFont def_font = RTL_CUSTOM_FONT;
Ref<Font> font;
bool variation = false;
bool def_size = false;
@ -341,17 +361,21 @@ private:
struct ItemTable : public Item {
struct Column {
String name;
bool expand = false;
bool shrink = true;
int expand_ratio = 0;
int min_width = 0;
int max_width = 0;
int width = 0;
int width_with_padding = 0;
};
LocalVector<Column> columns;
LocalVector<float> rows;
LocalVector<float> rows_no_padding;
LocalVector<float> rows_baseline;
String name;
int align_to_row = -1;
int total_width = 0;
@ -472,6 +496,7 @@ private:
VScrollBar *vscroll = nullptr;
TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_WORD_SMART;
BitField<TextServer::LineBreakFlag> autowrap_flags_trim = TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES;
bool scroll_visible = false;
bool scroll_follow = false;
@ -500,6 +525,9 @@ private:
Array custom_effects;
HashMap<RID, Rect2> ac_element_bounds_cache;
void _invalidate_accessibility();
void _invalidate_current_line(ItemFrame *p_frame);
void _thread_function(void *p_userdata);
@ -549,6 +577,11 @@ private:
bool deselect_on_focus_loss_enabled = true;
bool drag_and_drop_selection_enabled = true;
ItemFrame *keyboard_focus_frame = nullptr;
int keyboard_focus_line = 0;
Item *keyboard_focus_item = nullptr;
bool keyboard_focus_on_text = true;
bool context_menu_enabled = false;
bool shortcut_keys_enabled = true;
@ -577,6 +610,7 @@ private:
void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size);
int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs);
float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false, bool p_meta = false);
void _accessibility_update_line(RID p_id, ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep);
String _roman(int p_num, bool p_capitalize) const;
String _letters(int p_num, bool p_capitalize) const;
@ -643,6 +677,15 @@ private:
bool internal_stack_editing = false;
bool stack_externally_modified = false;
void _accessibility_action_menu(const Variant &p_data);
void _accessibility_scroll_down(const Variant &p_data);
void _accessibility_scroll_up(const Variant &p_data);
void _accessibility_scroll_set(const Variant &p_data);
void _accessibility_focus_item(const Variant &p_data, uint64_t p_item, bool p_line, bool p_foucs);
void _accessibility_scroll_to_item(const Variant &p_data, uint64_t p_item);
RID accessibility_scroll_element;
bool fit_content = false;
struct ThemeCache {
@ -689,9 +732,12 @@ private:
} theme_cache;
public:
virtual RID get_focused_accessibility_element() const override;
PackedStringArray get_accessibility_configuration_warnings() const override;
String get_parsed_text() const;
void add_text(const String &p_text);
void add_image(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), const Variant &p_key = Variant(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false);
void add_image(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), const Variant &p_key = Variant(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false, const String &p_alt_text = String());
void update_image(const Variant &p_key, BitField<ImageUpdateMask> p_mask, const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false);
void add_newline();
bool remove_paragraph(int p_paragraph, bool p_no_invalidate = false);
@ -717,7 +763,7 @@ public:
void push_list(int p_level, ListType p_list, bool p_capitalize, const String &p_bullet = String::utf8(""));
void push_meta(const Variant &p_meta, MetaUnderline p_underline_mode = META_UNDERLINE_ALWAYS, const String &p_tooltip = String());
void push_hint(const String &p_string);
void push_table(int p_columns, InlineAlignment p_alignment = INLINE_ALIGNMENT_TOP, int p_align_to_row = -1);
void push_table(int p_columns, InlineAlignment p_alignment = INLINE_ALIGNMENT_TOP, int p_align_to_row = -1, const String &p_name = String());
void push_fade(int p_start_index, int p_length);
void push_shake(int p_strength, float p_rate, bool p_connected);
void push_wave(float p_frequency, float p_amplitude, bool p_connected);
@ -729,6 +775,7 @@ public:
void push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment);
void push_context();
void set_table_column_expand(int p_column, bool p_expand, int p_ratio = 1, bool p_shrink = true);
void set_table_column_name(int p_column, const String &p_name);
void set_cell_row_background_color(const Color &p_odd_row_bg, const Color &p_even_row_bg);
void set_cell_border_color(const Color &p_color);
void set_cell_size_override(const Size2 &p_min_size, const Size2 &p_max_size);
@ -858,6 +905,9 @@ public:
void set_autowrap_mode(TextServer::AutowrapMode p_mode);
TextServer::AutowrapMode get_autowrap_mode() const;
void set_autowrap_trim_flags(BitField<TextServer::LineBreakFlag> p_flags);
BitField<TextServer::LineBreakFlag> get_autowrap_trim_flags() const;
void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
TextServer::StructuredTextParser get_structured_text_bidi_override() const;
@ -881,6 +931,7 @@ public:
Array get_effects();
void install_effect(const Variant effect);
void reload_effects();
virtual Size2 get_minimum_size() const override;
@ -892,5 +943,3 @@ VARIANT_ENUM_CAST(RichTextLabel::ListType);
VARIANT_ENUM_CAST(RichTextLabel::MenuItems);
VARIANT_ENUM_CAST(RichTextLabel::MetaUnderline);
VARIANT_BITFIELD_CAST(RichTextLabel::ImageUpdateMask);
#endif // RICH_TEXT_LABEL_H

View file

@ -224,6 +224,13 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
void ScrollBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SCROLL_BAR);
} break;
case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
@ -654,6 +661,8 @@ ScrollBar::ScrollBar(Orientation p_orientation) {
if (focus_by_default) {
set_focus_mode(FOCUS_ALL);
} else {
set_focus_mode(FOCUS_ACCESSIBILITY);
}
set_step(0);
}

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef SCROLL_BAR_H
#define SCROLL_BAR_H
#pragma once
#include "scene/gui/range.h"
@ -144,5 +143,3 @@ public:
VScrollBar() :
ScrollBar(VERTICAL) { set_h_size_flags(0); }
};
#endif // SCROLL_BAR_H

View file

@ -353,14 +353,53 @@ void ScrollContainer::_reposition_children() {
queue_redraw();
}
void ScrollContainer::_accessibility_action_scroll_set(const Variant &p_data) {
const Point2 &pos = p_data;
h_scroll->set_value(pos.x);
v_scroll->set_value(pos.y);
}
void ScrollContainer::_accessibility_action_scroll_up(const Variant &p_data) {
v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() / 8);
}
void ScrollContainer::_accessibility_action_scroll_down(const Variant &p_data) {
v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() / 8);
}
void ScrollContainer::_accessibility_action_scroll_left(const Variant &p_data) {
h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 8);
}
void ScrollContainer::_accessibility_action_scroll_right(const Variant &p_data) {
h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 8);
}
void ScrollContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SCROLL_VIEW);
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_DOWN, callable_mp(this, &ScrollContainer::_accessibility_action_scroll_down));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_LEFT, callable_mp(this, &ScrollContainer::_accessibility_action_scroll_left));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_RIGHT, callable_mp(this, &ScrollContainer::_accessibility_action_scroll_right));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_UP, callable_mp(this, &ScrollContainer::_accessibility_action_scroll_up));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_SCROLL_OFFSET, callable_mp(this, &ScrollContainer::_accessibility_action_scroll_set));
} break;
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
_updating_scrollbars = true;
callable_mp(this, is_ready() ? &ScrollContainer::_reposition_children : &ScrollContainer::_update_scrollbar_position).call_deferred();
if (p_what == NOTIFICATION_THEME_CHANGED) {
scroll_border = get_theme_constant(SNAME("scroll_border"), SNAME("Tree"));
scroll_speed = get_theme_constant(SNAME("scroll_speed"), SNAME("Tree"));
}
} break;
case NOTIFICATION_READY: {
@ -387,6 +426,43 @@ void ScrollContainer::_notification(int p_what) {
}
} break;
case NOTIFICATION_DRAG_BEGIN: {
if (scroll_on_drag_hover && is_visible_in_tree()) {
set_process_internal(true);
}
} break;
case NOTIFICATION_DRAG_END: {
set_process_internal(false);
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (scroll_on_drag_hover && get_viewport()->gui_is_dragging()) {
Point2 mouse_position = get_viewport()->get_mouse_position() - get_global_position();
Transform2D xform = get_transform();
if (Rect2(Point2(), xform.get_scale() * get_size()).grow(scroll_border).has_point(mouse_position)) {
Point2 point;
if ((Math::abs(mouse_position.x) < Math::abs(mouse_position.x - get_size().width)) && (Math::abs(mouse_position.x) < scroll_border)) {
point.x = mouse_position.x - scroll_border;
} else if (Math::abs(mouse_position.x - get_size().width) < scroll_border) {
point.x = mouse_position.x - (get_size().width - scroll_border);
}
if ((Math::abs(mouse_position.y) < Math::abs(mouse_position.y - get_size().height)) && (Math::abs(mouse_position.y) < scroll_border)) {
point.y = mouse_position.y - scroll_border;
} else if (Math::abs(mouse_position.y - get_size().height) < scroll_border) {
point.y = mouse_position.y - (get_size().height - scroll_border);
}
point *= scroll_speed * get_process_delta_time();
point += Point2(get_h_scroll(), get_v_scroll());
h_scroll->set_value(point.x);
v_scroll->set_value(point.y);
}
}
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (drag_touching) {
if (drag_touching_deaccel) {
@ -584,6 +660,10 @@ PackedStringArray ScrollContainer::get_configuration_warnings() const {
return warnings;
}
void ScrollContainer::set_scroll_on_drag_hover(bool p_scroll) {
scroll_on_drag_hover = p_scroll;
}
HScrollBar *ScrollContainer::get_h_scroll_bar() {
return h_scroll;
}

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef SCROLL_CONTAINER_H
#define SCROLL_CONTAINER_H
#pragma once
#include "container.h"
@ -63,12 +62,15 @@ private:
bool drag_touching = false;
bool drag_touching_deaccel = false;
bool beyond_deadzone = false;
bool scroll_on_drag_hover = false;
ScrollMode horizontal_scroll_mode = SCROLL_MODE_AUTO;
ScrollMode vertical_scroll_mode = SCROLL_MODE_AUTO;
int deadzone = 0;
bool follow_focus = false;
int scroll_border = 20;
int scroll_speed = 12;
struct ThemeCache {
Ref<StyleBox> panel_style;
@ -82,6 +84,7 @@ private:
bool draw_focus_border = false;
bool focus_border_is_drawn = false;
bool child_has_focus();
protected:
Size2 get_minimum_size() const override;
@ -96,9 +99,14 @@ protected:
void _update_scrollbar_position();
void _scroll_moved(float);
void _accessibility_action_scroll_set(const Variant &p_data);
void _accessibility_action_scroll_up(const Variant &p_data);
void _accessibility_action_scroll_down(const Variant &p_data);
void _accessibility_action_scroll_left(const Variant &p_data);
void _accessibility_action_scroll_right(const Variant &p_data);
public:
virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;
bool child_has_focus();
void set_h_scroll(int p_pos);
int get_h_scroll() const;
@ -124,6 +132,8 @@ public:
bool is_following_focus() const;
void set_follow_focus(bool p_follow);
void set_scroll_on_drag_hover(bool p_scroll);
HScrollBar *get_h_scroll_bar();
VScrollBar *get_v_scroll_bar();
void ensure_control_visible(Control *p_control);
@ -137,5 +147,3 @@ public:
};
VARIANT_ENUM_CAST(ScrollContainer::ScrollMode);
#endif // SCROLL_CONTAINER_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef SEPARATOR_H
#define SEPARATOR_H
#pragma once
#include "scene/gui/control.h"
class Separator : public Control {
@ -66,5 +65,3 @@ class HSeparator : public Separator {
public:
HSeparator();
};
#endif // SEPARATOR_H

View file

@ -89,12 +89,12 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
}
} else if (scrollable) {
if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) {
if (get_focus_mode() != FOCUS_NONE) {
if (get_focus_mode_with_recursive() != FOCUS_NONE) {
grab_focus();
}
set_value(get_value() + get_step());
} else if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN) {
if (get_focus_mode() != FOCUS_NONE) {
if (get_focus_mode_with_recursive() != FOCUS_NONE) {
grab_focus();
}
set_value(get_value() - get_step());
@ -237,7 +237,13 @@ void Slider::_notification(int p_what) {
}
}
}
} break;
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SLIDER);
} break;
case NOTIFICATION_THEME_CHANGED: {

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef SLIDER_H
#define SLIDER_H
#pragma once
#include "scene/gui/range.h"
@ -111,5 +110,3 @@ public:
VSlider() :
Slider(VERTICAL) { set_h_size_flags(0); }
};
#endif // SLIDER_H

View file

@ -34,6 +34,50 @@
#include "core/math/expression.h"
#include "scene/theme/theme_db.h"
void SpinBoxLineEdit::_accessibility_action_inc(const Variant &p_data) {
SpinBox *parent_sb = Object::cast_to<SpinBox>(get_parent());
if (parent_sb) {
double step = ((parent_sb->get_step() > 0) ? parent_sb->get_step() : 1);
parent_sb->set_value(parent_sb->get_value() + step);
}
}
void SpinBoxLineEdit::_accessibility_action_dec(const Variant &p_data) {
SpinBox *parent_sb = Object::cast_to<SpinBox>(get_parent());
if (parent_sb) {
double step = ((parent_sb->get_step() > 0) ? parent_sb->get_step() : 1);
parent_sb->set_value(parent_sb->get_value() - step);
}
}
void SpinBoxLineEdit::_notification(int p_what) {
ERR_MAIN_THREAD_GUARD;
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
SpinBox *parent_sb = Object::cast_to<SpinBox>(get_parent());
if (parent_sb) {
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SPIN_BUTTON);
DisplayServer::get_singleton()->accessibility_update_set_name(ae, parent_sb->get_accessibility_name());
DisplayServer::get_singleton()->accessibility_update_set_description(ae, parent_sb->get_accessibility_description());
DisplayServer::get_singleton()->accessibility_update_set_live(ae, parent_sb->get_accessibility_live());
DisplayServer::get_singleton()->accessibility_update_set_num_value(ae, parent_sb->get_value());
DisplayServer::get_singleton()->accessibility_update_set_num_range(ae, parent_sb->get_min(), parent_sb->get_max());
if (parent_sb->get_step() > 0) {
DisplayServer::get_singleton()->accessibility_update_set_num_step(ae, parent_sb->get_step());
} else {
DisplayServer::get_singleton()->accessibility_update_set_num_step(ae, 1);
}
//DisplayServer::get_singleton()->accessibility_update_set_num_jump(ae, ???);
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_DECREMENT, callable_mp(this, &SpinBoxLineEdit::_accessibility_action_dec));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_INCREMENT, callable_mp(this, &SpinBoxLineEdit::_accessibility_action_inc));
}
} break;
}
}
Size2 SpinBox::get_minimum_size() const {
Size2 ms = line_edit->get_combined_minimum_size();
ms.width += sizing_cache.buttons_block_width;
@ -63,21 +107,40 @@ void SpinBox::_update_text(bool p_only_update_if_value_changed) {
value += " " + suffix;
}
}
if (!accepted && update_on_text_changed && !line_edit->get_text().replace_char(',', '.').contains_char('.')) {
value = String::num(get_value(), 0);
}
line_edit->set_text_with_selection(value);
}
void SpinBox::_text_submitted(const String &p_string) {
if (p_string.is_empty()) {
_update_text();
return;
}
String text = p_string;
if (update_on_text_changed) {
// Convert commas ',' to dots '.' for French/German etc. keyboard layouts.
text = p_string.replace_char(',', '.');
if (!text.begins_with(".") && p_string.ends_with(".")) {
return;
}
if (text.begins_with(".")) {
line_edit->set_text("0.");
line_edit->set_caret_column(line_edit->get_text().length());
return;
}
}
Ref<Expression> expr;
expr.instantiate();
// Convert commas ',' to dots '.' for French/German etc. keyboard layouts.
String text = p_string.replace(",", ".");
text = text.replace(";", ",");
text = text.replace_char(';', ',');
text = TS->parse_number(text);
// Ignore the prefix and suffix in the expression.
text = text.trim_prefix(prefix + " ").trim_suffix(" " + suffix);
@ -107,12 +170,17 @@ void SpinBox::_text_submitted(const String &p_string) {
}
void SpinBox::_text_changed(const String &p_string) {
accepted = false;
int cursor_pos = line_edit->get_caret_column();
_text_submitted(p_string);
String text = p_string.replace_char(',', '.');
// Line edit 'set_text' method resets the cursor position so we need to undo that.
line_edit->set_caret_column(cursor_pos);
if (update_on_text_changed && !text.begins_with(".")) {
line_edit->set_caret_column(cursor_pos);
}
}
LineEdit *SpinBox::get_line_edit() {
@ -192,6 +260,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
if (mb.is_valid() && mb->is_pressed()) {
switch (mb->get_button_index()) {
case MouseButton::LEFT: {
accepted = true;
line_edit->grab_focus();
if (mouse_on_up_button || mouse_on_down_button) {
@ -268,7 +337,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
if (drag.enabled) {
drag.diff_y += mm->get_relative().y;
double diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8) * SIGN(drag.diff_y);
double diff_y = -0.01 * Math::pow(Math::abs(drag.diff_y), 1.8) * SIGN(drag.diff_y);
use_custom_arrow_step = false;
set_value(CLAMP(drag.base_val + step * diff_y, get_min(), get_max()));
} else if (drag.allowed && drag.capture_pos.distance_to(mm->get_position()) > 2) {
@ -291,9 +360,12 @@ void SpinBox::_line_edit_editing_toggled(bool p_toggled_on) {
line_edit->select_all();
}
} else {
accepted = true;
if (Input::get_singleton()->is_action_pressed("ui_cancel") || line_edit->get_text().is_empty()) {
_update_text(); // Revert text if editing was canceled.
} else {
line_edit->set_text(line_edit->get_text().trim_suffix(".").trim_suffix(","));
_update_text(true); // Update text in case value was changed this frame (e.g. on `focus_exited`).
_text_submitted(line_edit->get_text());
}
@ -622,7 +694,7 @@ void SpinBox::_bind_methods() {
}
SpinBox::SpinBox() {
line_edit = memnew(LineEdit);
line_edit = memnew(SpinBoxLineEdit);
line_edit->set_emoji_menu_enabled(false);
add_child(line_edit, false, INTERNAL_MODE_FRONT);

View file

@ -28,18 +28,33 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef SPIN_BOX_H
#define SPIN_BOX_H
#pragma once
#include "scene/gui/line_edit.h"
#include "scene/gui/range.h"
#include "scene/main/timer.h"
class SpinBoxLineEdit : public LineEdit {
GDCLASS(SpinBoxLineEdit, LineEdit);
protected:
void _notification(int p_what);
static void _bind_methods() {}
void _accessibility_action_inc(const Variant &p_data);
void _accessibility_action_dec(const Variant &p_data);
public:
SpinBoxLineEdit() {}
};
class SpinBox : public Range {
GDCLASS(SpinBox, Range);
LineEdit *line_edit = nullptr;
SpinBoxLineEdit *line_edit = nullptr;
bool update_on_text_changed = false;
bool accepted = true;
struct SizingCache {
int buttons_block_width = 0;
@ -173,5 +188,3 @@ public:
SpinBox();
};
#endif // SPIN_BOX_H

View file

@ -91,8 +91,59 @@ Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos
return Control::get_cursor_shape(p_pos);
}
void SplitContainerDragger::_accessibility_action_inc(const Variant &p_data) {
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) {
return;
}
sc->split_offset -= 10;
sc->_compute_split_offset(true);
sc->queue_sort();
}
void SplitContainerDragger::_accessibility_action_dec(const Variant &p_data) {
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) {
return;
}
sc->split_offset += 10;
sc->_compute_split_offset(true);
sc->queue_sort();
}
void SplitContainerDragger::_accessibility_action_set_value(const Variant &p_data) {
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) {
return;
}
sc->split_offset = p_data;
sc->_compute_split_offset(true);
sc->queue_sort();
}
void SplitContainerDragger::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SPLITTER);
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) {
return;
}
sc->_compute_split_offset(true);
DisplayServer::get_singleton()->accessibility_update_set_num_value(ae, sc->split_offset);
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_DECREMENT, callable_mp(this, &SplitContainerDragger::_accessibility_action_dec));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_INCREMENT, callable_mp(this, &SplitContainerDragger::_accessibility_action_inc));
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_VALUE, callable_mp(this, &SplitContainerDragger::_accessibility_action_set_value));
} break;
case NOTIFICATION_MOUSE_ENTER: {
mouse_inside = true;
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
@ -124,6 +175,10 @@ void SplitContainerDragger::_notification(int p_what) {
}
}
SplitContainerDragger::SplitContainerDragger() {
set_focus_mode(FOCUS_ACCESSIBILITY);
}
Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisibilityMode p_visibility_mode) const {
int idx = 0;
for (int i = 0; i < get_child_count(false); i++) {

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef SPLIT_CONTAINER_H
#define SPLIT_CONTAINER_H
#pragma once
#include "scene/gui/container.h"
@ -42,6 +41,10 @@ protected:
void _notification(int p_what);
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void _accessibility_action_inc(const Variant &p_data);
void _accessibility_action_dec(const Variant &p_data);
void _accessibility_action_set_value(const Variant &p_data);
private:
bool dragging = false;
int drag_from = 0;
@ -50,6 +53,8 @@ private:
public:
virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
SplitContainerDragger();
};
class SplitContainer : public Container {
@ -157,5 +162,3 @@ public:
VSplitContainer() :
SplitContainer(true) { is_fixed = true; }
};
#endif // SPLIT_CONTAINER_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef SUBVIEWPORT_CONTAINER_H
#define SUBVIEWPORT_CONTAINER_H
#pragma once
#include "scene/gui/container.h"
@ -77,5 +76,3 @@ public:
SubViewportContainer();
};
#endif // SUBVIEWPORT_CONTAINER_H

View file

@ -98,6 +98,10 @@ Size2 TabBar::get_minimum_size() const {
if (ms.width - ofs > style->get_minimum_size().width) {
ms.width -= theme_cache.h_separation;
}
if (i < tabs.size() - 1) {
ms.width += theme_cache.tab_separation;
}
}
if (clip_tabs) {
@ -203,6 +207,10 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
queue_redraw();
}
if (close_with_middle_mouse && mb->is_pressed() && mb->get_button_index() == MouseButton::MIDDLE) {
emit_signal(SNAME("tab_close_pressed"), hover);
}
if (mb->is_pressed() && (mb->get_button_index() == MouseButton::LEFT || (select_with_rmb && mb->get_button_index() == MouseButton::RIGHT))) {
Point2 pos = mb->get_position();
@ -347,6 +355,35 @@ void TabBar::_shape(int p_tab) {
tabs.write[p_tab].text_buf->add_string(atr(tabs[p_tab].text), theme_cache.font, theme_cache.font_size, tabs[p_tab].language);
}
RID TabBar::get_tab_accessibility_element(int p_tab) const {
RID ae = get_accessibility_element();
ERR_FAIL_COND_V(ae.is_null(), RID());
const Tab &item = tabs[p_tab];
if (item.accessibility_item_element.is_null()) {
item.accessibility_item_element = DisplayServer::get_singleton()->accessibility_create_sub_element(ae, DisplayServer::AccessibilityRole::ROLE_TAB);
item.accessibility_item_dirty = true;
}
return item.accessibility_item_element;
}
RID TabBar::get_focused_accessibility_element() const {
if (current == -1) {
return get_accessibility_element();
} else {
const Tab &item = tabs[current];
return item.accessibility_item_element;
}
}
void TabBar::_accessibility_action_scroll_into_view(const Variant &p_data, int p_index) {
ensure_tab_visible(p_index);
}
void TabBar::_accessibility_action_focus(const Variant &p_data, int p_index) {
set_current_tab(p_index);
}
void TabBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
@ -379,6 +416,46 @@ void TabBar::_notification(int p_what) {
}
} break;
case NOTIFICATION_EXIT_TREE:
case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
for (int i = 0; i < tabs.size(); i++) {
tabs.write[i].accessibility_item_element = RID();
}
} break;
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_TAB_BAR);
DisplayServer::get_singleton()->accessibility_update_set_list_item_count(ae, tabs.size());
for (int i = 0; i < tabs.size(); i++) {
const Tab &item = tabs[i];
if (item.accessibility_item_element.is_null()) {
item.accessibility_item_element = DisplayServer::get_singleton()->accessibility_create_sub_element(ae, DisplayServer::AccessibilityRole::ROLE_TAB);
item.accessibility_item_dirty = true;
}
if (item.accessibility_item_dirty) {
DisplayServer::get_singleton()->accessibility_update_add_action(item.accessibility_item_element, DisplayServer::AccessibilityAction::ACTION_SCROLL_INTO_VIEW, callable_mp(this, &TabBar::_accessibility_action_scroll_into_view).bind(i));
DisplayServer::get_singleton()->accessibility_update_add_action(item.accessibility_item_element, DisplayServer::AccessibilityAction::ACTION_FOCUS, callable_mp(this, &TabBar::_accessibility_action_focus).bind(i));
DisplayServer::get_singleton()->accessibility_update_set_list_item_index(item.accessibility_item_element, i);
DisplayServer::get_singleton()->accessibility_update_set_name(item.accessibility_item_element, atr(item.text));
DisplayServer::get_singleton()->accessibility_update_set_list_item_selected(item.accessibility_item_element, i == current);
DisplayServer::get_singleton()->accessibility_update_set_flag(item.accessibility_item_element, DisplayServer::AccessibilityFlags::FLAG_DISABLED, item.disabled);
DisplayServer::get_singleton()->accessibility_update_set_flag(item.accessibility_item_element, DisplayServer::AccessibilityFlags::FLAG_HIDDEN, item.hidden);
DisplayServer::get_singleton()->accessibility_update_set_tooltip(item.accessibility_item_element, item.tooltip);
DisplayServer::get_singleton()->accessibility_update_set_bounds(item.accessibility_item_element, Rect2(Point2(item.ofs_cache, 0), Size2(item.size_cache, get_size().height)));
item.accessibility_item_dirty = false;
}
}
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
queue_redraw();
} break;
@ -389,6 +466,7 @@ void TabBar::_notification(int p_what) {
_shape(i);
}
queue_accessibility_update();
queue_redraw();
update_minimum_size();
@ -430,9 +508,12 @@ void TabBar::_notification(int p_what) {
int limit_minus_buttons = size.width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width();
int ofs = tabs[offset].ofs_cache;
int tab_separation_offset = 0;
// Draw unselected tabs in the back.
for (int i = offset; i <= max_drawn_tab; i++) {
tab_separation_offset = (i - offset) * theme_cache.tab_separation;
if (tabs[i].hidden) {
continue;
}
@ -452,7 +533,7 @@ void TabBar::_notification(int p_what) {
col = theme_cache.font_unselected_color;
}
_draw_tab(sb, col, i, rtl ? size.width - ofs - tabs[i].size_cache : ofs, false);
_draw_tab(sb, col, i, rtl ? (size.width - ofs - tab_separation_offset - tabs[i].size_cache) : (ofs + tab_separation_offset), false);
}
ofs += tabs[i].size_cache;
@ -460,8 +541,13 @@ void TabBar::_notification(int p_what) {
// Draw selected tab in the front, but only if it's visible.
if (current >= offset && current <= max_drawn_tab && !tabs[current].hidden) {
tab_separation_offset = (current - offset) * theme_cache.tab_separation;
if (tab_alignment == ALIGNMENT_LEFT && (current - offset) > 1) {
tab_separation_offset = theme_cache.tab_separation;
}
Ref<StyleBox> sb = tabs[current].disabled ? theme_cache.tab_disabled_style : theme_cache.tab_selected_style;
float x = rtl ? size.width - tabs[current].ofs_cache - tabs[current].size_cache : tabs[current].ofs_cache;
float x = rtl ? (size.width - tabs[current].ofs_cache - tab_separation_offset - tabs[current].size_cache) : (tabs[current].ofs_cache + tab_separation_offset);
_draw_tab(sb, theme_cache.font_selected_color, current, x, has_focus());
}
@ -499,13 +585,41 @@ void TabBar::_notification(int p_what) {
if (dragging_valid_tab) {
int x;
int tab_hover = get_hovered_tab();
if (tab_hover != -1) {
Rect2 tab_rect = get_tab_rect(tab_hover);
int closest_tab = get_closest_tab_idx_to_point(get_local_mouse_position());
if (closest_tab != -1) {
Rect2 tab_rect = get_tab_rect(closest_tab);
x = tab_rect.position.x;
if (get_local_mouse_position().x > x + tab_rect.size.width / 2) {
x += tab_rect.size.width;
// Calculate midpoint between tabs.
if (rtl) {
if (get_local_mouse_position().x > tab_rect.position.x + tab_rect.size.width / 2) {
if (closest_tab > 0) { // On right side of closest_tab and not first tab.
Rect2 next_tab_rect = get_tab_rect(closest_tab - 1);
x = (tab_rect.position.x + tab_rect.size.width + next_tab_rect.position.x) / 2;
} else { // First tab, will appear on right edge.
x = tab_rect.position.x + tab_rect.size.width;
}
} else {
if (closest_tab < max_drawn_tab) { // On left side of closest_tab and not last tab.
Rect2 prev_tab_rect = get_tab_rect(closest_tab + 1);
x = (tab_rect.position.x + prev_tab_rect.position.x + prev_tab_rect.size.width) / 2;
} else { // Last tab, will appear on left edge.
x = tab_rect.position.x;
}
}
} else if (get_local_mouse_position().x > tab_rect.position.x + tab_rect.size.width / 2) {
if (closest_tab < max_drawn_tab) { // On right side of closest_tab and not last tab.
Rect2 next_tab_rect = get_tab_rect(closest_tab + 1);
x = (tab_rect.position.x + tab_rect.size.width + next_tab_rect.position.x) / 2;
} else { // Last tab, will appear on right edge.
x = tab_rect.position.x + tab_rect.size.width;
}
} else {
if (closest_tab > 0) { // On left side of closest_tab and not first tab.
Rect2 prev_tab_rect = get_tab_rect(closest_tab - 1);
x = (tab_rect.position.x + prev_tab_rect.position.x + prev_tab_rect.size.width) / 2;
} else { // First tab, will appear on left edge.
x = tab_rect.position.x;
}
}
} else {
if (rtl ^ (get_local_mouse_position().x < get_tab_rect(0).position.x)) {
@ -632,6 +746,15 @@ void TabBar::set_tab_count(int p_count) {
}
ERR_FAIL_COND(p_count < 0);
if (tabs.size() > p_count) {
for (int i = p_count; i < tabs.size(); i++) {
if (tabs[i].accessibility_item_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(tabs.write[i].accessibility_item_element);
tabs.write[i].accessibility_item_element = RID();
}
}
}
tabs.resize(p_count);
if (p_count == 0) {
@ -662,6 +785,7 @@ void TabBar::set_tab_count(int p_count) {
}
}
queue_accessibility_update();
queue_redraw();
update_minimum_size();
notify_property_list_changed();
@ -697,6 +821,7 @@ void TabBar::set_current_tab(int p_current) {
if (scroll_to_selected) {
ensure_tab_visible(current);
}
queue_accessibility_update();
queue_redraw();
emit_signal(SNAME("tab_changed"), p_current);
@ -745,6 +870,7 @@ void TabBar::set_tab_offset(int p_offset) {
ERR_FAIL_INDEX(p_offset, tabs.size());
offset = p_offset;
_update_cache();
queue_accessibility_update();
queue_redraw();
}
@ -771,6 +897,7 @@ void TabBar::set_tab_title(int p_tab, const String &p_title) {
if (scroll_to_selected) {
ensure_tab_visible(current);
}
queue_accessibility_update();
queue_redraw();
update_minimum_size();
}
@ -783,6 +910,7 @@ String TabBar::get_tab_title(int p_tab) const {
void TabBar::set_tab_tooltip(int p_tab, const String &p_tooltip) {
ERR_FAIL_INDEX(p_tab, tabs.size());
tabs.write[p_tab].tooltip = p_tooltip;
queue_accessibility_update();
}
String TabBar::get_tab_tooltip(int p_tab) const {
@ -796,7 +924,9 @@ void TabBar::set_tab_text_direction(int p_tab, Control::TextDirection p_text_dir
if (tabs[p_tab].text_direction != p_text_direction) {
tabs.write[p_tab].text_direction = p_text_direction;
_shape(p_tab);
queue_accessibility_update();
queue_redraw();
}
}
@ -811,12 +941,14 @@ void TabBar::set_tab_language(int p_tab, const String &p_language) {
if (tabs[p_tab].language != p_language) {
tabs.write[p_tab].language = p_language;
_shape(p_tab);
_update_cache();
_ensure_no_over_offset();
if (scroll_to_selected) {
ensure_tab_visible(current);
}
queue_accessibility_update();
queue_redraw();
update_minimum_size();
}
@ -887,6 +1019,7 @@ void TabBar::set_tab_disabled(int p_tab, bool p_disabled) {
if (scroll_to_selected) {
ensure_tab_visible(current);
}
queue_accessibility_update();
queue_redraw();
update_minimum_size();
}
@ -910,6 +1043,7 @@ void TabBar::set_tab_hidden(int p_tab, bool p_hidden) {
if (scroll_to_selected) {
ensure_tab_visible(current);
}
queue_accessibility_update();
queue_redraw();
update_minimum_size();
}
@ -1053,12 +1187,17 @@ void TabBar::_update_cache(bool p_update_hover) {
}
w += tabs[i].size_cache;
if ((i - offset) > 0) {
w += theme_cache.tab_separation;
}
// Check if all tabs would fit inside the area.
if (clip_tabs && i > offset && (w > limit || (offset > 0 && w > limit_minus_buttons))) {
tabs.write[i].ofs_cache = 0;
w -= tabs[i].size_cache;
w -= theme_cache.tab_separation;
max_drawn_tab = i - 1;
while (w > limit_minus_buttons && max_drawn_tab > offset) {
@ -1066,11 +1205,14 @@ void TabBar::_update_cache(bool p_update_hover) {
if (!tabs[max_drawn_tab].hidden) {
w -= tabs[max_drawn_tab].size_cache;
w -= theme_cache.tab_separation;
}
max_drawn_tab--;
}
}
tabs.write[i].accessibility_item_dirty = true;
}
missing_right = max_drawn_tab < tabs.size() - 1;
@ -1125,6 +1267,7 @@ void TabBar::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) {
if (scroll_to_selected) {
ensure_tab_visible(current);
}
queue_accessibility_update();
queue_redraw();
update_minimum_size();
@ -1143,12 +1286,19 @@ void TabBar::clear_tabs() {
return;
}
for (int i = 0; i < tabs.size(); i++) {
if (tabs[i].accessibility_item_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(tabs.write[i].accessibility_item_element);
tabs.write[i].accessibility_item_element = RID();
}
}
tabs.clear();
offset = 0;
max_drawn_tab = 0;
current = -1;
previous = -1;
queue_accessibility_update();
queue_redraw();
update_minimum_size();
notify_property_list_changed();
@ -1156,6 +1306,11 @@ void TabBar::clear_tabs() {
void TabBar::remove_tab(int p_idx) {
ERR_FAIL_INDEX(p_idx, tabs.size());
if (tabs[p_idx].accessibility_item_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(tabs.write[p_idx].accessibility_item_element);
tabs.write[p_idx].accessibility_item_element = RID();
}
tabs.remove_at(p_idx);
bool is_tab_changing = current == p_idx;
@ -1205,6 +1360,7 @@ void TabBar::remove_tab(int p_idx) {
}
}
queue_accessibility_update();
queue_redraw();
update_minimum_size();
notify_property_list_changed();
@ -1238,7 +1394,7 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) {
}
Variant TabBar::_handle_get_drag_data(const String &p_type, const Point2 &p_point) {
int tab_over = get_tab_idx_at_point(p_point);
int tab_over = (p_point == Vector2(Math::INF, Math::INF)) ? current : get_tab_idx_at_point(p_point);
if (tab_over < 0) {
return Variant();
}
@ -1303,7 +1459,7 @@ void TabBar::_handle_drop_data(const String &p_type, const Point2 &p_point, cons
if (String(d["type"]) == p_type) {
int tab_from_id = d["tab_index"];
int hover_now = get_tab_idx_at_point(p_point);
int hover_now = (p_point == Vector2(Math::INF, Math::INF)) ? current : get_closest_tab_idx_to_point(p_point);
NodePath from_path = d["from_path"];
NodePath to_path = get_path();
@ -1361,6 +1517,8 @@ void TabBar::_handle_drop_data(const String &p_type, const Point2 &p_point, cons
void TabBar::_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index) {
Tab moving_tab = p_from_tabbar->tabs[p_from_index];
moving_tab.accessibility_item_element = RID();
moving_tab.accessibility_item_dirty = true;
p_from_tabbar->remove_tab(p_from_index);
tabs.insert(p_to_index, moving_tab);
@ -1380,6 +1538,7 @@ void TabBar::_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_in
queue_redraw();
}
queue_accessibility_update();
update_minimum_size();
}
@ -1398,6 +1557,24 @@ int TabBar::get_tab_idx_at_point(const Point2 &p_point) const {
return hover_now;
}
int TabBar::get_closest_tab_idx_to_point(const Point2 &p_point) const {
int closest_tab = get_tab_idx_at_point(p_point); // See if we're hovering over a tab first.
if (closest_tab == -1) { // Didn't find a tab, so get the closest one.
float closest_distance = FLT_MAX;
for (int i = offset; i <= max_drawn_tab; i++) {
Vector2 center = get_tab_rect(i).get_center();
float distance = center.distance_to(p_point);
if (distance < closest_distance) {
closest_distance = distance;
closest_tab = i;
}
}
}
return closest_tab;
}
void TabBar::set_tab_alignment(AlignmentMode p_alignment) {
ERR_FAIL_INDEX(p_alignment, ALIGNMENT_MAX);
@ -1451,6 +1628,8 @@ void TabBar::move_tab(int p_from, int p_to) {
ERR_FAIL_INDEX(p_to, tabs.size());
Tab tab_from = tabs[p_from];
tab_from.accessibility_item_dirty = true;
tabs.remove_at(p_from);
tabs.insert(p_to, tab_from);
@ -1475,6 +1654,7 @@ void TabBar::move_tab(int p_from, int p_to) {
if (scroll_to_selected) {
ensure_tab_visible(current);
}
queue_accessibility_update();
queue_redraw();
notify_property_list_changed();
}
@ -1647,11 +1827,23 @@ void TabBar::ensure_tab_visible(int p_idx) {
Rect2 TabBar::get_tab_rect(int p_tab) const {
ERR_FAIL_INDEX_V(p_tab, tabs.size(), Rect2());
if (is_layout_rtl()) {
return Rect2(get_size().width - tabs[p_tab].ofs_cache - tabs[p_tab].size_cache, 0, tabs[p_tab].size_cache, get_size().height);
} else {
return Rect2(tabs[p_tab].ofs_cache, 0, tabs[p_tab].size_cache, get_size().height);
int tab_separation_offset = (p_tab - offset) * theme_cache.tab_separation;
if (tab_alignment == ALIGNMENT_LEFT && (p_tab - offset) > 1) {
tab_separation_offset = theme_cache.tab_separation;
}
if (is_layout_rtl()) {
return Rect2(get_size().width - tabs[p_tab].ofs_cache - tab_separation_offset - tabs[p_tab].size_cache, 0, tabs[p_tab].size_cache, get_size().height);
} else {
return Rect2(tabs[p_tab].ofs_cache + tab_separation_offset, 0, tabs[p_tab].size_cache, get_size().height);
}
}
void TabBar::set_close_with_middle_mouse(bool p_scroll_close) {
close_with_middle_mouse = p_scroll_close;
}
bool TabBar::get_close_with_middle_mouse() const {
return close_with_middle_mouse;
}
void TabBar::set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy) {
@ -1795,6 +1987,8 @@ void TabBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("ensure_tab_visible", "idx"), &TabBar::ensure_tab_visible);
ClassDB::bind_method(D_METHOD("get_tab_rect", "tab_idx"), &TabBar::get_tab_rect);
ClassDB::bind_method(D_METHOD("move_tab", "from", "to"), &TabBar::move_tab);
ClassDB::bind_method(D_METHOD("set_close_with_middle_mouse", "enabled"), &TabBar::set_close_with_middle_mouse);
ClassDB::bind_method(D_METHOD("get_close_with_middle_mouse"), &TabBar::get_close_with_middle_mouse);
ClassDB::bind_method(D_METHOD("set_tab_close_display_policy", "policy"), &TabBar::set_tab_close_display_policy);
ClassDB::bind_method(D_METHOD("get_tab_close_display_policy"), &TabBar::get_tab_close_display_policy);
ClassDB::bind_method(D_METHOD("set_max_tab_width", "width"), &TabBar::set_max_tab_width);
@ -1825,6 +2019,7 @@ void TabBar::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1"), "set_current_tab", "get_current_tab");
ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "close_with_middle_mouse"), "set_close_with_middle_mouse", "get_close_with_middle_mouse");
ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_close_display_policy", PROPERTY_HINT_ENUM, "Show Never,Show Active Only,Show Always"), "set_tab_close_display_policy", "get_tab_close_display_policy");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_tab_width", PROPERTY_HINT_RANGE, "0,99999,1,suffix:px"), "set_max_tab_width", "get_max_tab_width");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrolling_enabled"), "set_scrolling_enabled", "get_scrolling_enabled");
@ -1847,6 +2042,7 @@ void TabBar::_bind_methods() {
BIND_ENUM_CONSTANT(CLOSE_BUTTON_MAX);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, h_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, tab_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, icon_max_width);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_unselected_style, "tab_unselected");
@ -1888,6 +2084,7 @@ void TabBar::_bind_methods() {
}
TabBar::TabBar() {
set_focus_mode(FOCUS_ACCESSIBILITY);
set_size(Size2(get_size().width, get_minimum_size().height));
set_focus_mode(FOCUS_ALL);
connect(SceneStringName(mouse_exited), callable_mp(this, &TabBar::_on_mouse_exited));

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TAB_BAR_H
#define TAB_BAR_H
#pragma once
#include "scene/gui/control.h"
#include "scene/property_list_helper.h"
@ -55,6 +54,9 @@ public:
private:
struct Tab {
mutable RID accessibility_item_element;
mutable bool accessibility_item_dirty = true;
String text;
String tooltip;
@ -108,6 +110,7 @@ private:
int cb_hover = -1;
bool cb_pressing = false;
CloseButtonDisplayPolicy cb_displaypolicy = CLOSE_BUTTON_SHOW_NEVER;
bool close_with_middle_mouse = true;
int hover = -1; // Hovered tab.
int max_width = 0;
@ -127,6 +130,7 @@ private:
struct ThemeCache {
int h_separation = 0;
int tab_separation = 0;
int icon_max_width = 0;
Ref<StyleBox> tab_unselected_style;
@ -170,6 +174,9 @@ private:
void _shape(int p_tab);
void _draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x, bool p_focus);
void _accessibility_action_scroll_into_view(const Variant &p_data, int p_index);
void _accessibility_action_focus(const Variant &p_data, int p_index);
protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
virtual String get_tooltip(const Point2 &p_pos) const override;
@ -188,6 +195,9 @@ protected:
void _move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index);
public:
RID get_tab_accessibility_element(int p_tab) const;
virtual RID get_focused_accessibility_element() const override;
Variant _handle_get_drag_data(const String &p_type, const Point2 &p_point);
bool _handle_can_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data) const;
void _handle_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data, const Callable &p_move_tab_callback, const Callable &p_move_tab_from_other_callback);
@ -225,6 +235,7 @@ public:
Ref<Texture2D> get_tab_button_icon(int p_tab) const;
int get_tab_idx_at_point(const Point2 &p_point) const;
int get_closest_tab_idx_to_point(const Point2 &p_point) const;
void set_tab_alignment(AlignmentMode p_alignment);
AlignmentMode get_tab_alignment() const;
@ -239,6 +250,9 @@ public:
void set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy);
CloseButtonDisplayPolicy get_tab_close_display_policy() const;
void set_close_with_middle_mouse(bool p_scroll_close);
bool get_close_with_middle_mouse() const;
void set_tab_count(int p_count);
int get_tab_count() const;
@ -288,5 +302,3 @@ public:
VARIANT_ENUM_CAST(TabBar::AlignmentMode);
VARIANT_ENUM_CAST(TabBar::CloseButtonDisplayPolicy);
#endif // TAB_BAR_H

View file

@ -142,6 +142,45 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) {
void TabContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_INVALIDATE: {
tab_panels.clear();
} break;
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
int tab_index = 0;
int tab_cur = tab_bar->get_current_tab();
for (int i = 0; i < get_child_count(); i++) {
Node *child_node = get_child(i);
Window *child_wnd = Object::cast_to<Window>(child_node);
if (child_wnd && !child_wnd->is_embedded()) {
continue;
}
if (child_node->is_part_of_edited_scene()) {
continue;
}
Control *control = as_sortable_control(child_node, SortableVisibilityMode::IGNORE);
if (!control || control == tab_bar || children_removing.has(control)) {
DisplayServer::get_singleton()->accessibility_update_add_child(ae, child_node->get_accessibility_element());
} else {
if (!tab_panels.has(child_node)) {
tab_panels[child_node] = DisplayServer::get_singleton()->accessibility_create_sub_element(ae, DisplayServer::AccessibilityRole::ROLE_TAB_PANEL);
}
RID panel = tab_panels[child_node];
RID tab = tab_bar->get_tab_accessibility_element(tab_index);
DisplayServer::get_singleton()->accessibility_update_add_related_controls(tab, panel);
DisplayServer::get_singleton()->accessibility_update_add_related_labeled_by(panel, tab);
DisplayServer::get_singleton()->accessibility_update_set_flag(panel, DisplayServer::AccessibilityFlags::FLAG_HIDDEN, tab_index != tab_cur);
DisplayServer::get_singleton()->accessibility_update_add_child(panel, child_node->get_accessibility_element());
tab_index++;
}
}
} break;
case NOTIFICATION_ENTER_TREE: {
// If some nodes happen to be renamed outside the tree, the tab names need to be updated manually.
if (get_tab_count() > 0) {
@ -247,6 +286,7 @@ void TabContainer::_on_theme_changed() {
tab_bar->add_theme_font_size_override(SceneStringName(font_size), theme_cache.tab_font_size);
tab_bar->add_theme_constant_override(SNAME("h_separation"), theme_cache.icon_separation);
tab_bar->add_theme_constant_override(SNAME("tab_separation"), theme_cache.tab_separation);
tab_bar->add_theme_constant_override(SNAME("icon_max_width"), theme_cache.icon_max_width);
tab_bar->add_theme_constant_override(SNAME("outline_size"), theme_cache.outline_size);
@ -555,6 +595,7 @@ void TabContainer::add_child_notify(Node *p_child) {
if (get_tab_count() == 1) {
queue_redraw();
}
queue_accessibility_update();
p_child->connect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names));
p_child->connect(SceneStringName(visibility_changed), callable_mp(this, &TabContainer::_on_tab_visibility_changed).bind(c));
@ -578,11 +619,17 @@ void TabContainer::move_child_notify(Node *p_child) {
}
_refresh_tab_indices();
queue_accessibility_update();
}
void TabContainer::remove_child_notify(Node *p_child) {
Container::remove_child_notify(p_child);
if (tab_panels.has(p_child)) {
DisplayServer::get_singleton()->accessibility_free_element(tab_panels[p_child]);
tab_panels.erase(p_child);
}
if (p_child == tab_bar) {
return;
}
@ -606,6 +653,7 @@ void TabContainer::remove_child_notify(Node *p_child) {
if (get_tab_count() == 0) {
queue_redraw();
}
queue_accessibility_update();
p_child->remove_meta("_tab_index");
p_child->remove_meta("_tab_name");
@ -725,7 +773,7 @@ void TabContainer::set_tab_focus_mode(Control::FocusMode p_focus_mode) {
}
Control::FocusMode TabContainer::get_tab_focus_mode() const {
return tab_bar->get_focus_mode();
return tab_bar->get_focus_mode_with_recursive();
}
void TabContainer::set_clip_tabs(bool p_clip_tabs) {
@ -761,7 +809,6 @@ void TabContainer::set_all_tabs_in_front(bool p_in_front) {
remove_child(tab_bar);
add_child(tab_bar, false, all_tabs_in_front ? INTERNAL_MODE_FRONT : INTERNAL_MODE_BACK);
tab_bar->force_parent_owned();
}
bool TabContainer::is_all_tabs_in_front() const {
@ -951,7 +998,7 @@ void TabContainer::set_popup(Node *p_popup) {
Popup *TabContainer::get_popup() const {
if (popup_obj_id.is_valid()) {
Popup *popup = Object::cast_to<Popup>(ObjectDB::get_instance(popup_obj_id));
Popup *popup = ObjectDB::get_instance<Popup>(popup_obj_id);
if (popup) {
return popup;
} else {
@ -1078,6 +1125,7 @@ void TabContainer::_bind_methods() {
BIND_ENUM_CONSTANT(POSITION_MAX);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabContainer, side_margin);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabContainer, tab_separation);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, panel_style, "panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, tabbar_style, "tabbar_background");

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TAB_CONTAINER_H
#define TAB_CONTAINER_H
#pragma once
#include "scene/gui/container.h"
#include "scene/gui/popup.h"
@ -71,6 +70,7 @@ private:
// TabBar overrides.
int icon_separation = 0;
int tab_separation = 0;
int icon_max_width = 0;
int outline_size = 0;
@ -97,6 +97,8 @@ private:
int tab_font_size;
} theme_cache;
HashMap<Node *, RID> tab_panels;
int _get_tab_height() const;
Vector<Control *> _get_tab_controls() const;
void _on_theme_changed();
@ -129,6 +131,8 @@ protected:
static void _bind_methods();
public:
virtual bool accessibility_override_tree_hierarchy() const override { return true; }
TabBar *get_tab_bar() const;
int get_tab_idx_at_point(const Point2 &p_point) const;
@ -211,5 +215,3 @@ public:
};
VARIANT_ENUM_CAST(TabContainer::TabPosition);
#endif // TAB_CONTAINER_H

File diff suppressed because it is too large Load diff

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TEXT_EDIT_H
#define TEXT_EDIT_H
#pragma once
#include "scene/gui/control.h"
#include "scene/gui/popup_menu.h"
@ -145,12 +144,15 @@ private:
Color color = Color(1, 1, 1);
};
mutable int64_t next_item_id = 0;
struct Line {
Vector<Gutter> gutters;
String data;
Array bidi_override;
Ref<TextParagraph> data_buf;
Vector<RID> accessibility_text_root_element;
String ime_data;
Array ime_bidi_override;
@ -160,6 +162,7 @@ private:
int line_count = 0;
int height = 0;
int width = 0;
float indent_ofs = -1.0;
Line() {
data_buf.instantiate();
@ -227,8 +230,17 @@ private:
BitField<TextServer::LineBreakFlag> get_brk_flags() const;
int get_line_wrap_amount(int p_line) const;
const Vector<RID> get_accessibility_elements(int p_line);
void update_accessibility(int p_line, RID p_root);
void clear_accessibility() {
for (int i = 0; i < text.size(); i++) {
text.write[i].accessibility_text_root_element.clear();
}
}
Vector<Vector2i> get_line_wrap_ranges(int p_line) const;
const Ref<TextParagraph> get_line_data(int p_line) const;
float get_indent_offset(int p_line, bool p_rtl) const;
void set(int p_line, const String &p_text, const Array &p_bidi_override);
void set_ime(int p_line, const String &p_text, const Array &p_bidi_override);
@ -274,13 +286,22 @@ private:
/* Text */
Text text;
bool setting_text = false;
enum AltInputMode {
ALT_INPUT_NONE,
ALT_INPUT_UNICODE,
ALT_INPUT_OEM,
ALT_INPUT_WIN,
};
AltInputMode alt_mode = ALT_INPUT_NONE;
bool alt_start = false;
bool alt_start_no_hold = false;
uint32_t alt_code = 0;
bool tab_input_mode = true;
// Text properties.
String ime_text = "";
Point2 ime_selection;
@ -539,7 +560,7 @@ private:
void _scroll_lines_up();
void _scroll_lines_down();
void _adjust_viewport_to_caret_horizontally(int p_caret = 0);
void _adjust_viewport_to_caret_horizontally(int p_caret = 0, bool p_maximize_selection = true);
// Minimap.
bool draw_minimap = false;
@ -627,6 +648,8 @@ private:
bool draw_tabs = false;
bool draw_spaces = false;
RID accessibility_text_root_element_nl;
/*** Super internal Core API. Everything builds on it. ***/
bool text_changed_dirty = false;
void _text_changed();
@ -695,6 +718,8 @@ protected:
void _unhide_all_lines();
virtual void _unhide_carets();
int _get_wrapped_indent_level(int p_line, int &r_first_wrap) const;
// Symbol lookup.
String lookup_symbol_word;
void _set_symbol_lookup_word(const String &p_symbol);
@ -715,6 +740,17 @@ protected:
virtual void _paste_internal(int p_caret);
virtual void _paste_primary_clipboard_internal(int p_caret);
void _accessibility_action_set_selection(const Variant &p_data);
void _accessibility_action_replace_selected(const Variant &p_data);
void _accessibility_action_set_value(const Variant &p_data);
void _accessibility_action_menu(const Variant &p_data);
void _accessibility_scroll_down(const Variant &p_data);
void _accessibility_scroll_left(const Variant &p_data);
void _accessibility_scroll_right(const Variant &p_data);
void _accessibility_scroll_up(const Variant &p_data);
void _accessibility_scroll_set(const Variant &p_data);
void _accessibility_action_scroll_into_view(const Variant &p_data, int p_line, int p_wrap);
GDVIRTUAL2(_handle_unicode_input, int, int)
GDVIRTUAL1(_backspace, int)
GDVIRTUAL1(_cut, int)
@ -762,6 +798,9 @@ public:
void set_indent_wrapped_lines(bool p_enabled);
bool is_indent_wrapped_lines() const;
void set_tab_input_mode(bool p_enabled);
bool get_tab_input_mode() const;
// User controls
void set_overtype_mode_enabled(bool p_enabled);
bool is_overtype_mode_enabled() const;
@ -1138,5 +1177,3 @@ VARIANT_ENUM_CAST(TextEdit::SelectionMode);
VARIANT_ENUM_CAST(TextEdit::GutterType);
VARIANT_ENUM_CAST(TextEdit::MenuItems);
VARIANT_ENUM_CAST(TextEdit::SearchFlags);
#endif // TEXT_EDIT_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TEXTURE_BUTTON_H
#define TEXTURE_BUTTON_H
#pragma once
#include "scene/gui/base_button.h"
#include "scene/resources/bit_map.h"
@ -104,5 +103,3 @@ public:
};
VARIANT_ENUM_CAST(TextureButton::StretchMode);
#endif // TEXTURE_BUTTON_H

View file

@ -193,7 +193,7 @@ Point2 TextureProgressBar::unit_val_to_uv(float val) {
Point2 p = get_relative_center();
// Minimal version of Liang-Barsky clipping algorithm
float angle = (val * Math_TAU) - Math_PI * 0.5;
float angle = (val * Math::TAU) - Math::PI * 0.5;
Point2 dir = Vector2(Math::cos(angle), Math::sin(angle));
float t1 = 1.0;
float cp = 0.0;
@ -429,6 +429,13 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu
void TextureProgressBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_PROGRESS_INDICATOR);
} break;
case NOTIFICATION_DRAW: {
if (under.is_valid()) {
if (nine_patch_stretch) {

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TEXTURE_PROGRESS_BAR_H
#define TEXTURE_PROGRESS_BAR_H
#pragma once
#include "scene/gui/range.h"
@ -122,5 +121,3 @@ private:
};
VARIANT_ENUM_CAST(TextureProgressBar::FillMode);
#endif // TEXTURE_PROGRESS_BAR_H

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TEXTURE_RECT_H
#define TEXTURE_RECT_H
#pragma once
#include "scene/gui/control.h"
@ -95,5 +94,3 @@ public:
VARIANT_ENUM_CAST(TextureRect::ExpandMode);
VARIANT_ENUM_CAST(TextureRect::StretchMode);
#endif // TEXTURE_RECT_H

View file

@ -0,0 +1,41 @@
/**************************************************************************/
/* tree.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
void TreeItem::_add_button_bind_compat_76829(int p_column, const Ref<Texture2D> &p_button, int p_id, bool p_disabled, const String &p_tooltip) {
add_button(p_column, p_button, p_id, p_disabled, p_tooltip, String());
}
void TreeItem::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("add_button", "column", "button", "id", "disabled", "tooltip_text"), &TreeItem::_add_button_bind_compat_76829, DEFVAL(-1), DEFVAL(false), DEFVAL(""));
}
#endif // DISABLE_DEPRECATED

File diff suppressed because it is too large Load diff

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TREE_H
#define TREE_H
#pragma once
#include "scene/gui/control.h"
#include "scene/gui/line_edit.h"
@ -46,17 +45,18 @@ class TreeItem : public Object {
public:
enum TreeCellMode {
CELL_MODE_STRING, ///< just a string
CELL_MODE_CHECK, ///< string + check
CELL_MODE_RANGE, ///< Contains a range
CELL_MODE_ICON, ///< Contains an icon, not editable
CELL_MODE_CUSTOM, ///< Contains a custom value, show a string, and an edit button
CELL_MODE_STRING,
CELL_MODE_CHECK,
CELL_MODE_RANGE,
CELL_MODE_ICON,
CELL_MODE_CUSTOM,
};
private:
friend class Tree;
struct Cell {
mutable RID accessibility_cell_element;
TreeCellMode mode = TreeItem::CELL_MODE_STRING;
Ref<Texture2D> icon;
@ -65,6 +65,7 @@ private:
String text;
String xl_text;
Node::AutoTranslateMode auto_translate_mode = Node::AUTO_TRANSLATE_MODE_INHERIT;
String alt_text;
bool edit_multiline = false;
String suffix;
Ref<TextParagraph> text_buf;
@ -105,11 +106,13 @@ private:
Callable custom_draw_callback;
struct Button {
mutable RID accessibility_button_element;
int id = 0;
bool disabled = false;
Ref<Texture2D> texture;
Color color = Color(1, 1, 1, 1);
String tooltip;
String alt_text;
};
Vector<Button> buttons;
@ -126,23 +129,26 @@ private:
void draw_icon(const RID &p_where, const Point2 &p_pos, const Size2 &p_size = Size2(), const Color &p_color = Color()) const;
};
mutable RID accessibility_row_element;
mutable bool accessibility_row_dirty = true;
Vector<Cell> cells;
bool collapsed = false; // won't show children
bool collapsed = false; // Won't show children.
bool visible = true;
bool parent_visible_in_tree = true;
bool disable_folding = false;
int custom_min_height = 0;
TreeItem *parent = nullptr; // parent item
TreeItem *prev = nullptr; // previous in list
TreeItem *next = nullptr; // next in list
TreeItem *parent = nullptr; // Parent item.
TreeItem *prev = nullptr; // Previous in list.
TreeItem *next = nullptr; // Next in list.
TreeItem *first_child = nullptr;
TreeItem *last_child = nullptr;
Vector<TreeItem *> children_cache;
bool is_root = false; // for tree root
Tree *tree = nullptr; // tree (for reference)
bool is_root = false; // For tree root.
Tree *tree = nullptr; // Tree (for reference).
TreeItem(Tree *p_tree);
@ -166,6 +172,22 @@ private:
}
_FORCE_INLINE_ void _unlink_from_tree() {
if (accessibility_row_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(accessibility_row_element);
accessibility_row_element = RID();
}
for (Cell &cell : cells) {
if (cell.accessibility_cell_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(cell.accessibility_cell_element);
cell.accessibility_cell_element = RID();
}
for (Cell::Button &btn : cell.buttons) {
if (btn.accessibility_button_element.is_valid()) {
DisplayServer::get_singleton()->accessibility_free_element(btn.accessibility_button_element);
btn.accessibility_button_element = RID();
}
}
}
TreeItem *p = get_prev();
if (p) {
p->next = next;
@ -191,7 +213,12 @@ private:
protected:
static void _bind_methods();
// Bind helpers
#ifndef DISABLE_DEPRECATED
void _add_button_bind_compat_76829(int p_column, const Ref<Texture2D> &p_button, int p_id, bool p_disabled, const String &p_tooltip);
static void _bind_compatibility_methods();
#endif
// Bind helpers.
Dictionary _get_range_config(int p_column) {
Dictionary d;
double min = 0.0, max = 0.0, step = 0.0;
@ -207,19 +234,19 @@ protected:
void _call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
public:
/* cell mode */
// Cell mode.
void set_cell_mode(int p_column, TreeCellMode p_mode);
TreeCellMode get_cell_mode(int p_column) const;
/* auto translate mode */
// Auto translate mode.
void set_auto_translate_mode(int p_column, Node::AutoTranslateMode p_mode);
Node::AutoTranslateMode get_auto_translate_mode(int p_column) const;
/* multiline editable */
// Multiline editable.
void set_edit_multiline(int p_column, bool p_multiline);
bool is_edit_multiline(int p_column) const;
/* check mode */
// Check mode.
void set_checked(int p_column, bool p_checked);
void set_indeterminate(int p_column, bool p_indeterminate);
bool is_checked(int p_column) const;
@ -241,6 +268,9 @@ public:
void set_text(int p_column, String p_text);
String get_text(int p_column) const;
void set_alt_text(int p_column, String p_text);
String get_alt_text(int p_column) const;
void set_text_direction(int p_column, Control::TextDirection p_text_direction);
Control::TextDirection get_text_direction(int p_column) const;
@ -278,7 +308,7 @@ public:
int get_icon_max_width(int p_column) const;
void clear_buttons();
void add_button(int p_column, const Ref<Texture2D> &p_button, int p_id = -1, bool p_disabled = false, const String &p_tooltip = "");
void add_button(int p_column, const Ref<Texture2D> &p_button, int p_id = -1, bool p_disabled = false, const String &p_tooltip = "", const String &p_alt_text = "");
int get_button_count(int p_column) const;
String get_button_tooltip_text(int p_column, int p_index) const;
Ref<Texture2D> get_button(int p_column, int p_index) const;
@ -288,12 +318,12 @@ public:
Color get_button_color(int p_column, int p_index) const;
void set_button_tooltip_text(int p_column, int p_index, const String &p_tooltip);
void set_button(int p_column, int p_index, const Ref<Texture2D> &p_button);
void set_button_alt_text(int p_column, int p_index, const String &p_alt_text);
void set_button_color(int p_column, int p_index, const Color &p_color);
void set_button_disabled(int p_column, int p_index, bool p_disabled);
bool is_button_disabled(int p_column, int p_index) const;
/* range works for mode number or mode combo */
// Range works for mode number or mode combo.
void set_range(int p_column, double p_value);
double get_range(int p_column) const;
@ -367,8 +397,7 @@ public:
Size2 get_minimum_size(int p_column);
/* Item manipulation */
// Item manipulation.
TreeItem *create_child(int p_index = -1);
void add_child(TreeItem *p_item);
void remove_child(TreeItem *p_item);
@ -460,12 +489,10 @@ private:
bool propagate_mouse_activated = false;
//TreeItem *cursor_item;
//int cursor_column;
Rect2 custom_popup_rect;
int edited_col = -1;
int selected_col = -1;
int selected_button = -1;
int popup_edited_item_col = -1;
bool hide_root = false;
SelectMode select_mode = SELECT_SINGLE;
@ -475,6 +502,7 @@ private:
int drop_mode_flags = 0;
struct ColumnInfo {
mutable RID accessibility_col_element;
int custom_min_width = 0;
int expand_ratio = 1;
bool expand = true;
@ -499,6 +527,8 @@ private:
VBoxContainer *popup_editor_vb = nullptr;
bool popup_edit_committed = true;
RID accessibility_scroll_element;
Popup *popup_editor = nullptr;
LineEdit *line_editor = nullptr;
TextEdit *text_editor = nullptr;
@ -520,7 +550,6 @@ private:
void update_column(int p_col);
void update_item_cell(TreeItem *p_item, int p_col) const;
void update_item_cache(TreeItem *p_item) const;
//void draw_item_text(String p_text,const Ref<Texture2D>& p_icon,int p_icon_max_w,bool p_tool,Rect2i p_rect,const Color& p_color);
void draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color);
int draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item, int &r_self_height);
void select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_col, TreeItem *p_prev = nullptr, bool *r_in_range = nullptr, bool p_force_deselect = false);
@ -551,6 +580,8 @@ private:
Ref<StyleBox> hovered;
Ref<StyleBox> hovered_dimmed;
Ref<StyleBox> hovered_selected;
Ref<StyleBox> hovered_selected_focus;
Ref<StyleBox> selected;
Ref<StyleBox> selected_focus;
Ref<StyleBox> cursor;
@ -581,6 +612,7 @@ private:
Color font_color;
Color font_hovered_color;
Color font_hovered_dimmed_color;
Color font_hovered_selected_color;
Color font_selected_color;
Color font_disabled_color;
Color guide_color;
@ -628,7 +660,6 @@ private:
CLICK_NONE,
CLICK_TITLE,
CLICK_BUTTON,
};
ClickType click_type = Cache::CLICK_NONE;
@ -661,7 +692,6 @@ private:
void update_scrollbars();
Rect2 search_item_rect(TreeItem *p_from, TreeItem *p_item);
//Rect2 get_item_rect(TreeItem *p_item);
uint64_t last_keypress = 0;
String incr_search;
bool cursor_can_exit_tree = true;
@ -683,17 +713,9 @@ private:
FindColumnButtonResult _find_column_and_button_at_pos(int p_x, const TreeItem *p_item, int p_x_ofs, int p_x_limit) const;
/* float drag_speed;
float drag_accum;
float last_drag_accum;
float last_drag_time;
float time_since_motion;*/
float drag_speed = 0.0;
float drag_from = 0.0;
float drag_accum = 0.0;
Vector2 last_speed;
bool drag_touching = false;
bool drag_touching_deaccel = false;
bool click_handled = false;
@ -727,13 +749,39 @@ private:
Rect2 _get_content_rect() const; // Considering the background stylebox and scrollbars.
Rect2 _get_item_focus_rect(const TreeItem *p_item) const;
void _check_item_accessibility(TreeItem *p_item, PackedStringArray &r_warnings, int &r_row) const;
void _accessibility_clean_info(TreeItem *p_item);
void _accessibility_update_item(Point2 &r_ofs, TreeItem *p_item, int &r_row, int p_level);
protected:
virtual void _update_theme_item_cache() override;
void _notification(int p_what);
static void _bind_methods();
void _accessibility_action_scroll_down(const Variant &p_data);
void _accessibility_action_scroll_left(const Variant &p_data);
void _accessibility_action_scroll_right(const Variant &p_data);
void _accessibility_action_scroll_up(const Variant &p_data);
void _accessibility_action_scroll_set(const Variant &p_data);
void _accessibility_action_scroll_into_view(const Variant &p_data, TreeItem *p_item, int p_col);
void _accessibility_action_focus(const Variant &p_data, TreeItem *p_item, int p_col);
void _accessibility_action_blur(const Variant &p_data, TreeItem *p_item, int p_col);
void _accessibility_action_collapse(const Variant &p_data, TreeItem *p_item);
void _accessibility_action_expand(const Variant &p_data, TreeItem *p_item);
void _accessibility_action_set_text_value(const Variant &p_data, TreeItem *p_item, int p_col);
void _accessibility_action_set_num_value(const Variant &p_data, TreeItem *p_item, int p_col);
void _accessibility_action_set_bool_value(const Variant &p_data, TreeItem *p_item, int p_col);
void _accessibility_action_set_inc(const Variant &p_data, TreeItem *p_item, int p_col);
void _accessibility_action_set_dec(const Variant &p_data, TreeItem *p_item, int p_col);
void _accessibility_action_edit_custom(const Variant &p_data, TreeItem *p_item, int p_col);
void _accessibility_action_button_press(const Variant &p_data, TreeItem *p_item, int p_col, int p_btn);
public:
PackedStringArray get_accessibility_configuration_warnings() const override;
virtual RID get_focused_accessibility_element() const override;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
virtual String get_tooltip(const Point2 &p_pos) const override;
@ -854,5 +902,3 @@ public:
VARIANT_ENUM_CAST(Tree::SelectMode);
VARIANT_ENUM_CAST(Tree::DropModeFlags);
#endif // TREE_H

View file

@ -127,6 +127,13 @@ void VideoStreamPlayer::_mix_audio() {
void VideoStreamPlayer::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_VIDEO);
} break;
case NOTIFICATION_ENTER_TREE: {
AudioServer::get_singleton()->add_mix_callback(_mix_audios, this);
@ -136,6 +143,7 @@ void VideoStreamPlayer::_notification(int p_notification) {
} break;
case NOTIFICATION_EXIT_TREE: {
stop();
AudioServer::get_singleton()->remove_mix_callback(_mix_audios, this);
} break;
@ -175,7 +183,7 @@ void VideoStreamPlayer::_notification(int p_notification) {
return;
}
Size2 s = expand ? get_size() : texture->get_size();
Size2 s = expand ? get_size() : texture_size;
draw_texture_rect(texture, Rect2(Point2(), s), false);
} break;
@ -211,9 +219,25 @@ void VideoStreamPlayer::_notification(int p_notification) {
}
}
void VideoStreamPlayer::texture_changed(const Ref<Texture2D> &p_texture) {
const Size2 new_texture_size = p_texture.is_valid() ? p_texture->get_size() : Size2();
if (new_texture_size == texture_size) {
return;
}
texture_size = new_texture_size;
queue_redraw();
if (!expand) {
update_minimum_size();
}
}
Size2 VideoStreamPlayer::get_minimum_size() const {
if (!expand && texture.is_valid()) {
return texture->get_size();
return texture_size;
} else {
return Size2();
}
@ -265,10 +289,19 @@ void VideoStreamPlayer::set_stream(const Ref<VideoStream> &p_stream) {
stream->connect_changed(callable_mp(this, &VideoStreamPlayer::set_stream).bind(stream));
}
if (texture.is_valid()) {
texture->disconnect_changed(callable_mp(this, &VideoStreamPlayer::texture_changed));
}
if (playback.is_valid()) {
playback->set_paused(paused);
texture = playback->get_texture();
if (texture.is_valid()) {
texture_size = texture->get_size();
texture->connect_changed(callable_mp(this, &VideoStreamPlayer::texture_changed).bind(texture));
}
const int channels = playback->get_channels();
AudioServer::get_singleton()->lock();

View file

@ -28,8 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef VIDEO_STREAM_PLAYER_H
#define VIDEO_STREAM_PLAYER_H
#pragma once
#include "scene/gui/control.h"
#include "scene/resources/video_stream.h"
@ -52,6 +51,8 @@ class VideoStreamPlayer : public Control {
RID stream_rid;
Ref<Texture2D> texture;
Size2 texture_size;
void texture_changed(const Ref<Texture2D> &p_texture);
AudioRBResampler resampler;
Vector<AudioFrame> mix_buffer;
@ -126,5 +127,3 @@ public:
VideoStreamPlayer();
~VideoStreamPlayer();
};
#endif // VIDEO_STREAM_PLAYER_H

View file

@ -226,9 +226,7 @@ void ViewPanner::set_force_drag(bool p_force) {
}
ViewPanner::ViewPanner() {
Array inputs;
inputs.append(InputEventKey::create_reference(Key::SPACE));
Array inputs = { InputEventKey::create_reference(Key::SPACE) };
pan_view_shortcut.instantiate();
pan_view_shortcut->set_events(inputs);
}

Some files were not shown because too many files have changed in this diff Show more