Improve SpinBox interaction, split arrows, add theme attributes

This commit is contained in:
David Giardi 2024-03-04 02:07:15 +01:00
parent 739019e4e4
commit e371587147
12 changed files with 460 additions and 28 deletions

View File

@ -51,4 +51,12 @@
</description>
</signal>
</signals>
<theme_items>
<theme_item name="updown" data_type="icon" type="Texture2D">
Single texture representing both the up and down buttons.
</theme_item>
<theme_item name="updown_disabled" data_type="icon" type="Texture2D">
Single texture representing both the up and down buttons, when the control is readonly or disabled.
</theme_item>
</theme_items>
</class>

View File

@ -25,7 +25,7 @@
The above code will create a [SpinBox], disable context menu on it and set the text alignment to right.
See [Range] class for more options over the [SpinBox].
[b]Note:[/b] With the [SpinBox]'s context menu disabled, you can right-click the bottom half of the spinbox to set the value to its minimum, while right-clicking the top half sets the value to its maximum.
[b]Note:[/b] [SpinBox] relies on an underlying [LineEdit] node. To theme a [SpinBox]'s background, add theme items for [LineEdit] and customize them.
[b]Note:[/b] [SpinBox] relies on an underlying [LineEdit] node. To theme a [SpinBox]'s background, add theme items for [LineEdit] and customize them. The [LineEdit] has the [code]SpinBoxInnerLineEdit[/code] theme variation, so that you can give it a distinct appearance from regular [LineEdit]s.
[b]Note:[/b] If you want to implement drag and drop for the underlying [LineEdit], you can use [method Control.set_drag_forwarding] on the node returned by [method get_line_edit].
</description>
<tutorials>
@ -71,8 +71,98 @@
</member>
</members>
<theme_items>
<theme_item name="down_disabled_icon_modulate" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)">
Down button icon modulation color, when the button is disabled.
</theme_item>
<theme_item name="down_hover_icon_modulate" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
Down button icon modulation color, when the button is hovered.
</theme_item>
<theme_item name="down_icon_modulate" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)">
Down button icon modulation color.
</theme_item>
<theme_item name="down_pressed_icon_modulate" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
Down button icon modulation color, when the button is being pressed.
</theme_item>
<theme_item name="up_disabled_icon_modulate" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)">
Up button icon modulation color, when the button is disabled.
</theme_item>
<theme_item name="up_hover_icon_modulate" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
Up button icon modulation color, when the button is hovered.
</theme_item>
<theme_item name="up_icon_modulate" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)">
Up button icon modulation color.
</theme_item>
<theme_item name="up_pressed_icon_modulate" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
Up button icon modulation color, when the button is being pressed.
</theme_item>
<theme_item name="buttons_vertical_separation" data_type="constant" type="int" default="0">
Vertical separation between the up and down buttons.
</theme_item>
<theme_item name="buttons_width" data_type="constant" type="int" default="16">
Width of the up and down buttons. If smaller than any icon set on the buttons, the respective icon may overlap neighboring elements, unless [theme_item set_min_buttons_width_from_icons] is different than [code]0[/code].
</theme_item>
<theme_item name="field_and_buttons_separation" data_type="constant" type="int" default="2">
Width of the horizontal separation between the text input field ([LineEdit]) and the buttons.
</theme_item>
<theme_item name="set_min_buttons_width_from_icons" data_type="constant" type="int" default="1">
If not [code]0[/code], the minimum button width corresponds to the widest of all icons set on those buttons, even if [theme_item buttons_width] is smaller.
</theme_item>
<theme_item name="down" data_type="icon" type="Texture2D">
Down button icon, displayed in the middle of the down (value-decreasing) button.
</theme_item>
<theme_item name="down_disabled" data_type="icon" type="Texture2D">
Down button icon when the button is disabled.
</theme_item>
<theme_item name="down_hover" data_type="icon" type="Texture2D">
Down button icon when the button is hovered.
</theme_item>
<theme_item name="down_pressed" data_type="icon" type="Texture2D">
Down button icon when the button is being pressed.
</theme_item>
<theme_item name="up" data_type="icon" type="Texture2D">
Up button icon, displayed in the middle of the up (value-increasing) button.
</theme_item>
<theme_item name="up_disabled" data_type="icon" type="Texture2D">
Up button icon when the button is disabled.
</theme_item>
<theme_item name="up_hover" data_type="icon" type="Texture2D">
Up button icon when the button is hovered.
</theme_item>
<theme_item name="up_pressed" data_type="icon" type="Texture2D">
Up button icon when the button is being pressed.
</theme_item>
<theme_item name="updown" data_type="icon" type="Texture2D">
Sets a custom [Texture2D] for up and down arrows of the [SpinBox].
Single texture representing both the up and down buttons icons. It is displayed in the middle of the buttons and does not change upon interaction. It is recommended to use individual [theme_item up] and [theme_item down] graphics for better usability. This can also be used as additional decoration between the two buttons.
</theme_item>
<theme_item name="down_background" data_type="style" type="StyleBox">
Background style of the down button.
</theme_item>
<theme_item name="down_background_disabled" data_type="style" type="StyleBox">
Background style of the down button when disabled.
</theme_item>
<theme_item name="down_background_hovered" data_type="style" type="StyleBox">
Background style of the down button when hovered.
</theme_item>
<theme_item name="down_background_pressed" data_type="style" type="StyleBox">
Background style of the down button when being pressed.
</theme_item>
<theme_item name="field_and_buttons_separator" data_type="style" type="StyleBox">
[StyleBox] drawn in the space occupied by the separation between the input field and the buttons.
</theme_item>
<theme_item name="up_background" data_type="style" type="StyleBox">
Background style of the up button.
</theme_item>
<theme_item name="up_background_disabled" data_type="style" type="StyleBox">
Background style of the up button when disabled.
</theme_item>
<theme_item name="up_background_hovered" data_type="style" type="StyleBox">
Background style of the up button when hovered.
</theme_item>
<theme_item name="up_background_pressed" data_type="style" type="StyleBox">
Background style of the up button when being pressed.
</theme_item>
<theme_item name="up_down_buttons_separator" data_type="style" type="StyleBox">
[StyleBox] drawn in the space occupied by the separation between the up and down buttons.
</theme_item>
</theme_items>
</class>

