Add fit_to_longest_item to OptionButton

This commit is contained in:
kobewi 2022-03-19 02:15:55 +01:00
parent 8243c7ab5d
commit 24d02dfb47
5 changed files with 128 additions and 39 deletions

View File

@ -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>

View File

@ -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;
Ref<Texture2D> _icon;
if (icon.is_null() && has_theme_icon(SNAME("icon"))) {
_icon = Control::get_theme_icon(SNAME("icon"));
} else {
_icon = icon;
}
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);
}
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) {

View File

@ -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;

View File

@ -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() {

View File

@ -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 = "");