Add custom slider

This is a slider inspired by those used in Blender.

The control is a draggable slider displaying the current value as its
background. Clicking the control brings up a LineEdit for manual value
input.

When dragging the slider, hold CTRL to round to 0.1 increments or hold
SHIFT to round to 0.01 increments. Holding both CTRL and SHIFT rounds to
0.001 increments.
This commit is contained in:
Haoyu Qiu 2022-09-25 11:19:17 +08:00
parent 2a73615082
commit b5322f430f
7 changed files with 220 additions and 89 deletions

View File

@ -6,8 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Display current Slider value when hovered.
- Hold Ctrl to snap Slider value to 0.01 increments.
- Implement custom slider.
## [1.1.1] - 2022-07-30
### Fixed

View File

@ -0,0 +1,203 @@
tool
extends Control
signal value_changed(value)
export var value: float = 0.0 setget set_value
export var min_value: float = 0.0
export var max_value: float = 1.0
var _line_edit: LineEdit
var _stylebox_normal: StyleBox
var _stylebox_hover: StyleBox
var _stylebox_editing: StyleBox
var _stylebox_value: StyleBox
var _line_edit_just_closed := false
var _mouse_hovering := false
var _is_editing := false
var _drag_start_position: Vector2
var _drag_cancelled := true
var _drag_dist := 0.0
var _drag_start_factor: float
func _init() -> void:
mouse_default_cursor_shape = Control.CURSOR_HSIZE
rect_clip_content = true
focus_mode = Control.FOCUS_ALL
var style := StyleBoxEmpty.new()
style.content_margin_left = 8
style.content_margin_right = 8
_line_edit = LineEdit.new()
_line_edit.set_as_toplevel(true)
_line_edit.visible = false
_line_edit.add_stylebox_override("normal", style)
_line_edit.add_stylebox_override("focus", StyleBoxEmpty.new())
var _ret: int
_ret = _line_edit.connect("focus_exited", self, "_on_line_edit_focus_exited")
_ret = _line_edit.connect("text_entered", self, "_on_line_edit_text_entered")
_ret = _line_edit.connect("visibility_changed", self, "_on_line_edit_visibility_changed")
add_child(_line_edit)
func _draw() -> void:
var font := get_font("font", "LineEdit")
var color := get_color("highlighted_font_color" if _mouse_hovering else "font_color", "Editor")
var number_string := "%.3f" % value
var number_size := font.get_string_size(number_string)
var pos := Vector2(
(rect_size.x - number_size.x) / 2,
(rect_size.y - number_size.y) / 2 + font.get_ascent()
)
var stylebox := _stylebox_editing if _is_editing else _stylebox_hover if _mouse_hovering else _stylebox_normal
if _line_edit.visible:
draw_style_box(stylebox, Rect2(Vector2.ZERO, rect_size))
else:
var value_width := rect_size.x * ((value - min_value) / (max_value - min_value))
draw_style_box(stylebox, Rect2(value_width, 0, rect_size.x - value_width, rect_size.y))
draw_style_box(_stylebox_value, Rect2(0, 0, value_width, rect_size.y))
draw_string(font, pos, number_string, color)
func _get_minimum_size() -> Vector2:
var ms := _stylebox_normal.get_minimum_size()
ms.y += get_font("font", "LineEdit").get_height()
return ms
func _gui_input(event: InputEvent) -> void:
var mb := event as InputEventMouseButton
if mb and mb.button_index == BUTTON_LEFT:
if mb.pressed:
_drag_prepare(mb)
else:
_drag_done()
if _drag_cancelled:
_show_text_edit()
_drag_cancelled = true
_is_editing = mb.pressed
update()
var mm := event as InputEventMouseMotion
if mm and mm.button_mask & BUTTON_MASK_LEFT:
_drag_motion(mm)
_drag_cancelled = false
func _notification(what: int) -> void:
match what:
NOTIFICATION_ENTER_TREE, NOTIFICATION_THEME_CHANGED:
_update_stylebox()
NOTIFICATION_MOUSE_ENTER:
_mouse_hovering = true
update()
NOTIFICATION_MOUSE_EXIT:
_mouse_hovering = false
update()
NOTIFICATION_FOCUS_ENTER:
if (Input.is_action_pressed("ui_focus_next") or Input.is_action_pressed("ui_focus_prev")) and not _line_edit_just_closed:
_show_text_edit()
_line_edit_just_closed = false
func set_value(v: float) -> void:
if is_equal_approx(v, value):
return
value = v
emit_signal("value_changed", value)
update()
func _update_stylebox() -> void:
_stylebox_normal = get_stylebox("normal", "LineEdit")
_stylebox_hover = StyleBoxFlat.new()
_stylebox_hover.bg_color = get_color("highlight_color", "Editor")
_stylebox_editing = StyleBoxFlat.new()
_stylebox_editing.bg_color = get_color("dark_color_2", "Editor")
_stylebox_value = StyleBoxFlat.new()
_stylebox_value.bg_color = get_color("accent_color", "Editor") * Color(1, 1, 1, 0.4)
func _drag_prepare(mouse: InputEventMouse) -> void:
_drag_dist = 0.0
_drag_start_factor = (value - min_value) / (max_value - min_value)
_drag_start_position = mouse.global_position
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _drag_done() -> void:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
if _drag_cancelled:
Input.warp_mouse_position(_drag_start_position)
else:
Input.warp_mouse_position(rect_global_position + rect_size * Vector2(
(value - min_value) / (max_value - min_value),
0.5
))
func _drag_motion(motion: InputEventMouseMotion) -> void:
_drag_dist += motion.relative.x
var factor := _drag_start_factor + _drag_dist / rect_size.x
if factor < 0 or 1 < factor:
factor = clamp(factor, 0, 1)
_drag_dist = (factor - _drag_start_factor) * rect_size.x
var v := factor * (max_value - min_value) + min_value
var snap := motion.command or motion.shift
if snap and not (is_equal_approx(v, min_value) or is_equal_approx(v, max_value)):
if motion.shift and motion.command:
v = round(v * 1000.0) * 0.001
elif motion.shift:
v = round(v * 100.0) * 0.01
else:
v = round(v * 10.0) * 0.1
set_value(clamp(v, min_value, max_value))
update()
func _show_text_edit() -> void:
var gr := get_global_rect()
_line_edit.text = str(value)
_line_edit.set_position(gr.position)
_line_edit.set_size(gr.size)
_line_edit.show_modal()
_line_edit.select_all()
_line_edit.grab_focus()
_line_edit.focus_next = find_next_valid_focus().get_path()
_line_edit.focus_previous = find_prev_valid_focus().get_path()
func _on_line_edit_focus_exited():
if _line_edit.get_menu().visible:
return
if _line_edit.text.is_valid_float():
set_value(clamp(_line_edit.text.to_float(), min_value, max_value))
if not _line_edit_just_closed:
_line_edit.hide()
update()
func _on_line_edit_text_entered(_text: String):
_line_edit.hide()
func _on_line_edit_visibility_changed():
if not _line_edit.visible:
_line_edit_just_closed = true

