diff options
| -rw-r--r-- | doc/classes/OptionButton.xml | 4 | ||||
| -rw-r--r-- | scene/gui/button.cpp | 93 | ||||
| -rw-r--r-- | scene/gui/button.h | 4 | ||||
| -rw-r--r-- | scene/gui/option_button.cpp | 59 | ||||
| -rw-r--r-- | scene/gui/option_button.h | 7 | 
5 files changed, 128 insertions, 39 deletions
diff --git a/doc/classes/OptionButton.xml b/doc/classes/OptionButton.xml index a7b1f0ea33..737662fe69 100644 --- a/doc/classes/OptionButton.xml +++ b/doc/classes/OptionButton.xml @@ -194,6 +194,10 @@  	<members>  		<member name="action_mode" type="int" setter="set_action_mode" getter="get_action_mode" overrides="BaseButton" enum="BaseButton.ActionMode" default="0" />  		<member name="alignment" type="int" setter="set_text_alignment" getter="get_text_alignment" overrides="Button" enum="HorizontalAlignment" default="0" /> +		<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. +		</member>  		<member name="item_count" type="int" setter="set_item_count" getter="get_item_count" default="0">  			The number of items to select from.  		</member> diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index a67f850a86..9bcb061526 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -34,39 +34,14 @@  #include "servers/rendering_server.h"  Size2 Button::get_minimum_size() const { -	Size2 minsize = text_buf->get_size(); -	if (clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { -		minsize.width = 0; -	} - -	if (!expand_icon) { -		Ref<Texture2D> _icon; -		if (icon.is_null() && has_theme_icon(SNAME("icon"))) { -			_icon = Control::get_theme_icon(SNAME("icon")); -		} else { -			_icon = icon; -		} - -		if (!_icon.is_null()) { -			minsize.height = MAX(minsize.height, _icon->get_height()); - -			if (icon_alignment != HORIZONTAL_ALIGNMENT_CENTER) { -				minsize.width += _icon->get_width(); -				if (!xl_text.is_empty()) { -					minsize.width += get_theme_constant(SNAME("h_separation")); -				} -			} else { -				minsize.width = MAX(minsize.width, _icon->get_width()); -			} -		} -	} -	if (!xl_text.is_empty()) { -		Ref<Font> font = get_theme_font(SNAME("font")); -		float font_height = font->get_height(get_theme_font_size(SNAME("font_size"))); -		minsize.height = MAX(font_height, minsize.height); +	Ref<Texture2D> _icon; +	if (icon.is_null() && has_theme_icon(SNAME("icon"))) { +		_icon = Control::get_theme_icon(SNAME("icon")); +	} else { +		_icon = icon;  	} -	return get_theme_stylebox(SNAME("normal"))->get_minimum_size() + minsize; +	return get_minimum_size_for_text_and_icon("", _icon);  }  void Button::_set_internal_margin(Side p_side, float p_value) { @@ -352,18 +327,62 @@ void Button::_notification(int p_what) {  	}  } -void Button::_shape() { +Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Texture2D> p_icon) const { +	Ref<TextParagraph> paragraph; +	if (p_text.is_empty()) { +		paragraph = text_buf; +	} else { +		paragraph.instantiate(); +		const_cast<Button *>(this)->_shape(paragraph, p_text); +	} + +	Size2 minsize = paragraph->get_size(); +	if (clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { +		minsize.width = 0; +	} + +	if (!expand_icon && !p_icon.is_null()) { +		minsize.height = MAX(minsize.height, p_icon->get_height()); + +		if (icon_alignment != HORIZONTAL_ALIGNMENT_CENTER) { +			minsize.width += p_icon->get_width(); +			if (!xl_text.is_empty() || !p_text.is_empty()) { +				minsize.width += get_theme_constant(SNAME("hseparation")); +			} +		} else { +			minsize.width = MAX(minsize.width, p_icon->get_width()); +		} +	} + +	if (!xl_text.is_empty() || !p_text.is_empty()) { +		Ref<Font> font = get_theme_font(SNAME("font")); +		float font_height = font->get_height(get_theme_font_size(SNAME("font_size"))); +		minsize.height = MAX(font_height, minsize.height); +	} + +	return get_theme_stylebox(SNAME("normal"))->get_minimum_size() + minsize; +} + +void Button::_shape(Ref<TextParagraph> p_paragraph, String p_text) { +	if (p_paragraph.is_null()) { +		p_paragraph = text_buf; +	} + +	if (p_text.is_empty()) { +		p_text = xl_text; +	} +  	Ref<Font> font = get_theme_font(SNAME("font"));  	int font_size = get_theme_font_size(SNAME("font_size")); -	text_buf->clear(); +	p_paragraph->clear();  	if (text_direction == Control::TEXT_DIRECTION_INHERITED) { -		text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); +		p_paragraph->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);  	} else { -		text_buf->set_direction((TextServer::Direction)text_direction); +		p_paragraph->set_direction((TextServer::Direction)text_direction);  	} -	text_buf->add_string(xl_text, font, font_size, language); -	text_buf->set_text_overrun_behavior(overrun_behavior); +	p_paragraph->add_string(p_text, font, font_size, language); +	p_paragraph->set_text_overrun_behavior(overrun_behavior);  }  void Button::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) { diff --git a/scene/gui/button.h b/scene/gui/button.h index 9d8d457f7c..23b5c78166 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -54,7 +54,7 @@ private:  	HorizontalAlignment icon_alignment = HORIZONTAL_ALIGNMENT_LEFT;  	float _internal_margin[4] = {}; -	void _shape(); +	void _shape(Ref<TextParagraph> p_paragraph = Ref<TextParagraph>(), String p_text = "");  protected:  	void _set_internal_margin(Side p_side, float p_value); @@ -64,6 +64,8 @@ protected:  public:  	virtual Size2 get_minimum_size() const override; +	Size2 get_minimum_size_for_text_and_icon(const String &p_text, Ref<Texture2D> p_icon) const; +  	void set_text(const String &p_text);  	String get_text() const; diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index a10ec1db06..c58513df17 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -35,7 +35,12 @@  static const int NONE_SELECTED = -1;  Size2 OptionButton::get_minimum_size() const { -	Size2 minsize = Button::get_minimum_size(); +	Size2 minsize; +	if (fit_to_longest_item) { +		minsize = _cached_size; +	} else { +		minsize = Button::get_minimum_size(); +	}  	if (has_theme_icon(SNAME("arrow"))) {  		const Size2 padding = get_theme_stylebox(SNAME("normal"))->get_minimum_size(); @@ -107,6 +112,7 @@ void OptionButton::_notification(int p_what) {  					_set_internal_margin(SIDE_RIGHT, Control::get_theme_icon(SNAME("arrow"))->get_width());  				}  			} +			_refresh_size_cache();  		} break;  		case NOTIFICATION_VISIBILITY_CHANGED: { @@ -135,6 +141,10 @@ bool OptionButton::_set(const StringName &p_name, const Variant &p_value) {  			_select(idx, false);  		} +		if (property == "text" || property == "icon") { +			_queue_refresh_cache(); +		} +  		return valid;  	}  	return false; @@ -208,6 +218,7 @@ void OptionButton::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_l  	if (first_selectable) {  		select(get_item_count() - 1);  	} +	_queue_refresh_cache();  }  void OptionButton::add_item(const String &p_label, int p_id) { @@ -216,6 +227,7 @@ void OptionButton::add_item(const String &p_label, int p_id) {  	if (first_selectable) {  		select(get_item_count() - 1);  	} +	_queue_refresh_cache();  }  void OptionButton::set_item_text(int p_idx, const String &p_text) { @@ -224,6 +236,7 @@ void OptionButton::set_item_text(int p_idx, const String &p_text) {  	if (current == p_idx) {  		set_text(p_text);  	} +	_queue_refresh_cache();  }  void OptionButton::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) { @@ -232,6 +245,7 @@ void OptionButton::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {  	if (current == p_idx) {  		set_icon(p_icon);  	} +	_queue_refresh_cache();  }  void OptionButton::set_item_id(int p_idx, int p_id) { @@ -301,6 +315,7 @@ void OptionButton::set_item_count(int p_count) {  		}  	} +	_refresh_size_cache();  	notify_property_list_changed();  } @@ -333,6 +348,19 @@ int OptionButton::get_item_count() const {  	return popup->get_item_count();  } +void OptionButton::set_fit_to_longest_item(bool p_fit) { +	if (p_fit == fit_to_longest_item) { +		return; +	} +	fit_to_longest_item = p_fit; + +	_refresh_size_cache(); +} + +bool OptionButton::is_fit_to_longest_item() const { +	return fit_to_longest_item; +} +  void OptionButton::add_separator(const String &p_text) {  	popup->add_separator(p_text);  } @@ -341,6 +369,7 @@ void OptionButton::clear() {  	popup->clear();  	set_text("");  	current = NONE_SELECTED; +	_refresh_size_cache();  }  void OptionButton::_select(int p_which, bool p_emit) { @@ -380,6 +409,29 @@ void OptionButton::_select_int(int p_which) {  	_select(p_which, false);  } +void OptionButton::_refresh_size_cache() { +	cache_refresh_pending = false; + +	if (!fit_to_longest_item) { +		return; +	} + +	_cached_size = Vector2(); +	for (int i = 0; i < get_item_count(); i++) { +		_cached_size = _cached_size.max(get_minimum_size_for_text_and_icon(get_item_text(i), get_item_icon(i))); +	} +	update_minimum_size(); +} + +void OptionButton::_queue_refresh_cache() { +	if (cache_refresh_pending) { +		return; +	} +	cache_refresh_pending = true; + +	callable_mp(this, &OptionButton::_refresh_size_cache).call_deferredp(nullptr, 0); +} +  void OptionButton::select(int p_idx) {  	_select(p_idx, false);  } @@ -405,6 +457,7 @@ void OptionButton::remove_item(int p_idx) {  	if (current == p_idx) {  		_select(NONE_SELECTED);  	} +	_queue_refresh_cache();  }  PopupMenu *OptionButton::get_popup() const { @@ -453,10 +506,13 @@ void OptionButton::_bind_methods() {  	ClassDB::bind_method(D_METHOD("get_item_count"), &OptionButton::get_item_count);  	ClassDB::bind_method(D_METHOD("has_selectable_items"), &OptionButton::has_selectable_items);  	ClassDB::bind_method(D_METHOD("get_selectable_item", "from_last"), &OptionButton::get_selectable_item, DEFVAL(false)); +	ClassDB::bind_method(D_METHOD("set_fit_to_longest_item", "fit"), &OptionButton::set_fit_to_longest_item); +	ClassDB::bind_method(D_METHOD("is_fit_to_longest_item"), &OptionButton::is_fit_to_longest_item);  	// "selected" property must come after "item_count", otherwise GH-10213 occurs.  	ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_");  	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_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "index")));  	ADD_SIGNAL(MethodInfo("item_focused", PropertyInfo(Variant::INT, "index")));  } @@ -482,6 +538,7 @@ OptionButton::OptionButton(const String &p_text) :  	popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected));  	popup->connect("id_focused", callable_mp(this, &OptionButton::_focused));  	popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed).bind(false)); +	_refresh_size_cache();  }  OptionButton::~OptionButton() { diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index 5665296699..49b5eee910 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -39,11 +39,16 @@ class OptionButton : public Button {  	PopupMenu *popup = nullptr;  	int current = -1; +	bool fit_to_longest_item = true; +	Vector2 _cached_size; +	bool cache_refresh_pending = false;  	void _focused(int p_which);  	void _selected(int p_which);  	void _select(int p_which, bool p_emit = false);  	void _select_int(int p_which); +	void _refresh_size_cache(); +	void _queue_refresh_cache();  	virtual void pressed() override; @@ -85,6 +90,8 @@ public:  	void set_item_count(int p_count);  	int get_item_count() const; +	void set_fit_to_longest_item(bool p_fit); +	bool is_fit_to_longest_item() const;  	void add_separator(const String &p_text = "");  |