mirror of
https://github.com/godotengine/godot.git
synced 2024-11-25 05:33:11 +00:00
Add fit_to_longest_item to OptionButton
This commit is contained in:
parent
8243c7ab5d
commit
24d02dfb47
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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 = "");
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user