178 lines
5.6 KiB
GDScript
178 lines
5.6 KiB
GDScript
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
|
|
|
|
@export var audio_clip_scene: PackedScene
|
|
|
|
signal clip_added(clip: Control)
|
|
|
|
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 _ready():
|
|
await get_tree().process_frame
|
|
queue_redraw()
|
|
|
|
for c in track_list.get_children():
|
|
if c is Track:
|
|
var track = c as Track
|
|
track.on_deleted.connect(_track_deleted)
|
|
pass
|
|
pass
|
|
|
|
func _track_deleted(idx: int):
|
|
print("track deleted")
|
|
await get_tree().process_frame
|
|
queue_redraw()
|
|
pass
|
|
|
|
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 horizontal 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)
|
|
|
|
func local_x_to_timeline(x: float) -> float:
|
|
return (x + time_offset) / get_pixels_per_unit() * time_interval
|
|
|
|
|
|
func clip_dropped(at_position: Vector2, path: String, clip_name: String, clip_start_time: float, clip_end_time: float):
|
|
var local_position = at_position - global_position
|
|
var timeline_position = local_x_to_timeline(local_position.x)
|
|
|
|
var track_idx = get_track_idx_by_y(at_position.y)
|
|
add_audio_clip(path, clip_name, track_idx, timeline_position + clip_start_time, timeline_position + clip_end_time)
|
|
pass
|
|
|
|
func add_audio_clip(path: String, clip_name: String, track_idx: int, clip_start_time: float, clip_end_time: float):
|
|
var audio_clip = audio_clip_scene.instantiate() as AudioClip
|
|
audio_clip.clip_path = path
|
|
audio_clip.clip_name = clip_name
|
|
audio_clip.track_idx = track_idx
|
|
audio_clip.start_time = clip_start_time
|
|
audio_clip.end_time = clip_end_time
|
|
|
|
add_child(audio_clip)
|
|
|
|
clip_added.emit(audio_clip)
|
|
queue_redraw()
|
|
pass
|