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
@@ -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."