@tool class_name Timeline extends Container @export var track_list: VBoxContainer @export var font_scale: float = 1.0 @export var time_offset: float = 0.0 @export var time_interval: float = 1000.0 # 1 second @export var grid_space_ms: float = 250 # 0.25 seconds @export var label_interval_ms: float = 1000.0 # 1 second @export var line_thickness: int = 2 @export var major_line_step: int = 4 @export var cursor_width: int = 8 @export var zoom: float = 1.0 @export var start_time: float = 0.0 @export var end_time: float = 10000.0 # 10 seconds @export var min_zoom: float = 0.1 func format_time_ms_hours(ms: float) -> String: var total_seconds = ms / 1000 var hours = int(total_seconds / 3600) var minutes = int(total_seconds / 60) % 60 var seconds = int(total_seconds) % 60 var milliseconds = fmod(ms, 1000.0) return "%02d:%02d:%02d.%03d" % [hours, minutes, seconds, milliseconds] func format_time_ms_minutes(ms: float) -> String: var total_seconds = ms / 1000 var minutes = int(total_seconds / 60) % 60 var seconds = int(total_seconds) % 60 var milliseconds = fmod(ms, 1000.0) return "%02d:%02d.%03d" % [minutes, seconds, milliseconds] func get_track_idx_by_y(y: float): var idx = 0 for track in track_list.get_children(): if y >= track.global_position.y and y <= track.global_position.y + track.size.y: return idx idx += 1 pass return -1 func get_track_by_idx(idx: int) -> Control: return track_list.get_child(idx) func get_pixels_per_unit() -> float: return 50.0 * zoom func _draw(): var primary_color = get_theme_color("line_primary_color", "Timeline") var secondary_color = get_theme_color("line_secondary_color", "Timeline") var font = get_theme_default_font() var font_size = get_theme_default_font_size() var time_label_offset_x = get_theme_constant("time_label_offset_x", "Timeline") var time_label_offset_y = get_theme_constant("time_label_offset_y", "Timeline") # background var background_stylebox = get_theme_stylebox("background", "Timeline") draw_style_box(background_stylebox, Rect2(0.0, 0.0, size.x, size.y)) # top panel var stylebox = get_theme_stylebox("top_panel", "Timeline") draw_style_box(stylebox, Rect2(0.0, 0.0, size.x, 28.0)) var timeline_y := size.y var screen_width := size.x var pixels_per_unit := 50.0 * zoom var start := (time_offset / pixels_per_unit) - 1 var end := start + (screen_width / pixels_per_unit) + 2 # measure lines for t in range(int(start), int(end) + 1): var x := t * pixels_per_unit - time_offset if x >= 0 and x <= screen_width: if t % 4 == 0: draw_line(Vector2(x, 28.0), Vector2(x, size.y), primary_color, line_thickness) var time = t * int(time_interval) draw_string(font, Vector2(x - time_label_offset_x, time_label_offset_y), format_time_ms_minutes(time), HORIZONTAL_ALIGNMENT_CENTER, -1, font_size, primary_color) else: draw_line(Vector2(x, 28.0), Vector2(x, timeline_y), secondary_color, line_thickness) pass # track lines for t in track_list.get_children(): draw_line(Vector2(0.0, t.global_position.y - global_position.y + t.size.y), Vector2(size.x, t.global_position.y - global_position.y + t.size.y), secondary_color, line_thickness) pass queue_sort() pass func _notification(what): if what == NOTIFICATION_SORT_CHILDREN: for c in get_children(): if c is not AudioClip: continue var track = get_track_by_idx(c.track_idx) if track == null: return var pixels_per_unit := 50.0 * zoom var start = ((c.start_time / time_interval) * pixels_per_unit) - time_offset var width = (c.end_time - c.start_time) / time_interval * pixels_per_unit c.position = Vector2(start, track.global_position.y - global_position.y) c.size = Vector2(width, track.size.y) func _gui_input(event): var zoom_factor = 1.1 if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_WHEEL_DOWN: zoom /= zoom_factor queue_redraw() if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_WHEEL_UP: zoom *= zoom_factor queue_redraw() if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_WHEEL_LEFT: time_offset -= 10 queue_redraw() if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_WHEEL_RIGHT: time_offset += 10 queue_redraw() zoom = max(min_zoom, zoom) time_offset = max(0.0, time_offset)