View File

@ -9,13 +9,10 @@ margin_bottom = 40.0
rect_pivot_offset = Vector2( 41, -65 )
size_flags_horizontal = 3
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Label" type="Label" parent="."]
margin_top = 13.0
margin_right = 133.0
margin_right = 128.0
margin_bottom = 27.0
rect_min_size = Vector2( 100, 0 )
size_flags_horizontal = 3
@ -23,11 +20,11 @@ text = "Waveform"
align = 2
[node name="OptionButton" type="OptionButton" parent="."]
margin_left = 137.0
margin_left = 132.0
margin_top = 10.0
margin_right = 237.0
margin_bottom = 30.0
rect_min_size = Vector2( 100, 0 )
rect_min_size = Vector2( 105, 0 )
size_flags_vertical = 4
clip_text = true
@ -36,7 +33,9 @@ margin_left = 241.0
margin_top = 9.0
margin_right = 253.0
margin_bottom = 31.0
focus_mode = 0
size_flags_vertical = 4
enabled_focus_mode = 0
script = ExtResource( 2 )
icon_name = "ReloadSmall"

View File

@ -8,21 +8,6 @@ export var label: String setget set_label
export var parameter: String
export var bipolar := false setget set_bipolar
var is_dragging_slider := false
func _notification(what: int) -> void:
match what:
NOTIFICATION_ENTER_TREE, NOTIFICATION_THEME_CHANGED:
var display: Label = $HSlider/Anchor/ValueDisplay
var bg := StyleBoxFlat.new()
bg.bg_color = get_color("dark_color_3", "Editor")
bg.content_margin_bottom = 2
bg.content_margin_top = 2
bg.content_margin_left = 4
bg.content_margin_right = 4
display.add_stylebox_override("normal", bg)
func set_label(v: String) -> void:
label = v
@ -49,45 +34,10 @@ func set_resetable(v: bool) -> void:
$Reset.disabled = not v
func _update_slider_step():
$HSlider.step = 0.01 if is_dragging_slider and Input.is_key_pressed(KEY_CONTROL) else 0
func _update_value_display():
var anchor: Control = $HSlider/Anchor
var display: Label = $HSlider/Anchor/ValueDisplay
var slider: Slider = $HSlider
var grabber_size := slider.get_icon("Grabber").get_size()
display.text = str(slider.value)
anchor.rect_position.x = slider.ratio * (slider.rect_size.x - grabber_size.x) + grabber_size.x * 0.5
func _on_HSlider_value_changed(value: float):
_update_value_display()
emit_signal("param_changed", parameter, value)
func _on_HSlider_mouse_entered():
$HSlider/Anchor.show()
_update_value_display()
func _on_HSlider_mouse_exited():
$HSlider/Anchor.hide()
func _on_HSlider_gui_input(event: InputEvent):
var mb := event as InputEventMouseButton
if mb and mb.button_index == BUTTON_LEFT:
is_dragging_slider = mb.pressed
_update_slider_step()
var ek := event as InputEventKey
if ek and is_dragging_slider and ek.scancode == KEY_CONTROL:
_update_slider_step()
func _on_Reset_pressed():
emit_signal("param_reset", parameter)

