add kanban

This commit is contained in:
algodoogle
2026-07-16 15:52:34 +01:00
parent 329bb1f984
commit 5e0b58a984
94 changed files with 5384 additions and 1 deletions
+157
View File
@@ -0,0 +1,157 @@
@tool
extends VBoxContainer
## The visual representation of a kanban board.
const __Singletons := preload("../../plugin_singleton/singletons.gd")
const __Shortcuts := preload("../shortcuts.gd")
const __EditContext := preload("../edit_context.gd")
const __BoardData := preload("../../data/board.gd")
const __StageScript := preload("../stage/stage.gd")
const __StageScene := preload("../stage/stage.tscn")
const __Filter := preload("../filter.gd")
const __SettingsScript := preload("../settings/settings.gd")
signal show_documentation()
var board_data: __BoardData
@onready var search_bar: LineEdit = %SearchBar
@onready var button_advanced_search: Button = %AdvancedSearch
@onready var button_show_categories: Button = %ShowCategories
@onready var button_show_descriptions: Button = %ShowDescriptions
@onready var button_show_steps: Button = %ShowSteps
@onready var button_documentation: Button = %Documentation
@onready var button_settings: Button = %Settings
@onready var column_holder: HBoxContainer = %ColumnHolder
@onready var settings: __SettingsScript = %SettingsView
func _ready():
update()
board_data.layout.changed.connect(update)
settings.board_data = board_data
search_bar.text_changed.connect(__on_filter_changed)
search_bar.text_submitted.connect(__on_search_bar_entered)
button_advanced_search.toggled.connect(__on_filter_changed)
button_show_categories.toggled.connect(__on_show_categories_toggled)
button_show_descriptions.toggled.connect(__on_show_descriptions_toggled)
button_show_steps.toggled.connect(__on_show_steps_toggled)
notification(NOTIFICATION_THEME_CHANGED)
await get_tree().create_timer(0.0).timeout
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
ctx.settings.changed.connect(update)
ctx.filter_changed.connect(__on_filter_changed_external)
button_documentation.pressed.connect(func(): show_documentation.emit())
button_documentation.visible = Engine.is_editor_hint()
button_settings.pressed.connect(settings.popup_centered_ratio_no_fullscreen)
func _shortcut_input(event: InputEvent) -> void:
if not __Shortcuts.should_handle_shortcut(self):
return
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
if not event.is_echo() and event.is_pressed():
if shortcuts.search.matches_event(event):
search_bar.grab_focus()
get_viewport().set_input_as_handled()
elif shortcuts.undo.matches_event(event):
ctx.undo_redo.undo()
get_viewport().set_input_as_handled()
elif shortcuts.redo.matches_event(event):
ctx.undo_redo.redo()
get_viewport().set_input_as_handled()
func _notification(what):
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
if is_instance_valid(search_bar):
search_bar.right_icon = get_theme_icon(&"Search", &"EditorIcons")
if is_instance_valid(button_settings):
button_settings.icon = get_theme_icon(&"Tools", &"EditorIcons")
if is_instance_valid(button_documentation):
button_documentation.icon = get_theme_icon(&"Help", &"EditorIcons")
if is_instance_valid(button_advanced_search):
button_advanced_search.icon = get_theme_icon(&"Zoom", &"EditorIcons")
if is_instance_valid(button_show_categories):
button_show_categories.icon = get_theme_icon(&"Rectangle", &"EditorIcons")
if is_instance_valid(button_show_descriptions):
button_show_descriptions.icon = get_theme_icon(&"Script", &"EditorIcons")
if is_instance_valid(button_show_steps):
button_show_steps.icon = get_theme_icon(&"FileList", &"EditorIcons")
if is_instance_valid(settings):
settings.on_theme_changed()
func update() -> void:
for column in column_holder.get_children():
column.queue_free()
for column_data in board_data.layout.columns:
var column_scroll = ScrollContainer.new()
column_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
column_scroll.set_v_size_flags(Control.SIZE_EXPAND_FILL)
column_scroll.set_h_size_flags(Control.SIZE_EXPAND_FILL)
var column = VBoxContainer.new()
column.set_v_size_flags(Control.SIZE_EXPAND_FILL)
column.set_h_size_flags(Control.SIZE_EXPAND_FILL)
column_scroll.add_child(column)
column_holder.add_child(column_scroll)
for uuid in column_data:
var stage := __StageScene.instantiate()
stage.board_data = board_data
stage.data_uuid = uuid
column.add_child(stage)
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
button_show_categories.set_pressed_no_signal(ctx.settings.show_category_on_board)
button_show_descriptions.set_pressed_no_signal(ctx.settings.show_description_preview)
button_show_steps.set_pressed_no_signal(ctx.settings.show_steps_preview)
# Do not use parameters the method is bound to diffrent signals.
func __on_filter_changed(param1: Variant = null):
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
if ctx.filter_changed.is_connected(__on_filter_changed_external):
ctx.filter_changed.disconnect(__on_filter_changed_external)
ctx.filter = __Filter.new(search_bar.text, button_advanced_search.button_pressed)
ctx.filter_changed.connect(__on_filter_changed_external)
func __on_search_bar_entered(filter: String):
button_advanced_search.grab_focus()
func __on_filter_changed_external():
search_bar.text = ""
func __on_show_categories_toggled(button_pressed: bool):
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
ctx.settings.show_category_on_board = button_pressed
func __on_show_descriptions_toggled(button_pressed: bool):
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
ctx.settings.show_description_preview = button_pressed
func __on_show_steps_toggled(button_pressed: bool):
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
ctx.settings.show_steps_preview = button_pressed
@@ -0,0 +1 @@
uid://5hks8mhjyp62
+89
View File
@@ -0,0 +1,89 @@
[gd_scene load_steps=3 format=3 uid="uid://c5dk4lnyiag3w"]
[ext_resource type="Script" uid="uid://5hks8mhjyp62" path="res://addons/kanban_tasks/view/board/board.gd" id="1_p7lf4"]
[ext_resource type="PackedScene" uid="uid://dh1yunmhipirg" path="res://addons/kanban_tasks/view/settings/settings.tscn" id="2_by8mq"]
[node name="BoardView" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
mouse_filter = 0
theme_override_constants/separation = 5
script = ExtResource("1_p7lf4")
[node name="Header" type="HBoxContainer" parent="."]
layout_mode = 2
theme_override_constants/separation = 5
[node name="SearchBar" type="LineEdit" parent="Header"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Search"
clear_button_enabled = true
[node name="AdvancedSearch" type="Button" parent="Header"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Search in details."
toggle_mode = true
[node name="VSeparator" type="VSeparator" parent="Header"]
layout_mode = 2
tooltip_text = "Show categories"
theme_override_constants/separation = 0
[node name="ShowCategories" type="Button" parent="Header"]
unique_name_in_owner = true
visible = false
layout_mode = 2
toggle_mode = true
[node name="ShowDescriptions" type="Button" parent="Header"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Show descriptions."
toggle_mode = true
[node name="ShowSteps" type="Button" parent="Header"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Show steps."
toggle_mode = true
[node name="VSeparator2" type="VSeparator" parent="Header"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="Documentation" type="Button" parent="Header"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Open documentation."
flat = true
[node name="Settings" type="Button" parent="Header"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Manage board settings."
[node name="ScrollContainer" type="ScrollContainer" parent="."]
layout_mode = 2
size_flags_vertical = 3
mouse_filter = 0
vertical_scroll_mode = 0
[node name="ColumnHolder" type="HBoxContainer" parent="ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 5
alignment = 1
[node name="SettingsView" parent="." instance=ExtResource("2_by8mq")]
unique_name_in_owner = true
visible = false
@@ -0,0 +1,44 @@
@tool
extends PopupMenu
const __BoardData := preload("../../data/board.gd")
var board_data: __BoardData
signal uuid_selected(uuid)
func _init() -> void:
about_to_popup.connect(__update_items_from_board)
id_pressed.connect(__on_id_pressed)
func popup_at_local_position(source: CanvasItem, local_position: Vector2) -> void:
popup_at_global_position(source, source.get_global_transform() * local_position)
func popup_at_global_position(source: CanvasItem, global_position: Vector2) -> void:
position = global_position
if not source.get_window().gui_embed_subwindows:
position += source.get_window().position
popup()
func popup_at_mouse_position(source: CanvasItem) -> void:
popup_at_global_position(source, source.get_global_mouse_position())
func __update_items_from_board() -> void:
clear()
size = Vector2i.ZERO
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)
add_icon_item(t, board_data.get_category(uuid).title)
set_item_metadata(-1, uuid)
func __on_id_pressed(id) -> void:
uuid_selected.emit(get_item_metadata(id))
@@ -0,0 +1 @@
uid://pv2ghsb6w6fn
+225
View File
@@ -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
@@ -0,0 +1 @@
<svg width="100" height="100" version="1.1" viewBox="0 0 26.458 26.458" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round" stroke-linejoin="round"><path d="m1.3229 1.3229h10.583v23.812h-10.583z" fill="#575b64" stroke="#575b64" stroke-width="1.0583"/><path d="m14.552 1.3229h10.583v23.812h-10.583z" fill="#626771" stroke="#626771" stroke-width="1.0583"/><path d="m2.3476 10.695h15.217v3.9785h-15.217z" transform="matrix(.99153-.12989.11858.99294 0 0)" fill="#179ceb" stroke="#179ceb" stroke-width="1.0584"/></g><path d="m16.933 10.848c-.9234.09187-1.7773.46826-2.9104 1.1617 1.1037-.18609 2.0649-.1914 3.4396.16123l-.52917-1.3229" fill="#a4c1d3" fill-opacity=".60851"/><path d="m16.931 10.715c-.016153.000299-.032539.003438-.048059.009819-.04982.02053-.082375.069113-.082165.12299v1.0588c.000615.08981.088821.15306.17415.12506l.22738-.075448.050642.15193a.1825.1825 0 00.23048.11524.1825.1825 0 00.11524-.23048l-.050126-.15141.22118-.073381c.09245-.03185.11961-.14979.050643-.21911l-.79272-.79478c-.02598-.026228-.061098-.03993-.096635-.039274z" fill="#d8d8d8" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://3edv1ymvukp0"
path="res://.godot/imported/1.svg-15461a63252c61d47c72a31629894a26.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/kanban_tasks/view/documentation/1.svg"
dest_files=["res://.godot/imported/1.svg-15461a63252c61d47c72a31629894a26.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
svg/scale=4.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1 @@
<svg width="100" height="100" version="1.1" viewBox="0 0 26.458 26.458" xmlns="http://www.w3.org/2000/svg"><g fill="#575b64" stroke="#575b64" stroke-linejoin="round"><path d="m1.3229 1.3229h10.583v23.812h-10.583z" stroke-width="1.0583"/><path d="m14.552 14.552h10.583v10.583h-10.583z" stroke-width="1.058"/><path d="m14.552 1.3229h10.583v10.583h-10.583z" stroke-width="1.0581"/></g><path d="m1.852 2.9104h9.5251v1.3229h-9.5251z" fill="#179ceb" stroke="#179ceb" stroke-width="1.0581" stroke-linejoin="round"/><path d="m2.1167 3.6049h7.9375" fill="none" stroke="#106da5" stroke-width=".79375" stroke-linecap="round"/><path d="m8.4073 3.5781-.23386-.80682.60804.65481.40926-.42095.10524.63142.42095.22217-.52619.070158-.22217.38587-.26894-.39756-.57296.15201z" fill="#afb7c9" fill-opacity=".84681"/><path d="m8.2585 3.454-.23386-.80682.60804.65481.40926-.42095.10524.63142.42095.22217-.52619.070158-.22217.38587-.26894-.39756-.57296.15201z" fill="#afb7c9" fill-opacity=".65957"/><path d="m8.8615 3.5711c-.016153.000299-.032539.0034-.048059.0098-.04982.02053-.082375.06911-.082165.12299v1.0588c.000615.08981.088821.15306.17415.12506l.22738-.07545.050642.15193a.1825.1825 0 00.23048.11524.1825.1825 0 00.11524-.23048l-.050126-.15141.22118-.07338c.09245-.03185.11961-.14979.050643-.21911l-.79272-.79478c-.02598-.02623-.061098-.03993-.096635-.03927z" fill="#d8d8d8"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bbo1gfac2wymg"
path="res://.godot/imported/2.svg-053c5d997067871191f4000b81461e5d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/kanban_tasks/view/documentation/2.svg"
dest_files=["res://.godot/imported/2.svg-053c5d997067871191f4000b81461e5d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
svg/scale=4.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1 @@
<svg width="100" height="100" version="1.1" viewBox="0 0 26.458 26.458" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round" stroke-linejoin="round"><path d="m7.2099 4.0349h13.262v18.058h-13.262z" fill="#575b64" stroke="#575b64" stroke-width="1.0583"/><path d="m9.2603 22.49h9.5251v1.3229h-9.5251z" fill="#179ceb" stroke="#179ceb" stroke-width="1.0581"/><path d="m9.525 23.184h7.9375" fill="none" stroke="#106da5" stroke-width=".79375"/></g><circle cx="18.654" cy="23.184" r=".39464" fill="#106da5"/><path d="m18.362 23.157-.23386-.80682.60804.65481.40926-.42095.10524.63142.42095.22217-.52619.07016-.22217.38587-.26894-.39756-.57296.15201z" fill="#afb7c9" fill-opacity=".84681"/><path d="m18.816 23.15c-.01615.000299-.03254.0034-.04806.0098-.04982.02053-.08237.06911-.08216.12299v1.0588c.000615.08981.08882.15306.17415.12506l.22738-.07545.05064.15193a.1825.1825 0 00.23048.11524.1825.1825 0 00.11524-.23048l-.05013-.15141.22118-.07338c.09245-.03185.11961-.14979.05064-.21911l-.79271-.79478c-.02598-.02623-.0611-.03993-.09664-.03927z" fill="#d8d8d8"/><g fill="#646973" stroke="#646973" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.0583"><path d="m7.6729 4.653h12.336v1.1451h-12.336z"/><path d="m7.6729 7.828h8.2021v12.688h-8.2021z"/><path d="m17.793 7.828h2.2159v12.688h-2.2159z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bb8knc6dctfj1"
path="res://.godot/imported/3.svg-456dac6515246348acd4893e98f4bdc7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/kanban_tasks/view/documentation/3.svg"
dest_files=["res://.godot/imported/3.svg-456dac6515246348acd4893e98f4bdc7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
svg/scale=4.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1 @@
<svg width="100" height="100" version="1.1" viewBox="0 0 26.458 26.458" xmlns="http://www.w3.org/2000/svg"><path transform="translate(5.3909 6.057)" d="m7.8383-.63474c.61268 0 2.2256 4.4997 2.7212 4.8598.49567.36013 5.2736.50359 5.4629 1.0863.18933.5827-3.5917 3.5071-3.7811 4.0898-.18933.5827 1.1507 5.1711.65501 5.5312-.49567.36013-4.4454-2.3322-5.0581-2.3322s-4.5624 2.6923-5.0581 2.3322c-.49567-.36013.84434-4.9485.65501-5.5312-.18933-.5827-3.9704-3.5071-3.7811-4.0898.18933-.5827 4.9672-.72616 5.4629-1.0863.49567-.36013 2.1086-4.8598 2.7212-4.8598z" fill="#d0c268" stroke="#d0c268" stroke-width=".79375"/></svg>

After

Width:  |  Height:  |  Size: 617 B

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://phpa3kr3tjwu"
path="res://.godot/imported/4.svg-963688afa901818a04233dc49468c7c7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/kanban_tasks/view/documentation/4.svg"
dest_files=["res://.godot/imported/4.svg-963688afa901818a04233dc49468c7c7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
svg/scale=4.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1,21 @@
@tool
extends AcceptDialog
@onready var _shameless_plug: RichTextLabel = %ShamelessPlug
const EDITOR_ONLY_PLACEHOLDER := "$EDITORONLY$"
func _ready() -> void:
_shameless_plug.meta_clicked.connect(_on_shameless_plug_meta_clicked)
if not Engine.is_editor_hint():
_shameless_plug.text = _shameless_plug.text.substr(0, _shameless_plug.text.find(EDITOR_ONLY_PLACEHOLDER))
else:
_shameless_plug.text = _shameless_plug.text.replace(EDITOR_ONLY_PLACEHOLDER, "")
func _on_shameless_plug_meta_clicked(meta: Variant) -> void:
# Open clicked URLs.
OS.shell_open(str(meta))
@@ -0,0 +1 @@
uid://c6ccwvv8tjdsk
@@ -0,0 +1,125 @@
[gd_scene format=3 uid="uid://cwfixtyy5lpin"]
[ext_resource type="Script" uid="uid://c6ccwvv8tjdsk" path="res://addons/kanban_tasks/view/documentation/documentation.gd" id="1_fw3gy"]
[ext_resource type="Texture2D" uid="uid://3edv1ymvukp0" path="res://addons/kanban_tasks/view/documentation/1.svg" id="1_inmy7"]
[ext_resource type="Texture2D" uid="uid://bbo1gfac2wymg" path="res://addons/kanban_tasks/view/documentation/2.svg" id="2_1g6ul"]
[ext_resource type="Texture2D" uid="uid://bb8knc6dctfj1" path="res://addons/kanban_tasks/view/documentation/3.svg" id="3_e3hha"]
[sub_resource type="LabelSettings" id="LabelSettings_cmnk1"]
font_size = 20
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_cmvuv"]
[node name="AcceptDialog" type="AcceptDialog" unique_id=1501486445]
oversampling_override = 1.0
title = "Documentation"
size = Vector2i(1000, 600)
min_size = Vector2i(1000, 600)
theme_type_variation = &"EditorSettingsDialog"
ok_button_text = "Close"
script = ExtResource("1_fw3gy")
[node name="Help" type="ScrollContainer" parent="." unique_id=535755392]
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
horizontal_scroll_mode = 0
[node name="VBoxContainer" type="VBoxContainer" parent="Help" unique_id=1359404301]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 10
[node name="PanelContainer1" type="PanelContainer" parent="Help/VBoxContainer" unique_id=983778674]
layout_mode = 2
theme_type_variation = &"Panel"
[node name="HBoxContainer1" type="HBoxContainer" parent="Help/VBoxContainer/PanelContainer1" unique_id=861006902]
layout_mode = 2
theme_override_constants/separation = 50
alignment = 2
[node name="Control" type="Control" parent="Help/VBoxContainer/PanelContainer1/HBoxContainer1" unique_id=1543101361]
layout_mode = 2
[node name="Label" type="Label" parent="Help/VBoxContainer/PanelContainer1/HBoxContainer1" unique_id=1613673530]
layout_mode = 2
size_flags_horizontal = 3
text = "A kanban board helps you to organise tasks. After you created a task you can drag and drop it between the stages to reflect its current status. Add spontaneous ideas to the \"Todo\" stage. Move them into \"Doing\" when you are ready to tackle them. Once a task is done move it into \"Done\" to keep track of all your accomplishments."
label_settings = SubResource("LabelSettings_cmnk1")
autowrap_mode = 2
[node name="TextureRect" type="TextureRect" parent="Help/VBoxContainer/PanelContainer1/HBoxContainer1" unique_id=892191990]
custom_minimum_size = Vector2(200, 200)
layout_mode = 2
texture = ExtResource("1_inmy7")
expand_mode = 1
[node name="PanelContainer2" type="PanelContainer" parent="Help/VBoxContainer" unique_id=90515732]
layout_mode = 2
theme_type_variation = &"Panel"
[node name="HBoxContainer2" type="HBoxContainer" parent="Help/VBoxContainer/PanelContainer2" unique_id=1366977175]
layout_mode = 2
theme_override_constants/separation = 50
alignment = 2
[node name="TextureRect" type="TextureRect" parent="Help/VBoxContainer/PanelContainer2/HBoxContainer2" unique_id=698644339]
custom_minimum_size = Vector2(200, 200)
layout_mode = 2
texture = ExtResource("2_1g6ul")
expand_mode = 1
[node name="Label" type="Label" parent="Help/VBoxContainer/PanelContainer2/HBoxContainer2" unique_id=1865995285]
layout_mode = 2
size_flags_horizontal = 3
text = "Boost your productivity by customizing your board!
Double click stage or task names to change them. Configure categories and change the layout in the settings.
Find tasks by using the search bar."
label_settings = SubResource("LabelSettings_cmnk1")
autowrap_mode = 2
[node name="Control" type="Control" parent="Help/VBoxContainer/PanelContainer2/HBoxContainer2" unique_id=2057758449]
layout_mode = 2
[node name="PanelContainer3" type="PanelContainer" parent="Help/VBoxContainer" unique_id=792363036]
layout_mode = 2
theme_type_variation = &"Panel"
[node name="HBoxContainer3" type="HBoxContainer" parent="Help/VBoxContainer/PanelContainer3" unique_id=242955631]
layout_mode = 2
theme_override_constants/separation = 50
alignment = 2
[node name="Control" type="Control" parent="Help/VBoxContainer/PanelContainer3/HBoxContainer3" unique_id=1118352302]
layout_mode = 2
[node name="Label" type="Label" parent="Help/VBoxContainer/PanelContainer3/HBoxContainer3" unique_id=978285368]
layout_mode = 2
size_flags_horizontal = 3
text = "Edit the details of you tasks by clicking the edit button. Give your task a meaningful title and put the details into the description. Give your task a category to keep the overview."
label_settings = SubResource("LabelSettings_cmnk1")
autowrap_mode = 2
[node name="TextureRect" type="TextureRect" parent="Help/VBoxContainer/PanelContainer3/HBoxContainer3" unique_id=125050301]
custom_minimum_size = Vector2(200, 200)
layout_mode = 2
texture = ExtResource("3_e3hha")
expand_mode = 1
[node name="ShamelessPlug" type="RichTextLabel" parent="Help/VBoxContainer" unique_id=2067203823]
unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/normal_font_size = 17
theme_override_styles/normal = SubResource("StyleBoxEmpty_cmvuv")
bbcode_enabled = true
text = "[center][img height=2em]uid://phpa3kr3tjwu[/img] Leave a star on [url=https://github.com/HolonProduction/godot_kanban_tasks]Github[/url] or a like on the [url=https://store.godotengine.org/asset/holonproduction/kanban-tasks/]Asset Store[/url][/center]"
fit_content = true
scroll_active = false
shortcut_keys_enabled = false
+29
View File
@@ -0,0 +1,29 @@
@tool
extends Node
## Global stuff for the view system.
const __Filter := preload("filter.gd")
const __SettingData := preload("../data/settings.gd")
signal filter_changed()
signal save_board()
signal create_board()
signal reload_board(discard_changes: bool)
## The currently active filter.
var filter: __Filter = null:
set(value):
filter = value
filter_changed.emit()
## The undo redo for task operations.
var undo_redo := UndoRedo.new()
## uuid of the object that should have focus. This is used to persist focus
## when updating some views.
var focus: String = ""
## Settings that are not tied to the board.
var settings := __SettingData.new()
@@ -0,0 +1 @@
uid://3e38f0mlpigu
+14
View File
@@ -0,0 +1,14 @@
@tool
extends RefCounted
## A filter configuration for searching tasks.
var text: String
## Whether to search in descriptions.
var advanced: bool
func _init(p_text: String = "", p_advanced: bool = false) -> void:
text = p_text
advanced = p_advanced
+1
View File
@@ -0,0 +1 @@
uid://b8vdc7jbb0ocg
@@ -0,0 +1,55 @@
@tool
extends VBoxContainer
const __BoardData := preload("../../../data/board.gd")
const __CategoryEntry := preload("../../settings/categories/category_entry.gd")
const __CategoryData := preload("../../../data/category.gd")
const __EditLabel := preload("../../../edit_label/edit_label.gd")
var board_data: __BoardData
var randomizer := RandomNumberGenerator.new()
@onready var category_holder: VBoxContainer = %CategoryHolder
@onready var scroll_container: ScrollContainer = %ScrollContainer
@onready var add_category_button: Button = %Add
@onready var panel_container: PanelContainer = %PanelContainer
func _ready() -> void:
notification(NOTIFICATION_THEME_CHANGED)
randomizer.randomize()
add_category_button.pressed.connect(__add_category)
func _notification(what: int) -> void:
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
if is_instance_valid(add_category_button):
add_category_button.icon = get_theme_icon(&"Add", &"EditorIcons")
if is_instance_valid(panel_container):
panel_container.add_theme_stylebox_override(&"panel", get_theme_stylebox(&"panel", &"Panel"))
func update() -> void:
for category in category_holder.get_children():
category.queue_free()
for uuid in board_data.get_categories():
var entry := __CategoryEntry.new()
entry.board_data = board_data
entry.data_uuid = uuid
category_holder.add_child(entry)
func __add_category() -> void:
var color = Color.from_hsv(randomizer.randf(), randomizer.randf_range(0.8, 1.0), randomizer.randf_range(0.7, 1.0))
var data = __CategoryData.new("New category", color)
var uuid = board_data.add_category(data)
update()
for i in category_holder.get_children():
if i.data_uuid == uuid:
await get_tree().create_timer(0.0).timeout
i.grab_focus()
i.show_edit(__EditLabel.INTENTION.REPLACE)
@@ -0,0 +1 @@
uid://brn3wr8cbs41k
@@ -0,0 +1,40 @@
[gd_scene load_steps=2 format=3 uid="uid://b2likgss81t0s"]
[ext_resource type="Script" uid="uid://brn3wr8cbs41k" path="res://addons/kanban_tasks/view/settings/categories/categories.gd" id="1_n36ev"]
[node name="Categories" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
script = ExtResource("1_n36ev")
[node name="Header" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="\'Available Categories\'" type="Label" parent="Header"]
layout_mode = 2
size_flags_horizontal = 3
text = "Available Categories:"
[node name="Add" type="Button" parent="Header"]
unique_name_in_owner = true
layout_mode = 2
[node name="PanelContainer" type="PanelContainer" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
follow_focus = true
[node name="CategoryHolder" type="VBoxContainer" parent="PanelContainer/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
@@ -0,0 +1,94 @@
@tool
extends HBoxContainer
## Visual representation of a category.
const __EditLabel := preload("../../../edit_label/edit_label.gd")
const __BoardData := preload("../../../data/board.gd")
const __Singletons := preload("../../../plugin_singleton/singletons.gd")
const __Shortcuts := preload("../../shortcuts.gd")
var title: __EditLabel
var delete: Button
var color_picker: ColorPickerButton
var focus_box: StyleBoxFlat
var board_data: __BoardData
var data_uuid: String
func _ready() -> void:
set_h_size_flags(SIZE_EXPAND_FILL)
focus_mode = FOCUS_ALL
title = __EditLabel.new()
title.set_h_size_flags(SIZE_EXPAND_FILL)
title.text = board_data.get_category(data_uuid).title
title.text_changed.connect(__on_title_changed)
add_child(title)
color_picker = ColorPickerButton.new()
color_picker.custom_minimum_size.x = 100
color_picker.edit_alpha = false
color_picker.color = board_data.get_category(data_uuid).color
color_picker.focus_mode = Control.FOCUS_NONE
color_picker.flat = true
color_picker.popup_closed.connect(__on_color_changed)
add_child(color_picker)
delete = Button.new()
delete.focus_mode = FOCUS_NONE
delete.flat = true
delete.disabled = board_data.get_category_count() <= 1
delete.pressed.connect(__on_delete)
add_child(delete)
focus_box = StyleBoxFlat.new()
focus_box.bg_color = Color(1, 1, 1, 0.1)
notification(NOTIFICATION_THEME_CHANGED)
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()
title.show_edit()
func _notification(what) -> void:
match(what):
NOTIFICATION_THEME_CHANGED when not is_part_of_edited_scene():
if is_instance_valid(delete):
delete.icon = get_theme_icon(&"Remove", &"EditorIcons")
NOTIFICATION_DRAW:
if has_focus():
focus_box.draw(get_canvas_item(), Rect2(Vector2.ZERO, get_rect().size))
func show_edit(intention: int = title.default_intention) -> void:
title.show_edit(intention)
func __on_title_changed(new: String) -> void:
board_data.get_category(data_uuid).title = new
func __on_color_changed() -> void:
board_data.get_category(data_uuid).color = color_picker.color
# Hack to get the tasks to update their color.
board_data.layout.changed.emit()
func __on_delete() -> void:
board_data.remove_category(data_uuid)
var fallback_to = board_data.get_categories()[0]
for uuid in board_data.get_tasks():
if board_data.get_task(uuid).category == data_uuid:
board_data.get_task(uuid).category = fallback_to
get_parent().get_owner().update()
@@ -0,0 +1 @@
uid://dlf6mdc2al4qm
@@ -0,0 +1,150 @@
@tool
extends VBoxContainer
const __Singletons := preload("../../../plugin_singleton/singletons.gd")
const __EditContext := preload("../../edit_context.gd")
const __SettingData := preload("../../../data/settings.gd")
var data: __SettingData = null
var file_dialog_open_option: CheckBox
var file_dialog_save_option: CheckBox
var file_dialog_create_option: CheckBox
var file_dialog_option_button_group: ButtonGroup
@onready var show_description_preview: CheckBox = %ShowDescriptionPreview
@onready var show_steps_preview: CheckBox = %ShowStepsPreview
@onready var show_category_on_board: CheckBox = %ShowCategoriesOnBoard
@onready var edit_step_details_exclusively: CheckBox = %EditStepDetailsExclusively
@onready var max_displayed_lines_in_description: SpinBox = %MaxDisplayedLinesInDescription
@onready var description_on_board: OptionButton = %DescriptionOnBoard
# Keep IDs of the items of StepsOnBoard in sync with the values of setting.gd/StepsOnBoard
@onready var steps_on_board: OptionButton = %StepsOnBoard
@onready var max_steps_on_board: SpinBox = %MaxStepsOnBoard
@onready var data_file_path_label: Control = %DataFilePathLabel
@onready var data_file_path_container: Control = %DataFilePathContainer
@onready var data_file_path: LineEdit = %DataFilePath
@onready var data_file_path_button: Button = %DataFilePathButton
@onready var file_dialog: FileDialog = %FileDialog
@onready var panel_container: PanelContainer = %PanelContainer
func _ready() -> void:
await get_tree().create_timer(0.0).timeout
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
data = ctx.settings
data.changed.connect(update)
update()
show_description_preview.toggled.connect(func(x): __apply_changes())
show_steps_preview.toggled.connect(func(x): __apply_changes())
show_category_on_board.toggled.connect(func(x): __apply_changes())
edit_step_details_exclusively.toggled.connect(func(x): __apply_changes())
max_displayed_lines_in_description.value_changed.connect(func(x): __apply_changes())
description_on_board.item_selected.connect(func(x): __apply_changes())
steps_on_board.item_selected.connect(func(x): __apply_changes())
max_steps_on_board.value_changed.connect(func(x): __apply_changes())
if not Engine.is_editor_hint():
data_file_path_label.visible = false
data_file_path_container.visible = false
data_file_path_button.pressed.connect(__open_data_file_path_dialog)
file_dialog_open_option = CheckBox.new()
file_dialog_open_option.text = "Open board from existing file"
file_dialog.get_vbox().add_child(file_dialog_open_option)
file_dialog_save_option = CheckBox.new()
file_dialog_save_option.text = "Save current board to file"
file_dialog.get_vbox().add_child(file_dialog_save_option)
file_dialog_create_option = CheckBox.new()
file_dialog_create_option.text = "Create new board in file"
file_dialog.get_vbox().add_child(file_dialog_create_option)
file_dialog_option_button_group = ButtonGroup.new()
file_dialog_open_option.button_group = file_dialog_option_button_group
file_dialog_save_option.button_group = file_dialog_option_button_group
file_dialog_create_option.button_group = file_dialog_option_button_group
file_dialog_option_button_group.pressed.connect(func (button): __update_file_dialog())
file_dialog.get_line_edit().text_changed.connect(func (new_text): __update_file_dialog())
file_dialog_open_option.button_pressed = true
file_dialog.file_selected.connect(__update_editor_data_file)
notification(NOTIFICATION_THEME_CHANGED)
func _notification(what: int) -> void:
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
if is_instance_valid(panel_container):
panel_container.add_theme_stylebox_override(&"panel", get_theme_stylebox(&"panel", &"Panel"))
func update() -> void:
show_description_preview.button_pressed = data.show_description_preview
show_steps_preview.button_pressed = data.show_steps_preview
show_category_on_board.button_pressed = data.show_category_on_board
edit_step_details_exclusively.button_pressed = data.edit_step_details_exclusively
max_displayed_lines_in_description.value = data.max_displayed_lines_in_description
max_steps_on_board.value = data.max_steps_on_board
description_on_board.select(description_on_board.get_item_index(data.description_on_board))
steps_on_board.select(steps_on_board.get_item_index(data.steps_on_board))
data_file_path.text = data.editor_data_file_path
func __open_data_file_path_dialog() -> void:
file_dialog_open_option.set_pressed_no_signal(true)
file_dialog_save_option.set_pressed_no_signal(false)
file_dialog_create_option.set_pressed_no_signal(false)
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
file_dialog.clear_filters()
file_dialog.add_filter("*.kanban, *.json", "Kanban Board")
file_dialog.popup_centered(file_dialog.size)
func __update_file_dialog() -> void:
if file_dialog_save_option.button_pressed:
file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
file_dialog.title = file_dialog_save_option.text
file_dialog.ok_button_text = "Save"
elif file_dialog_create_option.button_pressed:
file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
file_dialog.title = file_dialog_create_option.text
file_dialog.ok_button_text = "Create"
else:
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
file_dialog.title = file_dialog_open_option.text
file_dialog.ok_button_text = "Open"
func __update_editor_data_file(path: String) -> void:
data_file_path.text = path
__apply_changes()
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
if file_dialog_save_option.button_pressed:
ctx.save_board.emit()
elif file_dialog_create_option.button_pressed:
ctx.create_board.emit()
else:
ctx.reload_board.emit(false)
func __apply_changes() -> void:
if data.changed.is_connected(update):
data.changed.disconnect(update)
data.__emit_changed = false
data.show_description_preview = show_description_preview.button_pressed
data.show_steps_preview = show_steps_preview.button_pressed
data.show_category_on_board = show_category_on_board.button_pressed
data.edit_step_details_exclusively = edit_step_details_exclusively.button_pressed
data.max_displayed_lines_in_description = max_displayed_lines_in_description.value
data.description_on_board = description_on_board.get_selected_id()
data.steps_on_board = steps_on_board.get_selected_id()
data.max_steps_on_board = max_steps_on_board.value
data.editor_data_file_path = data_file_path.text
data.__emit_changed = true
data.__notify_changed()
data.changed.connect(update)
@@ -0,0 +1 @@
uid://nnqalkr7jnvx
@@ -0,0 +1,157 @@
[gd_scene load_steps=2 format=3 uid="uid://due07vdflx4o"]
[ext_resource type="Script" uid="uid://nnqalkr7jnvx" path="res://addons/kanban_tasks/view/settings/general/general.gd" id="1_8tblh"]
[node name="General" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_8tblh")
[node name="PanelContainer" type="PanelContainer" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer"]
layout_mode = 2
[node name="GridContainer" type="GridContainer" parent="PanelContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 2
[node name="ShowDescriptionPreviewLabel" type="Label" parent="PanelContainer/ScrollContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Show description preview"
[node name="ShowDescriptionPreview" type="CheckBox" parent="PanelContainer/ScrollContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
text = "On"
[node name="ShowStepsPreviewLabel" type="Label" parent="PanelContainer/ScrollContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Show steps preview"
[node name="ShowStepsPreview" type="CheckBox" parent="PanelContainer/ScrollContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
text = "On"
[node name="ShowCategoriesOnBoardLabel" type="Label" parent="PanelContainer/ScrollContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Show categories on board"
[node name="ShowCategoriesOnBoard" type="CheckBox" parent="PanelContainer/ScrollContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
text = "On"
[node name="EditStepDetailsExclusivelyLabel" type="Label" parent="PanelContainer/ScrollContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Edit step details in fullscreen"
[node name="EditStepDetailsExclusively" type="CheckBox" parent="PanelContainer/ScrollContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
text = "On"
[node name="DescriptionOnBoardLabel" type="Label" parent="PanelContainer/ScrollContainer/GridContainer"]
layout_mode = 2
text = "Description on board"
[node name="DescriptionOnBoard" type="OptionButton" parent="PanelContainer/ScrollContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
selected = 1
item_count = 3
popup/item_0/text = "Full description"
popup/item_0/id = 0
popup/item_1/text = "First line of description"
popup/item_1/id = 1
popup/item_2/text = "Until first blank line of description"
popup/item_2/id = 2
[node name="MaxDisplayedLinesInDescriptionLabel" type="Label" parent="PanelContainer/ScrollContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Maximum displayed lines in description"
[node name="MaxDisplayedLinesInDescription" type="SpinBox" parent="PanelContainer/ScrollContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
allow_greater = true
[node name="StepsOnBoardLabel" type="Label" parent="PanelContainer/ScrollContainer/GridContainer"]
layout_mode = 2
text = "Steps on board"
[node name="StepsOnBoard" type="OptionButton" parent="PanelContainer/ScrollContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
selected = 0
item_count = 3
popup/item_0/text = "Only open steps"
popup/item_0/id = 0
popup/item_1/text = "All, but open first"
popup/item_1/id = 1
popup/item_2/text = "All in original order"
popup/item_2/id = 2
[node name="MaxStepsOnBoardLabel" type="Label" parent="PanelContainer/ScrollContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Maximum steps on board"
[node name="MaxStepsOnBoard" type="SpinBox" parent="PanelContainer/ScrollContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
value = 2.0
allow_greater = true
[node name="DataFilePathLabel" type="Label" parent="PanelContainer/ScrollContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Data file path"
[node name="DataFilePathContainer" type="HBoxContainer" parent="PanelContainer/ScrollContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
[node name="DataFilePath" type="LineEdit" parent="PanelContainer/ScrollContainer/GridContainer/DataFilePathContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "res://kanban_tasks_data.kanban"
editable = false
[node name="DataFilePathButton" type="Button" parent="PanelContainer/ScrollContainer/GridContainer/DataFilePathContainer"]
unique_name_in_owner = true
layout_mode = 2
text = " ... "
[node name="FileDialog" type="FileDialog" parent="."]
unique_name_in_owner = true
title = "Open board from existing file"
size = Vector2i(800, 600)
ok_button_text = "Open"
mode_overrides_title = false
file_mode = 0
@@ -0,0 +1,34 @@
@tool
extends AcceptDialog
const __BoardData := preload("../../data/board.gd")
const __CategoriesScene := preload("../settings/categories/categories.tscn")
const __CategoriesScript := preload("../settings/categories/categories.gd")
@onready var category_settings: __CategoriesScript = %Categories
@onready var stage_settings = %Stages
@onready var tab_container: TabContainer = %Settings
var board_data: __BoardData
func _ready() -> void:
# Wait for board to set board_data.
await get_tree().create_timer(0.0).timeout
category_settings.board_data = board_data
stage_settings.board_data = board_data
about_to_popup.connect(stage_settings.update)
about_to_popup.connect(category_settings.update)
# 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 on_theme_changed():
# Called from parent since changing this during this nodes theme change notification will create infinite recursion.
add_theme_stylebox_override(&"panel", get_theme_stylebox(&"panel", &"EditorSettingsDialog"))
@@ -0,0 +1 @@
uid://7jskmn63x3pt
@@ -0,0 +1,46 @@
[gd_scene format=3 uid="uid://dh1yunmhipirg"]
[ext_resource type="Script" uid="uid://7jskmn63x3pt" path="res://addons/kanban_tasks/view/settings/settings.gd" id="1_4eaw3"]
[ext_resource type="PackedScene" uid="uid://due07vdflx4o" path="res://addons/kanban_tasks/view/settings/general/general.tscn" id="1_dk7pg"]
[ext_resource type="PackedScene" uid="uid://b2likgss81t0s" path="res://addons/kanban_tasks/view/settings/categories/categories.tscn" id="3_iycb0"]
[ext_resource type="PackedScene" uid="uid://dapkpnkm8sow8" path="res://addons/kanban_tasks/view/settings/stages/stages.tscn" id="4_okolg"]
[node name="Settings" type="AcceptDialog" unique_id=650180224]
oversampling_override = 1.0
title = "Settings"
position = Vector2i(0, 36)
size = Vector2i(800, 400)
visible = true
script = ExtResource("1_4eaw3")
[node name="Settings" type="TabContainer" parent="." unique_id=690004289]
unique_name_in_owner = true
custom_minimum_size = Vector2(600, 300)
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
theme_type_variation = &"TabContainerOdd"
current_tab = 0
[node name="General" parent="Settings" unique_id=2109241999 instance=ExtResource("1_dk7pg")]
unique_name_in_owner = true
layout_mode = 2
metadata/_tab_index = 0
[node name="Categories" parent="Settings" unique_id=1569239372 instance=ExtResource("3_iycb0")]
unique_name_in_owner = true
visible = false
layout_mode = 2
metadata/_tab_index = 1
[node name="Stages" parent="Settings" unique_id=152864210 instance=ExtResource("4_okolg")]
unique_name_in_owner = true
visible = false
layout_mode = 2
metadata/_tab_index = 2
@@ -0,0 +1,205 @@
@tool
extends VBoxContainer
const __Singletons := preload("../../../plugin_singleton/singletons.gd")
const __EditContext := preload("../../edit_context.gd")
const __BoardData = preload("../../../data/board.gd")
const __StageData = preload("../../../data/stage.gd")
var board_data: __BoardData
var stylebox_n: StyleBoxFlat
var stylebox_hp: StyleBoxFlat
@onready var column_holder: HBoxContainer = %ColumnHolder
@onready var column_add: Button = %AddColumn
@onready var warning_sign: Button = %WarningSign
@onready var warn_about_empty_deletion: CheckBox = %WarnAboutEmptyDeletion
@onready var width_spin_box: SpinBox = %WidthSpinBox
@onready var confirm_not_empty: ConfirmationDialog = %ConfirmNotEmpty
@onready var confirm_empty: ConfirmationDialog = %ConfirmEmpty
@onready var task_destination: OptionButton = %TaskDestination
@onready var panel_container: PanelContainer = %PanelContainer
func _ready() -> void:
column_add.focus_mode = Control.FOCUS_NONE
column_add.pressed.connect(__on_add_stage.bind(-1))
var center_container := CenterContainer.new()
center_container.set_anchors_preset(Control.PRESET_FULL_RECT)
center_container.mouse_filter = Control.MOUSE_FILTER_IGNORE
column_add.add_child(center_container)
var plus := TextureRect.new()
plus.mouse_filter = Control.MOUSE_FILTER_IGNORE
center_container.add_child(plus)
notification(NOTIFICATION_THEME_CHANGED)
await get_tree().create_timer(0.0).timeout
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
ctx.settings.changed.connect(__settings_changed)
warn_about_empty_deletion.toggled.connect(__apply_settings_changes)
width_spin_box.value_changed.connect(__apply_settings_changes)
func _notification(what) -> void:
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
stylebox_n = get_theme_stylebox(&"normal", &"Button").duplicate()
stylebox_n.set_border_width_all(1)
stylebox_n.border_color = Color8(32, 32, 32, 255)
stylebox_hp = get_theme_stylebox(&"read_only", &"LineEdit").duplicate()
stylebox_hp.set_border_width_all(1)
stylebox_hp.border_color = Color8(32, 32, 32, 128)
if is_instance_valid(column_add):
column_add.get_child(0).get_child(0).texture = get_theme_icon(&"Add", &"EditorIcons")
column_add.add_theme_stylebox_override(&"normal", stylebox_n)
column_add.add_theme_stylebox_override(&"hover", stylebox_hp)
column_add.add_theme_stylebox_override(&"pressed", stylebox_hp)
if is_instance_valid(warning_sign):
warning_sign.icon = get_theme_icon(&"NodeWarning", &"EditorIcons")
if is_instance_valid(panel_container):
panel_container.add_theme_stylebox_override(&"panel", get_theme_stylebox(&"panel", &"Panel"))
func update() -> void:
if not board_data.layout.changed.is_connected(update):
board_data.layout.changed.connect(update)
var too_high = false
for column in board_data.layout.columns:
if len(column) > 3:
too_high = true
warning_sign.visible = too_high or len(board_data.layout.columns) > 4
for child in column_holder.get_children():
child.queue_free()
var index = 0
for column in board_data.layout.columns:
var column_entry := VBoxContainer.new()
column_entry.add_theme_constant_override(&"separation", 5)
column_holder.add_child(column_entry)
for stage in column:
var stage_entry := Button.new()
stage_entry.tooltip_text = board_data.get_stage(stage).title
stage_entry.focus_mode = Control.FOCUS_NONE
stage_entry.set_v_size_flags(SIZE_EXPAND_FILL)
stage_entry.custom_minimum_size = Vector2i(70, 50)
stage_entry.add_theme_stylebox_override(&"normal", stylebox_n)
stage_entry.add_theme_stylebox_override(&"hover", stylebox_hp)
stage_entry.add_theme_stylebox_override(&"pressed", stylebox_hp)
stage_entry.add_theme_stylebox_override(&"disabled", stylebox_hp)
stage_entry.pressed.connect(__on_remove_stage.bind(stage))
stage_entry.disabled = len(board_data.layout.columns) <= 1 and len(board_data.layout.columns[0]) <= 1
column_entry.add_child(stage_entry)
var center_container := CenterContainer.new()
center_container.set_anchors_preset(Control.PRESET_FULL_RECT)
center_container.mouse_filter = Control.MOUSE_FILTER_IGNORE
stage_entry.add_child(center_container)
var remove := TextureRect.new()
remove.mouse_filter = Control.MOUSE_FILTER_IGNORE
remove.texture = get_theme_icon(&"Remove", &"EditorIcons")
center_container.add_child(remove)
var add = Button.new()
add.custom_minimum_size = Vector2i(70, 40)
add.focus_mode = Control.FOCUS_NONE
add.pressed.connect(__on_add_stage.bind(index))
add.add_theme_stylebox_override(&"normal", stylebox_n)
add.add_theme_stylebox_override(&"hover", stylebox_hp)
add.add_theme_stylebox_override(&"pressed", stylebox_hp)
column_entry.add_child(add)
var center_container = CenterContainer.new()
center_container.set_anchors_preset(Control.PRESET_FULL_RECT)
center_container.mouse_filter = Control.MOUSE_FILTER_IGNORE
add.add_child(center_container)
var plus := TextureRect.new()
plus.mouse_filter = Control.MOUSE_FILTER_IGNORE
center_container.add_child(plus)
plus.texture = get_theme_icon(&"Add", &"EditorIcons")
index += 1
func __on_add_stage(column: int) -> void:
var data = __StageData.new("New Stage")
var uuid = board_data.add_stage(data, true)
var columns = board_data.layout.columns
if column < len(board_data.layout.columns) and column >= 0:
columns[column].append(uuid)
else:
columns.append(PackedStringArray([uuid]))
board_data.layout.columns = columns
func __on_remove_stage(uuid: String) -> void:
if len(board_data.get_stage(uuid).tasks) == 0:
if warn_about_empty_deletion.button_pressed:
if confirm_empty.confirmed.is_connected(__remove_stage):
confirm_empty.confirmed.disconnect(__remove_stage)
confirm_empty.confirmed.connect(__remove_stage.bind(uuid))
confirm_empty.popup_centered()
else:
__remove_stage(uuid)
else:
__update_task_destination(uuid)
if confirm_not_empty.confirmed.is_connected(__remove_stage):
confirm_not_empty.confirmed.disconnect(__remove_stage)
confirm_not_empty.confirmed.connect(__remove_stage.bind(uuid))
confirm_not_empty.popup_centered()
func __update_task_destination(uuid: String) -> void:
task_destination.clear()
for stage in board_data.get_stages():
if stage != uuid:
task_destination.add_item(board_data.get_stage(stage).title)
task_destination.set_item_metadata(-1, stage)
func __remove_stage(uuid: String) -> void:
if len(board_data.get_stage(uuid).tasks) > 0:
var old_tasks = board_data.get_stage(uuid).tasks
var new_tasks = board_data.get_stage(task_destination.get_selected_metadata()).tasks
for task in old_tasks.duplicate():
old_tasks.erase(task)
new_tasks.append(task)
board_data.get_stage(uuid).tasks = old_tasks
board_data.get_stage(task_destination.get_selected_metadata()).tasks = new_tasks
board_data.remove_stage(uuid, true)
var columns = board_data.layout.columns
for column in columns.duplicate():
if uuid in column:
column.remove_at(column.find(uuid))
if len(column) == 0:
columns.erase(column)
board_data.layout.columns = columns
func __settings_changed() -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
warn_about_empty_deletion.button_pressed = ctx.settings.warn_about_empty_deletion
width_spin_box.value = ctx.settings.stages_width
func __apply_settings_changes(warn: bool) -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
ctx.settings.changed.disconnect(__settings_changed)
ctx.settings.warn_about_empty_deletion = warn
ctx.settings.stages_width = int(width_spin_box.value)
ctx.settings.changed.connect(__settings_changed)
@@ -0,0 +1 @@
uid://kwwmdo325o42
@@ -0,0 +1,138 @@
[gd_scene format=3 uid="uid://dapkpnkm8sow8"]
[ext_resource type="Script" uid="uid://kwwmdo325o42" path="res://addons/kanban_tasks/view/settings/stages/stages.gd" id="1_1yycq"]
[node name="Stages" type="VBoxContainer" unique_id=730317252]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_1yycq")
[node name="Header" type="HBoxContainer" parent="." unique_id=560617268]
layout_mode = 2
theme_override_constants/separation = 10
[node name="\'EditStageLayout\'" type="Label" parent="Header" unique_id=348522360]
layout_mode = 2
size_flags_horizontal = 3
text = "Edit Stage Layout:"
[node name="\'StageWidth\'" type="Label" parent="Header" unique_id=827952690]
layout_mode = 2
text = "Stage Width"
[node name="WidthSpinBox" type="SpinBox" parent="Header" unique_id=2023771662]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 2
accessibility_labeled_by_nodes = Array[NodePath]([NodePath("../\'StageWidth\'")])
min_value = 200.0
max_value = 800.0
value = 200.0
rounded = true
allow_greater = true
alignment = 1
suffix = "px"
select_all_on_focus = true
[node name="WarnAboutEmptyDeletion" type="CheckBox" parent="Header" unique_id=1017071750]
unique_name_in_owner = true
layout_mode = 2
focus_mode = 0
text = "Warn about empty deletion."
[node name="PanelContainer" type="PanelContainer" parent="." unique_id=1613118127]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer" unique_id=2013896487]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="CenterContainer" type="CenterContainer" parent="PanelContainer/ScrollContainer" unique_id=1647933709]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Grid" type="HBoxContainer" parent="PanelContainer/ScrollContainer/CenterContainer" unique_id=1695163191]
layout_mode = 2
theme_override_constants/separation = 5
[node name="ColumnHolder" type="HBoxContainer" parent="PanelContainer/ScrollContainer/CenterContainer/Grid" unique_id=1263007802]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/separation = 5
[node name="AddColumn" type="VBoxContainer" parent="PanelContainer/ScrollContainer/CenterContainer/Grid" unique_id=2093623661]
layout_mode = 2
theme_override_constants/separation = 5
[node name="AddColumn" type="Button" parent="PanelContainer/ScrollContainer/CenterContainer/Grid/AddColumn" unique_id=1445148106]
unique_name_in_owner = true
custom_minimum_size = Vector2(40, 105)
layout_mode = 2
size_flags_vertical = 3
focus_mode = 0
[node name="Empty" type="Button" parent="PanelContainer/ScrollContainer/CenterContainer/Grid/AddColumn" unique_id=1868106499]
self_modulate = Color(1, 1, 1, 0)
custom_minimum_size = Vector2(40, 40)
layout_mode = 2
text = "+"
[node name="Warning" type="Control" parent="PanelContainer" unique_id=1339112746]
visible = false
layout_mode = 2
mouse_filter = 2
[node name="WarningSign" type="Button" parent="PanelContainer/Warning" unique_id=1578402273]
unique_name_in_owner = true
layout_mode = 0
anchor_left = 1.0
anchor_right = 1.0
grow_horizontal = 0
tooltip_text = "Adding to much stages can overflow the editor.
Recommended maximum: 4*3"
focus_mode = 0
flat = true
[node name="ConfirmNotEmpty" type="ConfirmationDialog" parent="." unique_id=1240101809]
unique_name_in_owner = true
title = "Delete Stage"
size = Vector2i(403, 159)
[node name="VBoxContainer" type="VBoxContainer" parent="ConfirmNotEmpty" unique_id=1173550062]
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="Label" type="Label" parent="ConfirmNotEmpty/VBoxContainer" unique_id=1210268278]
layout_mode = 2
text = "You are deleting a stage which has tasks assigned.
The tasks will be assigned to the following stage:"
[node name="TaskDestination" type="OptionButton" parent="ConfirmNotEmpty/VBoxContainer" unique_id=1622670225]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="ConfirmEmpty" type="ConfirmationDialog" parent="." unique_id=1107424921]
unique_name_in_owner = true
title = "Delete Stage"
size = Vector2i(316, 100)
dialog_text = "Do you really want to delete this stage?
You can not undo this."
+73
View File
@@ -0,0 +1,73 @@
@tool
extends Node
var delete := Shortcut.new()
var duplicate := Shortcut.new()
var create := Shortcut.new()
var rename := Shortcut.new()
var search := Shortcut.new()
var confirm := Shortcut.new()
var undo := Shortcut.new()
var redo := Shortcut.new()
var save := Shortcut.new()
var save_as := Shortcut.new()
## Returns whether a specific node should handle the shortcut.
static func should_handle_shortcut(node: Node) -> bool:
var focus_owner := node.get_viewport().gui_get_focus_owner()
return focus_owner and (node.is_ancestor_of(focus_owner) or focus_owner == node)
func _ready() -> void:
if Engine.is_editor_hint():
# TODO: Update on editor settings change.
__update_shortcuts_editor()
else:
__update_shortcuts_standalone()
func __update_shortcuts_editor() -> void:
var editor_settings = Engine.get_singleton(&"EditorInterface").get_editor_settings()
delete = editor_settings.get_shortcut("scene_tree/delete")
duplicate = editor_settings.get_shortcut("scene_tree/duplicate")
create = editor_settings.get_shortcut("scene_tree/add_child_node")
rename = editor_settings.get_shortcut("scene_tree/rename")
search = editor_settings.get_shortcut("editor/open_search")
confirm = editor_settings.get_shortcut("ui_accept")
undo = editor_settings.get_shortcut("ui_undo")
redo = editor_settings.get_shortcut("ui_redo")
func __create(key: Key, shift: bool, ctrl: bool):
var ev := InputEventKey.new()
ev.command_or_control_autoremap = ctrl
ev.shift_pressed = shift
ev.keycode = key
var shortcut := Shortcut.new()
shortcut.events.append(ev)
return shortcut
func __get(action: String, fallback: Shortcut = null) -> Shortcut:
if InputMap.has_action(action):
var shortcut := Shortcut.new()
shortcut.events = InputMap.action_get_events(action)
return shortcut
return fallback
func __update_shortcuts_standalone() -> void:
delete = __get("ui_kanban_delete", __get("ui_text_delete"))
duplicate = __get("ui_kanban_duplicate", __get("ui_graph_duplicate"))
create = __get("ui_kanban_create", __create(KEY_A, false, true))
rename = __get("ui_kanban_rename", __create(KEY_F2, false, false))
search = __get("ui_kanban_search", __get("ui_filedialog_find"))
confirm = __get("ui_accept")
undo = __get("ui_undo")
redo = __get("ui_redo")
save = __get("ui_kanban_save", __create(KEY_S, false, true))
save_as = __get("ui_kanban_save_as", __create(KEY_S, true, true))
@@ -0,0 +1 @@
uid://c17pmr5kre5dx
+270
View File
@@ -0,0 +1,270 @@
@tool
extends MarginContainer
## The visual representation of a stage.
const __Singletons := preload("../../plugin_singleton/singletons.gd")
const __Shortcuts := preload("../shortcuts.gd")
const __EditContext := preload("../edit_context.gd")
const __TaskData := preload("../../data/task.gd")
const __TaskScene := preload("../task/task.tscn")
const __TaskScript := preload("../task/task.gd")
const __EditLabel := preload("../../edit_label/edit_label.gd")
const __BoardData := preload("../../data/board.gd")
const __CategoryPopupMenu := preload("../category/category_popup_menu.gd")
var board_data: __BoardData
var data_uuid: String
var __category_menu := __CategoryPopupMenu.new()
@onready var panel_container: PanelContainer = %Panel
@onready var title_label: __EditLabel = %Title
@onready var create_button: Button = %Create
@onready var task_holder: VBoxContainer = %TaskHolder
@onready var scroll_container: ScrollContainer = %ScrollContainer
@onready var preview: Control = %Preview
@onready var preview_color: ColorRect = %Preview/Color
func _ready() -> void:
update()
board_data.get_stage(data_uuid).changed.connect(update.bind(true))
scroll_container.set_drag_forwarding(
_get_drag_data_fw.bind(scroll_container),
_can_drop_data_fw.bind(scroll_container),
_drop_data_fw.bind(scroll_container),
)
create_button.pressed.connect(__on_create_button_pressed)
add_child(__category_menu)
__category_menu.uuid_selected.connect(__on_category_create_popup_uuid_selected)
__category_menu.popup_hide.connect(create_button.set_pressed_no_signal.bind(false))
notification(NOTIFICATION_THEME_CHANGED)
await get_tree().create_timer(0.0).timeout
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
if ctx.focus == data_uuid:
ctx.focus = ""
grab_focus()
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
if not Rect2(Vector2(), size).has_point(get_local_mouse_position()):
preview.visible = false
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.create.matches_event(event):
get_viewport().set_input_as_handled()
__category_menu.popup_at_mouse_position(self)
elif shortcuts.rename.matches_event(event):
get_viewport().set_input_as_handled()
title_label.show_edit()
func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
preview.visible = true
preview.position.y = __target_height_from_position(at_position)
return data is Dictionary and data.has("task") and data.has("stage")
func _can_drop_data_fw(at_position: Vector2, data: Variant, from: Control) -> bool:
var local_pos = (at_position + from.get_global_rect().position) - get_global_rect().position
return _can_drop_data(local_pos, data)
func _get_drag_data_fw(at_position: Vector2, from: Control) -> Variant:
if from is __TaskScript:
var control := Control.new()
var rect := ColorRect.new()
control.add_child(rect)
rect.size = from.get_rect().size
rect.position = -at_position
rect.color = board_data.get_category(board_data.get_task(from.data_uuid).category).color
from.set_drag_preview(control)
return {
"task": from.data_uuid,
"stage": data_uuid,
}
return null
func _drop_data(at_position: Vector2, data: Variant) -> void:
var index := __target_index_from_position(at_position)
preview.hide()
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
ctx.undo_redo.create_action("Move task")
var tasks := board_data.get_stage(data["stage"]).tasks
if data["stage"] == data_uuid:
var old_index := tasks.find(data["task"])
if index < old_index:
tasks.erase(data["task"])
tasks.insert(index, data["task"])
elif index > old_index + 1:
tasks.erase(data["task"])
tasks.insert(index - 1, data["task"])
else:
tasks.erase(data["task"])
ctx.undo_redo.add_do_property(board_data.get_stage(data["stage"]), &"tasks", tasks.duplicate())
ctx.undo_redo.add_undo_property(board_data.get_stage(data["stage"]), &"tasks", board_data.get_stage(data["stage"]).tasks)
tasks = board_data.get_stage(data_uuid).tasks
tasks.insert(index, data["task"])
ctx.focus = data["task"]
ctx.undo_redo.add_do_property(board_data.get_stage(data_uuid), &"tasks", tasks)
ctx.undo_redo.add_undo_property(board_data.get_stage(data_uuid), &"tasks", board_data.get_stage(data_uuid).tasks)
ctx.undo_redo.commit_action()
func _drop_data_fw(at_position: Vector2, data: Variant, from: Control) -> void:
var local_pos = (at_position + from.get_global_rect().position) - get_global_rect().position
_drop_data(local_pos, data)
func _notification(what: int) -> void:
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
if is_instance_valid(panel_container):
panel_container.add_theme_stylebox_override(&"panel", get_theme_stylebox(&"panel", &"Panel"))
if is_instance_valid(create_button):
create_button.icon = get_theme_icon(&"Add", &"EditorIcons")
if is_instance_valid(preview_color):
preview_color.color = get_theme_color(&"font_selected_color", &"TabBar")
func update(single: bool = false) -> void:
var focus_owner := get_viewport().gui_get_focus_owner()
if single:
grab_focus()
if title_label.text_changed.is_connected(__set_title):
title_label.text_changed.disconnect(__set_title)
title_label.text = board_data.get_stage(data_uuid).title
title_label.text_changed.connect(__set_title)
var old_scroll := scroll_container.scroll_vertical
if is_instance_valid(focus_owner) and (is_ancestor_of(focus_owner) or focus_owner == self):
if focus_owner is __TaskScript:
__Singletons.instance_of(__EditContext, self).focus = focus_owner.data_uuid
for task in task_holder.get_children():
task.queue_free()
for uuid in board_data.get_stage(data_uuid).tasks:
var task: __TaskScript = __TaskScene.instantiate()
task.board_data = board_data
task.data_uuid = uuid
task.set_drag_forwarding(
_get_drag_data_fw.bind(task),
_can_drop_data_fw.bind(task),
_drop_data_fw.bind(task),
)
task_holder.add_child(task)
custom_minimum_size.x = __Singletons.instance_of(__EditContext, self).settings.stages_width
scroll_container.scroll_vertical = old_scroll
__update_category_menus()
func __update_category_menus() -> void:
__category_menu.board_data = board_data
func __target_index_from_position(pos: Vector2) -> int:
var global_pos := pos + get_global_position()
if not scroll_container.get_global_rect().has_point(global_pos):
return 0
var scroll_pos := global_pos - task_holder.get_global_position()
var c := 0
for task in task_holder.get_children():
var y = task.position.y + task.size.y/2
if scroll_pos.y < y:
return c
c += 1
return task_holder.get_child_count()
func __set_title(value: String) -> void:
board_data.get_stage(data_uuid).title = value
func __on_create_button_pressed() -> void:
if board_data.get_category_count() > 1:
__category_menu.popup_at_local_position(create_button, Vector2(0, create_button.get_global_rect().size.y))
else:
__create_task(board_data.get_categories()[0])
create_button.set_pressed_no_signal(false)
func __create_task(category: String) -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
var stage_data := board_data.get_stage(data_uuid)
var task_data := __TaskData.new("New task", "", category)
var uuid = board_data.add_task(task_data)
var tasks = stage_data.tasks
tasks.append(uuid)
ctx.undo_redo.create_action("Add task")
ctx.undo_redo.add_do_method(board_data.__add_task.bind(task_data, uuid))
ctx.undo_redo.add_do_property(stage_data, &"tasks", tasks)
ctx.undo_redo.add_undo_property(stage_data, &"tasks", stage_data.tasks)
ctx.undo_redo.add_undo_method(board_data.remove_task.bind(uuid))
ctx.undo_redo.commit_action(false)
stage_data.tasks = tasks
for task in task_holder.get_children():
if task.data_uuid == uuid:
await get_tree().create_timer(0.0).timeout
task.grab_focus()
task.show_edit(__EditLabel.INTENTION.REPLACE)
ctx.filter = null
func __target_height_from_position(pos: Vector2) -> float:
var global_pos = pos + get_global_position()
if not scroll_container.get_global_rect().has_point(global_pos):
return - float(task_holder.get_theme_constant(&"separation")) / 2.0
var scroll_pos: Vector2 = global_pos - task_holder.get_global_position()
var c := 0.0
for task in task_holder.get_children():
if not task.visible:
continue
var y = task.position.y + task.size.y/2.0
if scroll_pos.y < y:
return c - float(task_holder.get_theme_constant(&"separation")) / 2.0
c += task.size.y + task_holder.get_theme_constant(&"separation")
return c
func __on_category_create_popup_uuid_selected(uuid) -> void:
__create_task(uuid)
@@ -0,0 +1 @@
uid://efin40vb2jqn
+125
View File
@@ -0,0 +1,125 @@
[gd_scene format=3 uid="uid://bjmtdjfx7iqgp"]
[ext_resource type="Script" uid="uid://efin40vb2jqn" path="res://addons/kanban_tasks/view/stage/stage.gd" id="1_i5556"]
[ext_resource type="Script" uid="uid://cmnh7scftf1d" path="res://addons/kanban_tasks/edit_label/edit_label.gd" id="2"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_h7hiu"]
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="Stage" type="MarginContainer" unique_id=1130122747]
editor_description = "This container is needed because the panel style cannot be updated from a script on the panel container."
custom_minimum_size = Vector2(200, 200)
size_flags_horizontal = 3
size_flags_vertical = 3
focus_mode = 1
theme_override_constants/margin_left = 0
theme_override_constants/margin_top = 0
theme_override_constants/margin_right = 0
theme_override_constants/margin_bottom = 0
script = ExtResource("1_i5556")
[node name="Panel" type="PanelContainer" parent="." unique_id=1067610553]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_h7hiu")
[node name="VBoxContainer" type="VBoxContainer" parent="Panel" unique_id=714031570]
layout_mode = 2
mouse_filter = 2
theme_override_constants/separation = 5
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBoxContainer" unique_id=1792440654]
layout_mode = 2
mouse_filter = 2
theme_override_constants/separation = 0
[node name="Title" type="VBoxContainer" parent="Panel/VBoxContainer/HBoxContainer" unique_id=68481716]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 34.1)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 4
alignment = 1
script = ExtResource("2")
default_intention = 0
[node name="Create" type="Button" parent="Panel/VBoxContainer/HBoxContainer" unique_id=1670067720]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Add task."
focus_mode = 0
toggle_mode = true
action_mode = 0
[node name="HSeparator" type="HSeparator" parent="Panel/VBoxContainer" unique_id=544264424]
layout_mode = 2
mouse_filter = 2
theme_override_constants/separation = 0
[node name="ScrollContainer" type="ScrollContainer" parent="Panel/VBoxContainer" unique_id=1588977243]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
follow_focus = true
horizontal_scroll_mode = 0
[node name="MarginContainer" type="MarginContainer" parent="Panel/VBoxContainer/ScrollContainer" unique_id=1069040072]
layout_mode = 2
size_flags_horizontal = 3
mouse_filter = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 5
theme_override_constants/margin_right = 5
theme_override_constants/margin_bottom = 5
[node name="TaskHolder" type="VBoxContainer" parent="Panel/VBoxContainer/ScrollContainer/MarginContainer" unique_id=482042085]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
mouse_filter = 2
theme_override_constants/separation = 5
[node name="PreviewHolder" type="Control" parent="Panel/VBoxContainer/ScrollContainer/MarginContainer" unique_id=62677879]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 0
mouse_filter = 2
[node name="Preview" type="Control" parent="Panel/VBoxContainer/ScrollContainer/MarginContainer/PreviewHolder" unique_id=439308420]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 14
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 0
size_flags_vertical = 0
[node name="Color" type="ColorRect" parent="Panel/VBoxContainer/ScrollContainer/MarginContainer/PreviewHolder/Preview" unique_id=1744652142]
custom_minimum_size = Vector2(0, 1)
layout_mode = 1
anchors_preset = 14
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_top = -0.5
offset_bottom = 0.5
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
color = Color(0.95, 0.95, 0.95, 1)
+59
View File
@@ -0,0 +1,59 @@
@tool
extends Control
const __Singletons := preload("../../plugin_singleton/singletons.gd")
const __EditContext := preload("../edit_context.gd")
signal create_board()
signal open_board(path: String)
@onready var create_board_button: LinkButton = %CreateBoard
@onready var open_board_button: LinkButton = %OpenBoard
@onready var recent_board_holder: VBoxContainer = %RecentBoardHolder
@onready var delete_from_recent_dialog: ConfirmationDialog = %DeleteFromRecent
func _ready() -> void:
create_board_button.pressed.connect(func(): create_board.emit())
open_board_button.pressed.connect(func(): open_board.emit(""))
await get_tree().create_timer(0.0).timeout
await get_tree().create_timer(0.0).timeout
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
ctx.settings.changed.connect(update)
update()
func update() -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
for child in recent_board_holder.get_children():
child.queue_free()
for board in ctx.settings.recent_files:
var button := LinkButton.new()
button.underline = LinkButton.UNDERLINE_MODE_NEVER
button.text = board
button.add_theme_color_override(&"font_color", Color(1, 1, 1, 0.2))
button.pressed.connect(__on_open_recent.bind(board))
recent_board_holder.add_child(button)
func __delete_from_recent(path: String) -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self) as __EditContext
var recent = ctx.settings.recent_files
var i = recent.find(path)
if i >= 0:
recent.remove_at(i)
ctx.settings.recent_files = recent
func __on_open_recent(path: String) -> void:
if not FileAccess.file_exists(path):
if delete_from_recent_dialog.confirmed.is_connected(__delete_from_recent):
delete_from_recent_dialog.confirmed.disconnect(__delete_from_recent)
delete_from_recent_dialog.confirmed.connect(__delete_from_recent.bind(path))
delete_from_recent_dialog.popup_centered()
return
open_board.emit(path)
@@ -0,0 +1 @@
uid://b671wepab3eng
+103
View File
@@ -0,0 +1,103 @@
[gd_scene load_steps=4 format=3 uid="uid://bemcl1rqpeqty"]
[ext_resource type="Script" uid="uid://b671wepab3eng" path="res://addons/kanban_tasks/view/start/start.gd" id="1_fkeby"]
[sub_resource type="LabelSettings" id="LabelSettings_0i4nn"]
font_size = 22
[sub_resource type="LabelSettings" id="LabelSettings_5febo"]
font_size = 20
font_color = Color(1, 1, 1, 0.384314)
[node name="Start" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_fkeby")
[node name="CenterContainer" type="CenterContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="HBoxContainer" type="HBoxContainer" parent="CenterContainer"]
layout_mode = 2
theme_override_constants/separation = 40
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 11
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = -10
[node name="Label" type="Label" parent="CenterContainer/HBoxContainer/VBoxContainer/VBoxContainer"]
layout_mode = 2
text = "Kanban Tasks"
label_settings = SubResource("LabelSettings_0i4nn")
[node name="Label2" type="Label" parent="CenterContainer/HBoxContainer/VBoxContainer/VBoxContainer"]
layout_mode = 2
text = "Todo Manager"
label_settings = SubResource("LabelSettings_5febo")
[node name="MarginContainer" type="MarginContainer" parent="CenterContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 0
theme_override_constants/margin_right = 0
theme_override_constants/margin_bottom = 0
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/HBoxContainer/VBoxContainer/MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="CreateBoard" type="LinkButton" parent="CenterContainer/HBoxContainer/VBoxContainer/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Create Board"
underline = 2
[node name="OpenBoard" type="LinkButton" parent="CenterContainer/HBoxContainer/VBoxContainer/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Open Board"
underline = 2
[node name="VSeparator" type="VSeparator" parent="CenterContainer/HBoxContainer"]
layout_mode = 2
[node name="VBoxContainer2" type="VBoxContainer" parent="CenterContainer/HBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="CenterContainer/HBoxContainer/VBoxContainer2"]
layout_mode = 2
text = "Recent Boards"
[node name="MarginContainer2" type="MarginContainer" parent="CenterContainer/HBoxContainer/VBoxContainer2"]
layout_mode = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 0
theme_override_constants/margin_right = 0
theme_override_constants/margin_bottom = 0
[node name="RecentBoardHolder" type="VBoxContainer" parent="CenterContainer/HBoxContainer/VBoxContainer2/MarginContainer2"]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/separation = 0
[node name="DeleteFromRecent" type="ConfirmationDialog" parent="."]
unique_name_in_owner = true
title = "Board not found"
size = Vector2i(388, 106)
ok_button_text = "Delete"
dialog_text = "The board does not seem to exist anymore.
You may choos to remove it from the recent list."
cancel_button_text = "Keep"
@@ -0,0 +1,32 @@
@tool
extends Label
@export var auto_size_height: bool = true:
set(value):
auto_size_height = value
queue_redraw()
func _init() -> void:
draw.connect(__before_draw)
func __before_draw() -> void:
# This is needed if wrapping is turned on in an autosized label,
# otherwise the conatiner will give 0 height
# (As the label itself cannot decide what size to ask from the container
# as due to wrapping, no size is fixed. But fortunatelly the label
# makes internal calculation according to intended width before the draw)
if auto_size_height:
var stylebox := get_theme_stylebox(&"normal")
var line_spacing = get_theme_constant(&"line_spacing")
var height := max(0, stylebox.content_margin_top)
for i in get_line_count():
if max_lines_visible >= 0 and i >= max_lines_visible:
break
if i > 0:
height += line_spacing
height += get_line_height(i)
height += max(0, stylebox.content_margin_bottom)
custom_minimum_size.y = height
@@ -0,0 +1 @@
uid://cqsmighbbfdl5
+441
View File
@@ -0,0 +1,441 @@
@tool
extends MarginContainer
## The visual representation of a task.
const __Singletons := preload("../../plugin_singleton/singletons.gd")
const __Shortcuts := preload("../shortcuts.gd")
const __EditContext := preload("../edit_context.gd")
const __Filter := preload("../filter.gd")
const __BoardData := preload("../../data/board.gd")
const __EditLabel := preload("../../edit_label/edit_label.gd")
const __ExpandButton := preload("../../expand_button/expand_button.gd")
const __TaskData := preload("../../data/task.gd")
const __DetailsScript := preload("../details/details.gd")
const __StepHolder := preload("../details/step_holder.gd")
const __TooltipScript := preload("../tooltip.gd")
const __CategoryPopupMenu := preload("../category/category_popup_menu.gd")
enum ACTIONS {
DETAILS,
RENAME,
DELETE,
DUPLICATE,
}
const COLOR_WIDTH: int = 8
var board_data: __BoardData:
set(value):
board_data = value
__update_category_menu()
var data_uuid: String
var __style_focus: StyleBoxFlat
var __style_panel: StyleBoxFlat
var __category_menu := __CategoryPopupMenu.new()
@onready var panel_container: PanelContainer = %Panel
@onready var category_button: Button = %CategoryButton
@onready var title_label: __EditLabel = %Title
@onready var description_label: Label = %Description
@onready var step_holder: __StepHolder = %StepHolder
@onready var expand_button: __ExpandButton = %ExpandButton
@onready var edit_button: Button = %Edit
@onready var context_menu: PopupMenu = %ContextMenu
@onready var details: __DetailsScript = %Details
func _ready() -> void:
__style_focus = StyleBoxFlat.new()
__style_focus.set_border_width_all(1)
__style_focus.draw_center = false
__style_panel = StyleBoxFlat.new()
__style_panel.set_border_width_all(0)
__style_panel.border_width_left = COLOR_WIDTH
__style_panel.draw_center = false
if not is_part_of_edited_scene():
panel_container.add_theme_stylebox_override(&"panel", __style_panel)
context_menu.id_pressed.connect(__action)
edit_button.pressed.connect(__action.bind(ACTIONS.DETAILS))
expand_button.state_changed.connect(func (expanded): __update_step_holder())
category_button.pressed.connect(__on_category_button_pressed)
add_child(__category_menu)
__category_menu.uuid_selected.connect(__on_category_menu_uuid_selected)
notification(NOTIFICATION_THEME_CHANGED)
await get_tree().create_timer(0.0).timeout
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
update()
board_data.get_task(data_uuid).changed.connect(update)
board_data.changed.connect(__update_category_button)
if data_uuid == ctx.focus:
ctx.focus = ""
grab_focus()
if not ctx.filter_changed.is_connected(__apply_filter):
ctx.filter_changed.connect(__apply_filter)
ctx.settings.changed.connect(update)
__apply_filter()
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT:
accept_event()
__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.DETAILS)
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.delete.matches_event(event):
get_viewport().set_input_as_handled()
__action(ACTIONS.DELETE)
elif shortcuts.confirm.matches_event(event):
get_viewport().set_input_as_handled()
__action(ACTIONS.DETAILS)
elif shortcuts.rename.matches_event(event):
get_viewport().set_input_as_handled()
__action(ACTIONS.RENAME)
elif shortcuts.duplicate.matches_event(event):
get_viewport().set_input_as_handled()
__action(ACTIONS.DUPLICATE)
func _make_custom_tooltip(for_text) -> Object:
var tooltip := __TooltipScript.new()
tooltip.text = for_text
tooltip.mimic_paragraphs()
return tooltip
func _notification(what: int) -> void:
match(what):
NOTIFICATION_THEME_CHANGED when not is_part_of_edited_scene():
if panel_container:
var tab_panel = get_theme_stylebox(&"panel", &"TabContainer")
if tab_panel is StyleBoxFlat:
__style_panel.bg_color = tab_panel.bg_color
__style_panel.draw_center = true
else:
__style_panel.draw_center = false
if edit_button:
edit_button.icon = get_theme_icon(&"Edit", &"EditorIcons")
NOTIFICATION_DRAW:
if has_focus():
await get_tree().create_timer(0.0).timeout
__style_focus.draw(
get_canvas_item(),
Rect2(
panel_container.get_global_rect().position - get_global_rect().position,
panel_container.size
),
)
func update() -> void:
if not is_inside_tree():
# The node might linger in the undoredo manager.
return
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
var task := board_data.get_task(data_uuid)
var task_category := board_data.get_category(task.category)
__style_focus.border_color = task_category.color
__style_panel.border_color = task_category.color
category_button.text = task_category.title
category_button.visible = ctx.settings.show_category_on_board
if ctx.settings.show_description_preview:
var description: String
match ctx.settings.description_on_board:
ctx.settings.DescriptionOnBoard.FIRST_LINE:
description = task.description
var idx := description.find("\n")
description = description.substr(0, idx)
ctx.settings.DescriptionOnBoard.UNTIL_FIRST_BLANK_LINE:
description = task.description
var idx := description.find("\n\n")
description = description.substr(0, idx)
_:
description = task.description
description_label.text = description
if ctx.settings.max_displayed_lines_in_description > 0:
description_label.max_lines_visible = ctx.settings.max_displayed_lines_in_description
else:
description_label.max_lines_visible = -1
description_label.visible = ctx.settings.show_description_preview and description_label.text.strip_edges().length() != 0
else:
description_label.text = ""
description_label.visible = (description_label.text.length() > 0)
__update_step_holder()
var steps := board_data.get_task(data_uuid).steps
for step in steps:
if not step.changed.is_connected(__update_step_holder):
step.changed.connect(__update_step_holder)
if title_label.text_changed.is_connected(__set_title):
title_label.text_changed.disconnect(__set_title)
title_label.text = board_data.get_task(data_uuid).title
title_label.text_changed.connect(__set_title)
__update_category_menu()
__update_category_button()
__update_tooltip()
queue_redraw()
func show_edit(intention: __EditLabel.INTENTION) -> void:
title_label.show_edit(intention)
func __update_step_holder() -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
var task := board_data.get_task(data_uuid)
var expanded := expand_button.expanded
step_holder.clear_steps()
var step_count := 0
var expandable := false
if ctx.settings.show_steps_preview:
var steps := board_data.get_task(data_uuid).steps
var max_step_count := ctx.settings.max_steps_on_board
match ctx.settings.steps_on_board:
ctx.settings.StepsOnBoard.ONLY_OPEN:
for i in steps.size():
if steps[i].done:
continue
if max_step_count > 0 and step_count >= max_step_count:
expandable = true
if not expanded:
break
step_holder.add_step(steps[i])
step_count += 1
ctx.settings.StepsOnBoard.ALL_OPEN_FIRST:
for i in steps.size():
if steps[i].done:
continue
if max_step_count > 0 and step_count >= max_step_count:
expandable = true
if not expanded:
break
step_holder.add_step(steps[i])
step_count += 1
for i in steps.size():
if not steps[i].done:
continue
if max_step_count > 0 and step_count >= max_step_count:
expandable = true
if not expanded:
break
step_holder.add_step(steps[i])
step_count += 1
ctx.settings.StepsOnBoard.ALL_IN_ORDER:
for i in steps.size():
if max_step_count > 0 and step_count >= max_step_count:
expandable = true
if not expanded:
break
step_holder.add_step(steps[i])
step_count += 1
_:
pass
step_holder.visible = (step_count > 0)
expand_button.visible = expandable
func __update_category_menu() -> void:
__category_menu.board_data = board_data
func __update_category_button() -> void:
if board_data.get_category_count() > 1:
category_button.mouse_filter = Control.MOUSE_FILTER_STOP
else:
category_button.mouse_filter = Control.MOUSE_FILTER_IGNORE
func __update_tooltip() -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
var task := board_data.get_task(data_uuid)
var task_category := board_data.get_category(task.category)
var steps := board_data.get_task(data_uuid).steps
var category_bullet = "[bgcolor=#" + task_category.color.to_html(false) + "] [/bgcolor] "
#var category_bullet = "[color=#" + task_category.color.to_html(false) + "]\u2588\u2588[/color]"
#var category_bullet = "[color=#" + task_category.color.to_html(false) + "]\u220E[/color]"
#var category_bullet = "[color=#" + task_category.color.to_html(false) + "]\u25A0[/color]"
tooltip_text = category_bullet + " " + board_data.get_category(task.category).title + ": " + task.title
if task.description !=null and task.description.length() > 0:
tooltip_text += "[p]" + task.description + "[/p]"
var open_steps = []
var done_steps = []
for step in steps:
(done_steps if step.done else open_steps).append(step)
#var open_step_bullet = "\u25A1" # Unfilled square
#var open_step_bullet = "[color=#808080]\u25A0[/color]" # Filled gray square
#var done_step_bullet = "\u25A0" # Filled square
#var open_step_bullet = "\u2718" # Heavy ballot X
#var done_step_bullet = "\u2714" # Heavy check mark
#var open_step_bullet = "\u2717" # Ballot X
#var done_step_bullet = "\u2713" # Check mark
var open_step_bullet = "[color=#F08080]\u25A0[/color]" # Filled red square
var done_step_bullet = "[color=#98FB98]\u25A0[/color]" # Filled green square
if open_steps.size() > 0 or done_steps.size() > 0:
tooltip_text += "[p]"
if open_steps.size() > 0:
tooltip_text += "Open steps:\n[table=2]"
for step in open_steps:
tooltip_text += "[cell]" + open_step_bullet + "[/cell][cell]" + step.details + "[/cell]\n"
tooltip_text += "[/table]\n"
if done_steps.size() > 0:
tooltip_text += "Done steps:\n[table=2]"
for step in done_steps:
tooltip_text += "[cell]" + done_step_bullet + "[/cell][cell]" + step.details + "[/cell]\n"
tooltip_text += "[/table]\n"
tooltip_text += "[/p]"
func __apply_filter() -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
if not ctx.filter or ctx.filter.text.length() == 0:
show()
return
var task = board_data.get_task(data_uuid)
var filter_simple := __simplify_string(ctx.filter.text)
var filter_matches := false
if not filter_matches:
var text_simple := __simplify_string(task.title)
if text_simple.matchn("*" + filter_simple + "*"):
filter_matches = true
if not filter_matches:
var category = board_data.get_category(task.category)
var text_simple := __simplify_string(category.title)
if text_simple.matchn("*" + filter_simple + "*"):
filter_matches = true
if not filter_matches and ctx.filter.advanced:
var text_simple := __simplify_string(task.description)
if text_simple.matchn("*" + filter_simple + "*"):
filter_matches = true
if not filter_matches and ctx.filter.advanced:
for step in task.steps:
if not filter_matches:
var text_simple := __simplify_string(step.details)
if text_simple.matchn("*" + filter_simple + "*"):
filter_matches = true
else:
break
if filter_matches:
show()
else:
hide()
func __simplify_string(string: String) -> String:
return string.replace(" ", "").replace("\t", "")
func __update_context_menu() -> void:
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
context_menu.clear()
context_menu.add_item("Details", ACTIONS.DETAILS)
context_menu.add_separator()
context_menu.add_icon_item(get_theme_icon(&"Rename", &"EditorIcons"), "Rename", ACTIONS.RENAME)
context_menu.set_item_shortcut(context_menu.get_item_index(ACTIONS.RENAME), shortcuts.rename)
context_menu.add_icon_item(get_theme_icon(&"Duplicate", &"EditorIcons"), "Duplicate", ACTIONS.DUPLICATE)
context_menu.set_item_shortcut(context_menu.get_item_index(ACTIONS.DUPLICATE), shortcuts.duplicate)
context_menu.add_icon_item(get_theme_icon(&"Remove", &"EditorIcons"), "Delete", ACTIONS.DELETE)
context_menu.set_item_shortcut(context_menu.get_item_index(ACTIONS.DELETE), shortcuts.delete)
func __action(action) -> void:
var undo_redo: UndoRedo = __Singletons.instance_of(__EditContext, self).undo_redo
match(action):
ACTIONS.DELETE:
var task = board_data.get_task(data_uuid)
for uuid in board_data.get_stages():
var tasks := board_data.get_stage(uuid).tasks
if data_uuid in tasks:
tasks.erase(data_uuid)
undo_redo.create_action("Delete task")
undo_redo.add_do_property(board_data.get_stage(uuid), &"tasks", tasks)
undo_redo.add_do_method(board_data.remove_task.bind(data_uuid, true))
undo_redo.add_undo_method(board_data.__add_task.bind(task, data_uuid))
undo_redo.add_undo_property(board_data.get_stage(uuid), &"tasks", board_data.get_stage(uuid).tasks)
undo_redo.add_undo_reference(task)
undo_redo.commit_action()
break
ACTIONS.DETAILS:
details.board_data = board_data
details.data_uuid = data_uuid
details.popup_centered_ratio_no_fullscreen(0.5)
ACTIONS.DUPLICATE:
var copy := __TaskData.new()
copy.from_json(board_data.get_task(data_uuid).to_json())
var copy_uuid := board_data.add_task(copy)
for uuid in board_data.get_stages():
var tasks := board_data.get_stage(uuid).tasks
if data_uuid in tasks:
tasks.insert(tasks.find(data_uuid), copy_uuid)
undo_redo.create_action("Duplicate task")
undo_redo.add_do_method(board_data.__add_task.bind(copy, copy_uuid))
undo_redo.add_do_property(board_data.get_stage(uuid), &"tasks", tasks)
undo_redo.add_undo_property(board_data.get_stage(uuid), &"tasks", board_data.get_stage(uuid).tasks)
undo_redo.add_undo_method(board_data.remove_task.bind(copy_uuid))
undo_redo.commit_action(false)
board_data.get_stage(uuid).tasks = tasks
break
ACTIONS.RENAME:
if context_menu.visible:
await context_menu.popup_hide
title_label.show_edit()
func __set_title(value: String) -> void:
board_data.get_task(data_uuid).title = value
func __on_category_button_pressed() -> void:
__category_menu.popup_at_local_position(category_button, Vector2(0, category_button.size.y))
func __on_category_menu_uuid_selected(category_uuid) -> void:
var task = board_data.get_task(data_uuid)
task.category = category_uuid
@@ -0,0 +1 @@
uid://bmsawo6qqtluu
+103
View File
@@ -0,0 +1,103 @@
[gd_scene format=3 uid="uid://ckqrwj5kxr6vl"]
[ext_resource type="Script" uid="uid://bmsawo6qqtluu" path="res://addons/kanban_tasks/view/task/task.gd" id="1_dslv8"]
[ext_resource type="Script" uid="uid://cmnh7scftf1d" path="res://addons/kanban_tasks/edit_label/edit_label.gd" id="2_iitpi"]
[ext_resource type="Script" uid="uid://cqsmighbbfdl5" path="res://addons/kanban_tasks/view/task/autosize_label.gd" id="3_1qkab"]
[ext_resource type="PackedScene" uid="uid://bwi22eyrmeeet" path="res://addons/kanban_tasks/view/details/details.tscn" id="3_2ol5j"]
[ext_resource type="PackedScene" uid="uid://dwjg5vyxx4g48" path="res://addons/kanban_tasks/view/details/step_holder.tscn" id="4_4e7a7"]
[ext_resource type="Script" uid="uid://guc56bqr2khq" path="res://addons/kanban_tasks/expand_button/expand_button.gd" id="5_sgwao"]
[node name="Task" type="MarginContainer" unique_id=698399779]
editor_description = "This container is needed because the panel style cannot be updated from a script on the panel container."
custom_minimum_size = Vector2(150, 0)
offset_right = 150.0
offset_bottom = 50.0
size_flags_horizontal = 3
focus_mode = 2
script = ExtResource("1_dslv8")
[node name="Panel" type="PanelContainer" parent="." unique_id=1704620960]
unique_name_in_owner = true
show_behind_parent = true
layout_mode = 2
size_flags_horizontal = 3
mouse_filter = 2
[node name="HBoxContainer" type="HBoxContainer" parent="Panel" unique_id=878429944]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
mouse_filter = 2
theme_override_constants/separation = 0
[node name="MarginContainer" type="MarginContainer" parent="Panel/HBoxContainer" unique_id=2075946638]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
mouse_filter = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 5
theme_override_constants/margin_right = 0
theme_override_constants/margin_bottom = 5
[node name="VBoxContainer" type="VBoxContainer" parent="Panel/HBoxContainer/MarginContainer" unique_id=1102637383]
layout_mode = 2
alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/HBoxContainer/MarginContainer/VBoxContainer" unique_id=1244035275]
layout_mode = 2
[node name="CategoryButton" type="Button" parent="Panel/HBoxContainer/MarginContainer/VBoxContainer/HBoxContainer" unique_id=1009888406]
unique_name_in_owner = true
layout_mode = 2
focus_mode = 0
[node name="Title" type="VBoxContainer" parent="Panel/HBoxContainer/MarginContainer/VBoxContainer/HBoxContainer" unique_id=562777166]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 34.1)
layout_mode = 2
size_flags_horizontal = 3
alignment = 1
script = ExtResource("2_iitpi")
[node name="Description" type="Label" parent="Panel/HBoxContainer/MarginContainer/VBoxContainer" unique_id=1197087444]
unique_name_in_owner = true
visible = false
modulate = Color(1, 1, 1, 0.443137)
layout_mode = 2
size_flags_horizontal = 3
autowrap_mode = 3
text_overrun_behavior = 3
script = ExtResource("3_1qkab")
[node name="StepHolder" parent="Panel/HBoxContainer/MarginContainer/VBoxContainer" unique_id=1751671221 instance=ExtResource("4_4e7a7")]
unique_name_in_owner = true
layout_mode = 2
scrollable = false
steps_can_be_removed = false
steps_can_be_reordered = false
steps_have_context_menu = false
[node name="ExpandButton" type="Button" parent="Panel/HBoxContainer/MarginContainer/VBoxContainer" unique_id=1264164075]
unique_name_in_owner = true
layout_mode = 2
focus_mode = 0
theme_type_variation = &"ExpandButton"
flat = true
icon_alignment = 1
script = ExtResource("5_sgwao")
expanded = false
[node name="Edit" type="Button" parent="Panel/HBoxContainer" unique_id=1289048325]
unique_name_in_owner = true
layout_mode = 2
focus_mode = 0
flat = true
[node name="ContextMenu" type="PopupMenu" parent="." unique_id=1856471290]
unique_name_in_owner = true
oversampling_override = 1.0
allow_search = false
[node name="Details" parent="." unique_id=1680695291 instance=ExtResource("3_2ol5j")]
unique_name_in_owner = true
+47
View File
@@ -0,0 +1,47 @@
@tool
extends RichTextLabel
@export var mimicked_paragraph_spacing_font_size: int = 6
func _init() -> void:
bbcode_enabled = true
fit_content = true
custom_minimum_size.x = 500
resized.connect(__on_resized)
func _notification(what) -> void:
match what:
NOTIFICATION_ENTER_TREE:
__take_over_label_style()
func mimic_paragraphs() -> void:
var what_in_order: PackedStringArray = [
"[/p]\n[p]",
"[/p][p]",
"[p][/p]",
"[p]",
"[/p]",
]
var forwhat = "\n[font_size=%s]\n[/font_size]\n" % mimicked_paragraph_spacing_font_size
var new_text := text
new_text = new_text.trim_prefix("[p]").trim_suffix("[/p]")
for what in what_in_order:
new_text = new_text.replace(what, forwhat)
new_text = new_text.trim_prefix("\n").trim_suffix("\n")
text = new_text
func __take_over_label_style() -> void:
add_theme_stylebox_override(&"normal", get_theme_stylebox(&"normal", &"Label"))
func __on_resized() -> void:
# Reduce width if unnecessary, as there is no line wraps
var stylebox = get_theme_stylebox(&"normal")
var required_width = get_content_width() + stylebox.content_margin_left + stylebox.content_margin_right
if required_width < custom_minimum_size.x:
custom_minimum_size.x = required_width
+1
View File
@@ -0,0 +1 @@
uid://c4xhesd3gxby6