OverviewFilesCommitsIssuesDocsReleasesPeers

godot-witchy_world_demo / addons / zylann.hterrain / tools / util / interval_slider.gd

198 lines · gd ref:

# Slider with two handles representing an interval.

tool
extends Control

const VALUE_LOW = 0
const VALUE_HIGH = 1
const VALUE_COUNT = 2

const FG_MARGIN = 1

signal changed

var _min_value := 0.0
var _max_value := 1.0
var _values = [0.2, 0.6]
var _grabbing := false


func _get_property_list():
	return [
		{
			"name": "min_value",
			"type": TYPE_REAL,
			"usage": PROPERTY_USAGE_EDITOR
		},
		{
			"name": "max_value",
			"type": TYPE_REAL,
			"usage": PROPERTY_USAGE_EDITOR
		},
		{
			"name": "range",
			"type": TYPE_VECTOR2,
			"usage": PROPERTY_USAGE_STORAGE
		}
	]


func _get(key: String):
	match key:
		"min_value":
			return _min_value
		"max_value":
			return _max_value
		"range":
			return Vector2(_min_value, _max_value)


func _set(key: String, value):
	match key:
		"min_value":
			_min_value = min(value, _max_value)
			update()
		"max_value":
			_max_value = max(value, _min_value)
			update()
		"range":
			_min_value = value.x
			_max_value = value.y


func set_values(low: float, high: float):
	if low > high:
		low = high
	if high < low:
		high = low
	_values[VALUE_LOW] = low
	_values[VALUE_HIGH] = high
	update()


func set_value(i: int, v: float, notify_change: bool):
	var min_value = _min_value
	var max_value = _max_value
	
	match i:
		VALUE_LOW:
			max_value = _values[VALUE_HIGH]
		VALUE_HIGH:
			min_value = _values[VALUE_LOW]
		_:
			assert(false)
	
	v = clamp(v, min_value, max_value)
	if v != _values[i]:
		_values[i] = v
		update()
		if notify_change:
			emit_signal("changed")


func get_value(i: int) -> float:
	return _values[i]


func get_low_value() -> float:
	return _values[VALUE_LOW]


func get_high_value() -> float:
	return _values[VALUE_HIGH]


func get_ratio(i: int) -> float:
	return _value_to_ratio(_values[i])


func get_low_ratio() -> float:
	return get_ratio(VALUE_LOW)


func get_high_ratio() -> float:
	return get_ratio(VALUE_HIGH)


func _ratio_to_value(r: float) -> float:
	return r * (_max_value - _min_value) + _min_value


func _value_to_ratio(v: float) -> float:
	if abs(_max_value - _min_value) < 0.001:
		return 0.0
	return (v - _min_value) / (_max_value - _min_value)


func _get_closest_index(ratio: float) -> int:
	var distance_low := abs(ratio - get_low_ratio())
	var distance_high := abs(ratio - get_high_ratio())
	if distance_low < distance_high:
		return VALUE_LOW
	return VALUE_HIGH


func _set_from_pixel(px: float):
	var r := (px - FG_MARGIN) / (rect_size.x - FG_MARGIN * 2.0)
	var i := _get_closest_index(r)
	var v := _ratio_to_value(r)
	set_value(i, v, true)


func _gui_input(event):
	if event is InputEventMouseButton:
		if event.pressed:
			if event.button_index == BUTTON_LEFT:
				_grabbing = true
				_set_from_pixel(event.position.x)
		else:
			if event.button_index == BUTTON_LEFT:
				_grabbing = false
				
	elif event is InputEventMouseMotion:
		if _grabbing:
			_set_from_pixel(event.position.x)			


func _draw():
	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(), rect_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)
	
	var low_ratio := get_low_ratio()
	var high_ratio := get_high_ratio()
	
	var low_x := fg_rect.position.x + low_ratio * fg_rect.size.x
	var high_x := fg_rect.position.x + high_ratio * fg_rect.size.x

	var interval_rect := Rect2(
		low_x, fg_rect.position.y, high_x - low_x, fg_rect.size.y)
	draw_rect(interval_rect, interval_color)

	low_x = fg_rect.position.x + low_ratio * (fg_rect.size.x - grabber_width)
	high_x = fg_rect.position.x + high_ratio * (fg_rect.size.x - grabber_width)
	
	for x in [low_x, high_x]:
		var grabber_rect := Rect2(
			x,
			fg_rect.position.y,
			grabber_width,
			fg_rect.size.y)
		draw_rect(grabber_rect, grabber_color)