View File

@ -2,13 +2,7 @@
[ext_resource path="res://addons/gdfxr/editor/ParamSlider.gd" type="Script" id=1]
[ext_resource path="res://addons/gdfxr/editor/EditorIconButton.gd" type="Script" id=2]
[sub_resource type="StyleBoxFlat" id=1]
content_margin_left = 4.0
content_margin_right = 4.0
content_margin_top = 2.0
content_margin_bottom = 2.0
bg_color = Color( 0, 0, 0, 1 )
[ext_resource path="res://addons/gdfxr/editor/EditSlider.gd" type="Script" id=3]
[node name="ParamSlider" type="HBoxContainer"]
margin_right = 253.0
@ -18,45 +12,31 @@ script = ExtResource( 1 )
[node name="Label" type="Label" parent="."]
margin_top = 13.0
margin_right = 143.0
margin_right = 128.0
margin_bottom = 27.0
rect_min_size = Vector2( 100, 0 )
size_flags_horizontal = 3
align = 2
[node name="HSlider" type="HSlider" parent="."]
margin_left = 147.0
margin_top = 12.0
[node name="HSlider" type="Control" parent="."]
margin_left = 132.0
margin_right = 237.0
margin_bottom = 28.0
rect_min_size = Vector2( 90, 0 )
hint_tooltip = "Hold Ctrl to snap to 0.01 increments."
size_flags_vertical = 4
max_value = 1.0
step = 0.0
[node name="Anchor" type="Control" parent="HSlider"]
visible = false
[node name="ValueDisplay" type="Label" parent="HSlider/Anchor"]
margin_bottom = -2.0
grow_horizontal = 2
grow_vertical = 0
custom_styles/normal = SubResource( 1 )
align = 1
valign = 1
margin_bottom = 40.0
rect_min_size = Vector2( 105, 0 )
rect_clip_content = true
focus_mode = 2
mouse_default_cursor_shape = 10
script = ExtResource( 3 )
[node name="Reset" type="ToolButton" parent="."]
margin_left = 241.0
margin_top = 9.0
margin_right = 253.0
margin_bottom = 31.0
focus_mode = 0
size_flags_vertical = 4
script = ExtResource( 2 )
icon_name = "ReloadSmall"
[connection signal="gui_input" from="HSlider" to="." method="_on_HSlider_gui_input"]
[connection signal="mouse_entered" from="HSlider" to="." method="_on_HSlider_mouse_entered"]
[connection signal="mouse_exited" from="HSlider" to="." method="_on_HSlider_mouse_exited"]
[connection signal="value_changed" from="HSlider" to="." method="_on_HSlider_value_changed"]
[connection signal="pressed" from="Reset" to="." method="_on_Reset_pressed"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB