Files
MovementTests/addons/shaker/src/shaker_graph.gd
2026-01-20 15:27:59 +01:00

266 lines
10 KiB
GDScript

@tool
extends Panel
const TIME_LINE_SCRIPT: GDScript = preload("res://addons/shaker/src/shaker_timeline.gd")
const ShakerBase3d = preload("res://addons/shaker/src/Vector3/ShakerBase3D.gd")
const ShakerBase2d = preload("res://addons/shaker/src/Vector2/ShakerBase2D.gd")
# Graph properties
var graph_points:Array[PackedFloat32Array]
var y_scale: float = 32.0
var width: float = 2.0
var shake: Resource
var graph_offset: Vector2 = Vector2(15.0, 0.0)
# Private variables
var _graph_min: float = -1.0
var _graph_max: float = 1.0
var _graph_max_total: float = 1.0
var layout_box: HBoxContainer = HBoxContainer.new()
var axis_button: OptionButton = OptionButton.new()
var fit_button: Button = Button.new()
var category_button: OptionButton
var time_line: Control
var flip_y: bool = true
var selected_category: int = 0
var graph_pressing: bool = false
var graph_middle_pressing: bool = false
var point_color_by_axis:Array[Color] = [Color.RED, Color.GREEN, Color.DEEP_SKY_BLUE]
var _unselected_opacity:float = .10
# Graph time offset property
var graph_time_offset: float = 0.0:
set = set_graph_time_offset,
get = get_graph_time_offset
# Called when the node enters the scene tree for the first timeX
func _ready() -> void:
shake.property_changed.connect(on_property_changed)
clip_contents = true
_setup_timeline()
_setup_layout_box()
_setup_fit_button()
_setup_category_button()
_setup_axis_button()
_update_graph()
_on_fit_button_clicked()
# Sets up the timeline
func _setup_timeline() -> void:
if shake is ShakerPresetBase:
time_line = TIME_LINE_SCRIPT.new()
time_line.GRAPH = self
add_child(time_line)
# Sets up the layout box
func _setup_layout_box() -> void:
add_child(layout_box)
layout_box.set_anchors_preset(Control.PRESET_TOP_WIDE)
layout_box.custom_minimum_size.y = 16
layout_box.alignment = BoxContainer.ALIGNMENT_END
# Sets up the fit button
func _setup_fit_button() -> void:
layout_box.add_child(fit_button)
fit_button.text = "Fit"
fit_button.pressed.connect(_on_fit_button_clicked)
# Sets up the category button
func _setup_category_button() -> void:
if shake is ShakerPresetBase:
category_button = OptionButton.new()
category_button.item_selected.connect(_on_category_selected)
layout_box.add_child(category_button)
category_button.custom_minimum_size = Vector2(16, 16)
for _category_index in shake.Categories.size():
var _category_name: StringName = shake.Categories.keys()[_category_index]
var _category_value: int = shake.Categories.values()[_category_index]
category_button.add_item(_category_name, _category_value)
if shake.Categories.size() > 0: category_button.select(0)
# Sets up the axis button
func _setup_axis_button() -> void:
if axis_button.get_parent() != layout_box:
layout_box.add_child(axis_button)
axis_button.position = Vector2(-32, 4)
axis_button.custom_minimum_size = Vector2(16, 16)
axis_button.item_selected.connect(_axis_selected)
axis_button.add_theme_color_override("background_color", Color.GREEN)
var selected:int = max(axis_button.get_selected_id(), 0)
axis_button.clear()
var Axis:Array = []
if shake is ShakerTypeBase:
Axis = shake.GraphAxis.keys()
elif shake is ShakerPresetBase:
var shakes:Array = shake.get_shakes_by_category(category_button.selected)
if shakes.size() > 0 && shakes[0]:
Axis = shakes[0].get_script().GraphAxis.keys()
for axis in Axis:
axis_button.add_item(axis)
selected = min(selected, Axis.size())
if Axis.size() > 0:
axis_button.select(selected)
# Updates the graph
func _update_graph() -> void:
graph_points.clear()
if not axis_button.get_selected_id() < 0:
var _baked: float = round(shake.bake_internal)
_graph_min = 0.0
_graph_max = 0.0
graph_points.resize(axis_button.item_count)
for axis_index in axis_button.item_count:
for i in _baked + 1:
var _args: Array = [graph_time_offset + (i / _baked)]
if shake is ShakerPresetBase:
_args.append(selected_category)
var _val = shake.callv("get_value", _args)
if typeof(_val) != TYPE_FLOAT: _val = _val[axis_index]
var _result: float = _val * (-1 if flip_y else 1)
graph_points[axis_index].append(_result)
_graph_min = min(_graph_min, _result)
_graph_max = max(_graph_max, _result)
_graph_max_total = max(abs(_graph_max), abs(_graph_min))
# Draws the graph
func _draw() -> void:
_draw_zero_line()
_draw_min_max()
_draw_graph_points()
_draw_graph_info()
# Draws the zero line
func _draw_zero_line() -> void:
var font_size: int = 8
draw_line(Vector2(0, size.y * 0.5), Vector2(size.x, size.y * 0.5), Color.DIM_GRAY, 1, false)
draw_string(ThemeDB.fallback_font, Vector2(5.0, size.y * 0.5 + font_size * 0.5), "0", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color.DIM_GRAY, TextServer.JUSTIFICATION_NONE, TextServer.DIRECTION_AUTO, TextServer.ORIENTATION_HORIZONTAL)
# Draws the min/max values
func _draw_min_max() -> void:
var font_size: int = 8
var _view_percent: float = (y_scale * _graph_max_total) / (size.y * 0.5)
if (_view_percent * _graph_max_total) > 0.25:
var _padding: int = 10
var _up_offset: float = max((-size.y * 0.5) * min(_view_percent, 1.0), -size.y * 0.5 + _padding)
var _down_offset: float = min((size.y * 0.5) * min(_view_percent, 1.0), size.y * 0.5 - _padding)
var _min_max_percent: float = 1.0 / max(_view_percent, 1.0)
draw_string(ThemeDB.fallback_font, Vector2(5.0, size.y * 0.5 + font_size * 0.5 + _up_offset), "%.2f" % (1.0 * _graph_max_total * _min_max_percent), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color.DIM_GRAY, TextServer.JUSTIFICATION_NONE, TextServer.DIRECTION_AUTO, TextServer.ORIENTATION_HORIZONTAL)
draw_string(ThemeDB.fallback_font, Vector2(5.0, size.y * 0.5 + font_size * 0.5 + _down_offset), "%.2f" % (1.0 * _graph_min * _min_max_percent), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color.DIM_GRAY, TextServer.JUSTIFICATION_NONE, TextServer.DIRECTION_AUTO, TextServer.ORIENTATION_HORIZONTAL)
# Draws the graph points
func _draw_graph_points() -> void:
for axis_index in graph_points.size():
var _point_length:int = graph_points[axis_index].size()
var _point_color:Color = point_color_by_axis[fmod(axis_index, point_color_by_axis.size())]
var alpha:float = _unselected_opacity if axis_index != axis_button.get_selected_id() else 1.0
var _final_size: Vector2 = size - graph_offset
var _offset: Vector2 = Vector2(1, y_scale)
for point_index in _point_length:
var _size_offset = Vector2((_final_size.x / (_point_length)) * point_index, _final_size.y * 0.5)
var point:float = graph_points[axis_index][point_index]
if point_index < _point_length - 1:
var _size_offset_next = Vector2((_final_size.x / (_point_length)) * (point_index + 1), _final_size.y * 0.5)
var point_next:float = graph_points[axis_index][point_index + 1]
var _final_point_1: Vector2 = graph_offset + _size_offset + Vector2(0.0, point) * _offset
var _final_point_2: Vector2 = graph_offset + _size_offset_next + Vector2(0.0, point_next) * _offset
draw_line(_final_point_1, _final_point_2, _point_color * Color(1,1,1, alpha), width, false)
# Draws the graph info
func _draw_graph_info() -> void:
var font_size: int = 8
var _text = "Zoom: %.2f | Zoom IN / OUT with mouse scroll" % (y_scale / (size.y * 0.5 / _graph_max_total))
var _text_size: float = _text.length() * font_size * 0.5
draw_string(ThemeDB.fallback_font, Vector2(size.x - _text_size, size.y - 8.0), _text, HORIZONTAL_ALIGNMENT_RIGHT, -1, font_size, Color.GRAY, TextServer.JUSTIFICATION_NONE, TextServer.DIRECTION_AUTO, TextServer.ORIENTATION_HORIZONTAL)
# Handles GUI input
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton:
_handle_mouse_button(event)
if event is InputEventMouseMotion:
_handle_mouse_motion(event)
# Handles mouse button input
func _handle_mouse_button(event: InputEventMouseButton) -> void:
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
y_scale -= 1.0 / _graph_max_total
accept_event()
elif event.button_index == MOUSE_BUTTON_WHEEL_UP:
y_scale += 1.0 / _graph_max_total
accept_event()
elif event.button_index == MOUSE_BUTTON_LEFT:
if shake is ShakerPresetBase:
graph_pressing = event.pressed
update_timeline_position(event.position)
if event.button_index == MOUSE_BUTTON_MIDDLE:
if shake is ShakerPresetBase:
graph_middle_pressing = event.pressed
y_scale = max(y_scale, 1)
queue_redraw()
# Handles mouse motion input
func _handle_mouse_motion(event: InputEventMouseMotion) -> void:
if graph_pressing:
update_timeline_position(event.position)
if graph_middle_pressing:
graph_time_offset += -(event.relative.x / size.x)
graph_time_offset = max(graph_time_offset, 0.0)
# Updates the timeline position
func update_timeline_position(pos: Vector2) -> void:
if shake is ShakerPresetBase:
var _press_percent: float = max(((pos.x) - graph_offset.x) / (size.x - graph_offset.x), 0.0)
if shake.parent is ShakerBase3d || shake.parent is ShakerBase2d:
var _shaker_component = shake.parent
if _shaker_component != null:
_shaker_component.set_progress(_press_percent + graph_time_offset)
# Called when a property changes
func on_property_changed(_name: StringName) -> void:
_setup_axis_button()
_update_graph()
queue_redraw()
if is_inf(y_scale) or is_nan(y_scale):
_on_fit_button_clicked()
# Called when an axis is selected
func _axis_selected(item: int) -> void:
_update_graph()
_on_fit_button_clicked()
# Called when the fit button is clicked
func _on_fit_button_clicked() -> void:
y_scale = (size.y * 0.5) / _graph_max_total
graph_time_offset = 0.0
queue_redraw()
# Called when a category is selected
func _on_category_selected(item: int) -> void:
selected_category = item
_setup_axis_button()
_update_graph()
_on_fit_button_clicked()
# Setter for graph_time_offset
func set_graph_time_offset(value: float) -> void:
graph_time_offset = value
if time_line != null:
time_line.queue_redraw()
_update_graph()
queue_redraw()
# Getter for graph_time_offset
func get_graph_time_offset() -> float:
return graph_time_offset
func select_axis(index:int) -> void:
if axis_button.has_selectable_items():
axis_button.select(index)
func select_category(index:int) -> void:
if category_button.has_selectable_items():
category_button.select(index)