View File

@ -35,6 +35,7 @@
#include "core/os/keyboard.h"
#include "editor/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/theme/theme_db.h"
bool EditorSpinSlider::is_text_field() const {
return true;
@ -383,7 +384,7 @@ void EditorSpinSlider::_draw_spin_slider() {
if (!hide_slider) {
if (get_step() == 1) {
Ref<Texture2D> updown2 = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
Ref<Texture2D> updown2 = is_read_only() ? theme_cache.updown_disabled_icon : theme_cache.updown_icon;
int updown_vofs = (size.height - updown2->get_height()) / 2;
if (rtl) {
updown_offset = sb->get_margin(SIDE_LEFT);
@ -701,6 +702,9 @@ void EditorSpinSlider::_bind_methods() {
ADD_SIGNAL(MethodInfo("ungrabbed"));
ADD_SIGNAL(MethodInfo("value_focus_entered"));
ADD_SIGNAL(MethodInfo("value_focus_exited"));
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, EditorSpinSlider, updown_icon, "updown");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, EditorSpinSlider, updown_disabled_icon, "updown_disabled");
}
void EditorSpinSlider::_ensure_input_popup() {

View File

@ -87,6 +87,11 @@ class EditorSpinSlider : public Range {
void _ensure_input_popup();
void _draw_spin_slider();
struct ThemeCache {
Ref<Texture2D> updown_icon;
Ref<Texture2D> updown_disabled_icon;
} theme_cache;
protected:
void _notification(int p_what);
virtual void gui_input(const Ref<InputEvent> &p_event) override;

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="8"><path fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".8" stroke-width="2" d="m12 2-4 4-4-4"/></svg>

After

Width:  |  Height:  |  Size: 206 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="8"><path fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".8" stroke-width="2" d="m4 6 4-4 4 4"/></svg>

After

Width:  |  Height:  |  Size: 205 B

View File

@ -1471,8 +1471,44 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
}
// SpinBox.
p_theme->set_icon("updown", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxUpdown"), EditorStringName(EditorIcons)));
p_theme->set_icon("updown_disabled", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxUpdownDisabled"), EditorStringName(EditorIcons)));
{
Ref<Texture2D> empty_icon = memnew(ImageTexture);
p_theme->set_icon("updown", "SpinBox", empty_icon);
p_theme->set_icon("up", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxUp"), EditorStringName(EditorIcons)));
p_theme->set_icon("up_hover", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxUp"), EditorStringName(EditorIcons)));
p_theme->set_icon("up_pressed", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxUp"), EditorStringName(EditorIcons)));
p_theme->set_icon("up_disabled", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxUp"), EditorStringName(EditorIcons)));
p_theme->set_icon("down", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxDown"), EditorStringName(EditorIcons)));
p_theme->set_icon("down_hover", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxDown"), EditorStringName(EditorIcons)));
p_theme->set_icon("down_pressed", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxDown"), EditorStringName(EditorIcons)));
p_theme->set_icon("down_disabled", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxDown"), EditorStringName(EditorIcons)));
p_theme->set_stylebox("up_background", "SpinBox", make_empty_stylebox());
p_theme->set_stylebox("up_background_hovered", "SpinBox", p_config.button_style_hover);
p_theme->set_stylebox("up_background_pressed", "SpinBox", p_config.button_style_pressed);
p_theme->set_stylebox("up_background_disabled", "SpinBox", make_empty_stylebox());
p_theme->set_stylebox("down_background", "SpinBox", make_empty_stylebox());
p_theme->set_stylebox("down_background_hovered", "SpinBox", p_config.button_style_hover);
p_theme->set_stylebox("down_background_pressed", "SpinBox", p_config.button_style_pressed);
p_theme->set_stylebox("down_background_disabled", "SpinBox", make_empty_stylebox());
p_theme->set_color("up_icon_modulate", "SpinBox", p_config.font_color);
p_theme->set_color("up_hover_icon_modulate", "SpinBox", p_config.font_hover_color);
p_theme->set_color("up_pressed_icon_modulate", "SpinBox", p_config.font_pressed_color);
p_theme->set_color("up_disabled_icon_modulate", "SpinBox", p_config.font_disabled_color);
p_theme->set_color("down_icon_modulate", "SpinBox", p_config.font_color);
p_theme->set_color("down_hover_icon_modulate", "SpinBox", p_config.font_hover_color);
p_theme->set_color("down_pressed_icon_modulate", "SpinBox", p_config.font_pressed_color);
p_theme->set_color("down_disabled_icon_modulate", "SpinBox", p_config.font_disabled_color);
p_theme->set_stylebox("field_and_buttons_separator", "SpinBox", make_empty_stylebox());
p_theme->set_stylebox("up_down_buttons_separator", "SpinBox", make_empty_stylebox());
p_theme->set_constant("buttons_vertical_separation", "SpinBox", 0);
p_theme->set_constant("field_and_buttons_separation", "SpinBox", 2);
p_theme->set_constant("buttons_width", "SpinBox", 16);
p_theme->set_constant("set_min_buttons_width_from_icons", "SpinBox", 1);
}
// ProgressBar.
p_theme->set_stylebox("background", "ProgressBar", make_stylebox(p_theme->get_icon(SNAME("GuiProgressBar"), EditorStringName(EditorIcons)), 4, 4, 4, 4, 0, 0, 0, 0));
@ -1858,6 +1894,10 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
editor_spin_label_bg->set_border_width_all(0);
p_theme->set_stylebox("label_bg", "EditorSpinSlider", editor_spin_label_bg);
// TODO Use separate arrows instead like on SpinBox. Planned for a different PR.
p_theme->set_icon("updown", "EditorSpinSlider", p_theme->get_icon(SNAME("GuiSpinboxUpdown"), EditorStringName(EditorIcons)));
p_theme->set_icon("updown_disabled", "EditorSpinSlider", p_theme->get_icon(SNAME("GuiSpinboxUpdownDisabled"), EditorStringName(EditorIcons)));
// Launch Pad and Play buttons.
Ref<StyleBoxFlat> style_launch_pad = make_flat_stylebox(p_config.dark_color_1, 2 * EDSCALE, 0, 2 * EDSCALE, 0, p_config.corner_radius);
style_launch_pad->set_corner_radius_all(p_config.corner_radius * EDSCALE);

View File

@ -36,7 +36,7 @@
Size2 SpinBox::get_minimum_size() const {
Size2 ms = line_edit->get_combined_minimum_size();
ms.width += last_w;
ms.width += sizing_cache.buttons_block_width;
return ms;
}
@ -128,7 +128,7 @@ void SpinBox::_range_click_timeout() {
}
}
void SpinBox::_release_mouse() {
void SpinBox::_release_mouse_from_drag_mode() {
if (drag.enabled) {
drag.enabled = false;
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_HIDDEN);
@ -137,6 +137,14 @@ void SpinBox::_release_mouse() {
}
}
void SpinBox::_mouse_exited() {
if (state_cache.up_button_hovered || state_cache.down_button_hovered) {
state_cache.up_button_hovered = false;
state_cache.down_button_hovered = false;
queue_redraw();
}
}
void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
@ -144,18 +152,36 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
return;
}
Ref<InputEventMouse> me = p_event;
Ref<InputEventMouseButton> mb = p_event;
Ref<InputEventMouseMotion> mm = p_event;
double step = get_custom_arrow_step() != 0.0 ? get_custom_arrow_step() : get_step();
if (mb.is_valid() && mb->is_pressed()) {
bool up = mb->get_position().y < (get_size().height / 2);
Vector2 mpos;
bool mouse_on_up_button = false;
bool mouse_on_down_button = false;
if (mb.is_valid() || mm.is_valid()) {
Rect2 up_button_rc = Rect2(sizing_cache.buttons_left, 0, sizing_cache.buttons_width, sizing_cache.button_up_height);
Rect2 down_button_rc = Rect2(sizing_cache.buttons_left, sizing_cache.second_button_top, sizing_cache.buttons_width, sizing_cache.button_down_height);
mpos = me->get_position();
mouse_on_up_button = up_button_rc.has_point(mpos);
mouse_on_down_button = down_button_rc.has_point(mpos);
}
if (mb.is_valid() && mb->is_pressed()) {
switch (mb->get_button_index()) {
case MouseButton::LEFT: {
line_edit->grab_focus();
set_value(get_value() + (up ? step : -step));
if (mouse_on_up_button || mouse_on_down_button) {
set_value(get_value() + (mouse_on_up_button ? step : -step));
}
state_cache.up_button_pressed = mouse_on_up_button;
state_cache.down_button_pressed = mouse_on_down_button;
queue_redraw();
range_click_timer->set_wait_time(0.6);
range_click_timer->set_one_shot(true);
@ -166,7 +192,9 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
} break;
case MouseButton::RIGHT: {
line_edit->grab_focus();
set_value((up ? get_max() : get_min()));
if (mouse_on_up_button || mouse_on_down_button) {
set_value(mouse_on_up_button ? get_max() : get_min());
}
} break;
case MouseButton::WHEEL_UP: {
if (line_edit->has_focus()) {
@ -186,14 +214,30 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
}
if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
if (state_cache.up_button_pressed || state_cache.down_button_pressed) {
state_cache.up_button_pressed = false;
state_cache.down_button_pressed = false;
queue_redraw();
}
//set_default_cursor_shape(CURSOR_ARROW);
range_click_timer->stop();
_release_mouse();
_release_mouse_from_drag_mode();
drag.allowed = false;
line_edit->clear_pending_select_all_on_focus();
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
bool old_up_hovered = state_cache.up_button_hovered;
bool old_down_hovered = state_cache.down_button_hovered;
state_cache.up_button_hovered = mouse_on_up_button;
state_cache.down_button_hovered = mouse_on_down_button;
if (old_up_hovered != state_cache.up_button_hovered || old_down_hovered != state_cache.down_button_hovered) {
queue_redraw();
}
}
if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
if (drag.enabled) {
@ -239,41 +283,131 @@ void SpinBox::_line_edit_focus_exit() {
_text_submitted(line_edit->get_text());
}
inline void SpinBox::_adjust_width_for_icon(const Ref<Texture2D> &icon) {
int w = icon->get_width();
if ((w != last_w)) {
inline void SpinBox::_compute_sizes() {
int buttons_block_wanted_width = theme_cache.buttons_width + theme_cache.field_and_buttons_separation;
int buttons_block_icon_enforced_width = _get_widest_button_icon_width() + theme_cache.field_and_buttons_separation;
int w = theme_cache.set_min_buttons_width_from_icons != 0 ? MAX(buttons_block_icon_enforced_width, buttons_block_wanted_width) : buttons_block_wanted_width;
if (w != sizing_cache.buttons_block_width) {
line_edit->set_offset(SIDE_LEFT, 0);
line_edit->set_offset(SIDE_RIGHT, -w);
last_w = w;
sizing_cache.buttons_block_width = w;
}
Size2i size = get_size();
sizing_cache.buttons_width = w - theme_cache.field_and_buttons_separation;
sizing_cache.buttons_vertical_separation = CLAMP(theme_cache.buttons_vertical_separation, 0, size.height);
sizing_cache.buttons_left = is_layout_rtl() ? 0 : size.width - sizing_cache.buttons_width;
sizing_cache.button_up_height = (size.height - sizing_cache.buttons_vertical_separation) / 2;
sizing_cache.button_down_height = size.height - sizing_cache.button_up_height - sizing_cache.buttons_vertical_separation;
sizing_cache.second_button_top = size.height - sizing_cache.button_down_height;
sizing_cache.buttons_separator_top = sizing_cache.button_up_height;
sizing_cache.field_and_buttons_separator_left = is_layout_rtl() ? sizing_cache.buttons_width : size.width - sizing_cache.buttons_block_width;
sizing_cache.field_and_buttons_separator_width = theme_cache.field_and_buttons_separation;
}
inline int SpinBox::_get_widest_button_icon_width() {
int max = 0;
max = MAX(max, theme_cache.updown_icon->get_width());
max = MAX(max, theme_cache.up_icon->get_width());
max = MAX(max, theme_cache.up_hover_icon->get_width());
max = MAX(max, theme_cache.up_pressed_icon->get_width());
max = MAX(max, theme_cache.up_disabled_icon->get_width());
max = MAX(max, theme_cache.down_icon->get_width());
max = MAX(max, theme_cache.down_hover_icon->get_width());
max = MAX(max, theme_cache.down_pressed_icon->get_width());
max = MAX(max, theme_cache.down_disabled_icon->get_width());
return max;
}
void SpinBox::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
_update_text(true);
_adjust_width_for_icon(theme_cache.updown_icon);
_compute_sizes();
RID ci = get_canvas_item();
Size2i size = get_size();
if (is_layout_rtl()) {
theme_cache.updown_icon->draw(ci, Point2i(0, (size.height - theme_cache.updown_icon->get_height()) / 2));
} else {
theme_cache.updown_icon->draw(ci, Point2i(size.width - theme_cache.updown_icon->get_width(), (size.height - theme_cache.updown_icon->get_height()) / 2));
Ref<StyleBox> up_stylebox = theme_cache.up_base_stylebox;
Ref<StyleBox> down_stylebox = theme_cache.down_base_stylebox;
Ref<Texture2D> up_icon = theme_cache.up_icon;
Ref<Texture2D> down_icon = theme_cache.down_icon;
Color up_icon_modulate = theme_cache.up_icon_modulate;
Color down_icon_modulate = theme_cache.down_icon_modulate;
bool is_fully_disabled = !is_editable();
if (state_cache.up_button_disabled || is_fully_disabled) {
up_stylebox = theme_cache.up_disabled_stylebox;
up_icon = theme_cache.up_disabled_icon;
up_icon_modulate = theme_cache.up_disabled_icon_modulate;
} else if (state_cache.up_button_pressed && !drag.enabled) {
up_stylebox = theme_cache.up_pressed_stylebox;
up_icon = theme_cache.up_pressed_icon;
up_icon_modulate = theme_cache.up_pressed_icon_modulate;
} else if (state_cache.up_button_hovered && !drag.enabled) {
up_stylebox = theme_cache.up_hover_stylebox;
up_icon = theme_cache.up_hover_icon;
up_icon_modulate = theme_cache.up_hover_icon_modulate;
}
if (state_cache.down_button_disabled || is_fully_disabled) {
down_stylebox = theme_cache.down_disabled_stylebox;
down_icon = theme_cache.down_disabled_icon;
down_icon_modulate = theme_cache.down_disabled_icon_modulate;
} else if (state_cache.down_button_pressed && !drag.enabled) {
down_stylebox = theme_cache.down_pressed_stylebox;
down_icon = theme_cache.down_pressed_icon;
down_icon_modulate = theme_cache.down_pressed_icon_modulate;
} else if (state_cache.down_button_hovered && !drag.enabled) {
down_stylebox = theme_cache.down_hover_stylebox;
down_icon = theme_cache.down_hover_icon;
down_icon_modulate = theme_cache.down_hover_icon_modulate;
}
int updown_icon_left = sizing_cache.buttons_left + (sizing_cache.buttons_width - theme_cache.updown_icon->get_width()) / 2;
int updown_icon_top = (size.height - theme_cache.updown_icon->get_height()) / 2;
// Compute center icon positions once we know which one is used.
int up_icon_left = sizing_cache.buttons_left + (sizing_cache.buttons_width - up_icon->get_width()) / 2;
int up_icon_top = (sizing_cache.button_up_height - up_icon->get_height()) / 2;
int down_icon_left = sizing_cache.buttons_left + (sizing_cache.buttons_width - down_icon->get_width()) / 2;
int down_icon_top = sizing_cache.second_button_top + (sizing_cache.button_down_height - down_icon->get_height()) / 2;
// Draw separators.
draw_style_box(theme_cache.up_down_buttons_separator, Rect2(sizing_cache.buttons_left, sizing_cache.buttons_separator_top, sizing_cache.buttons_width, sizing_cache.buttons_vertical_separation));
draw_style_box(theme_cache.field_and_buttons_separator, Rect2(sizing_cache.field_and_buttons_separator_left, 0, sizing_cache.field_and_buttons_separator_width, size.height));
// Draw buttons.
draw_style_box(up_stylebox, Rect2(sizing_cache.buttons_left, 0, sizing_cache.buttons_width, sizing_cache.button_up_height));
draw_style_box(down_stylebox, Rect2(sizing_cache.buttons_left, sizing_cache.second_button_top, sizing_cache.buttons_width, sizing_cache.button_down_height));
// Draw arrows.
theme_cache.updown_icon->draw(ci, Point2i(updown_icon_left, updown_icon_top));
draw_texture(up_icon, Point2i(up_icon_left, up_icon_top), up_icon_modulate);
draw_texture(down_icon, Point2i(down_icon_left, down_icon_top), down_icon_modulate);
} break;
case NOTIFICATION_MOUSE_EXIT: {
_mouse_exited();
} break;
case NOTIFICATION_ENTER_TREE: {
_adjust_width_for_icon(theme_cache.updown_icon);
_compute_sizes();
_update_text();
_update_buttons_state_for_current_value();
} break;
case NOTIFICATION_VISIBILITY_CHANGED:
drag.allowed = false;
[[fallthrough]];
case NOTIFICATION_EXIT_TREE: {
_release_mouse();
_release_mouse_from_drag_mode();
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
@ -353,6 +487,7 @@ bool SpinBox::is_select_all_on_focus() const {
void SpinBox::set_editable(bool p_enabled) {
line_edit->set_editable(p_enabled);
queue_redraw();
}
bool SpinBox::is_editable() const {
@ -371,6 +506,22 @@ double SpinBox::get_custom_arrow_step() const {
return custom_arrow_step;
}
void SpinBox::_value_changed(double p_value) {
_update_buttons_state_for_current_value();
}
void SpinBox::_update_buttons_state_for_current_value() {
double value = get_value();
bool should_disable_up = value == get_max() && !is_greater_allowed();
bool should_disable_down = value == get_min() && !is_lesser_allowed();
if (state_cache.up_button_disabled != should_disable_up || state_cache.down_button_disabled != should_disable_down) {
state_cache.up_button_disabled = should_disable_up;
state_cache.down_button_disabled = should_disable_down;
queue_redraw();
}
}
void SpinBox::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &SpinBox::set_horizontal_alignment);
ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &SpinBox::get_horizontal_alignment);
@ -397,13 +548,48 @@ void SpinBox::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_arrow_step", PROPERTY_HINT_RANGE, "0,10000,0.0001,or_greater"), "set_custom_arrow_step", "get_custom_arrow_step");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_all_on_focus"), "set_select_all_on_focus", "is_select_all_on_focus");
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_vertical_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, field_and_buttons_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_width);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, set_min_buttons_width_from_icons);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, updown_icon, "updown");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_icon, "up");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_hover_icon, "up_hover");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_pressed_icon, "up_pressed");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_disabled_icon, "up_disabled");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_icon, "down");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_hover_icon, "down_hover");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_pressed_icon, "down_pressed");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_disabled_icon, "down_disabled");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_base_stylebox, "up_background");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_hover_stylebox, "up_background_hovered");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_pressed_stylebox, "up_background_pressed");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_disabled_stylebox, "up_background_disabled");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_base_stylebox, "down_background");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_hover_stylebox, "down_background_hovered");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_pressed_stylebox, "down_background_pressed");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_disabled_stylebox, "down_background_disabled");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_icon_modulate, "up_icon_modulate");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_hover_icon_modulate, "up_hover_icon_modulate");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_pressed_icon_modulate, "up_pressed_icon_modulate");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_disabled_icon_modulate, "up_disabled_icon_modulate");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_icon_modulate, "down_icon_modulate");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_hover_icon_modulate, "down_hover_icon_modulate");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_pressed_icon_modulate, "down_pressed_icon_modulate");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_disabled_icon_modulate, "down_disabled_icon_modulate");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, field_and_buttons_separator, "field_and_buttons_separator");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_down_buttons_separator, "up_down_buttons_separator");
}
SpinBox::SpinBox() {
line_edit = memnew(LineEdit);
add_child(line_edit, false, INTERNAL_MODE_FRONT);
line_edit->set_theme_type_variation("SpinBoxInnerLineEdit");
line_edit->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
line_edit->set_mouse_filter(MOUSE_FILTER_PASS);
line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT);

