678 lines
7.8 KiB
GDScript
678 lines
7.8 KiB
GDScript
@tool
|
|
class_name SimulationTimeline
|
|
extends VBoxContainer
|
|
|
|
signal pause_requested
|
|
signal resume_requested
|
|
signal step_requested(
|
|
seconds: float
|
|
)
|
|
signal reset_requested
|
|
|
|
signal sequence_selected(
|
|
index: int
|
|
)
|
|
|
|
signal run_sequence_requested
|
|
signal stop_sequence_requested
|
|
signal replay_sequence_requested
|
|
|
|
var sequence_select: OptionButton
|
|
var run_button: Button
|
|
var stop_button: Button
|
|
var replay_button: Button
|
|
|
|
var tree: Tree
|
|
var clear_button: Button
|
|
var count_label: Label
|
|
|
|
var _first_trace_time: float = -1.0
|
|
var _entry_count: int = 0
|
|
|
|
var pause_button: Button
|
|
var step_button: Button
|
|
var reset_button: Button
|
|
|
|
var _paused: bool = false
|
|
|
|
func _ready() -> void:
|
|
|
|
if tree != null:
|
|
return
|
|
|
|
_build_ui()
|
|
|
|
|
|
func _build_ui() -> void:
|
|
|
|
var toolbar := HBoxContainer.new()
|
|
|
|
add_child(
|
|
toolbar
|
|
)
|
|
|
|
|
|
var title := Label.new()
|
|
|
|
title.text = "Simulation Timeline"
|
|
|
|
title.size_flags_horizontal = (
|
|
Control.SIZE_EXPAND_FILL
|
|
)
|
|
|
|
toolbar.add_child(
|
|
title
|
|
)
|
|
|
|
|
|
count_label = Label.new()
|
|
|
|
count_label.text = "0 events"
|
|
|
|
toolbar.add_child(
|
|
count_label
|
|
)
|
|
|
|
sequence_select = OptionButton.new()
|
|
|
|
sequence_select.custom_minimum_size.x = 180.0
|
|
|
|
sequence_select.item_selected.connect(
|
|
func(index: int):
|
|
sequence_selected.emit(
|
|
index
|
|
)
|
|
)
|
|
|
|
toolbar.add_child(
|
|
sequence_select
|
|
)
|
|
|
|
|
|
run_button = Button.new()
|
|
|
|
run_button.text = "Run"
|
|
|
|
run_button.pressed.connect(
|
|
func():
|
|
run_sequence_requested.emit()
|
|
)
|
|
|
|
toolbar.add_child(
|
|
run_button
|
|
)
|
|
|
|
|
|
stop_button = Button.new()
|
|
|
|
stop_button.text = "Stop"
|
|
|
|
stop_button.disabled = true
|
|
|
|
stop_button.pressed.connect(
|
|
func():
|
|
stop_sequence_requested.emit()
|
|
)
|
|
|
|
toolbar.add_child(
|
|
stop_button
|
|
)
|
|
|
|
|
|
replay_button = Button.new()
|
|
|
|
replay_button.text = "Replay"
|
|
|
|
replay_button.pressed.connect(
|
|
func():
|
|
replay_sequence_requested.emit()
|
|
)
|
|
|
|
toolbar.add_child(
|
|
replay_button
|
|
)
|
|
|
|
pause_button = Button.new()
|
|
|
|
pause_button.text = "Pause"
|
|
|
|
pause_button.pressed.connect(
|
|
_on_pause_button_pressed
|
|
)
|
|
|
|
toolbar.add_child(
|
|
pause_button
|
|
)
|
|
|
|
|
|
step_button = Button.new()
|
|
|
|
step_button.text = "Step 0.1s"
|
|
|
|
step_button.pressed.connect(
|
|
func():
|
|
step_requested.emit(
|
|
0.1
|
|
)
|
|
)
|
|
|
|
toolbar.add_child(
|
|
step_button
|
|
)
|
|
|
|
|
|
reset_button = Button.new()
|
|
|
|
reset_button.text = "Reset"
|
|
|
|
reset_button.pressed.connect(
|
|
func():
|
|
reset_requested.emit()
|
|
)
|
|
|
|
toolbar.add_child(
|
|
reset_button
|
|
)
|
|
|
|
clear_button = Button.new()
|
|
|
|
clear_button.text = "Clear"
|
|
|
|
clear_button.pressed.connect(
|
|
clear
|
|
)
|
|
|
|
toolbar.add_child(
|
|
clear_button
|
|
)
|
|
|
|
|
|
tree = Tree.new()
|
|
|
|
tree.size_flags_horizontal = (
|
|
Control.SIZE_EXPAND_FILL
|
|
)
|
|
|
|
tree.size_flags_vertical = (
|
|
Control.SIZE_EXPAND_FILL
|
|
)
|
|
|
|
tree.custom_minimum_size.y = 180.0
|
|
|
|
tree.columns = 4
|
|
|
|
tree.set_column_title(
|
|
0,
|
|
"Time"
|
|
)
|
|
|
|
tree.set_column_title(
|
|
1,
|
|
"Type"
|
|
)
|
|
|
|
tree.set_column_title(
|
|
2,
|
|
"Source / Target"
|
|
)
|
|
|
|
tree.set_column_title(
|
|
3,
|
|
"Detail"
|
|
)
|
|
|
|
tree.column_titles_visible = true
|
|
|
|
tree.set_column_expand(
|
|
0,
|
|
false
|
|
)
|
|
|
|
tree.set_column_custom_minimum_width(
|
|
0,
|
|
80
|
|
)
|
|
|
|
tree.set_column_expand(
|
|
1,
|
|
false
|
|
)
|
|
|
|
tree.set_column_custom_minimum_width(
|
|
1,
|
|
90
|
|
)
|
|
|
|
tree.set_column_expand(
|
|
2,
|
|
true
|
|
)
|
|
|
|
tree.set_column_expand(
|
|
3,
|
|
true
|
|
)
|
|
|
|
add_child(
|
|
tree
|
|
)
|
|
|
|
tree.create_item()
|
|
set_paused(
|
|
false
|
|
)
|
|
|
|
func clear() -> void:
|
|
|
|
_first_trace_time = -1.0
|
|
_entry_count = 0
|
|
|
|
if count_label != null:
|
|
count_label.text = "0 events"
|
|
|
|
if tree == null:
|
|
return
|
|
|
|
tree.clear()
|
|
|
|
tree.create_item()
|
|
|
|
|
|
func add_trace_entry(
|
|
entry: Dictionary
|
|
) -> void:
|
|
|
|
if tree == null:
|
|
_build_ui()
|
|
|
|
var absolute_time := float(
|
|
entry.get(
|
|
"time",
|
|
0.0
|
|
)
|
|
)
|
|
|
|
if _first_trace_time < 0.0:
|
|
_first_trace_time = absolute_time
|
|
|
|
var relative_time := (
|
|
absolute_time
|
|
- _first_trace_time
|
|
)
|
|
|
|
var root := tree.get_root()
|
|
|
|
if root == null:
|
|
root = tree.create_item()
|
|
|
|
var item := tree.create_item(
|
|
root
|
|
)
|
|
|
|
item.set_text(
|
|
0,
|
|
"%.3f" % relative_time
|
|
)
|
|
|
|
var entry_type := StringName(
|
|
entry.get(
|
|
"type",
|
|
&"unknown"
|
|
)
|
|
)
|
|
|
|
item.set_text(
|
|
1,
|
|
String(entry_type).to_upper()
|
|
)
|
|
|
|
item.set_text(
|
|
2,
|
|
_get_subject_text(
|
|
entry
|
|
)
|
|
)
|
|
|
|
item.set_text(
|
|
3,
|
|
_get_detail_text(
|
|
entry
|
|
)
|
|
)
|
|
|
|
_entry_count += 1
|
|
|
|
if count_label != null:
|
|
|
|
count_label.text = (
|
|
"%d events"
|
|
% _entry_count
|
|
)
|
|
|
|
tree.scroll_to_item(
|
|
item
|
|
)
|
|
|
|
|
|
func _get_subject_text(
|
|
entry: Dictionary
|
|
) -> String:
|
|
|
|
var entry_type := StringName(
|
|
entry.get(
|
|
"type",
|
|
&""
|
|
)
|
|
)
|
|
|
|
match entry_type:
|
|
|
|
&"event":
|
|
return String(
|
|
entry.get(
|
|
"source_id",
|
|
&""
|
|
)
|
|
)
|
|
|
|
&"rule":
|
|
return String(
|
|
entry.get(
|
|
"rule_id",
|
|
&""
|
|
)
|
|
)
|
|
|
|
&"action":
|
|
return String(
|
|
entry.get(
|
|
"target_id",
|
|
&""
|
|
)
|
|
)
|
|
|
|
&"gpio":
|
|
return String(
|
|
entry.get(
|
|
"cabinet_id",
|
|
&""
|
|
)
|
|
)
|
|
|
|
&"device":
|
|
return String(
|
|
entry.get(
|
|
"device_id",
|
|
&""
|
|
)
|
|
)
|
|
|
|
&"input":
|
|
return String(
|
|
entry.get(
|
|
"source_id",
|
|
&""
|
|
)
|
|
)
|
|
|
|
&"sequence":
|
|
return String(
|
|
entry.get(
|
|
"display_name",
|
|
entry.get(
|
|
"sequence_id",
|
|
&""
|
|
)
|
|
)
|
|
)
|
|
_:
|
|
return ""
|
|
|
|
|
|
func _get_detail_text(
|
|
entry: Dictionary
|
|
) -> String:
|
|
|
|
var entry_type := StringName(
|
|
entry.get(
|
|
"type",
|
|
&""
|
|
)
|
|
)
|
|
|
|
match entry_type:
|
|
|
|
&"event":
|
|
|
|
return String(
|
|
entry.get(
|
|
"event_id",
|
|
&""
|
|
)
|
|
)
|
|
|
|
|
|
&"rule":
|
|
|
|
var target_id := String(
|
|
entry.get(
|
|
"target_id",
|
|
&""
|
|
)
|
|
)
|
|
|
|
var action_id := String(
|
|
entry.get(
|
|
"action_id",
|
|
&""
|
|
)
|
|
)
|
|
|
|
var delay := float(
|
|
entry.get(
|
|
"delay_seconds",
|
|
0.0
|
|
)
|
|
)
|
|
|
|
if delay > 0.0:
|
|
|
|
return (
|
|
"→ %s.%s after %.3fs"
|
|
% [
|
|
target_id,
|
|
action_id,
|
|
delay
|
|
]
|
|
)
|
|
|
|
return (
|
|
"→ %s.%s"
|
|
% [
|
|
target_id,
|
|
action_id
|
|
]
|
|
)
|
|
|
|
|
|
&"action":
|
|
|
|
return String(
|
|
entry.get(
|
|
"action_id",
|
|
&""
|
|
)
|
|
)
|
|
|
|
|
|
&"gpio":
|
|
|
|
var pin := int(
|
|
entry.get(
|
|
"gpio_pin",
|
|
-1
|
|
)
|
|
)
|
|
|
|
var electrical_state := bool(
|
|
entry.get(
|
|
"electrical_state",
|
|
false
|
|
)
|
|
)
|
|
|
|
var action_id := String(
|
|
entry.get(
|
|
"action_id",
|
|
&""
|
|
)
|
|
)
|
|
|
|
var level := "LOW"
|
|
|
|
if electrical_state:
|
|
level = "HIGH"
|
|
|
|
return (
|
|
"GPIO %d %s (%s)"
|
|
% [
|
|
pin,
|
|
level,
|
|
action_id
|
|
]
|
|
)
|
|
|
|
&"device":
|
|
|
|
var active := bool(
|
|
entry.get(
|
|
"active",
|
|
false
|
|
)
|
|
)
|
|
|
|
if active:
|
|
return "ACTIVE"
|
|
|
|
return "INACTIVE"
|
|
|
|
&"input":
|
|
|
|
var event_id := String(
|
|
entry.get(
|
|
"event_id",
|
|
&""
|
|
)
|
|
)
|
|
|
|
var note := String(
|
|
entry.get(
|
|
"note",
|
|
""
|
|
)
|
|
)
|
|
|
|
if not note.is_empty():
|
|
|
|
return (
|
|
"%s — %s"
|
|
% [
|
|
event_id,
|
|
note
|
|
]
|
|
)
|
|
|
|
return event_id
|
|
|
|
|
|
&"sequence":
|
|
|
|
var state := String(
|
|
entry.get(
|
|
"state",
|
|
&""
|
|
)
|
|
)
|
|
|
|
return state.to_upper()
|
|
_:
|
|
return str(
|
|
entry
|
|
)
|
|
|
|
func _on_pause_button_pressed() -> void:
|
|
|
|
if _paused:
|
|
|
|
resume_requested.emit()
|
|
|
|
else:
|
|
|
|
pause_requested.emit()
|
|
|
|
func set_paused(
|
|
value: bool
|
|
) -> void:
|
|
|
|
_paused = value
|
|
|
|
if pause_button != null:
|
|
|
|
if _paused:
|
|
pause_button.text = "Resume"
|
|
else:
|
|
pause_button.text = "Pause"
|
|
|
|
if step_button != null:
|
|
step_button.disabled = not _paused
|
|
|
|
func set_sequences(
|
|
sequences: Array[TestSequence]
|
|
) -> void:
|
|
|
|
if sequence_select == null:
|
|
return
|
|
|
|
sequence_select.clear()
|
|
|
|
for sequence in sequences:
|
|
|
|
if sequence == null:
|
|
continue
|
|
|
|
var label := (
|
|
sequence.display_name
|
|
)
|
|
|
|
if label.is_empty():
|
|
label = String(
|
|
sequence.sequence_id
|
|
)
|
|
|
|
sequence_select.add_item(
|
|
label
|
|
)
|
|
|
|
if sequence_select.item_count > 0:
|
|
|
|
sequence_select.select(
|
|
0
|
|
)
|
|
|
|
|
|
func set_sequence_running(
|
|
running: bool
|
|
) -> void:
|
|
|
|
if run_button != null:
|
|
run_button.disabled = running
|
|
|
|
if stop_button != null:
|
|
stop_button.disabled = not running
|
|
|
|
if replay_button != null:
|
|
replay_button.disabled = running
|
|
|
|
if sequence_select != null:
|
|
sequence_select.disabled = running
|