386 lines
7.9 KiB
GDScript3
386 lines
7.9 KiB
GDScript3
|
@tool
|
||
|
extends Control
|
||
|
|
||
|
const FG_MARGIN = 2
|
||
|
const MAX_DECIMALS_VISUAL = 3
|
||
|
|
||
|
signal value_changed(value)
|
||
|
|
||
|
|
||
|
var _value := 0.0
|
||
|
@export var value: float:
|
||
|
get:
|
||
|
return _value
|
||
|
set(v):
|
||
|
set_value_no_notify(v)
|
||
|
|
||
|
|
||
|
var _min_value := 0.0
|
||
|
@export var min_value: float:
|
||
|
get:
|
||
|
return _min_value
|
||
|
set(v):
|
||
|
set_min_value(v)
|
||
|
|
||
|
|
||
|
var _max_value := 100.0
|
||
|
@export var max_value: float:
|
||
|
get:
|
||
|
return _max_value
|
||
|
set(v):
|
||
|
set_max_value(v)
|
||
|
|
||
|
|
||
|
var _prefix := ""
|
||
|
@export var prefix: String:
|
||
|
get:
|
||
|
return _prefix
|
||
|
set(v):
|
||
|
set_prefix(v)
|
||
|
|
||
|
|
||
|
var _suffix := ""
|
||
|
@export var suffix: String:
|
||
|
get:
|
||
|
return _suffix
|
||
|
set(v):
|
||
|
set_suffix(v)
|
||
|
|
||
|
|
||
|
var _rounded := false
|
||
|
@export var rounded: bool:
|
||
|
get:
|
||
|
return _rounded
|
||
|
set(v):
|
||
|
set_rounded(v)
|
||
|
|
||
|
|
||
|
var _centered := true
|
||
|
@export var centered: bool:
|
||
|
get:
|
||
|
return _centered
|
||
|
set(v):
|
||
|
set_centered(v)
|
||
|
|
||
|
|
||
|
var _allow_greater := false
|
||
|
@export var allow_greater: bool:
|
||
|
get:
|
||
|
return _allow_greater
|
||
|
set(v):
|
||
|
set_allow_greater(v)
|
||
|
|
||
|
|
||
|
# There is still a limit when typing a larger value, but this one is to prevent software
|
||
|
# crashes or freezes. The regular min and max values are for slider UX. Exceeding it should be
|
||
|
# a corner case.
|
||
|
var _greater_max_value := 10000.0
|
||
|
@export var greater_max_value: float:
|
||
|
get:
|
||
|
return _greater_max_value
|
||
|
set(v):
|
||
|
set_greater_max_value(v)
|
||
|
|
||
|
|
||
|
var _label : Label
|
||
|
var _label2 : Label
|
||
|
var _line_edit : LineEdit
|
||
|
var _ignore_line_edit := false
|
||
|
var _pressing := false
|
||
|
var _grabbing := false
|
||
|
var _press_pos := Vector2()
|
||
|
|
||
|
|
||
|
func _init():
|
||
|
custom_minimum_size = Vector2(32, 28)
|
||
|
|
||
|
_label = Label.new()
|
||
|
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
|
_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||
|
_label.clip_text = true
|
||
|
#_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
|
_label.anchor_top = 0
|
||
|
_label.anchor_left = 0
|
||
|
_label.anchor_right = 1
|
||
|
_label.anchor_bottom = 1
|
||
|
_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
|
_label.add_theme_color_override("font_color_shadow", Color(0,0,0,0.5))
|
||
|
_label.add_theme_constant_override("shadow_offset_x", 1)
|
||
|
_label.add_theme_constant_override("shadow_offset_y", 1)
|
||
|
add_child(_label)
|
||
|
|
||
|
_label2 = Label.new()
|
||
|
_label2.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||
|
_label2.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||
|
_label2.clip_text = true
|
||
|
#_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
|
_label2.anchor_top = 0
|
||
|
_label2.anchor_left = 0
|
||
|
_label2.anchor_right = 1
|
||
|
_label2.anchor_bottom = 1
|
||
|
_label2.offset_left = 8
|
||
|
_label2.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
|
_label2.add_theme_color_override("font_color_shadow", Color(0,0,0,0.5))
|
||
|
_label2.add_theme_constant_override("shadow_offset_x", 1)
|
||
|
_label2.add_theme_constant_override("shadow_offset_y", 1)
|
||
|
_label2.hide()
|
||
|
add_child(_label2)
|
||
|
|
||
|
_line_edit = LineEdit.new()
|
||
|
_line_edit.alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
|
_line_edit.anchor_top = 0
|
||
|
_line_edit.anchor_left = 0
|
||
|
_line_edit.anchor_right = 1
|
||
|
_line_edit.anchor_bottom = 1
|
||
|
_line_edit.gui_input.connect(_on_LineEdit_gui_input)
|
||
|
_line_edit.focus_exited.connect(_on_LineEdit_focus_exited)
|
||
|
_line_edit.text_submitted.connect(_on_LineEdit_text_submitted)
|
||
|
_line_edit.hide()
|
||
|
add_child(_line_edit)
|
||
|
|
||
|
mouse_default_cursor_shape = Control.CURSOR_HSIZE
|
||
|
|
||
|
|
||
|
func _ready():
|
||
|
pass # Replace with function body.
|
||
|
|
||
|
|
||
|
func set_centered(p_centered: bool):
|
||
|
_centered = p_centered
|
||
|
if _centered:
|
||
|
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
|
_label.offset_right = 0
|
||
|
_label2.hide()
|
||
|
else:
|
||
|
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||
|
_label.offset_right = -8
|
||
|
_label2.show()
|
||
|
queue_redraw()
|
||
|
|
||
|
|
||
|
func is_centered() -> bool:
|
||
|
return _centered
|
||
|
|
||
|
|
||
|
func set_value_no_notify(v: float):
|
||
|
set_value(v, false, false)
|
||
|
|
||
|
|
||
|
func set_value(v: float, notify_change: bool, use_slider_maximum: bool = false):
|
||
|
if _allow_greater and not use_slider_maximum:
|
||
|
v = clampf(v, _min_value, _greater_max_value)
|
||
|
else:
|
||
|
v = clampf(v, _min_value, _max_value)
|
||
|
|
||
|
if v != _value:
|
||
|
_value = v
|
||
|
|
||
|
queue_redraw()
|
||
|
|
||
|
if notify_change:
|
||
|
value_changed.emit(get_value())
|
||
|
|
||
|
|
||
|
func get_value():
|
||
|
if _rounded:
|
||
|
return int(roundf(_value))
|
||
|
return _value
|
||
|
|
||
|
|
||
|
func set_min_value(minv: float):
|
||
|
_min_value = minv
|
||
|
#queue_redraw()
|
||
|
|
||
|
|
||
|
func get_min_value() -> float:
|
||
|
return _min_value
|
||
|
|
||
|
|
||
|
func set_max_value(maxv: float):
|
||
|
_max_value = maxv
|
||
|
#queue_redraw()
|
||
|
|
||
|
|
||
|
func get_max_value() -> float:
|
||
|
return _max_value
|
||
|
|
||
|
|
||
|
func set_greater_max_value(gmax: float):
|
||
|
_greater_max_value = gmax
|
||
|
|
||
|
|
||
|
func get_greater_max_value() -> float:
|
||
|
return _greater_max_value
|
||
|
|
||
|
|
||
|
func set_rounded(b: bool):
|
||
|
_rounded = b
|
||
|
queue_redraw()
|
||
|
|
||
|
|
||
|
func is_rounded() -> bool:
|
||
|
return _rounded
|
||
|
|
||
|
|
||
|
func set_prefix(p_prefix: String):
|
||
|
_prefix = p_prefix
|
||
|
queue_redraw()
|
||
|
|
||
|
|
||
|
func get_prefix() -> String:
|
||
|
return _prefix
|
||
|
|
||
|
|
||
|
func set_suffix(p_suffix: String):
|
||
|
_suffix = p_suffix
|
||
|
queue_redraw()
|
||
|
|
||
|
|
||
|
func get_suffix() -> String:
|
||
|
return _suffix
|
||
|
|
||
|
|
||
|
func set_allow_greater(allow: bool):
|
||
|
_allow_greater = allow
|
||
|
|
||
|
|
||
|
func is_allowing_greater() -> bool:
|
||
|
return _allow_greater
|
||
|
|
||
|
|
||
|
func _set_from_pixel(px: float):
|
||
|
var r := (px - FG_MARGIN) / (size.x - FG_MARGIN * 2.0)
|
||
|
var v := _ratio_to_value(r)
|
||
|
set_value(v, true, true)
|
||
|
|
||
|
|
||
|
func get_ratio() -> float:
|
||
|
return _value_to_ratio(get_value())
|
||
|
|
||
|
|
||
|
func _ratio_to_value(r: float) -> float:
|
||
|
return r * (_max_value - _min_value) + _min_value
|
||
|
|
||
|
|
||
|
func _value_to_ratio(v: float) -> float:
|
||
|
if absf(_max_value - _min_value) < 0.001:
|
||
|
return 0.0
|
||
|
return (v - _min_value) / (_max_value - _min_value)
|
||
|
|
||
|
|
||
|
func _on_LineEdit_gui_input(event: InputEvent):
|
||
|
if event is InputEventKey:
|
||
|
if event.pressed:
|
||
|
if event.keycode == KEY_ESCAPE:
|
||
|
_ignore_line_edit = true
|
||
|
_hide_line_edit()
|
||
|
grab_focus()
|
||
|
_ignore_line_edit = false
|
||
|
|
||
|
|
||
|
func _on_LineEdit_focus_exited():
|
||
|
if _ignore_line_edit:
|
||
|
return
|
||
|
_enter_text()
|
||
|
|
||
|
|
||
|
func _on_LineEdit_text_submitted(text: String):
|
||
|
_enter_text()
|
||
|
|
||
|
|
||
|
func _enter_text():
|
||
|
var s = _line_edit.text.strip_edges()
|
||
|
if s.is_valid_float():
|
||
|
var v := s.to_float()
|
||
|
if not _allow_greater:
|
||
|
v = minf(v, _max_value)
|
||
|
set_value(v, true, false)
|
||
|
_hide_line_edit()
|
||
|
|
||
|
|
||
|
func _hide_line_edit():
|
||
|
_line_edit.hide()
|
||
|
_label.show()
|
||
|
queue_redraw()
|
||
|
|
||
|
|
||
|
func _show_line_edit():
|
||
|
_line_edit.show()
|
||
|
_line_edit.text = str(get_value())
|
||
|
_line_edit.select_all()
|
||
|
_line_edit.grab_focus()
|
||
|
_label.hide()
|
||
|
queue_redraw()
|
||
|
|
||
|
|
||
|
func _gui_input(event: InputEvent):
|
||
|
if event is InputEventMouseButton:
|
||
|
if event.pressed:
|
||
|
if event.button_index == MOUSE_BUTTON_LEFT:
|
||
|
_press_pos = event.position
|
||
|
_pressing = true
|
||
|
else:
|
||
|
if event.button_index == MOUSE_BUTTON_LEFT:
|
||
|
_pressing = false
|
||
|
if _grabbing:
|
||
|
_grabbing = false
|
||
|
_set_from_pixel(event.position.x)
|
||
|
else:
|
||
|
_show_line_edit()
|
||
|
|
||
|
elif event is InputEventMouseMotion:
|
||
|
if _pressing and _press_pos.distance_to(event.position) > 2.0:
|
||
|
_grabbing = true
|
||
|
if _grabbing:
|
||
|
_set_from_pixel(event.position.x)
|
||
|
|
||
|
|
||
|
func _draw():
|
||
|
if _line_edit.visible:
|
||
|
return
|
||
|
|
||
|
#var grabber_width := 3
|
||
|
var background_v_margin := 0
|
||
|
var foreground_margin := FG_MARGIN
|
||
|
#var grabber_color := Color(0.8, 0.8, 0.8)
|
||
|
var interval_color := Color(0.4,0.4,0.4)
|
||
|
var background_color := Color(0.1, 0.1, 0.1)
|
||
|
|
||
|
var control_rect := Rect2(Vector2(), size)
|
||
|
|
||
|
var bg_rect := Rect2(
|
||
|
control_rect.position.x,
|
||
|
control_rect.position.y + background_v_margin,
|
||
|
control_rect.size.x,
|
||
|
control_rect.size.y - 2 * background_v_margin)
|
||
|
draw_rect(bg_rect, background_color)
|
||
|
|
||
|
var fg_rect := control_rect.grow(-foreground_margin)
|
||
|
# Clamping the ratio because the value can be allowed to exceed the slider's boundaries
|
||
|
var ratio := clampf(get_ratio(), 0.0, 1.0)
|
||
|
fg_rect.size.x *= ratio
|
||
|
draw_rect(fg_rect, interval_color)
|
||
|
|
||
|
var value_text := str(get_value())
|
||
|
|
||
|
var dot_pos := value_text.find(".")
|
||
|
if dot_pos != -1:
|
||
|
var decimal_count := len(value_text) - dot_pos
|
||
|
if decimal_count > MAX_DECIMALS_VISUAL:
|
||
|
value_text = value_text.substr(0, dot_pos + MAX_DECIMALS_VISUAL + 1)
|
||
|
|
||
|
if _centered:
|
||
|
var text := value_text
|
||
|
if _prefix != "":
|
||
|
text = str(_prefix, " ", text)
|
||
|
if _suffix != "":
|
||
|
text = str(text, " ", _suffix)
|
||
|
_label.text = text
|
||
|
|
||
|
else:
|
||
|
_label2.text = _prefix
|
||
|
var text := value_text
|
||
|
if _suffix != "":
|
||
|
text = str(text, " ", _suffix)
|
||
|
_label.text = text
|