View File

@ -39,12 +39,24 @@ class SpinBox : public Range {
GDCLASS(SpinBox, Range);
LineEdit *line_edit = nullptr;
int last_w = 0;
bool update_on_text_changed = false;
struct SizingCache {
int buttons_block_width = 0;
int buttons_width = 0;
int buttons_vertical_separation = 0;
int buttons_left = 0;
int button_up_height = 0;
int button_down_height = 0;
int second_button_top = 0;
int buttons_separator_top = 0;
int field_and_buttons_separator_left = 0;
int field_and_buttons_separator_width = 0;
} sizing_cache;
Timer *range_click_timer = nullptr;
void _range_click_timeout();
void _release_mouse();
void _release_mouse_from_drag_mode();
void _update_text(bool p_keep_line_edit = false);
void _text_submitted(const String &p_string);
@ -65,17 +77,66 @@ class SpinBox : public Range {
double diff_y = 0.0;
} drag;
struct StateCache {
bool up_button_hovered = false;
bool up_button_pressed = false;
bool up_button_disabled = false;
bool down_button_hovered = false;
bool down_button_pressed = false;
bool down_button_disabled = false;
} state_cache;
void _line_edit_focus_enter();
void _line_edit_focus_exit();
inline void _adjust_width_for_icon(const Ref<Texture2D> &icon);
inline void _compute_sizes();
inline int _get_widest_button_icon_width();
struct ThemeCache {
Ref<Texture2D> updown_icon;
Ref<Texture2D> up_icon;
Ref<Texture2D> up_hover_icon;
Ref<Texture2D> up_pressed_icon;
Ref<Texture2D> up_disabled_icon;
Ref<Texture2D> down_icon;
Ref<Texture2D> down_hover_icon;
Ref<Texture2D> down_pressed_icon;
Ref<Texture2D> down_disabled_icon;
Ref<StyleBox> up_base_stylebox;
Ref<StyleBox> up_hover_stylebox;
Ref<StyleBox> up_pressed_stylebox;
Ref<StyleBox> up_disabled_stylebox;
Ref<StyleBox> down_base_stylebox;
Ref<StyleBox> down_hover_stylebox;
Ref<StyleBox> down_pressed_stylebox;
Ref<StyleBox> down_disabled_stylebox;
Color up_icon_modulate;
Color up_hover_icon_modulate;
Color up_pressed_icon_modulate;
Color up_disabled_icon_modulate;
Color down_icon_modulate;
Color down_hover_icon_modulate;
Color down_pressed_icon_modulate;
Color down_disabled_icon_modulate;
Ref<StyleBox> field_and_buttons_separator;
Ref<StyleBox> up_down_buttons_separator;
int buttons_vertical_separation = 0;
int field_and_buttons_separation = 0;
int buttons_width = 0;
int set_min_buttons_width_from_icons = 0;
} theme_cache;
void _mouse_exited();
void _update_buttons_state_for_current_value();
protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void _value_changed(double p_value) override;
void _notification(int p_what);
static void _bind_methods();

View File

@ -613,7 +613,41 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
// SpinBox
theme->set_icon("updown", "SpinBox", icons["updown"]);
theme->set_icon("updown", "SpinBox", empty_icon);
theme->set_icon("up", "SpinBox", icons["value_up"]);
theme->set_icon("up_hover", "SpinBox", icons["value_up"]);
theme->set_icon("up_pressed", "SpinBox", icons["value_up"]);
theme->set_icon("up_disabled", "SpinBox", icons["value_up"]);
theme->set_icon("down", "SpinBox", icons["value_down"]);
theme->set_icon("down_hover", "SpinBox", icons["value_down"]);
theme->set_icon("down_pressed", "SpinBox", icons["value_down"]);
theme->set_icon("down_disabled", "SpinBox", icons["value_down"]);
theme->set_stylebox("up_background", "SpinBox", make_empty_stylebox());
theme->set_stylebox("up_background_hovered", "SpinBox", button_hover);
theme->set_stylebox("up_background_pressed", "SpinBox", button_pressed);
theme->set_stylebox("up_background_disabled", "SpinBox", make_empty_stylebox());
theme->set_stylebox("down_background", "SpinBox", make_empty_stylebox());
theme->set_stylebox("down_background_hovered", "SpinBox", button_hover);
theme->set_stylebox("down_background_pressed", "SpinBox", button_pressed);
theme->set_stylebox("down_background_disabled", "SpinBox", make_empty_stylebox());
theme->set_color("up_icon_modulate", "SpinBox", control_font_color);
theme->set_color("up_hover_icon_modulate", "SpinBox", control_font_hover_color);
theme->set_color("up_pressed_icon_modulate", "SpinBox", control_font_hover_color);
theme->set_color("up_disabled_icon_modulate", "SpinBox", control_font_disabled_color);
theme->set_color("down_icon_modulate", "SpinBox", control_font_color);
theme->set_color("down_hover_icon_modulate", "SpinBox", control_font_hover_color);
theme->set_color("down_pressed_icon_modulate", "SpinBox", control_font_hover_color);
theme->set_color("down_disabled_icon_modulate", "SpinBox", control_font_disabled_color);
theme->set_stylebox("field_and_buttons_separator", "SpinBox", make_empty_stylebox());
theme->set_stylebox("up_down_buttons_separator", "SpinBox", make_empty_stylebox());
theme->set_constant("buttons_vertical_separation", "SpinBox", 0);
theme->set_constant("field_and_buttons_separation", "SpinBox", 2);
theme->set_constant("buttons_width", "SpinBox", 16);
theme->set_constant("set_min_buttons_width_from_icons", "SpinBox", 1);
// ScrollContainer

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="8"><path fill="none" stroke="#fff" stroke-width="2" d="m12 2-4 3.5L4 2"/></svg>

After

Width:  |  Height:  |  Size: 138 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="8"><path fill="none" stroke="#fff" stroke-width="2" d="m4 6 4-3.5L12 6"/></svg>

After

Width:  |  Height:  |  Size: 138 B