Merge pull request #114236 from warriormaster12/san-popupmenu-search-bar
Implement search bar for `PopupMenu`
This commit is contained in:
@@ -140,6 +140,12 @@
|
||||
Returns [code]true[/code] if the item at index [param idx] is marked as a separator.
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_search_bar_enabled" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns [code]true[/code] if the search bar is enabled.
|
||||
</description>
|
||||
</method>
|
||||
<method name="remove_item">
|
||||
<return type="void" />
|
||||
<param index="0" name="idx" type="int" />
|
||||
@@ -233,6 +239,9 @@
|
||||
<member name="allow_reselect" type="bool" setter="set_allow_reselect" getter="get_allow_reselect" default="false">
|
||||
If [code]true[/code], the currently selected item can be selected again.
|
||||
</member>
|
||||
<member name="enable_search_bar_on_item_count" type="int" setter="set_search_bar_enabled_on_item_count" getter="get_search_bar_enabled_on_item_count" default="0">
|
||||
Enables the [PopupMenu] search bar if the item count is greater than [code]0[/code].
|
||||
</member>
|
||||
<member name="fit_to_longest_item" type="bool" setter="set_fit_to_longest_item" getter="is_fit_to_longest_item" default="true">
|
||||
If [code]true[/code], minimum size will be determined by the longest item's text, instead of the currently selected one's.
|
||||
[b]Note:[/b] For performance reasons, the minimum size doesn't update immediately when adding, removing or modifying items.
|
||||
|
||||
@@ -408,6 +408,12 @@
|
||||
Returns [code]true[/code] if the system native menu is supported and currently used by this [PopupMenu].
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_search_bar_enabled" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns [code]true[/code] if search bar is currently enabled.
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_system_menu" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
@@ -669,6 +675,10 @@
|
||||
If [code]true[/code], [MenuBar] will use native menu when supported.
|
||||
[b]Note:[/b] If [PopupMenu] is linked to [StatusIndicator], [MenuBar], or another [PopupMenu] item it can use native menu regardless of this property, use [method is_native_menu] to check it.
|
||||
</member>
|
||||
<member name="search_bar_enabled_on_item_count" type="int" setter="set_search_bar_enabled_on_item_count" getter="get_search_bar_enabled_on_item_count" default="0">
|
||||
Enables the [PopupMenu] search bar if the item count is greater than [code]0[/code].
|
||||
[b]Note:[/b] When enabled, [member allow_search] is ignored.
|
||||
</member>
|
||||
<member name="shrink_height" type="bool" setter="set_shrink_height" getter="get_shrink_height" default="true">
|
||||
If [code]true[/code], shrinks [PopupMenu] to minimum height when it's shown.
|
||||
</member>
|
||||
@@ -791,6 +801,9 @@
|
||||
<theme_item name="radio_unchecked_disabled" data_type="icon" type="Texture2D">
|
||||
[Texture2D] icon for the unchecked radio button items when they are disabled.
|
||||
</theme_item>
|
||||
<theme_item name="search" data_type="icon" type="Texture2D">
|
||||
[Texture2D] icon for the search bar's search icon.
|
||||
</theme_item>
|
||||
<theme_item name="submenu" data_type="icon" type="Texture2D">
|
||||
[Texture2D] icon for the submenu arrow (for left-to-right layouts).
|
||||
</theme_item>
|
||||
|
||||
@@ -578,6 +578,7 @@ EditorPropertyTextEnum::EditorPropertyTextEnum() {
|
||||
option_button->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
option_button->set_clip_text(true);
|
||||
option_button->set_flat(true);
|
||||
option_button->set_search_bar_enabled_on_item_count(10);
|
||||
option_button->set_theme_type_variation(SNAME("EditorInspectorButton"));
|
||||
option_button->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
default_layout->add_child(option_button);
|
||||
@@ -985,6 +986,7 @@ EditorPropertyEnum::EditorPropertyEnum() {
|
||||
options->set_flat(true);
|
||||
options->set_theme_type_variation(SNAME("EditorInspectorButton"));
|
||||
options->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
options->set_search_bar_enabled_on_item_count(10);
|
||||
add_child(options);
|
||||
add_focusable(options);
|
||||
options->connect(SceneStringName(item_selected), callable_mp(this, &EditorPropertyEnum::_option_selected));
|
||||
|
||||
@@ -1126,6 +1126,7 @@ void ThemeClassic::populate_standard_styles(const Ref<EditorTheme> &p_theme, Edi
|
||||
p_theme->set_icon("radio_unchecked_disabled", "PopupMenu", p_theme->get_icon(SNAME("GuiRadioUncheckedDisabled"), EditorStringName(EditorIcons)));
|
||||
p_theme->set_icon("submenu", "PopupMenu", p_theme->get_icon(SNAME("ArrowRight"), EditorStringName(EditorIcons)));
|
||||
p_theme->set_icon("submenu_mirrored", "PopupMenu", p_theme->get_icon(SNAME("ArrowLeft"), EditorStringName(EditorIcons)));
|
||||
p_theme->set_icon("search", "PopupMenu", p_theme->get_icon(SNAME("Search"), EditorStringName(EditorIcons)));
|
||||
|
||||
int v_sep = (p_config.enable_touch_optimizations ? 12 : p_config.forced_even_separation) * EDSCALE;
|
||||
p_theme->set_constant("v_separation", "PopupMenu", v_sep);
|
||||
|
||||
@@ -1101,6 +1101,7 @@ void ThemeModern::populate_standard_styles(const Ref<EditorTheme> &p_theme, Edit
|
||||
p_theme->set_icon("radio_unchecked_disabled", "PopupMenu", p_theme->get_icon(SNAME("GuiRadioUncheckedDisabled"), EditorStringName(EditorIcons)));
|
||||
p_theme->set_icon("submenu", "PopupMenu", p_theme->get_icon(SNAME("ArrowRight"), EditorStringName(EditorIcons)));
|
||||
p_theme->set_icon("submenu_mirrored", "PopupMenu", p_theme->get_icon(SNAME("ArrowLeft"), EditorStringName(EditorIcons)));
|
||||
p_theme->set_icon("search", "PopupMenu", p_theme->get_icon(SNAME("Search"), EditorStringName(EditorIcons)));
|
||||
|
||||
p_theme->set_constant("h_separation", "PopupMenu", p_config.base_margin * 1.75 * EDSCALE);
|
||||
int v_sep = (p_config.enable_touch_optimizations ? 12 : p_config.base_margin * 1.75) * EDSCALE;
|
||||
|
||||
@@ -390,6 +390,18 @@ bool OptionButton::get_allow_reselect() const {
|
||||
return allow_reselect;
|
||||
}
|
||||
|
||||
bool OptionButton::is_search_bar_enabled() const {
|
||||
return popup->is_search_bar_enabled();
|
||||
}
|
||||
|
||||
void OptionButton::set_search_bar_enabled_on_item_count(int p_count) {
|
||||
popup->set_search_bar_enabled_on_item_count(p_count);
|
||||
}
|
||||
|
||||
int OptionButton::get_search_bar_enabled_on_item_count() const {
|
||||
return popup->get_search_bar_enabled_on_item_count();
|
||||
}
|
||||
|
||||
void OptionButton::add_separator(const String &p_text) {
|
||||
popup->add_separator(p_text);
|
||||
}
|
||||
@@ -570,6 +582,7 @@ void OptionButton::_bind_methods() {
|
||||
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("set_search_bar_enabled_on_item_count", "counts"), &OptionButton::set_search_bar_enabled_on_item_count);
|
||||
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);
|
||||
@@ -579,6 +592,8 @@ void OptionButton::_bind_methods() {
|
||||
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("is_search_bar_enabled"), &OptionButton::is_search_bar_enabled);
|
||||
ClassDB::bind_method(D_METHOD("get_search_bar_enabled_on_item_count"), &OptionButton::get_search_bar_enabled_on_item_count);
|
||||
ClassDB::bind_method(D_METHOD("add_separator", "text"), &OptionButton::add_separator, DEFVAL(String()));
|
||||
ClassDB::bind_method(D_METHOD("clear"), &OptionButton::clear);
|
||||
ClassDB::bind_method(D_METHOD("select", "idx"), &OptionButton::select);
|
||||
@@ -604,6 +619,7 @@ void OptionButton::_bind_methods() {
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "selected"), "_select_int", "get_selected");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_to_longest_item"), "set_fit_to_longest_item", "is_fit_to_longest_item");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_reselect"), "set_allow_reselect", "get_allow_reselect");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "enable_search_bar_on_item_count", PROPERTY_HINT_RANGE, "0,20,1,or_greater"), "set_search_bar_enabled_on_item_count", "get_search_bar_enabled_on_item_count");
|
||||
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "index")));
|
||||
|
||||
@@ -107,6 +107,7 @@ public:
|
||||
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);
|
||||
void set_search_bar_enabled_on_item_count(int p_count);
|
||||
|
||||
String get_item_text(int p_idx) const;
|
||||
Ref<Texture2D> get_item_icon(int p_idx) const;
|
||||
@@ -117,6 +118,8 @@ public:
|
||||
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 is_search_bar_enabled() const;
|
||||
int get_search_bar_enabled_on_item_count() const;
|
||||
|
||||
bool has_selectable_items() const;
|
||||
int get_selectable_item(bool p_from_last = false) const;
|
||||
|
||||
+152
-16
@@ -37,7 +37,9 @@
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/os/os.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/graph_edit.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/menu_bar.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/main/timer.h"
|
||||
@@ -319,6 +321,9 @@ int PopupMenu::_get_items_total_height() const {
|
||||
// Get total height of all items by taking max of icon height and font height
|
||||
int items_total_height = 0;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (!items[i].visible) {
|
||||
continue;
|
||||
}
|
||||
items_total_height += _get_item_height(i) + theme_cache.v_separation;
|
||||
}
|
||||
|
||||
@@ -343,6 +348,9 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
|
||||
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++) {
|
||||
if (!items[i].visible) {
|
||||
continue;
|
||||
}
|
||||
bottom_edge += theme_cache.v_separation;
|
||||
bottom_edge += _get_item_height(i);
|
||||
if (bottom_edge > over_control_y) {
|
||||
@@ -378,8 +386,8 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
|
||||
Rect2i screen_rect = is_embedded() ? Rect2i(get_embedder()->get_visible_rect()) : get_parent_rect();
|
||||
active_submenu_target_line.clear();
|
||||
|
||||
panel_offset_start = Point2(panel->get_offset(SIDE_LEFT), panel->get_offset(SIDE_TOP)) * win_scale;
|
||||
const Point2 panel_offset_end = Point2(-panel->get_offset(SIDE_RIGHT), -panel->get_offset(SIDE_BOTTOM)) * win_scale;
|
||||
scroll_container_offset_start = Point2(scroll_container->get_offset(SIDE_LEFT), scroll_container->get_offset(SIDE_TOP)) * win_scale;
|
||||
const Point2 scroll_container_offset_end = Point2(-scroll_container->get_offset(SIDE_RIGHT), -scroll_container->get_offset(SIDE_BOTTOM)) * win_scale;
|
||||
const Vector2 scaled_this_size = this_rect.size * win_scale;
|
||||
const float scaled_theme_v_separation = theme_cache.v_separation * win_scale;
|
||||
const float scroll_offset = control->get_position().y;
|
||||
@@ -388,17 +396,17 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
|
||||
|
||||
if (is_layout_rtl()) {
|
||||
is_active_submenu_left = true;
|
||||
submenu_pos += this_pos + Point2(-submenu_size.width + panel_offset_end.x, scaled_ofs_cache + scroll_offset - int(scaled_theme_v_separation * 0.5) + panel_offset_start.y);
|
||||
submenu_pos += this_pos + Point2(-submenu_size.width + scroll_container_offset_end.x, scaled_ofs_cache + scroll_offset - int(scaled_theme_v_separation * 0.5) + scroll_container_offset_start.y);
|
||||
if (submenu_pos.x < screen_rect.position.x) {
|
||||
submenu_pos.x = this_pos.x + this_rect.size.width - panel_offset_start.x;
|
||||
submenu_pos.x = this_pos.x + this_rect.size.width - scroll_container_offset_start.x;
|
||||
is_active_submenu_left = false;
|
||||
}
|
||||
|
||||
} else {
|
||||
is_active_submenu_left = false;
|
||||
submenu_pos += this_pos + Point2(scaled_this_size.x + panel_offset_start.x, scaled_ofs_cache + scroll_offset - int(scaled_theme_v_separation * 0.5) + panel_offset_start.y);
|
||||
submenu_pos += this_pos + Point2(scaled_this_size.x + scroll_container_offset_start.x, scaled_ofs_cache + scroll_offset - int(scaled_theme_v_separation * 0.5) + scroll_container_offset_start.y);
|
||||
if (submenu_pos.x + submenu_size.width > screen_rect.position.x + screen_rect.size.width) {
|
||||
submenu_pos.x = this_pos.x - submenu_size.width + panel_offset_end.x;
|
||||
submenu_pos.x = this_pos.x - submenu_size.width + scroll_container_offset_end.x;
|
||||
is_active_submenu_left = true;
|
||||
}
|
||||
}
|
||||
@@ -513,7 +521,7 @@ 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) {
|
||||
if (!items[i].separator && !items[i].disabled && items[i].visible) {
|
||||
prev_mouse_over = mouse_over;
|
||||
mouse_over = i;
|
||||
emit_signal(SNAME("id_focused"), items[i].id);
|
||||
@@ -529,7 +537,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
if (!match_found) {
|
||||
// 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) {
|
||||
if (!items[i].separator && !items[i].disabled && items[i].visible) {
|
||||
prev_mouse_over = mouse_over;
|
||||
mouse_over = i;
|
||||
emit_signal(SNAME("id_focused"), items[i].id);
|
||||
@@ -556,7 +564,7 @@ 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) {
|
||||
if (!items[i].separator && !items[i].disabled && items[i].visible) {
|
||||
prev_mouse_over = mouse_over;
|
||||
mouse_over = i;
|
||||
emit_signal(SNAME("id_focused"), items[i].id);
|
||||
@@ -572,7 +580,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
if (!match_found) {
|
||||
// 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) {
|
||||
if (!items[i].separator && !items[i].disabled && items[i].visible) {
|
||||
prev_mouse_over = mouse_over;
|
||||
mouse_over = i;
|
||||
emit_signal(SNAME("id_focused"), items[i].id);
|
||||
@@ -692,6 +700,9 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
|
||||
int over = _get_mouse_over(b->get_position());
|
||||
if (over < 0) {
|
||||
if (search_bar->is_visible() && search_bar->get_global_rect().has_point(b->get_position() / win_scale)) {
|
||||
return;
|
||||
}
|
||||
if (!was_during_grabbed_click) {
|
||||
hide();
|
||||
}
|
||||
@@ -724,7 +735,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
if (this_submenu_index != -1) { // Is a submenu.
|
||||
PopupMenu *parent_popup = Object::cast_to<PopupMenu>(get_parent());
|
||||
ERR_FAIL_NULL(parent_popup);
|
||||
Point2 areas_mouse_pos = get_mouse_position() - parent_popup->panel_offset_start;
|
||||
Point2 areas_mouse_pos = get_mouse_position() - parent_popup->scroll_container_offset_start;
|
||||
for (const Rect2 &E : autohide_areas) {
|
||||
if (!scroll_container->get_global_rect().has_point(m->get_position()) && E.has_point(areas_mouse_pos)) {
|
||||
_close_or_suspend();
|
||||
@@ -758,7 +769,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
|
||||
Ref<InputEventKey> k = p_event;
|
||||
|
||||
if (allow_search && k.is_valid() && k->get_unicode() && k->is_pressed()) {
|
||||
if (!search_bar->is_visible() && allow_search && k.is_valid() && k->get_unicode() && k->is_pressed()) {
|
||||
uint64_t now = OS::get_singleton()->get_ticks_msec();
|
||||
uint64_t diff = now - search_time_msec;
|
||||
uint64_t max_interval = uint64_t(GLOBAL_GET_CACHED(uint64_t, "gui/timers/incremental_search_max_interval_msec"));
|
||||
@@ -872,7 +883,7 @@ void PopupMenu::_draw_items() {
|
||||
bool has_check_gutter = false;
|
||||
bool gutter_compact = theme_cache.gutter_compact;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items[i].separator) {
|
||||
if (items[i].separator || !items[i].visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -896,6 +907,10 @@ void PopupMenu::_draw_items() {
|
||||
|
||||
// Loop through all items and draw each.
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (!items[i].visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// For the first item only add half a separation. For all other items, add a whole separation to the offset.
|
||||
ofs.y += i > 0 ? theme_cache.v_separation : theme_cache.v_separation / 2;
|
||||
|
||||
@@ -1052,6 +1067,59 @@ void PopupMenu::_draw_items() {
|
||||
}
|
||||
}
|
||||
|
||||
void PopupMenu::_search_bar_input(const Ref<InputEvent> &p_event) {
|
||||
// Redirect navigational key events to the tree.
|
||||
Ref<InputEventKey> key = p_event;
|
||||
if (key.is_valid()) {
|
||||
if (key->is_action("ui_up", true) || key->is_action("ui_down", true) || key->is_action("ui_page_up") || key->is_action("ui_page_down")) {
|
||||
search_bar->accept_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PopupMenu::_search_bar_text_changed(const String &p_new_text) {
|
||||
_filter_items(p_new_text);
|
||||
|
||||
queue_accessibility_update();
|
||||
control->queue_redraw();
|
||||
child_controls_changed();
|
||||
notify_property_list_changed();
|
||||
_menu_changed();
|
||||
}
|
||||
|
||||
void PopupMenu::_filter_items(const String &p_query) {
|
||||
for (PopupMenu::Item &item : items) {
|
||||
bool all_sub_items_invisible = true;
|
||||
if (item.submenu) {
|
||||
item.submenu->_filter_items(p_query);
|
||||
for (const PopupMenu::Item &submenu_item : item.submenu->items) {
|
||||
if (submenu_item.visible) {
|
||||
all_sub_items_invisible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p_query.length() > 0) {
|
||||
bool contains = item.text.containsn(p_query);
|
||||
if (item.submenu) {
|
||||
if (contains) {
|
||||
item.visible = true;
|
||||
for (PopupMenu::Item &submenu_item : item.submenu->items) {
|
||||
submenu_item.visible = true;
|
||||
}
|
||||
} else {
|
||||
item.visible = !all_sub_items_invisible;
|
||||
}
|
||||
} else {
|
||||
item.visible = contains;
|
||||
}
|
||||
} else {
|
||||
item.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PopupMenu::_close_pressed() {
|
||||
if (this_submenu_index != -1 && active_submenu_index != -1) { // Close secondary submenus on first submenu close.
|
||||
ERR_FAIL_INDEX_MSG(active_submenu_index, items.size(), vformat("Invalid active_submenu_index index %d in _close_pressed.", active_submenu_index));
|
||||
@@ -1268,6 +1336,9 @@ void PopupMenu::_notification(int p_what) {
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
const Item &item = items.write[i];
|
||||
if (!item.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid discrepancy between min size and drawn items due to rounding.
|
||||
ofs.y += i > 0 ? theme_cache.v_separation : theme_cache.v_separation - (theme_cache.v_separation / 2);
|
||||
@@ -1328,6 +1399,8 @@ void PopupMenu::_notification(int p_what) {
|
||||
|
||||
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
search_bar->set_right_icon(get_theme_icon(SNAME("search")));
|
||||
|
||||
panel->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style);
|
||||
|
||||
if (is_visible()) {
|
||||
@@ -1364,6 +1437,9 @@ void PopupMenu::_notification(int p_what) {
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_WM_MOUSE_ENTER: {
|
||||
if (is_embedded() && search_bar->is_menu_visible()) {
|
||||
return;
|
||||
}
|
||||
grab_focus();
|
||||
} break;
|
||||
|
||||
@@ -1411,7 +1487,7 @@ void PopupMenu::_notification(int p_what) {
|
||||
|
||||
bool match_found = false;
|
||||
for (int i = search_from; i < items.size(); i++) {
|
||||
if (!items[i].separator && !items[i].disabled) {
|
||||
if (!items[i].separator && !items[i].disabled && items[i].visible) {
|
||||
mouse_over = i;
|
||||
emit_signal(SNAME("id_focused"), items[i].id);
|
||||
scroll_to_item(i);
|
||||
@@ -1476,7 +1552,7 @@ void PopupMenu::_notification(int p_what) {
|
||||
ERR_FAIL_NULL(parent_popup);
|
||||
const float win_scale = get_content_scale_factor();
|
||||
Point2 mouse_pos = DisplayServer::get_singleton()->mouse_get_position() - get_position();
|
||||
Point2 areas_mouse_pos = mouse_pos - parent_popup->panel_offset_start;
|
||||
Point2 areas_mouse_pos = mouse_pos - parent_popup->scroll_container_offset_start;
|
||||
for (const Rect2 &E : autohide_areas) {
|
||||
if (!Rect2(Point2(), control->get_size() * win_scale).has_point(areas_mouse_pos) && E.has_point(areas_mouse_pos)) {
|
||||
_close_or_suspend();
|
||||
@@ -1490,6 +1566,21 @@ void PopupMenu::_notification(int p_what) {
|
||||
if (!is_visible()) {
|
||||
this_submenu_index = -1;
|
||||
|
||||
if (!search_bar->get_text().is_empty()) {
|
||||
search_bar->clear();
|
||||
_search_bar_text_changed("");
|
||||
}
|
||||
|
||||
if (is_embedded()) {
|
||||
LineEdit *parent_search_bar = Object::cast_to<LineEdit>(get_parent());
|
||||
if (parent_search_bar && parent_search_bar->is_visible()) {
|
||||
Window *window_parent = Object::cast_to<Window>(parent_search_bar->get_viewport());
|
||||
if (window_parent) {
|
||||
window_parent->grab_focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mouse_over >= 0) {
|
||||
prev_mouse_over = mouse_over;
|
||||
mouse_over = -1;
|
||||
@@ -3102,6 +3193,18 @@ String PopupMenu::get_tooltip(const Point2 &p_pos) const {
|
||||
return items[over].tooltip;
|
||||
}
|
||||
|
||||
bool PopupMenu::is_search_bar_enabled() const {
|
||||
return search_bar_enabled_on_item_count > 0 && items.size() >= search_bar_enabled_on_item_count;
|
||||
}
|
||||
|
||||
void PopupMenu::set_search_bar_enabled_on_item_count(int p_count) {
|
||||
search_bar_enabled_on_item_count = p_count;
|
||||
}
|
||||
|
||||
int PopupMenu::get_search_bar_enabled_on_item_count() const {
|
||||
return search_bar_enabled_on_item_count;
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
PackedStringArray PopupMenu::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Popup::get_configuration_warnings();
|
||||
@@ -3285,6 +3388,9 @@ void PopupMenu::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("is_system_menu"), &PopupMenu::is_system_menu);
|
||||
ClassDB::bind_method(D_METHOD("set_system_menu", "system_menu_id"), &PopupMenu::set_system_menu);
|
||||
ClassDB::bind_method(D_METHOD("get_system_menu"), &PopupMenu::get_system_menu);
|
||||
ClassDB::bind_method(D_METHOD("is_search_bar_enabled"), &PopupMenu::is_search_bar_enabled);
|
||||
ClassDB::bind_method(D_METHOD("set_search_bar_enabled_on_item_count", "count"), &PopupMenu::set_search_bar_enabled_on_item_count);
|
||||
ClassDB::bind_method(D_METHOD("get_search_bar_enabled_on_item_count"), &PopupMenu::get_search_bar_enabled_on_item_count);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_shrink_height", "shrink"), &PopupMenu::set_shrink_height);
|
||||
ClassDB::bind_method(D_METHOD("get_shrink_height"), &PopupMenu::get_shrink_height);
|
||||
@@ -3302,6 +3408,8 @@ void PopupMenu::_bind_methods() {
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shrink_height"), "set_shrink_height", "get_shrink_height");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shrink_width"), "set_shrink_width", "get_shrink_width");
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "search_bar_enabled_on_item_count", PROPERTY_HINT_RANGE, "0,20,1,or_greater"), "set_search_bar_enabled_on_item_count", "get_search_bar_enabled_on_item_count");
|
||||
|
||||
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "id")));
|
||||
@@ -3333,6 +3441,7 @@ void PopupMenu::_bind_methods() {
|
||||
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, radio_unchecked);
|
||||
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, radio_unchecked_disabled);
|
||||
|
||||
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, search);
|
||||
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, submenu);
|
||||
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, submenu_mirrored);
|
||||
|
||||
@@ -3501,6 +3610,16 @@ void PopupMenu::set_visible(bool p_visible) {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (p_visible) {
|
||||
PopupMenu *parent_popup = Object::cast_to<PopupMenu>(get_parent());
|
||||
if (!parent_popup) {
|
||||
search_bar->set_visible(is_search_bar_enabled());
|
||||
if (search_bar->is_visible()) {
|
||||
search_bar->edit(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (native) {
|
||||
if (p_visible) {
|
||||
_native_popup(Rect2i(get_position(), get_size()));
|
||||
@@ -3522,10 +3641,27 @@ PopupMenu::PopupMenu() {
|
||||
panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
|
||||
add_child(panel, false, INTERNAL_MODE_FRONT);
|
||||
|
||||
vbox_container = memnew(VBoxContainer);
|
||||
vbox_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
|
||||
vbox_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
vbox_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
panel->add_child(vbox_container, false, INTERNAL_MODE_FRONT);
|
||||
|
||||
search_bar = memnew(LineEdit);
|
||||
search_bar->set_visible(is_search_bar_enabled());
|
||||
search_bar->set_clear_button_enabled(true);
|
||||
search_bar->set_placeholder(ETR("Search"));
|
||||
search_bar->set_keep_editing_on_text_submit(true);
|
||||
search_bar->connect(SceneStringName(text_changed), callable_mp(this, &PopupMenu::_search_bar_text_changed));
|
||||
search_bar->connect(SceneStringName(gui_input), callable_mp(this, &PopupMenu::_search_bar_input));
|
||||
vbox_container->add_child(search_bar, false, INTERNAL_MODE_FRONT);
|
||||
|
||||
// Scroll Container
|
||||
scroll_container = memnew(ScrollContainer);
|
||||
scroll_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
|
||||
panel->add_child(scroll_container, false, INTERNAL_MODE_FRONT);
|
||||
scroll_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
scroll_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
vbox_container->add_child(scroll_container, false, INTERNAL_MODE_FRONT);
|
||||
|
||||
// The control which will display the items
|
||||
control = memnew(Control);
|
||||
|
||||
+16
-1
@@ -38,6 +38,8 @@
|
||||
#include "servers/display/native_menu.h"
|
||||
|
||||
class PanelContainer;
|
||||
class VBoxContainer;
|
||||
class LineEdit;
|
||||
class Timer;
|
||||
|
||||
class PopupMenu : public Popup {
|
||||
@@ -62,6 +64,7 @@ class PopupMenu : public Popup {
|
||||
AutoTranslateMode auto_translate_mode = AUTO_TRANSLATE_MODE_INHERIT;
|
||||
|
||||
bool checked = false;
|
||||
bool visible = true;
|
||||
enum {
|
||||
CHECKABLE_TYPE_NONE,
|
||||
CHECKABLE_TYPE_CHECK_BOX,
|
||||
@@ -137,7 +140,7 @@ class PopupMenu : public Popup {
|
||||
Point2 last_submenu_mouse_position;
|
||||
int submenu_mouse_exited_ticks_msec = -1;
|
||||
bool mouse_movement_was_tested = false;
|
||||
Point2 panel_offset_start;
|
||||
Point2 scroll_container_offset_start;
|
||||
float submenu_timer_popup_delay = 0.2;
|
||||
const float CLOSE_SUSPENDED_TIMER_DELAY = 0.5;
|
||||
String _get_accel_text(const Item &p_item) const;
|
||||
@@ -176,7 +179,10 @@ class PopupMenu : public Popup {
|
||||
uint64_t search_time_msec = 0;
|
||||
String search_string = "";
|
||||
|
||||
int search_bar_enabled_on_item_count = 0;
|
||||
PanelContainer *panel = nullptr;
|
||||
VBoxContainer *vbox_container = nullptr;
|
||||
LineEdit *search_bar = nullptr;
|
||||
ScrollContainer *scroll_container = nullptr;
|
||||
Control *control = nullptr;
|
||||
|
||||
@@ -210,6 +216,7 @@ class PopupMenu : public Popup {
|
||||
Ref<Texture2D> radio_unchecked;
|
||||
Ref<Texture2D> radio_unchecked_disabled;
|
||||
|
||||
Ref<Texture2D> search;
|
||||
Ref<Texture2D> submenu;
|
||||
Ref<Texture2D> submenu_mirrored;
|
||||
|
||||
@@ -231,6 +238,9 @@ class PopupMenu : public Popup {
|
||||
} theme_cache;
|
||||
|
||||
void _draw_items();
|
||||
void _search_bar_input(const Ref<InputEvent> &p_event);
|
||||
void _search_bar_text_changed(const String &p_new_text);
|
||||
void _filter_items(const String &p_query);
|
||||
|
||||
void _close_pressed();
|
||||
void _menu_changed();
|
||||
@@ -374,6 +384,11 @@ public:
|
||||
void set_prefer_native_menu(bool p_enabled);
|
||||
bool is_prefer_native_menu() const;
|
||||
|
||||
bool is_search_bar_enabled() const;
|
||||
|
||||
void set_search_bar_enabled_on_item_count(int p_count);
|
||||
int get_search_bar_enabled_on_item_count() const;
|
||||
|
||||
bool is_native_menu() const;
|
||||
|
||||
void scroll_to_item(int p_idx);
|
||||
|
||||
@@ -760,6 +760,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
|
||||
theme->set_icon("radio_unchecked_disabled", "PopupMenu", icons["radio_unchecked_disabled"]);
|
||||
theme->set_icon("submenu", "PopupMenu", icons["popup_menu_arrow_right"]);
|
||||
theme->set_icon("submenu_mirrored", "PopupMenu", icons["popup_menu_arrow_left"]);
|
||||
theme->set_icon("search", "PopupMenu", icons["search"]);
|
||||
|
||||
theme->set_font(SceneStringName(font), "PopupMenu", Ref<Font>());
|
||||
theme->set_font("font_separator", "PopupMenu", Ref<Font>());
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M10.168 8.754a5 5 0 1 0-1.414 1.414l4.316 4.316 1.414-1.414zM6 3a3 3 0 0 1 0 6 3 3 0 0 1 0-6z"/></svg>
|
||||
|
After Width: | Height: | Size: 190 B |
Reference in New Issue
Block a user