diff options
Diffstat (limited to 'game/addons/zylann.hterrain/tools/util/spin_slider.gd')
-rw-r--r-- | game/addons/zylann.hterrain/tools/util/spin_slider.gd | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/game/addons/zylann.hterrain/tools/util/spin_slider.gd b/game/addons/zylann.hterrain/tools/util/spin_slider.gd new file mode 100644 index 0000000..fa690d5 --- /dev/null +++ b/game/addons/zylann.hterrain/tools/util/spin_slider.gd @@ -0,0 +1,385 @@ +@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 |