Merge pull request #41851 from EricEzaM/PR/popup-menu-hysteresis
Added hysteresis for popup sub-menus
This commit is contained in:
commit
94875f5f48
|
@ -93,7 +93,7 @@ void Popup::_notification(int p_what) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Popup::_parent_focused() {
|
void Popup::_parent_focused() {
|
||||||
if (popped_up) {
|
if (popped_up && close_on_parent_focus) {
|
||||||
_close_pressed();
|
_close_pressed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,19 @@ void Popup::set_as_minsize() {
|
||||||
set_size(get_contents_minimum_size());
|
set_size(get_contents_minimum_size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Popup::set_close_on_parent_focus(bool p_close) {
|
||||||
|
close_on_parent_focus = p_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Popup::get_close_on_parent_focus() {
|
||||||
|
return close_on_parent_focus;
|
||||||
|
}
|
||||||
|
|
||||||
void Popup::_bind_methods() {
|
void Popup::_bind_methods() {
|
||||||
|
ClassDB::bind_method(D_METHOD("set_close_on_parent_focus", "close"), &Popup::set_close_on_parent_focus);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_close_on_parent_focus"), &Popup::get_close_on_parent_focus);
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "close_on_parent_focus"), "set_close_on_parent_focus", "get_close_on_parent_focus");
|
||||||
|
|
||||||
ADD_SIGNAL(MethodInfo("popup_hide"));
|
ADD_SIGNAL(MethodInfo("popup_hide"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ class Popup : public Window {
|
||||||
|
|
||||||
LocalVector<Window *> visible_parents;
|
LocalVector<Window *> visible_parents;
|
||||||
bool popped_up = false;
|
bool popped_up = false;
|
||||||
|
bool close_on_parent_focus = true;
|
||||||
|
|
||||||
void _input_from_window(const Ref<InputEvent> &p_event);
|
void _input_from_window(const Ref<InputEvent> &p_event);
|
||||||
|
|
||||||
|
@ -57,6 +58,10 @@ protected:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void set_as_minsize();
|
void set_as_minsize();
|
||||||
|
|
||||||
|
void set_close_on_parent_focus(bool p_close);
|
||||||
|
bool get_close_on_parent_focus();
|
||||||
|
|
||||||
Popup();
|
Popup();
|
||||||
~Popup();
|
~Popup();
|
||||||
};
|
};
|
||||||
|
|
|
@ -173,11 +173,11 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopupMenu::_activate_submenu(int over) {
|
void PopupMenu::_activate_submenu(int p_over) {
|
||||||
Node *n = get_node(items[over].submenu);
|
Node *n = get_node(items[p_over].submenu);
|
||||||
ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[over].submenu + ".");
|
ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[p_over].submenu + ".");
|
||||||
Popup *submenu_popup = Object::cast_to<Popup>(n);
|
Popup *submenu_popup = Object::cast_to<Popup>(n);
|
||||||
ERR_FAIL_COND_MSG(!submenu_popup, "Item subnode is not a Popup: " + items[over].submenu + ".");
|
ERR_FAIL_COND_MSG(!submenu_popup, "Item subnode is not a Popup: " + items[p_over].submenu + ".");
|
||||||
if (submenu_popup->is_visible()) {
|
if (submenu_popup->is_visible()) {
|
||||||
return; //already visible!
|
return; //already visible!
|
||||||
}
|
}
|
||||||
|
@ -190,7 +190,7 @@ void PopupMenu::_activate_submenu(int over) {
|
||||||
|
|
||||||
float scroll_offset = control->get_position().y;
|
float scroll_offset = control->get_position().y;
|
||||||
|
|
||||||
Point2 submenu_pos = this_pos + Point2(this_rect.size.width, items[over]._ofs_cache + scroll_offset);
|
Point2 submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset);
|
||||||
Size2 submenu_size = submenu_popup->get_size();
|
Size2 submenu_size = submenu_popup->get_size();
|
||||||
|
|
||||||
// Fix pos if going outside parent rect
|
// Fix pos if going outside parent rect
|
||||||
|
@ -198,6 +198,7 @@ void PopupMenu::_activate_submenu(int over) {
|
||||||
submenu_pos.x = this_pos.x - submenu_size.width;
|
submenu_pos.x = this_pos.x - submenu_size.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
submenu_popup->set_close_on_parent_focus(false);
|
||||||
submenu_popup->set_position(submenu_pos);
|
submenu_popup->set_position(submenu_pos);
|
||||||
submenu_popup->set_as_minsize(); // Shrink the popup size to it's contents.
|
submenu_popup->set_as_minsize(); // Shrink the popup size to it's contents.
|
||||||
submenu_popup->popup();
|
submenu_popup->popup();
|
||||||
|
@ -210,11 +211,11 @@ void PopupMenu::_activate_submenu(int over) {
|
||||||
|
|
||||||
// Autohide area above the submenu item
|
// Autohide area above the submenu item
|
||||||
submenu_pum->clear_autohide_areas();
|
submenu_pum->clear_autohide_areas();
|
||||||
submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2));
|
submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2));
|
||||||
|
|
||||||
// If there is an area below the submenu item, add an autohide area there.
|
// If there is an area below the submenu item, add an autohide area there.
|
||||||
if (items[over]._ofs_cache + items[over]._height_cache + scroll_offset <= control->get_size().height) {
|
if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) {
|
||||||
int from = items[over]._ofs_cache + items[over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height;
|
int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height;
|
||||||
submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from));
|
submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -547,6 +548,31 @@ void PopupMenu::_draw_background() {
|
||||||
style->draw(ci2, Rect2(Point2(), margin_container->get_size()));
|
style->draw(ci2, Rect2(Point2(), margin_container->get_size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PopupMenu::_minimum_lifetime_timeout() {
|
||||||
|
close_allowed = true;
|
||||||
|
// If the mouse still isn't in this popup after timer expires, close.
|
||||||
|
if (!get_visible_rect().has_point(get_mouse_position())) {
|
||||||
|
_close_pressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenu::_close_pressed() {
|
||||||
|
// Only apply minimum lifetime to submenus.
|
||||||
|
PopupMenu *parent_pum = Object::cast_to<PopupMenu>(get_parent());
|
||||||
|
if (!parent_pum) {
|
||||||
|
Popup::_close_pressed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the timer has expired, close. If timer is still running, do nothing.
|
||||||
|
if (close_allowed) {
|
||||||
|
close_allowed = false;
|
||||||
|
Popup::_close_pressed();
|
||||||
|
} else if (minimum_lifetime_timer->is_stopped()) {
|
||||||
|
minimum_lifetime_timer->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PopupMenu::_notification(int p_what) {
|
void PopupMenu::_notification(int p_what) {
|
||||||
switch (p_what) {
|
switch (p_what) {
|
||||||
case NOTIFICATION_ENTER_TREE: {
|
case NOTIFICATION_ENTER_TREE: {
|
||||||
|
@ -566,7 +592,7 @@ void PopupMenu::_notification(int p_what) {
|
||||||
control->update();
|
control->update();
|
||||||
} break;
|
} break;
|
||||||
case NOTIFICATION_WM_MOUSE_ENTER: {
|
case NOTIFICATION_WM_MOUSE_ENTER: {
|
||||||
//grab_focus();
|
grab_focus();
|
||||||
} break;
|
} break;
|
||||||
case NOTIFICATION_WM_MOUSE_EXIT: {
|
case NOTIFICATION_WM_MOUSE_EXIT: {
|
||||||
if (mouse_over >= 0 && (items[mouse_over].submenu == "" || submenu_over != -1)) {
|
if (mouse_over >= 0 && (items[mouse_over].submenu == "" || submenu_over != -1)) {
|
||||||
|
@ -1484,6 +1510,12 @@ PopupMenu::PopupMenu() {
|
||||||
submenu_timer->set_one_shot(true);
|
submenu_timer->set_one_shot(true);
|
||||||
submenu_timer->connect("timeout", callable_mp(this, &PopupMenu::_submenu_timeout));
|
submenu_timer->connect("timeout", callable_mp(this, &PopupMenu::_submenu_timeout));
|
||||||
add_child(submenu_timer);
|
add_child(submenu_timer);
|
||||||
|
|
||||||
|
minimum_lifetime_timer = memnew(Timer);
|
||||||
|
minimum_lifetime_timer->set_wait_time(0.3);
|
||||||
|
minimum_lifetime_timer->set_one_shot(true);
|
||||||
|
minimum_lifetime_timer->connect("timeout", callable_mp(this, &PopupMenu::_minimum_lifetime_timeout));
|
||||||
|
add_child(minimum_lifetime_timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupMenu::~PopupMenu() {
|
PopupMenu::~PopupMenu() {
|
||||||
|
|
|
@ -86,6 +86,9 @@ class PopupMenu : public Popup {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool close_allowed = false;
|
||||||
|
|
||||||
|
Timer *minimum_lifetime_timer = nullptr;
|
||||||
Timer *submenu_timer;
|
Timer *submenu_timer;
|
||||||
List<Rect2> autohide_areas;
|
List<Rect2> autohide_areas;
|
||||||
Vector<Item> items;
|
Vector<Item> items;
|
||||||
|
@ -102,7 +105,7 @@ class PopupMenu : public Popup {
|
||||||
void _scroll_to_item(int p_item);
|
void _scroll_to_item(int p_item);
|
||||||
|
|
||||||
void _gui_input(const Ref<InputEvent> &p_event);
|
void _gui_input(const Ref<InputEvent> &p_event);
|
||||||
void _activate_submenu(int over);
|
void _activate_submenu(int p_over);
|
||||||
void _submenu_timeout();
|
void _submenu_timeout();
|
||||||
|
|
||||||
uint64_t popup_time_msec = 0;
|
uint64_t popup_time_msec = 0;
|
||||||
|
@ -130,6 +133,9 @@ class PopupMenu : public Popup {
|
||||||
void _draw_items();
|
void _draw_items();
|
||||||
void _draw_background();
|
void _draw_background();
|
||||||
|
|
||||||
|
void _minimum_lifetime_timeout();
|
||||||
|
void _close_pressed();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class MenuButton;
|
friend class MenuButton;
|
||||||
void _notification(int p_what);
|
void _notification(int p_what);
|
||||||
|
|
Loading…
Reference in a new issue