add kanban
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
@tool
|
||||
extends AcceptDialog
|
||||
|
||||
|
||||
const __BoardData := preload("../../data/board.gd")
|
||||
const __StepData := preload("../../data/step.gd")
|
||||
const __StepEntry := preload("../details/step_entry.gd")
|
||||
const __Singletons := preload("../../plugin_singleton/singletons.gd")
|
||||
const __EditContext := preload("../edit_context.gd")
|
||||
|
||||
var board_data: __BoardData
|
||||
var data_uuid: String
|
||||
|
||||
var __step_data: __StepData
|
||||
|
||||
@onready var category_select: OptionButton = %Category
|
||||
@onready var h_split_container: HSplitContainer = %HSplitContainer
|
||||
@onready var description_edit: TextEdit = %Description
|
||||
@onready var step_holder: VBoxContainer = %StepHolder
|
||||
@onready var steps_panel_container: PanelContainer = %PanelContainer
|
||||
@onready var create_step_edit: LineEdit = %CreateStepEdit
|
||||
@onready var step_details: VBoxContainer = %StepDetails
|
||||
@onready var close_step_details_button: Button = %CloseStepDetails
|
||||
@onready var step_edit: TextEdit = %StepEdit
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
about_to_popup.connect(__on_about_to_popup)
|
||||
create_step_edit.text_submitted.connect(__create_step)
|
||||
close_step_details_button.pressed.connect(__close_step_details)
|
||||
notification(NOTIFICATION_THEME_CHANGED)
|
||||
step_holder.entry_action_triggered.connect(__on_step_action_triggered)
|
||||
step_holder.entry_move_requesed.connect(__step_move_requesed)
|
||||
|
||||
visibility_changed.connect(__save_internal_state)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
|
||||
if is_instance_valid(steps_panel_container):
|
||||
steps_panel_container.add_theme_stylebox_override(&"panel", get_theme_stylebox(&"panel", &"Panel"))
|
||||
if is_instance_valid(create_step_edit):
|
||||
create_step_edit.right_icon = get_theme_icon(&"Add", &"EditorIcons")
|
||||
if is_instance_valid(close_step_details_button):
|
||||
close_step_details_button.icon = get_theme_icon(&"Close", &"EditorIcons")
|
||||
|
||||
|
||||
func update() -> void:
|
||||
if description_edit.text_changed.is_connected(__on_description_changed):
|
||||
description_edit.text_changed.disconnect(__on_description_changed)
|
||||
if description_edit.text != board_data.get_task(data_uuid).description:
|
||||
description_edit.text = board_data.get_task(data_uuid).description
|
||||
description_edit.text_changed.connect(__on_description_changed)
|
||||
|
||||
title = "Task Details: " + board_data.get_task(data_uuid).title
|
||||
|
||||
if category_select.item_selected.is_connected(__on_category_selected):
|
||||
category_select.item_selected.disconnect(__on_category_selected)
|
||||
category_select.clear()
|
||||
for uuid in board_data.get_categories():
|
||||
var i = Image.create(16, 16, false, Image.FORMAT_RGB8)
|
||||
i.fill(board_data.get_category(uuid).color)
|
||||
var t = ImageTexture.create_from_image(i)
|
||||
category_select.add_icon_item(t, board_data.get_category(uuid).title)
|
||||
category_select.set_item_metadata(-1, uuid)
|
||||
if uuid == board_data.get_task(data_uuid).category:
|
||||
category_select.select(category_select.item_count - 1)
|
||||
|
||||
category_select.item_selected.connect(__on_category_selected)
|
||||
|
||||
step_holder.clear_steps()
|
||||
for step in board_data.get_task(data_uuid).steps:
|
||||
step_holder.add_step(step)
|
||||
for entry in step_holder.get_step_entries():
|
||||
entry.being_edited = (entry.step_data == __step_data)
|
||||
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
|
||||
step_details.visible = is_instance_valid(__step_data)
|
||||
description_edit.visible = not (ctx.settings.edit_step_details_exclusively and is_instance_valid(__step_data))
|
||||
if is_instance_valid(__step_data):
|
||||
if step_edit.text_changed.is_connected(__on_step_details_changed):
|
||||
step_edit.text_changed.disconnect(__on_step_details_changed)
|
||||
step_edit.text = __step_data.details
|
||||
step_edit.text_changed.connect(__on_step_details_changed)
|
||||
|
||||
|
||||
# Workaround for godotengine/godot#70451
|
||||
func popup_centered_ratio_no_fullscreen(ratio: float = 0.8) -> void:
|
||||
var viewport: Viewport = get_parent().get_viewport()
|
||||
popup(Rect2i(Vector2(viewport.position) + viewport.size / 2.0 - viewport.size * ratio / 2.0, viewport.size * ratio))
|
||||
|
||||
|
||||
func edit_step_details(step: __StepData) -> void:
|
||||
if is_instance_valid(__step_data):
|
||||
__step_data.changed.disconnect(update)
|
||||
__step_data = step
|
||||
__step_data.changed.connect(update)
|
||||
update()
|
||||
step_edit.set_caret_line(step_edit.get_line_count())
|
||||
step_edit.set_caret_column(len(step_edit.get_line(step_edit.get_line_count() - 1)))
|
||||
step_edit.grab_focus.call_deferred()
|
||||
|
||||
|
||||
func move_step_up(step: __StepData) -> void:
|
||||
var steps = board_data.get_task(data_uuid).steps
|
||||
if step in steps and steps[0] != step:
|
||||
var index = steps.find(step)
|
||||
steps.erase(step)
|
||||
steps.insert(index - 1, step)
|
||||
board_data.get_task(data_uuid).steps = steps
|
||||
update()
|
||||
|
||||
|
||||
func move_step_down(step: __StepData) -> void:
|
||||
var steps = board_data.get_task(data_uuid).steps
|
||||
if step in steps and steps[-1] != step:
|
||||
var index = steps.find(step)
|
||||
steps.erase(step)
|
||||
steps.insert(index + 1, step)
|
||||
board_data.get_task(data_uuid).steps = steps
|
||||
update()
|
||||
|
||||
|
||||
func delete_step(step: __StepData) -> void:
|
||||
close_step_details(step)
|
||||
var steps = board_data.get_task(data_uuid).steps
|
||||
if step in steps:
|
||||
steps.erase(step)
|
||||
board_data.get_task(data_uuid).steps = steps
|
||||
update()
|
||||
|
||||
|
||||
func close_step_details(step: __StepData) -> void:
|
||||
if __step_data == step:
|
||||
__close_step_details()
|
||||
|
||||
|
||||
func __on_step_action_triggered(entry: __StepEntry, action: __StepEntry.Actions) -> void:
|
||||
match action:
|
||||
__StepEntry.Actions.EDIT_HARD:
|
||||
edit_step_details(entry.step_data)
|
||||
__StepEntry.Actions.EDIT_SOFT:
|
||||
if is_instance_valid(__step_data):
|
||||
edit_step_details(entry.step_data)
|
||||
__StepEntry.Actions.CLOSE:
|
||||
close_step_details(entry.step_data)
|
||||
__StepEntry.Actions.DELETE:
|
||||
delete_step(entry.step_data)
|
||||
__StepEntry.Actions.MOVE_UP:
|
||||
move_step_up(entry.step_data)
|
||||
__StepEntry.Actions.MOVE_DOWN:
|
||||
move_step_down(entry.step_data)
|
||||
|
||||
|
||||
func __step_move_requesed(moved_entry: __StepEntry, target_entry: __StepEntry, move_after_target: bool) -> void:
|
||||
var steps = board_data.get_task(data_uuid).steps
|
||||
var moved_idx = steps.find(moved_entry.step_data)
|
||||
var target_idx = steps.find(target_entry.step_data)
|
||||
if moved_idx < 0 or target_idx < 0 or moved_idx == target_idx:
|
||||
return
|
||||
steps.erase(moved_entry.step_data)
|
||||
if moved_idx < target_idx:
|
||||
target_idx -= 1
|
||||
if move_after_target:
|
||||
steps.insert(target_idx + 1, moved_entry.step_data)
|
||||
else:
|
||||
steps.insert(target_idx, moved_entry.step_data)
|
||||
board_data.get_task(data_uuid).steps = steps
|
||||
update()
|
||||
|
||||
|
||||
func __load_internal_state() -> void:
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
if ctx.settings.internal_states.has("details_editor_step_holder_width"):
|
||||
h_split_container.split_offset = ctx.settings.internal_states["details_editor_step_holder_width"]
|
||||
|
||||
|
||||
func __save_internal_state() -> void:
|
||||
if not visible:
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
ctx.settings.set_internal_state("details_editor_step_holder_width", h_split_container.split_offset)
|
||||
|
||||
|
||||
func __close_step_details() -> void:
|
||||
__step_data.changed.disconnect(update)
|
||||
__step_data = null
|
||||
update()
|
||||
|
||||
|
||||
func __on_step_details_changed() -> void:
|
||||
if __step_data.changed.is_connected(update):
|
||||
__step_data.changed.disconnect(update)
|
||||
__step_data.details = step_edit.text
|
||||
__step_data.changed.connect(update)
|
||||
|
||||
|
||||
func __on_about_to_popup() -> void:
|
||||
if is_instance_valid(__step_data):
|
||||
__close_step_details()
|
||||
update()
|
||||
__load_internal_state()
|
||||
if board_data.get_task(data_uuid).description.is_empty():
|
||||
description_edit.grab_focus.call_deferred()
|
||||
|
||||
|
||||
func __on_description_changed() -> void:
|
||||
board_data.get_task(data_uuid).description = description_edit.text
|
||||
|
||||
|
||||
func __on_category_selected(index: int) -> void:
|
||||
board_data.get_task(data_uuid).category = category_select.get_item_metadata(index)
|
||||
|
||||
|
||||
func __create_step(text: String) -> void:
|
||||
if text.is_empty():
|
||||
return
|
||||
var task = board_data.get_task(data_uuid)
|
||||
var data = __StepData.new(text)
|
||||
task.add_step(data)
|
||||
create_step_edit.text = ""
|
||||
update()
|
||||
for step in step_holder.get_step_entries():
|
||||
if step.step_data == data:
|
||||
step.grab_focus.call_deferred()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cfuka8bfmcwog
|
||||
@@ -0,0 +1,100 @@
|
||||
[gd_scene format=3 uid="uid://bwi22eyrmeeet"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cfuka8bfmcwog" path="res://addons/kanban_tasks/view/details/details.gd" id="1_gh7s6"]
|
||||
[ext_resource type="PackedScene" uid="uid://dwjg5vyxx4g48" path="res://addons/kanban_tasks/view/details/step_holder.tscn" id="2_0ptaf"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g2k57"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 5.0
|
||||
bg_color = Color(0.1, 0.1, 0.1, 0.6)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 5
|
||||
|
||||
[node name="Details" type="AcceptDialog" unique_id=537054767]
|
||||
oversampling_override = 1.0
|
||||
title = "Task Details"
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(916, 557)
|
||||
ok_button_text = "Close"
|
||||
script = ExtResource("1_gh7s6")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1730906135]
|
||||
custom_minimum_size = Vector2(900, 500)
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -49.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Category" type="OptionButton" parent="VBoxContainer" unique_id=879562296]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer" unique_id=1738893748]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VSplitContainer" type="VSplitContainer" parent="VBoxContainer/HSplitContainer" unique_id=1951989716]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Description" type="TextEdit" parent="VBoxContainer/HSplitContainer/VSplitContainer" unique_id=2088816932]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
wrap_mode = 1
|
||||
|
||||
[node name="StepDetails" type="VBoxContainer" parent="VBoxContainer/HSplitContainer/VSplitContainer" unique_id=1125161346]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails" unique_id=2007120957]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails/HBoxContainer" unique_id=609616893]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Step Details:"
|
||||
|
||||
[node name="CloseStepDetails" type="Button" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails/HBoxContainer" unique_id=1389055250]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Close"
|
||||
flat = true
|
||||
|
||||
[node name="StepEdit" type="TextEdit" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails" unique_id=2005028969]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
wrap_mode = 1
|
||||
|
||||
[node name="StepList" type="VBoxContainer" parent="VBoxContainer/HSplitContainer" unique_id=1447925866]
|
||||
custom_minimum_size = Vector2(200, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CreateStepEdit" type="LineEdit" parent="VBoxContainer/HSplitContainer/StepList" unique_id=479211446]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
placeholder_text = "Create Step"
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/HSplitContainer/StepList" unique_id=1566602760]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_g2k57")
|
||||
|
||||
[node name="StepHolder" parent="VBoxContainer/HSplitContainer/StepList/PanelContainer" unique_id=128405745 instance=ExtResource("2_0ptaf")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
steps_focus_mode = 2
|
||||
@@ -0,0 +1,135 @@
|
||||
@tool
|
||||
extends HBoxContainer
|
||||
|
||||
## Visual representation of a step.
|
||||
|
||||
|
||||
signal action_triggered(entry: __StepEntry, action: Actions)
|
||||
|
||||
const __EditLabel := preload("../../edit_label/edit_label.gd")
|
||||
const __StepData := preload("../../data/step.gd")
|
||||
const __Singletons := preload("../../plugin_singleton/singletons.gd")
|
||||
const __Shortcuts := preload("../shortcuts.gd")
|
||||
const __StepEntry := preload("step_entry.gd")
|
||||
|
||||
enum Actions {
|
||||
DELETE,
|
||||
MOVE_UP,
|
||||
MOVE_DOWN,
|
||||
EDIT_HARD, ## Forces the step details to open.
|
||||
EDIT_SOFT, ## Only switches to this step if the details are opened.
|
||||
CLOSE,
|
||||
}
|
||||
|
||||
@export var context_menu_enabled: bool = true
|
||||
|
||||
var done: CheckBox
|
||||
var title_label: Label
|
||||
var focus_box: StyleBoxFlat
|
||||
var context_menu: PopupMenu
|
||||
|
||||
var step_data: __StepData
|
||||
|
||||
var being_edited := false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_h_size_flags(SIZE_EXPAND_FILL)
|
||||
|
||||
context_menu = PopupMenu.new()
|
||||
context_menu.id_pressed.connect(__action)
|
||||
add_child(context_menu)
|
||||
|
||||
done = CheckBox.new()
|
||||
done.focus_mode = Control.FOCUS_NONE
|
||||
done.toggled.connect(__set_done)
|
||||
add_child(done)
|
||||
|
||||
title_label = Label.new()
|
||||
title_label.set_h_size_flags(SIZE_EXPAND_FILL)
|
||||
title_label.text = step_data.details
|
||||
title_label.max_lines_visible = 1
|
||||
title_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
add_child(title_label)
|
||||
|
||||
focus_box = StyleBoxFlat.new()
|
||||
focus_box.bg_color = Color(1, 1, 1, 0.1)
|
||||
|
||||
notification(NOTIFICATION_THEME_CHANGED)
|
||||
|
||||
step_data.changed.connect(update)
|
||||
update()
|
||||
|
||||
|
||||
func _shortcut_input(event: InputEvent) -> void:
|
||||
if not __Shortcuts.should_handle_shortcut(self):
|
||||
return
|
||||
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
|
||||
if not event.is_echo() and event.is_pressed():
|
||||
if shortcuts.rename.matches_event(event):
|
||||
get_viewport().set_input_as_handled()
|
||||
__action(Actions.EDIT_HARD)
|
||||
elif shortcuts.confirm.matches_event(event):
|
||||
get_viewport().set_input_as_handled()
|
||||
done.button_pressed = not done.button_pressed
|
||||
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
accept_event()
|
||||
if context_menu_enabled:
|
||||
__update_context_menu()
|
||||
context_menu.position = get_global_mouse_position()
|
||||
if not get_window().gui_embed_subwindows:
|
||||
context_menu.position += get_window().position
|
||||
context_menu.popup()
|
||||
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed() and event.is_double_click():
|
||||
__action(Actions.EDIT_HARD)
|
||||
|
||||
|
||||
func _notification(what) -> void:
|
||||
match(what):
|
||||
NOTIFICATION_DRAW:
|
||||
if has_focus() or being_edited:
|
||||
focus_box.draw(get_canvas_item(), Rect2(Vector2.ZERO, get_rect().size))
|
||||
NOTIFICATION_FOCUS_ENTER:
|
||||
__action(Actions.EDIT_SOFT)
|
||||
|
||||
|
||||
func update() -> void:
|
||||
tooltip_text = step_data.details
|
||||
done.set_pressed_no_signal(step_data.done)
|
||||
|
||||
title_label.text = step_data.details
|
||||
|
||||
|
||||
func __action(what: Actions) -> void:
|
||||
action_triggered.emit(self, what)
|
||||
|
||||
|
||||
func __update_context_menu() -> void:
|
||||
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
|
||||
|
||||
context_menu.clear()
|
||||
context_menu.size = Vector2.ZERO
|
||||
|
||||
if being_edited:
|
||||
context_menu.add_icon_item(get_theme_icon(&"Close", &"EditorIcons"), "Close", Actions.CLOSE)
|
||||
else:
|
||||
context_menu.add_icon_item(get_theme_icon(&"Rename", &"EditorIcons"), "Edit", Actions.EDIT_HARD)
|
||||
context_menu.set_item_shortcut(context_menu.get_item_index(Actions.EDIT_HARD), shortcuts.rename)
|
||||
|
||||
context_menu.add_icon_item(get_theme_icon(&"MoveUp", &"EditorIcons"), "Move Up", Actions.MOVE_UP)
|
||||
context_menu.set_item_disabled(context_menu.get_item_index(Actions.MOVE_UP), get_index() == 0)
|
||||
context_menu.add_icon_item(get_theme_icon(&"MoveDown", &"EditorIcons"), "Move Down", Actions.MOVE_DOWN)
|
||||
context_menu.set_item_disabled(context_menu.get_item_index(Actions.MOVE_DOWN), get_index() == get_parent().get_child_count() - 1)
|
||||
|
||||
context_menu.add_separator()
|
||||
|
||||
context_menu.add_icon_item(get_theme_icon(&"Remove", &"EditorIcons"), "Delete", Actions.DELETE)
|
||||
|
||||
|
||||
func __set_done(done: bool) -> void:
|
||||
__action(Actions.CLOSE)
|
||||
step_data.done = done
|
||||
@@ -0,0 +1 @@
|
||||
uid://cdyn8h04v5qqh
|
||||
@@ -0,0 +1,190 @@
|
||||
@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:
|
||||
__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)
|
||||
notification(NOTIFICATION_THEME_CHANGED)
|
||||
__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 _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
|
||||
if is_instance_valid(__remove_area):
|
||||
__remove_area.icon = get_theme_icon(&"Remove", &"EditorIcons")
|
||||
|
||||
|
||||
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)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dw1afc1ntflyw
|
||||
@@ -0,0 +1,45 @@
|
||||
[gd_scene format=3 uid="uid://dwjg5vyxx4g48"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dw1afc1ntflyw" path="res://addons/kanban_tasks/view/details/step_holder.gd" id="1_exd17"]
|
||||
|
||||
[sub_resource type="Theme" id="Theme_1hs0w"]
|
||||
StepHolder/base_type = &"VBoxContainer"
|
||||
StepHolder/colors/step_move_review_color = Color(0.439216, 0.729412, 0.980392, 0.501961)
|
||||
|
||||
[node name="StepHolder" type="VBoxContainer" unique_id=1736962155]
|
||||
offset_right = 326.0
|
||||
offset_bottom = 500.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme = SubResource("Theme_1hs0w")
|
||||
theme_type_variation = &"StepHolder"
|
||||
script = ExtResource("1_exd17")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="." unique_id=1852819234]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
horizontal_scroll_mode = 0
|
||||
metadata/_edit_use_anchors_ = true
|
||||
|
||||
[node name="StepList" type="VBoxContainer" parent="ScrollContainer" unique_id=643748775]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="RemoveSeparator" type="HSeparator" parent="." unique_id=106663436]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="RemoveArea" type="Button" parent="." unique_id=1184154065]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
focus_mode = 0
|
||||
mouse_filter = 2
|
||||
button_mask = 0
|
||||
flat = true
|
||||
icon_alignment = 1
|
||||
Reference in New Issue
Block a user