@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)