소스 검색

Merge pull request #41851 from EricEzaM/PR/popup-menu-hysteresis

Added hysteresis for popup sub-menus
Rémi Verschelde 4 년 전
부모
커밋
94875f5f48
4개의 변경된 파일66개의 추가작업 그리고 11개의 파일을 삭제
  1. 13 1
      scene/gui/popup.cpp
  2. 5 0
      scene/gui/popup.h
  3. 41 9
      scene/gui/popup_menu.cpp
  4. 7 1
      scene/gui/popup_menu.h

+ 13 - 1
scene/gui/popup.cpp

@@ -93,7 +93,7 @@ void Popup::_notification(int p_what) {
 }
 
 void Popup::_parent_focused() {
-	if (popped_up) {
+	if (popped_up && close_on_parent_focus) {
 		_close_pressed();
 	}
 }
@@ -112,7 +112,19 @@ void Popup::set_as_minsize() {
 	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() {
+	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"));
 }
 

+ 5 - 0
scene/gui/popup.h

@@ -40,6 +40,7 @@ class Popup : public Window {
 
 	LocalVector<Window *> visible_parents;
 	bool popped_up = false;
+	bool close_on_parent_focus = true;
 
 	void _input_from_window(const Ref<InputEvent> &p_event);
 
@@ -57,6 +58,10 @@ protected:
 
 public:
 	void set_as_minsize();
+
+	void set_close_on_parent_focus(bool p_close);
+	bool get_close_on_parent_focus();
+
 	Popup();
 	~Popup();
 };

+ 41 - 9
scene/gui/popup_menu.cpp

@@ -173,11 +173,11 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
 	return -1;
 }
 
-void PopupMenu::_activate_submenu(int over) {
-	Node *n = get_node(items[over].submenu);
-	ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[over].submenu + ".");
+void PopupMenu::_activate_submenu(int p_over) {
+	Node *n = get_node(items[p_over].submenu);
+	ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[p_over].submenu + ".");
 	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()) {
 		return; //already visible!
 	}
@@ -190,7 +190,7 @@ void PopupMenu::_activate_submenu(int over) {
 
 	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();
 
 	// 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_popup->set_close_on_parent_focus(false);
 	submenu_popup->set_position(submenu_pos);
 	submenu_popup->set_as_minsize(); // Shrink the popup size to it's contents.
 	submenu_popup->popup();
@@ -210,11 +211,11 @@ void PopupMenu::_activate_submenu(int over) {
 
 		// Autohide area above the submenu item
 		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 (items[over]._ofs_cache + items[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;
+		if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().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));
 		}
 	}
@@ -547,6 +548,31 @@ void PopupMenu::_draw_background() {
 	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) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE: {
@@ -566,7 +592,7 @@ void PopupMenu::_notification(int p_what) {
 			control->update();
 		} break;
 		case NOTIFICATION_WM_MOUSE_ENTER: {
-			//grab_focus();
+			grab_focus();
 		} break;
 		case NOTIFICATION_WM_MOUSE_EXIT: {
 			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->connect("timeout", callable_mp(this, &PopupMenu::_submenu_timeout));
 	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() {

+ 7 - 1
scene/gui/popup_menu.h

@@ -86,6 +86,9 @@ class PopupMenu : public Popup {
 		}
 	};
 
+	bool close_allowed = false;
+
+	Timer *minimum_lifetime_timer = nullptr;
 	Timer *submenu_timer;
 	List<Rect2> autohide_areas;
 	Vector<Item> items;
@@ -102,7 +105,7 @@ class PopupMenu : public Popup {
 	void _scroll_to_item(int p_item);
 
 	void _gui_input(const Ref<InputEvent> &p_event);
-	void _activate_submenu(int over);
+	void _activate_submenu(int p_over);
 	void _submenu_timeout();
 
 	uint64_t popup_time_msec = 0;
@@ -130,6 +133,9 @@ class PopupMenu : public Popup {
 	void _draw_items();
 	void _draw_background();
 
+	void _minimum_lifetime_timeout();
+	void _close_pressed();
+
 protected:
 	friend class MenuButton;
 	void _notification(int p_what);