summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/classes/OptionButton.xml4
-rw-r--r--scene/gui/button.cpp93
-rw-r--r--scene/gui/button.h4
-rw-r--r--scene/gui/option_button.cpp59
-rw-r--r--scene/gui/option_button.h7
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 = "");