185 lines
6.3 KiB
GDScript
185 lines
6.3 KiB
GDScript
@tool
|
|
extends VBoxContainer
|
|
|
|
|
|
signal entry_action_triggered(entry: __StepEntry, action: __StepEntry.Actions)
|
|
signal entry_move_requesed(moved_entry: __StepEntry, target_entry: __StepEntry, move_after_target: bool)
|
|
|
|
const __StepData := preload("../../data/step.gd")
|
|
const __StepEntry := preload("../details/step_entry.gd")
|
|
|
|
@export var scrollable: bool = true:
|
|
set(value):
|
|
if value != scrollable:
|
|
scrollable = value
|
|
__update_children_settings()
|
|
|
|
@export var steps_can_be_removed: bool = true:
|
|
set(value):
|
|
if value != steps_can_be_removed:
|
|
steps_can_be_removed = value
|
|
__update_children_settings()
|
|
|
|
@export var steps_can_be_reordered: bool = true:
|
|
set(value):
|
|
if value != steps_can_be_reordered:
|
|
steps_can_be_reordered = value
|
|
__update_children_settings()
|
|
|
|
@export var steps_have_context_menu: bool = true:
|
|
set(value):
|
|
if value != steps_have_context_menu:
|
|
steps_have_context_menu = value
|
|
__update_children_settings()
|
|
|
|
@export var steps_focus_mode := FocusMode.FOCUS_NONE:
|
|
set(value):
|
|
if value != steps_focus_mode:
|
|
steps_focus_mode = value
|
|
__update_children_settings()
|
|
|
|
var __mouse_entered_step_list: bool = false
|
|
var __move_target_entry: __StepEntry = null
|
|
var __move_after_target: bool = false
|
|
|
|
@onready var __scroll_container: ScrollContainer = %ScrollContainer
|
|
@onready var __remove_separator: HSeparator = %RemoveSeparator
|
|
@onready var __step_list: VBoxContainer = %StepList
|
|
@onready var __remove_area: Button = %RemoveArea
|
|
|
|
|
|
func _ready() -> void:
|
|
__remove_area.icon = get_theme_icon(&"Remove", &"EditorIcons")
|
|
__step_list.draw.connect(__on_step_list_draw)
|
|
__step_list.mouse_exited.connect(__on_step_list_mouse_exited)
|
|
__step_list.mouse_entered.connect(__on_step_list_mouse_entered)
|
|
__update_children_settings()
|
|
|
|
|
|
func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
|
|
if not steps_can_be_removed and not steps_can_be_reordered:
|
|
return false
|
|
if data is __StepEntry:
|
|
if __remove_area.get_global_rect().has_point(get_global_transform() * at_position):
|
|
return true
|
|
__update_move_target(at_position)
|
|
return (__move_target_entry != null)
|
|
return false
|
|
|
|
|
|
func _get_drag_data(at_position: Vector2) -> Variant:
|
|
if not steps_can_be_removed and not steps_can_be_reordered:
|
|
return null
|
|
for entry in get_step_entries():
|
|
if entry.get_global_rect().has_point(get_global_transform() * at_position):
|
|
var preview := Label.new()
|
|
preview.text = entry.step_data.details
|
|
set_drag_preview(preview)
|
|
return entry
|
|
return null
|
|
|
|
|
|
func _drop_data(at_position: Vector2, data: Variant) -> void:
|
|
if __move_target_entry != null:
|
|
entry_move_requesed.emit(data, __move_target_entry, __move_after_target)
|
|
__move_target_entry = null
|
|
if data is __StepEntry:
|
|
if __remove_area.get_global_rect().has_point(get_global_transform() * at_position):
|
|
data.__action(__StepEntry.Actions.DELETE)
|
|
|
|
|
|
func add_step(step: __StepData) -> void:
|
|
var entry = __StepEntry.new()
|
|
entry.step_data = step
|
|
entry.show_behind_parent = true
|
|
entry.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
__step_list.add_child(entry)
|
|
entry.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
entry.action_triggered.connect(__on_entry_action_triggered)
|
|
entry.context_menu_enabled = steps_have_context_menu
|
|
entry.focus_mode = steps_focus_mode
|
|
|
|
|
|
func clear_steps() -> void:
|
|
for step in get_step_entries():
|
|
__step_list.remove_child(step)
|
|
step.queue_free()
|
|
|
|
|
|
func get_step_entries() -> Array[__StepEntry]:
|
|
var step_entries: Array[__StepEntry] = []
|
|
if is_instance_valid(__step_list):
|
|
for child in __step_list.get_children():
|
|
if child is __StepEntry:
|
|
step_entries.append(child)
|
|
return step_entries
|
|
|
|
|
|
func __update_children_settings() -> void:
|
|
if is_instance_valid(__scroll_container):
|
|
__scroll_container.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO if scrollable else ScrollContainer.SCROLL_MODE_DISABLED
|
|
if is_instance_valid(__remove_separator):
|
|
__remove_separator.visible = steps_can_be_removed
|
|
if is_instance_valid(__remove_area):
|
|
__remove_area.visible = steps_can_be_removed
|
|
for entry in get_step_entries():
|
|
entry.context_menu_enabled = steps_have_context_menu
|
|
entry.focus_mode = steps_focus_mode
|
|
|
|
|
|
func __update_move_target(at_position: Vector2) -> void:
|
|
var at_global_position := get_global_transform() * at_position
|
|
# This __mouse_entered_step_list is needed here, as this seemed to be the only reliable solution, as:
|
|
# 1) something is NOK with transforming at_position to global and compare with step_list.global_rect
|
|
# 2) cannot decide what is the visible rect of the step_list
|
|
# 3) _can_drop_data was called even after mouse is outside the list (to the bottom direction)
|
|
if __mouse_entered_step_list:
|
|
var closes_entry: __StepEntry = null
|
|
var smallest_distance: float
|
|
var position_is_after_closes_entry: bool
|
|
for e in get_step_entries():
|
|
var entry_global_rect = e.get_global_rect()
|
|
var distance := abs(at_global_position.y - entry_global_rect.position.y)
|
|
if closes_entry == null or distance < smallest_distance:
|
|
closes_entry = e
|
|
smallest_distance = distance
|
|
position_is_after_closes_entry = false
|
|
distance = abs(at_global_position.y - entry_global_rect.end.y)
|
|
if closes_entry == null or distance < smallest_distance:
|
|
closes_entry = e
|
|
smallest_distance = distance
|
|
position_is_after_closes_entry = true
|
|
__move_target_entry = closes_entry
|
|
__move_after_target = position_is_after_closes_entry
|
|
else:
|
|
__move_target_entry = null
|
|
__step_list.queue_redraw()
|
|
|
|
|
|
func __on_step_list_mouse_entered() -> void:
|
|
__mouse_entered_step_list = true
|
|
|
|
|
|
func __on_step_list_mouse_exited() -> void:
|
|
__mouse_entered_step_list = false
|
|
__update_move_target(get_local_mouse_position())
|
|
|
|
|
|
func __on_step_list_draw() -> void:
|
|
if __move_target_entry != null:
|
|
var target_rect := __step_list.get_global_transform().inverse() * __move_target_entry.get_global_rect()
|
|
var separation = __step_list.get_theme_constant(&"separation")
|
|
var preview_rect := Rect2(
|
|
Vector2(0, target_rect.end.y if __move_after_target else target_rect.position.y - separation),
|
|
Vector2(target_rect.size.x, separation)
|
|
)
|
|
if preview_rect.position.y < 0:
|
|
preview_rect.position.y = 0
|
|
if preview_rect.end.y > __step_list.size.y:
|
|
preview_rect.position.y -= (preview_rect.end.y - __step_list.size.y)
|
|
__step_list.draw_rect(preview_rect, get_theme_color(&"step_move_review_color"))
|
|
|
|
|
|
func __on_entry_action_triggered(entry, action) -> void:
|
|
entry_action_triggered.emit(entry, action)
|