Added Kanban plugin
This commit is contained in:
parent
58e0c84523
commit
eb236c5426
19
addons/kanban_tasks/LICENSE
Normal file
19
addons/kanban_tasks/LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2022-2024 HolonProduction
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
305
addons/kanban_tasks/data/board.gd
Normal file
305
addons/kanban_tasks/data/board.gd
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
@tool
|
||||||
|
extends "kanban_resource.gd"
|
||||||
|
|
||||||
|
## Manages the loading and saving of other data.
|
||||||
|
|
||||||
|
|
||||||
|
const __UUID := preload("../uuid/uuid.gd")
|
||||||
|
const __Category := preload("category.gd")
|
||||||
|
const __Layout := preload("layout.gd")
|
||||||
|
const __Stage := preload("stage.gd")
|
||||||
|
const __Task := preload("task.gd")
|
||||||
|
const __KanbanResource := preload("kanban_resource.gd")
|
||||||
|
|
||||||
|
var layout: __Layout:
|
||||||
|
set(value):
|
||||||
|
if layout:
|
||||||
|
layout.changed.disconnect(__notify_changed)
|
||||||
|
layout = value
|
||||||
|
layout.changed.connect(__notify_changed)
|
||||||
|
|
||||||
|
var __categories: Dictionary
|
||||||
|
var __stages: Dictionary
|
||||||
|
var __tasks: Dictionary
|
||||||
|
|
||||||
|
|
||||||
|
## Generates a json representation of the board.
|
||||||
|
func to_json() -> Dictionary:
|
||||||
|
var dict := {}
|
||||||
|
|
||||||
|
var category_data := __propagate_uuid_dict(__categories)
|
||||||
|
dict["categories"] = category_data
|
||||||
|
|
||||||
|
var stage_data := __propagate_uuid_dict(__stages)
|
||||||
|
dict["stages"] = stage_data
|
||||||
|
|
||||||
|
var task_data := __propagate_uuid_dict(__tasks)
|
||||||
|
dict["tasks"] = task_data
|
||||||
|
|
||||||
|
dict["layout"] = layout.to_json()
|
||||||
|
|
||||||
|
return dict
|
||||||
|
|
||||||
|
|
||||||
|
## Save the board at `path`.
|
||||||
|
func save(path: String) -> void:
|
||||||
|
var file = FileAccess.open(path, FileAccess.WRITE)
|
||||||
|
if not file:
|
||||||
|
push_error("Error " + str(FileAccess.get_open_error()) + " while opening file for saving board data at " + path)
|
||||||
|
file.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
var string := JSON.stringify(to_json(), "\t", false)
|
||||||
|
file.store_string(string)
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
|
||||||
|
## Initializes the board state from json data.
|
||||||
|
func from_json(json: Dictionary) -> void:
|
||||||
|
__instantiate_uuid_array(json.get("categories", null), __Category, __add_category)
|
||||||
|
__instantiate_uuid_array(json.get("stages", null), __Stage, __add_stage)
|
||||||
|
__instantiate_uuid_array(json.get("tasks", null), __Task, __add_task)
|
||||||
|
|
||||||
|
layout = __Layout.new([])
|
||||||
|
if json.get("layout", null) is Dictionary:
|
||||||
|
layout.from_json(json["layout"])
|
||||||
|
else:
|
||||||
|
push_warning("Loading incomplete board data which is missing layout data.")
|
||||||
|
|
||||||
|
|
||||||
|
## Loads the data from `path` into the current instance.
|
||||||
|
func load(path: String) -> void:
|
||||||
|
var file = FileAccess.open(path, FileAccess.READ)
|
||||||
|
if not file:
|
||||||
|
push_error("Error " + str(FileAccess.get_open_error()) + " while opening file for loading board data at " + path)
|
||||||
|
file.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
var json = JSON.new()
|
||||||
|
var err = json.parse(file.get_as_text())
|
||||||
|
file.close()
|
||||||
|
if err != OK:
|
||||||
|
push_error("Error " + str(err) + " while parsing board at " + path + " to json. At line " + str(json.get_error_line()) + " the following problem occured:\n" + json.get_error_message())
|
||||||
|
return
|
||||||
|
|
||||||
|
if json.data.has("columns"):
|
||||||
|
__from_legacy_file(json.data)
|
||||||
|
else:
|
||||||
|
from_json(json.data)
|
||||||
|
|
||||||
|
|
||||||
|
## Adds a category and returns the uuid which is associated with it.
|
||||||
|
func add_category(category: __Category, silent: bool = false) -> String:
|
||||||
|
var res := __add_category(category)
|
||||||
|
if not silent:
|
||||||
|
__notify_changed()
|
||||||
|
return res
|
||||||
|
|
||||||
|
## Returns the category associated with the given uuid or `null` if there is none.
|
||||||
|
func get_category(uuid: String) -> __Category:
|
||||||
|
if not __categories.has(uuid) and uuid != "":
|
||||||
|
push_warning('There is no category with the uuid "' + uuid + '".')
|
||||||
|
return __categories.get(uuid, null)
|
||||||
|
|
||||||
|
## Returns the count of categories.
|
||||||
|
func get_category_count() -> int:
|
||||||
|
return len(__categories)
|
||||||
|
|
||||||
|
## Returns the uuid's of all categories.
|
||||||
|
func get_categories() -> Array[String]:
|
||||||
|
var temp: Array[String] = []
|
||||||
|
temp.assign(__categories.keys())
|
||||||
|
return temp
|
||||||
|
|
||||||
|
## Removes a category by uuid.
|
||||||
|
func remove_category(uuid: String, silent: bool = false) -> void:
|
||||||
|
if __categories.has(uuid):
|
||||||
|
__categories[uuid].changed.disconnect(__notify_changed)
|
||||||
|
__categories.erase(uuid)
|
||||||
|
if not silent:
|
||||||
|
__notify_changed()
|
||||||
|
else:
|
||||||
|
push_warning("Trying to remove uuid wich is not associated with a category.")
|
||||||
|
|
||||||
|
|
||||||
|
## Adds a stage and returns the uuid which is associated with it.
|
||||||
|
func add_stage(stage: __Stage, silent: bool = false) -> String:
|
||||||
|
var res := __add_stage(stage)
|
||||||
|
if not silent:
|
||||||
|
__notify_changed()
|
||||||
|
return res
|
||||||
|
|
||||||
|
## Returns the stage associated with the given uuid or `null` if there is none.
|
||||||
|
func get_stage(uuid: String) -> __Stage:
|
||||||
|
if not __stages.has(uuid) and uuid != "":
|
||||||
|
push_warning('There is no stage with the uuid "' + uuid + '".')
|
||||||
|
return __stages.get(uuid, null)
|
||||||
|
|
||||||
|
## Returns the count of stages.
|
||||||
|
func get_stage_count() -> int:
|
||||||
|
return len(__stages)
|
||||||
|
|
||||||
|
## Returns the uuid's of all stages.
|
||||||
|
func get_stages() -> Array[String]:
|
||||||
|
var temp: Array[String] = []
|
||||||
|
temp.assign(__stages.keys())
|
||||||
|
return temp
|
||||||
|
|
||||||
|
## Removes a stage by uuid.
|
||||||
|
func remove_stage(uuid: String, silent: bool = false) -> void:
|
||||||
|
if __stages.has(uuid):
|
||||||
|
__stages[uuid].changed.disconnect(__notify_changed)
|
||||||
|
__stages.erase(uuid)
|
||||||
|
if not silent:
|
||||||
|
__notify_changed()
|
||||||
|
else:
|
||||||
|
push_warning("Trying to remove uuid wich is not associated with a stage.")
|
||||||
|
|
||||||
|
|
||||||
|
## Adds a task and returns the uuid which is associated with it.
|
||||||
|
func add_task(task: __Task, silent: bool = false) -> String:
|
||||||
|
var res := __add_task(task)
|
||||||
|
if not silent:
|
||||||
|
__notify_changed()
|
||||||
|
return res
|
||||||
|
|
||||||
|
## Returns the task associated with the given uuid or `null` if there is none.
|
||||||
|
func get_task(uuid: String) -> __Task:
|
||||||
|
if not __tasks.has(uuid) and uuid != "":
|
||||||
|
push_warning('There is no task with the uuid "' + uuid + '".')
|
||||||
|
return __tasks.get(uuid, null)
|
||||||
|
|
||||||
|
## Returns the count of tasks.
|
||||||
|
func get_task_count() -> int:
|
||||||
|
return len(__tasks)
|
||||||
|
|
||||||
|
## Returns the uuid's of all tasks.
|
||||||
|
func get_tasks() -> Array[String]:
|
||||||
|
var temp: Array[String] = []
|
||||||
|
temp.assign(__tasks.keys())
|
||||||
|
return temp
|
||||||
|
|
||||||
|
## Removes a task by uuid.
|
||||||
|
func remove_task(uuid: String, silent: bool = false) -> void:
|
||||||
|
if __tasks.has(uuid):
|
||||||
|
if __tasks[uuid].changed.is_connected(__notify_changed):
|
||||||
|
__tasks[uuid].changed.disconnect(__notify_changed)
|
||||||
|
__tasks.erase(uuid)
|
||||||
|
if not silent:
|
||||||
|
__notify_changed()
|
||||||
|
else:
|
||||||
|
push_warning("Trying to remove uuid wich is not associated with a task.")
|
||||||
|
|
||||||
|
|
||||||
|
# Internal version of `add_category` which can be provided with an uuid suggestion.
|
||||||
|
# The uuid that is passed can be altered by the board if it is already used by
|
||||||
|
# an other category. Therefore always use the returned uuid.
|
||||||
|
func __add_category(category: __Category, uuid: String = "") -> String:
|
||||||
|
category.changed.connect(__notify_changed)
|
||||||
|
|
||||||
|
if __categories.has(uuid):
|
||||||
|
push_warning("The uuid " + uuid + ' is already used. A new one will be generated for the category "' + category.title + '".')
|
||||||
|
|
||||||
|
if uuid == "":
|
||||||
|
uuid = __UUID.v4()
|
||||||
|
|
||||||
|
while uuid in __categories.keys():
|
||||||
|
uuid = __UUID.v4()
|
||||||
|
|
||||||
|
__categories[uuid] = category
|
||||||
|
return uuid
|
||||||
|
|
||||||
|
|
||||||
|
# Internal version of `add_stage` which can be provided with an uuid suggestion.
|
||||||
|
func __add_stage(stage: __Stage, uuid: String = "") -> String:
|
||||||
|
stage.changed.connect(__notify_changed)
|
||||||
|
|
||||||
|
if __stages.has(uuid):
|
||||||
|
push_warning("The uuid " + uuid + ' is already used. A new one will be generated for the stage "' + stage.title + '".')
|
||||||
|
|
||||||
|
if uuid == "":
|
||||||
|
uuid = __UUID.v4()
|
||||||
|
|
||||||
|
while uuid in __stages.keys():
|
||||||
|
uuid = __UUID.v4()
|
||||||
|
|
||||||
|
__stages[uuid] = stage
|
||||||
|
return uuid
|
||||||
|
|
||||||
|
|
||||||
|
# Internal version of `add_task` which can be provided with an uuid suggestion.
|
||||||
|
func __add_task(task: __Task, uuid: String = "") -> String:
|
||||||
|
task.changed.connect(__notify_changed)
|
||||||
|
|
||||||
|
if __tasks.has(uuid):
|
||||||
|
push_warning("The uuid " + uuid + ' is already used. A new one will be generated for the task "' + task.title + '".')
|
||||||
|
|
||||||
|
if uuid == "":
|
||||||
|
uuid = __UUID.v4()
|
||||||
|
|
||||||
|
while uuid in __tasks.keys():
|
||||||
|
uuid = __UUID.v4()
|
||||||
|
|
||||||
|
__tasks[uuid] = task
|
||||||
|
return uuid
|
||||||
|
|
||||||
|
|
||||||
|
# HACK: `array` should have the type `Array` but then `null` could not be passed.
|
||||||
|
func __instantiate_uuid_array(array, type: Script, add_callback: Callable) -> void:
|
||||||
|
if array == null:
|
||||||
|
push_warning("Loading incomplete board data which is missing data for '" + type.resource_path + "'.")
|
||||||
|
return
|
||||||
|
|
||||||
|
for data in array:
|
||||||
|
var instance: __KanbanResource = type.new()
|
||||||
|
instance.from_json(data)
|
||||||
|
add_callback.call(instance, data.get("uuid", ""))
|
||||||
|
|
||||||
|
|
||||||
|
# Converts a dictionary with (uuid, kanban_resource) pairs into a list
|
||||||
|
# json representations with the uuid added.
|
||||||
|
func __propagate_uuid_dict(dict: Dictionary) -> Array:
|
||||||
|
var res := []
|
||||||
|
for key in dict.keys():
|
||||||
|
var json: Dictionary = {"uuid": key}
|
||||||
|
json.merge(dict[key].to_json())
|
||||||
|
res.append(json)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Remove this sometime in the future.
|
||||||
|
## Loads a board from the old file format.
|
||||||
|
func __from_legacy_file(data: Dictionary) -> void:
|
||||||
|
var categories: Array[String] = []
|
||||||
|
var tasks: Array[String] = []
|
||||||
|
var stages: Array[String] = []
|
||||||
|
|
||||||
|
for c in data["categories"]:
|
||||||
|
categories.append(
|
||||||
|
__add_category(__Category.new(c["title"], c["color"])),
|
||||||
|
)
|
||||||
|
|
||||||
|
for t in data["tasks"]:
|
||||||
|
tasks.append(
|
||||||
|
__add_task(
|
||||||
|
__Task.new(t["title"], t["details"], categories[t["category"]]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
for s in data["stages"]:
|
||||||
|
var contained_tasks: Array[String] = []
|
||||||
|
for t in s["tasks"]:
|
||||||
|
contained_tasks.append(tasks[t])
|
||||||
|
stages.append(
|
||||||
|
__add_stage(
|
||||||
|
__Stage.new(s["title"], contained_tasks),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
var columns: Array[PackedStringArray] = []
|
||||||
|
for c in data["columns"]:
|
||||||
|
var column = PackedStringArray([])
|
||||||
|
for s in c["stages"]:
|
||||||
|
column.append(stages[s])
|
||||||
|
columns.append(column)
|
||||||
|
layout = __Layout.new(columns)
|
||||||
43
addons/kanban_tasks/data/category.gd
Normal file
43
addons/kanban_tasks/data/category.gd
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
@tool
|
||||||
|
extends "kanban_resource.gd"
|
||||||
|
|
||||||
|
## Data of a category.
|
||||||
|
|
||||||
|
|
||||||
|
var title: String:
|
||||||
|
set(value):
|
||||||
|
title = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var color: Color:
|
||||||
|
set(value):
|
||||||
|
color = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func _init(p_title: String = "", p_color: Color = Color()) -> void:
|
||||||
|
title = p_title
|
||||||
|
color = p_color
|
||||||
|
super._init()
|
||||||
|
|
||||||
|
|
||||||
|
func to_json() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"title": title,
|
||||||
|
"color": color.to_html(false),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func from_json(json: Dictionary) -> void:
|
||||||
|
title = "Missing data."
|
||||||
|
color = Color.CORNFLOWER_BLUE
|
||||||
|
|
||||||
|
if json.has("title"):
|
||||||
|
title = json["title"]
|
||||||
|
else:
|
||||||
|
push_warning("Loading incomplete json data which is missing a title.")
|
||||||
|
|
||||||
|
if json.has("color"):
|
||||||
|
color = Color.html(json["color"])
|
||||||
|
else:
|
||||||
|
push_warning("Loading incomplete json data which is missing a color.")
|
||||||
30
addons/kanban_tasks/data/kanban_resource.gd
Normal file
30
addons/kanban_tasks/data/kanban_resource.gd
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
@tool
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
## Base class for kanban tasks data structures.
|
||||||
|
|
||||||
|
|
||||||
|
## Emitted when the resource changed. The properties are updated before emitting.
|
||||||
|
signal changed()
|
||||||
|
|
||||||
|
var __emit_changed := true
|
||||||
|
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
## Serializes the object as json.
|
||||||
|
func to_json() -> Dictionary:
|
||||||
|
push_error("Method to_json not implemented.")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
## Deserializes the object from json.
|
||||||
|
func from_json(json: Dictionary) -> void:
|
||||||
|
push_error("Method from_json not implemented.")
|
||||||
|
|
||||||
|
|
||||||
|
func __notify_changed() -> void:
|
||||||
|
if __emit_changed:
|
||||||
|
changed.emit()
|
||||||
49
addons/kanban_tasks/data/layout.gd
Normal file
49
addons/kanban_tasks/data/layout.gd
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
@tool
|
||||||
|
extends "kanban_resource.gd"
|
||||||
|
|
||||||
|
## Layout data.
|
||||||
|
|
||||||
|
|
||||||
|
# Use `PackedStringArray` because nested typed collections are not supported.
|
||||||
|
var columns: Array[PackedStringArray] = []:
|
||||||
|
get:
|
||||||
|
return columns.duplicate()
|
||||||
|
set(value):
|
||||||
|
columns = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func _init(p_columns: Array[PackedStringArray] = []) -> void:
|
||||||
|
columns = p_columns
|
||||||
|
super._init()
|
||||||
|
|
||||||
|
|
||||||
|
func to_json() -> Dictionary:
|
||||||
|
var cols := []
|
||||||
|
for c in columns:
|
||||||
|
var col = []
|
||||||
|
for uuid in c:
|
||||||
|
col.append(uuid)
|
||||||
|
cols.append(col)
|
||||||
|
return {
|
||||||
|
"columns": cols,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func from_json(json: Dictionary) -> void:
|
||||||
|
if json.has("columns"):
|
||||||
|
if json["columns"] is Array:
|
||||||
|
var cols: Array[PackedStringArray] = []
|
||||||
|
for c in json["columns"]:
|
||||||
|
var arr := PackedStringArray()
|
||||||
|
if c is Array:
|
||||||
|
for id in c:
|
||||||
|
arr.append(id)
|
||||||
|
else:
|
||||||
|
push_warning("Layout data is corrupted.")
|
||||||
|
cols.append(arr)
|
||||||
|
columns = cols
|
||||||
|
else:
|
||||||
|
push_warning("Layout data is corrupted.")
|
||||||
|
else:
|
||||||
|
push_warning("Loading incomplete json data which is missing a list of columns.")
|
||||||
151
addons/kanban_tasks/data/settings.gd
Normal file
151
addons/kanban_tasks/data/settings.gd
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
@tool
|
||||||
|
extends "kanban_resource.gd"
|
||||||
|
|
||||||
|
## Contains settings that are not bound to a board.
|
||||||
|
|
||||||
|
|
||||||
|
const DEFAULT_EDITOR_DATA_PATH: String = "res://kanban_tasks_data.kanban"
|
||||||
|
|
||||||
|
enum DescriptionOnBoard {
|
||||||
|
FULL,
|
||||||
|
FIRST_LINE,
|
||||||
|
UNTIL_FIRST_BLANK_LINE,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StepsOnBoard {
|
||||||
|
ONLY_OPEN,
|
||||||
|
ALL_OPEN_FIRST,
|
||||||
|
ALL_IN_ORDER
|
||||||
|
}
|
||||||
|
|
||||||
|
## Whether the first line of the description is shown on the board.
|
||||||
|
var show_description_preview: bool = true:
|
||||||
|
set(value):
|
||||||
|
show_description_preview = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var show_steps_preview: bool = true:
|
||||||
|
set(value):
|
||||||
|
show_steps_preview = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var show_category_on_board: bool = false:
|
||||||
|
set(value):
|
||||||
|
show_category_on_board = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var edit_step_details_exclusively: bool = false:
|
||||||
|
set(value):
|
||||||
|
edit_step_details_exclusively = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var max_displayed_lines_in_description: int = 0:
|
||||||
|
set(value):
|
||||||
|
max_displayed_lines_in_description = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var description_on_board := DescriptionOnBoard.FIRST_LINE:
|
||||||
|
set(value):
|
||||||
|
description_on_board = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var steps_on_board := StepsOnBoard.ONLY_OPEN:
|
||||||
|
set(value):
|
||||||
|
steps_on_board = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var max_steps_on_board: int = 2:
|
||||||
|
set(value):
|
||||||
|
max_steps_on_board = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var editor_data_file_path: String = DEFAULT_EDITOR_DATA_PATH:
|
||||||
|
set(value):
|
||||||
|
editor_data_file_path = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var warn_about_empty_deletion: bool = false:
|
||||||
|
set(value):
|
||||||
|
warn_about_empty_deletion = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var recent_file_count: int = 5:
|
||||||
|
set(value):
|
||||||
|
recent_file_count = value
|
||||||
|
recent_files.resize(value)
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var recent_files: PackedStringArray = []:
|
||||||
|
get:
|
||||||
|
return recent_files.duplicate()
|
||||||
|
set(value):
|
||||||
|
recent_files = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
# Here such settings can come, which is own responsibiity of a user control.
|
||||||
|
# When it just want to persist its own state, but the setting is not used by anything else.
|
||||||
|
# In this case there is no need to mess up this class with bolerplate code
|
||||||
|
# E.g. the splitter position in the details editor window
|
||||||
|
# Set via set_internal_state to trigger notification
|
||||||
|
# (As no clean-up, during develolpment some mess can remain in it.
|
||||||
|
# Use clear or erase in your code in such cases, just don't forget there)
|
||||||
|
var internal_states: Dictionary = { }
|
||||||
|
|
||||||
|
|
||||||
|
func set_internal_state(property: String, value: Variant) -> void:
|
||||||
|
internal_states[property] = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func to_json() -> Dictionary:
|
||||||
|
var res := {
|
||||||
|
"show_description_preview": show_description_preview,
|
||||||
|
"warn_about_empty_deletion": warn_about_empty_deletion,
|
||||||
|
"edit_step_details_exclusively": edit_step_details_exclusively,
|
||||||
|
"max_displayed_lines_in_description": max_displayed_lines_in_description,
|
||||||
|
"description_on_board": description_on_board,
|
||||||
|
"show_steps_preview": show_steps_preview,
|
||||||
|
"show_category_on_board": show_category_on_board,
|
||||||
|
"steps_on_board": steps_on_board,
|
||||||
|
"max_steps_on_board": max_steps_on_board,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not Engine.is_editor_hint():
|
||||||
|
res["recent_file_count"] = recent_file_count
|
||||||
|
res["recent_files"] = recent_files
|
||||||
|
else:
|
||||||
|
res["editor_data_file_path"] = editor_data_file_path
|
||||||
|
|
||||||
|
res["internal_states"] = internal_states
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
func from_json(json: Dictionary) -> void:
|
||||||
|
if json.has("show_description_preview"):
|
||||||
|
show_description_preview = json["show_description_preview"]
|
||||||
|
if json.has("warn_about_empty_deletion"):
|
||||||
|
warn_about_empty_deletion = json["warn_about_empty_deletion"]
|
||||||
|
if json.has("edit_step_details_exclusively"):
|
||||||
|
edit_step_details_exclusively = json["edit_step_details_exclusively"]
|
||||||
|
if json.has("max_displayed_lines_in_description"):
|
||||||
|
max_displayed_lines_in_description = json["max_displayed_lines_in_description"]
|
||||||
|
if json.has("description_on_board"):
|
||||||
|
description_on_board = json["description_on_board"]
|
||||||
|
if json.has("editor_data_file_path"):
|
||||||
|
editor_data_file_path = json["editor_data_file_path"]
|
||||||
|
if json.has("recent_file_count"):
|
||||||
|
recent_file_count = json["recent_file_count"]
|
||||||
|
if json.has("recent_files"):
|
||||||
|
recent_files = PackedStringArray(json["recent_files"])
|
||||||
|
if json.has("show_steps_preview"):
|
||||||
|
show_steps_preview = json["show_steps_preview"]
|
||||||
|
if json.has("steps_on_board"):
|
||||||
|
steps_on_board = json["steps_on_board"]
|
||||||
|
if json.has("max_steps_on_board"):
|
||||||
|
max_steps_on_board = json["max_steps_on_board"]
|
||||||
|
if json.has("show_category_on_board"):
|
||||||
|
show_category_on_board = json["show_category_on_board"]
|
||||||
|
if json.has("internal_states"):
|
||||||
|
internal_states = json["internal_states"]
|
||||||
|
__notify_changed()
|
||||||
48
addons/kanban_tasks/data/stage.gd
Normal file
48
addons/kanban_tasks/data/stage.gd
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
@tool
|
||||||
|
extends "kanban_resource.gd"
|
||||||
|
|
||||||
|
## Data of a stage.
|
||||||
|
|
||||||
|
|
||||||
|
var title: String:
|
||||||
|
set(value):
|
||||||
|
title = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var tasks: Array[String] = []:
|
||||||
|
get:
|
||||||
|
# Pass by value to avoid appending without emitting `changed`.
|
||||||
|
return tasks.duplicate()
|
||||||
|
set(value):
|
||||||
|
tasks = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func _init(p_title: String = "", p_tasks: Array[String] = []) -> void:
|
||||||
|
title = p_title
|
||||||
|
tasks = p_tasks
|
||||||
|
super._init()
|
||||||
|
|
||||||
|
|
||||||
|
func to_json() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"title": title,
|
||||||
|
"tasks": tasks,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func from_json(json: Dictionary) -> void:
|
||||||
|
if json.has("title"):
|
||||||
|
title = json["title"]
|
||||||
|
else:
|
||||||
|
push_warning("Loading incomplete json data which is missing a title.")
|
||||||
|
|
||||||
|
if json.has("tasks"):
|
||||||
|
# HACK: Workaround for casting to typed array.
|
||||||
|
var s: Array[String] = []
|
||||||
|
for i in json["tasks"]:
|
||||||
|
s.append(i)
|
||||||
|
|
||||||
|
tasks = s
|
||||||
|
else:
|
||||||
|
push_warning("Loading incomplete json data which is missing a list of tasks.")
|
||||||
40
addons/kanban_tasks/data/step.gd
Normal file
40
addons/kanban_tasks/data/step.gd
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
@tool
|
||||||
|
extends "kanban_resource.gd"
|
||||||
|
|
||||||
|
## Data of a step.
|
||||||
|
|
||||||
|
|
||||||
|
var details: String:
|
||||||
|
set(value):
|
||||||
|
details = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var done: bool:
|
||||||
|
set(value):
|
||||||
|
done = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func _init(p_details: String = "", p_done: bool = false) -> void:
|
||||||
|
details = p_details
|
||||||
|
done = p_done
|
||||||
|
super._init()
|
||||||
|
|
||||||
|
|
||||||
|
func to_json() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"details": details,
|
||||||
|
"done": done,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func from_json(json: Dictionary) -> void:
|
||||||
|
if json.has("details"):
|
||||||
|
details = json["details"]
|
||||||
|
else:
|
||||||
|
push_warning("Loading incomplete json data which is missing details.")
|
||||||
|
|
||||||
|
if json.has("done"):
|
||||||
|
done = json["done"]
|
||||||
|
else:
|
||||||
|
push_warning("Loading incomplete json data which is missing 'done'.")
|
||||||
86
addons/kanban_tasks/data/task.gd
Normal file
86
addons/kanban_tasks/data/task.gd
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
@tool
|
||||||
|
extends "kanban_resource.gd"
|
||||||
|
|
||||||
|
## Data of a task.
|
||||||
|
|
||||||
|
|
||||||
|
const __Step := preload("step.gd")
|
||||||
|
|
||||||
|
var title: String:
|
||||||
|
set(value):
|
||||||
|
title = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var description: String:
|
||||||
|
set(value):
|
||||||
|
description = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var category: String:
|
||||||
|
set(value):
|
||||||
|
category = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
var steps: Array[__Step]:
|
||||||
|
get:
|
||||||
|
return steps.duplicate()
|
||||||
|
set(value):
|
||||||
|
steps = value
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func _init(p_title: String = "", p_description: String = "", p_category: String = "", p_steps: Array[__Step] = []) -> void:
|
||||||
|
title = p_title
|
||||||
|
description = p_description
|
||||||
|
category = p_category
|
||||||
|
steps = p_steps
|
||||||
|
super._init()
|
||||||
|
|
||||||
|
|
||||||
|
func add_step(step: __Step, silent: bool = false) -> void:
|
||||||
|
var new_steps = steps
|
||||||
|
new_steps.append(step)
|
||||||
|
steps = new_steps
|
||||||
|
step.changed.connect(__notify_changed)
|
||||||
|
if not silent:
|
||||||
|
__notify_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func to_json() -> Dictionary:
|
||||||
|
var s: Array[Dictionary] = []
|
||||||
|
for step in steps:
|
||||||
|
s.append(step.to_json())
|
||||||
|
|
||||||
|
return {
|
||||||
|
"title": title,
|
||||||
|
"description": description,
|
||||||
|
"category": category,
|
||||||
|
"steps": s,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func from_json(json: Dictionary) -> void:
|
||||||
|
if json.has("title"):
|
||||||
|
title = json["title"]
|
||||||
|
else:
|
||||||
|
push_warning("Loading incomplete json data which is missing a title.")
|
||||||
|
|
||||||
|
if json.has("description"):
|
||||||
|
description = json["description"]
|
||||||
|
else:
|
||||||
|
push_warning("Loading incomplete json data which is missing a description.")
|
||||||
|
|
||||||
|
if json.has("category"):
|
||||||
|
category = json["category"]
|
||||||
|
else:
|
||||||
|
push_warning("Loading incomplete json data which is missing a category.")
|
||||||
|
|
||||||
|
if json.has("steps"):
|
||||||
|
var s: Array[__Step] = []
|
||||||
|
for step in json["steps"]:
|
||||||
|
s.append(__Step.new())
|
||||||
|
s[-1].from_json(step)
|
||||||
|
s[-1].changed.connect(__notify_changed)
|
||||||
|
steps = s
|
||||||
|
else:
|
||||||
|
push_warning("Loading incomplete json data which is missing steps.")
|
||||||
192
addons/kanban_tasks/edit_label/edit_label.gd
Normal file
192
addons/kanban_tasks/edit_label/edit_label.gd
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
@tool
|
||||||
|
extends VBoxContainer
|
||||||
|
|
||||||
|
## A label with editable content.
|
||||||
|
##
|
||||||
|
## While not editing the text is displayed in a label. So it does not stick out
|
||||||
|
## of some layout to much. Only while editing the label it is replaced with an
|
||||||
|
## line edit.
|
||||||
|
## Click onto the label to start editing it or start the editing mode via code
|
||||||
|
## by using [member show_edit]. When editing press [kbd]Enter[/kbd] to finish
|
||||||
|
## editing or press [kbd]Esc[/kbd] to discard your changes.
|
||||||
|
|
||||||
|
|
||||||
|
## Emitted when the text changed.
|
||||||
|
##
|
||||||
|
## [b]Note:[/b] This is only emitted when you confirm your editing by pressing
|
||||||
|
## [kbd]Enter[/kbd]. If you need access to all changes while editing use the
|
||||||
|
## line edit directly. You can get it by calling [method get_edit].
|
||||||
|
signal text_changed(new_text: String)
|
||||||
|
|
||||||
|
## The intentions with which the label can be edited.
|
||||||
|
enum INTENTION {
|
||||||
|
REPLACE, ## The text will be marked completly when editing.
|
||||||
|
ADDITION, ## The cursor is placed at the end when editing.
|
||||||
|
}
|
||||||
|
|
||||||
|
## The text to display and edit.
|
||||||
|
@export var text: String = "":
|
||||||
|
set(value):
|
||||||
|
text = value
|
||||||
|
__update_content()
|
||||||
|
text_changed.emit(text)
|
||||||
|
|
||||||
|
## The default intention when editing the [member text].
|
||||||
|
@export var default_intention := INTENTION.ADDITION
|
||||||
|
|
||||||
|
## Whether a double click is needed for editing. If [code]false[/code] a single
|
||||||
|
## click is enough.
|
||||||
|
@export var double_click: bool = true
|
||||||
|
|
||||||
|
# The line edit which is used to edit the text.
|
||||||
|
var __edit: LineEdit
|
||||||
|
|
||||||
|
# The label which is used to display the text.
|
||||||
|
var __label: Label
|
||||||
|
|
||||||
|
# The node which had focus before editing started. Used
|
||||||
|
# to give focus back to it, when [kbd]Enter[/kbd] is used.
|
||||||
|
var __old_focus: Control = null
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
|
mouse_filter = Control.MOUSE_FILTER_PASS
|
||||||
|
|
||||||
|
# Setup the internal label.
|
||||||
|
__label = Label.new()
|
||||||
|
__label.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||||
|
__label.size_flags_vertical = SIZE_SHRINK_CENTER
|
||||||
|
__label.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||||
|
|
||||||
|
__label.clip_text = true
|
||||||
|
__label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||||
|
|
||||||
|
__label.gui_input.connect(__on_label_gui_input)
|
||||||
|
add_child(__label)
|
||||||
|
|
||||||
|
# Setup the internal line edit.
|
||||||
|
__edit = LineEdit.new()
|
||||||
|
__edit.visible = false
|
||||||
|
__edit.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||||
|
__edit.size_flags_vertical = SIZE_FILL
|
||||||
|
__edit.text_submitted.connect(__on_edit_text_submitted)
|
||||||
|
__edit.gui_input.connect(__on_edit_gui_input)
|
||||||
|
__edit.focus_exited.connect(__on_edit_focus_exited, CONNECT_DEFERRED)
|
||||||
|
add_child(__edit)
|
||||||
|
|
||||||
|
__update_content()
|
||||||
|
|
||||||
|
# Wait for the label to get its true size.
|
||||||
|
await get_tree().create_timer(0.0).timeout
|
||||||
|
# Keep the same size when changing the edit mode.
|
||||||
|
custom_minimum_size.y = max(__label.size.y, __edit.size.y) * 1.1
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
# End the editing when somewhere else was clicked.
|
||||||
|
if (event is InputEventMouseButton) and event.pressed and __edit.visible:
|
||||||
|
var local = __edit.make_input_local(event)
|
||||||
|
if not Rect2(Vector2.ZERO, __edit.size).has_point(local.position):
|
||||||
|
show_label()
|
||||||
|
|
||||||
|
|
||||||
|
## Start editing the text and pass an optional intention.
|
||||||
|
## This can be used to open the edit interface via code.
|
||||||
|
func show_edit(intention: INTENTION = default_intention) -> void:
|
||||||
|
if __edit.visible:
|
||||||
|
return
|
||||||
|
|
||||||
|
# When this node can grab focus the focus should not be given back to the
|
||||||
|
# old focus owner.
|
||||||
|
__old_focus = get_viewport().gui_get_focus_owner() if focus_mode == FOCUS_NONE else null
|
||||||
|
|
||||||
|
__update_content()
|
||||||
|
__label.visible = false
|
||||||
|
__edit.visible = true
|
||||||
|
|
||||||
|
__edit.grab_focus()
|
||||||
|
|
||||||
|
match intention:
|
||||||
|
INTENTION.ADDITION:
|
||||||
|
__edit.caret_column = len(__edit.text)
|
||||||
|
INTENTION.REPLACE:
|
||||||
|
__edit.select_all()
|
||||||
|
|
||||||
|
|
||||||
|
## Ends editing. If [code]apply_changes[/code] is [code]true[/code] the changed
|
||||||
|
## text will be applied to the own [member text]. Otherwise the changes will
|
||||||
|
## be discarded.
|
||||||
|
func show_label(apply_changes: bool = true) -> void:
|
||||||
|
if __label.visible:
|
||||||
|
return
|
||||||
|
|
||||||
|
if apply_changes:
|
||||||
|
text = __edit.text
|
||||||
|
|
||||||
|
if is_instance_valid(__old_focus):
|
||||||
|
__old_focus.grab_focus()
|
||||||
|
else:
|
||||||
|
if focus_mode == FOCUS_NONE:
|
||||||
|
__edit.release_focus()
|
||||||
|
else:
|
||||||
|
grab_focus()
|
||||||
|
|
||||||
|
__edit.visible = false
|
||||||
|
__label.visible = true
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the [LineEdit] used to edit the text.
|
||||||
|
##
|
||||||
|
## [b]Warning:[/b] This is a required internal node, romoving and freeing it
|
||||||
|
## may cause a crash. Feel free to edit its parameters to change, how the
|
||||||
|
## [member text] is displayed.
|
||||||
|
func get_edit() -> LineEdit:
|
||||||
|
return __edit
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the [Label] used display the text.
|
||||||
|
##
|
||||||
|
## [b]Warning:[/b] This is a required internal node, romoving and freeing it
|
||||||
|
## may cause a crash. Feel free to edit its parameters to change, how the
|
||||||
|
## [member text] is displayed.
|
||||||
|
func get_label() -> Label:
|
||||||
|
return __label
|
||||||
|
|
||||||
|
|
||||||
|
# Updates the diplayed text of [member __edit] and
|
||||||
|
# [member __label] based on [member text].
|
||||||
|
func __update_content() -> void:
|
||||||
|
if __label:
|
||||||
|
__label.text = text
|
||||||
|
if __edit:
|
||||||
|
__edit.text = text
|
||||||
|
|
||||||
|
|
||||||
|
func __on_label_gui_input(event: InputEvent) -> void:
|
||||||
|
# Edit when the label is clicked.
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
if event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT:
|
||||||
|
if double_click == event.is_double_click():
|
||||||
|
# Mark event as handled.
|
||||||
|
__label.accept_event()
|
||||||
|
show_edit()
|
||||||
|
|
||||||
|
|
||||||
|
func __on_edit_gui_input(event: InputEvent) -> void:
|
||||||
|
# Discard changes if ui_cancel action is pressed.
|
||||||
|
if event is InputEventKey and event.is_pressed():
|
||||||
|
if event.is_action(&"ui_cancel"):
|
||||||
|
show_label(false)
|
||||||
|
|
||||||
|
|
||||||
|
func __on_edit_text_submitted(_new_text: String) -> void:
|
||||||
|
# For some reason line edit does not accept the event on its own in GD4.
|
||||||
|
__edit.accept_event()
|
||||||
|
show_label()
|
||||||
|
|
||||||
|
|
||||||
|
func __on_edit_focus_exited() -> void:
|
||||||
|
if __edit.visible:
|
||||||
|
__old_focus = null
|
||||||
|
show_label()
|
||||||
39
addons/kanban_tasks/expand_button/expand_button.gd
Normal file
39
addons/kanban_tasks/expand_button/expand_button.gd
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
@tool
|
||||||
|
extends Button
|
||||||
|
|
||||||
|
|
||||||
|
signal state_changed(expanded: bool)
|
||||||
|
|
||||||
|
@export var expanded: bool = true:
|
||||||
|
set(value):
|
||||||
|
if value != expanded:
|
||||||
|
expanded = value
|
||||||
|
__update_icon()
|
||||||
|
state_changed.emit(expanded)
|
||||||
|
|
||||||
|
var __texture_rect := TextureRect.new()
|
||||||
|
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
focus_mode = Control.FOCUS_NONE
|
||||||
|
flat = true
|
||||||
|
var center := CenterContainer.new()
|
||||||
|
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
add_child(center)
|
||||||
|
center.add_child(__texture_rect)
|
||||||
|
pressed.connect(__on_pressed)
|
||||||
|
text = " "
|
||||||
|
__update_icon()
|
||||||
|
|
||||||
|
|
||||||
|
func _notification(what) -> void:
|
||||||
|
if what == NOTIFICATION_THEME_CHANGED:
|
||||||
|
__texture_rect.texture = get_theme_icon(&"Collapse", &"EditorIcons")
|
||||||
|
|
||||||
|
|
||||||
|
func __update_icon() -> void:
|
||||||
|
__texture_rect.flip_v = expanded
|
||||||
|
|
||||||
|
|
||||||
|
func __on_pressed() -> void:
|
||||||
|
expanded = !expanded
|
||||||
33
addons/kanban_tasks/icon.svg
Normal file
33
addons/kanban_tasks/icon.svg
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
id="svg4"
|
||||||
|
version="1.1"
|
||||||
|
width="128"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
height="128"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<style
|
||||||
|
id="style833" />
|
||||||
|
<metadata
|
||||||
|
id="metadata10">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs
|
||||||
|
id="defs8" />
|
||||||
|
<path
|
||||||
|
id="path2"
|
||||||
|
fill="#e0e0e0"
|
||||||
|
style="display:inline;fill-opacity:1;stroke-width:1"
|
||||||
|
d="m 124.42825,124.42808 c 1.04925,-1.04923 0.52463,-3.67233 -0.52461,-8.91851 l -5.2462,-26.230876 c -1.04923,-5.246181 -1.12134,-5.44896 -4.19694,-8.393751 L 42.06322,8.4876541 C 37.413004,3.8374388 29.925632,3.8374601 25.275439,8.4876504 L 8.4876601,25.275428 c -4.650214,4.650214 -4.650214,12.137563 5e-6,16.787778 L 80.88496,114.46034 c 3.147711,3.14772 3.147711,3.14772 8.393892,4.19694 l 26.230888,5.24619 c 5.24617,1.04921 7.86929,1.57385 8.91851,0.52461 z M 88.229612,96.623329 c -2.31791,2.317909 -6.075982,2.317912 -8.393892,-8e-6 L 46.260169,63.047932 c -2.317902,-2.317902 -2.317917,-6.075987 -7e-6,-8.393897 2.317904,-2.317902 6.075992,-2.317892 8.393895,9e-6 l 33.575566,33.575408 c 2.317898,2.317898 2.317898,6.075975 -1.1e-5,8.393877 z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
38
addons/kanban_tasks/icon.svg.import
Normal file
38
addons/kanban_tasks/icon.svg.import
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bc22jf62qsikb"
|
||||||
|
path="res://.godot/imported/icon.svg-055e2d1e864a196c183290847b905009.ctex"
|
||||||
|
metadata={
|
||||||
|
"has_editor_variant": true,
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/kanban_tasks/icon.svg"
|
||||||
|
dest_files=["res://.godot/imported/icon.svg-055e2d1e864a196c183290847b905009.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
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/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=1
|
||||||
|
svg/scale=0.125
|
||||||
|
editor/scale_with_editor_scale=true
|
||||||
|
editor/convert_colors_with_editor_theme=true
|
||||||
7
addons/kanban_tasks/plugin.cfg
Normal file
7
addons/kanban_tasks/plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="Kanban Tasks - Todo Manager"
|
||||||
|
description="Another kanban board plugin for the godot engine."
|
||||||
|
author="HolonProduction"
|
||||||
|
version="2.0"
|
||||||
|
script="plugin.gd"
|
||||||
441
addons/kanban_tasks/plugin.gd
Normal file
441
addons/kanban_tasks/plugin.gd
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
@tool
|
||||||
|
extends "standalone_plugin.gd"
|
||||||
|
|
||||||
|
|
||||||
|
const __Singletons := preload("./plugin_singleton/singletons.gd")
|
||||||
|
const __Shortcuts := preload("./view/shortcuts.gd")
|
||||||
|
const __EditContext := preload("./view/edit_context.gd")
|
||||||
|
const __Settings := preload("./data/settings.gd")
|
||||||
|
const __BoardData := preload("./data/board.gd")
|
||||||
|
const __LayoutData := preload("./data/layout.gd")
|
||||||
|
const __TaskData := preload("./data/task.gd")
|
||||||
|
const __CategoryData := preload("./data/category.gd")
|
||||||
|
const __StageData := preload("./data/stage.gd")
|
||||||
|
const __BoardView := preload("./view/board/board.tscn")
|
||||||
|
const __BoardViewType := preload("./view/board/board.gd")
|
||||||
|
const __StartView := preload("./view/start/start.tscn")
|
||||||
|
const __StartViewType := preload("./view/start/start.gd")
|
||||||
|
const __DocumentationView := preload("./view/documentation/documentation.tscn")
|
||||||
|
|
||||||
|
const SETTINGS_KEY: String = "kanban_tasks/general/settings"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ACTION_SAVE,
|
||||||
|
ACTION_SAVE_AS,
|
||||||
|
ACTION_OPEN,
|
||||||
|
ACTION_CREATE,
|
||||||
|
ACTION_CLOSE,
|
||||||
|
ACTION_DOCUMENTATION,
|
||||||
|
ACTION_QUIT,
|
||||||
|
}
|
||||||
|
|
||||||
|
var main_panel_frame: MarginContainer
|
||||||
|
var start_view: __StartViewType
|
||||||
|
var file_dialog_save: FileDialog
|
||||||
|
var file_dialog_open: FileDialog
|
||||||
|
var discard_changes_dialog: ConfirmationDialog
|
||||||
|
var documentation_dialog: AcceptDialog
|
||||||
|
|
||||||
|
var file_menu: PopupMenu
|
||||||
|
var help_menu: PopupMenu
|
||||||
|
|
||||||
|
var board_view: __BoardViewType
|
||||||
|
var board_label: Label
|
||||||
|
var board_path: String = "":
|
||||||
|
set(value):
|
||||||
|
board_path = value
|
||||||
|
__update_board_label()
|
||||||
|
__update_menus()
|
||||||
|
var board_changed: bool = false:
|
||||||
|
set(value):
|
||||||
|
board_changed = value
|
||||||
|
__update_board_label()
|
||||||
|
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
board_label = Label.new()
|
||||||
|
if not Engine.is_editor_hint():
|
||||||
|
add_control_to_container(CONTAINER_TOOLBAR, board_label)
|
||||||
|
|
||||||
|
file_menu = PopupMenu.new()
|
||||||
|
file_menu.name = "File"
|
||||||
|
file_menu.add_item("Save board", ACTION_SAVE)
|
||||||
|
file_menu.add_item("Save board as...", ACTION_SAVE_AS)
|
||||||
|
file_menu.add_item("Close board", ACTION_CLOSE)
|
||||||
|
file_menu.add_item("Open board...", ACTION_OPEN)
|
||||||
|
file_menu.add_item("Create board", ACTION_CREATE)
|
||||||
|
file_menu.add_separator()
|
||||||
|
file_menu.add_item("Quit", ACTION_QUIT)
|
||||||
|
file_menu.id_pressed.connect(__action)
|
||||||
|
add_menu(file_menu)
|
||||||
|
|
||||||
|
help_menu = PopupMenu.new()
|
||||||
|
help_menu.name = "Help"
|
||||||
|
help_menu.add_item("Documentation", ACTION_DOCUMENTATION)
|
||||||
|
help_menu.id_pressed.connect(__action)
|
||||||
|
add_menu(help_menu)
|
||||||
|
|
||||||
|
file_dialog_save = FileDialog.new()
|
||||||
|
file_dialog_save.access = FileDialog.ACCESS_FILESYSTEM
|
||||||
|
file_dialog_save.file_mode = FileDialog.FILE_MODE_SAVE_FILE
|
||||||
|
file_dialog_save.add_filter("*.kanban, *.json", "Kanban Board")
|
||||||
|
file_dialog_save.min_size = Vector2(800, 500)
|
||||||
|
file_dialog_save.file_selected.connect(__save_board)
|
||||||
|
get_editor_interface().get_base_control().add_child(file_dialog_save)
|
||||||
|
|
||||||
|
file_dialog_open = FileDialog.new()
|
||||||
|
file_dialog_open.access = FileDialog.ACCESS_FILESYSTEM
|
||||||
|
file_dialog_open.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||||
|
file_dialog_open.add_filter("*.kanban, *.json", "Kanban Board")
|
||||||
|
file_dialog_open.min_size = Vector2(800, 500)
|
||||||
|
file_dialog_open.file_selected.connect(__open_board)
|
||||||
|
get_editor_interface().get_base_control().add_child(file_dialog_open)
|
||||||
|
|
||||||
|
discard_changes_dialog = ConfirmationDialog.new()
|
||||||
|
discard_changes_dialog.dialog_text = "All unsaved changes will be discarded."
|
||||||
|
discard_changes_dialog.unresizable = true
|
||||||
|
get_editor_interface().get_base_control().add_child(discard_changes_dialog)
|
||||||
|
|
||||||
|
documentation_dialog = __DocumentationView.instantiate()
|
||||||
|
get_editor_interface().get_base_control().add_child(documentation_dialog)
|
||||||
|
|
||||||
|
main_panel_frame = MarginContainer.new()
|
||||||
|
main_panel_frame.add_theme_constant_override(&"margin_top", 5)
|
||||||
|
main_panel_frame.add_theme_constant_override(&"margin_left", 5)
|
||||||
|
main_panel_frame.add_theme_constant_override(&"margin_bottom", 5)
|
||||||
|
main_panel_frame.add_theme_constant_override(&"margin_right", 5)
|
||||||
|
main_panel_frame.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
|
get_editor_interface().get_editor_main_screen().add_child(main_panel_frame)
|
||||||
|
|
||||||
|
start_view = __StartView.instantiate()
|
||||||
|
start_view.create_board.connect(__action.bind(ACTION_CREATE))
|
||||||
|
start_view.open_board.connect(__on_start_view_open_board)
|
||||||
|
main_panel_frame.add_child(start_view)
|
||||||
|
|
||||||
|
_make_visible(false)
|
||||||
|
|
||||||
|
await get_tree().create_timer(0.0).timeout
|
||||||
|
|
||||||
|
__load_settings()
|
||||||
|
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||||
|
var editor_data_file_path = ctx.settings.editor_data_file_path
|
||||||
|
if FileAccess.file_exists(editor_data_file_path):
|
||||||
|
__open_board(editor_data_file_path)
|
||||||
|
elif FileAccess.file_exists("res://addons/kanban_tasks/data.json"):
|
||||||
|
# TODO: Remove sometime in the future.
|
||||||
|
# Migrate from old version.
|
||||||
|
__open_board("res://addons/kanban_tasks/data.json")
|
||||||
|
__save_board(editor_data_file_path)
|
||||||
|
else:
|
||||||
|
__create_board()
|
||||||
|
__save_board(editor_data_file_path)
|
||||||
|
|
||||||
|
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||||
|
ctx.save_board.connect(__editor_save_board)
|
||||||
|
ctx.reload_board.connect(__editor_reload_board)
|
||||||
|
ctx.create_board.connect(__editor_create_board)
|
||||||
|
|
||||||
|
__update_menus()
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
if not Engine.is_editor_hint():
|
||||||
|
remove_control_from_container(CONTAINER_TOOLBAR, board_label)
|
||||||
|
board_label.queue_free()
|
||||||
|
|
||||||
|
remove_menu(file_menu)
|
||||||
|
file_menu.queue_free()
|
||||||
|
|
||||||
|
remove_menu(help_menu)
|
||||||
|
file_menu.queue_free()
|
||||||
|
|
||||||
|
file_dialog_save.queue_free()
|
||||||
|
file_dialog_open.queue_free()
|
||||||
|
discard_changes_dialog.queue_free()
|
||||||
|
documentation_dialog.queue_free()
|
||||||
|
|
||||||
|
main_panel_frame.queue_free()
|
||||||
|
start_view.queue_free()
|
||||||
|
|
||||||
|
if is_instance_valid(board_view):
|
||||||
|
board_view.queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func _shortcut_input(event: InputEvent) -> void:
|
||||||
|
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
|
||||||
|
if not Engine.is_editor_hint() and shortcuts.save.matches_event(event):
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
__action(ACTION_SAVE)
|
||||||
|
|
||||||
|
if not Engine.is_editor_hint() and shortcuts.save_as.matches_event(event):
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
__action(ACTION_SAVE_AS)
|
||||||
|
|
||||||
|
|
||||||
|
func _has_main_screen() -> bool:
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _make_visible(visible) -> void:
|
||||||
|
if main_panel_frame:
|
||||||
|
main_panel_frame.visible = visible
|
||||||
|
|
||||||
|
|
||||||
|
func _get_plugin_name() -> String:
|
||||||
|
return "Tasks"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_plugin_icon() -> Texture2D:
|
||||||
|
return preload("./icon.svg")
|
||||||
|
|
||||||
|
|
||||||
|
func _notification(what: int) -> void:
|
||||||
|
match what:
|
||||||
|
NOTIFICATION_WM_CLOSE_REQUEST:
|
||||||
|
if not Engine.is_editor_hint():
|
||||||
|
if not board_changed:
|
||||||
|
get_tree().quit()
|
||||||
|
else:
|
||||||
|
__request_discard_changes(get_tree().quit)
|
||||||
|
|
||||||
|
|
||||||
|
func __update_menus() -> void:
|
||||||
|
if not is_instance_valid(file_menu):
|
||||||
|
return
|
||||||
|
|
||||||
|
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
|
||||||
|
|
||||||
|
file_menu.set_item_disabled(
|
||||||
|
file_menu.get_item_index(ACTION_SAVE),
|
||||||
|
not is_instance_valid(board_view),
|
||||||
|
)
|
||||||
|
file_menu.set_item_shortcut(
|
||||||
|
file_menu.get_item_index(ACTION_SAVE),
|
||||||
|
shortcuts.save,
|
||||||
|
)
|
||||||
|
file_menu.set_item_disabled(
|
||||||
|
file_menu.get_item_index(ACTION_SAVE_AS),
|
||||||
|
not is_instance_valid(board_view),
|
||||||
|
)
|
||||||
|
file_menu.set_item_shortcut(
|
||||||
|
file_menu.get_item_index(ACTION_SAVE_AS),
|
||||||
|
shortcuts.save_as,
|
||||||
|
)
|
||||||
|
file_menu.set_item_disabled(
|
||||||
|
file_menu.get_item_index(ACTION_CLOSE),
|
||||||
|
not is_instance_valid(board_view),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func __update_board_label() -> void:
|
||||||
|
if not is_instance_valid(board_label):
|
||||||
|
return
|
||||||
|
if is_instance_valid(board_view):
|
||||||
|
if board_path.is_empty():
|
||||||
|
board_label.text = "unsaved"
|
||||||
|
else:
|
||||||
|
board_label.text = board_path
|
||||||
|
if board_changed:
|
||||||
|
board_label.text += "*"
|
||||||
|
else:
|
||||||
|
board_label.text = ""
|
||||||
|
|
||||||
|
|
||||||
|
func __action(id: int) -> void:
|
||||||
|
match id:
|
||||||
|
ACTION_SAVE:
|
||||||
|
__request_save()
|
||||||
|
ACTION_SAVE_AS:
|
||||||
|
__request_save(true)
|
||||||
|
ACTION_CREATE:
|
||||||
|
if not board_changed:
|
||||||
|
__create_board()
|
||||||
|
else:
|
||||||
|
__request_discard_changes(__create_board)
|
||||||
|
ACTION_OPEN:
|
||||||
|
if not board_changed:
|
||||||
|
file_dialog_open.popup_centered()
|
||||||
|
else:
|
||||||
|
__request_discard_changes(file_dialog_open.popup_centered)
|
||||||
|
ACTION_CLOSE:
|
||||||
|
if not board_changed:
|
||||||
|
__close_board()
|
||||||
|
else:
|
||||||
|
__request_discard_changes(__close_board)
|
||||||
|
ACTION_DOCUMENTATION:
|
||||||
|
documentation_dialog.popup_centered()
|
||||||
|
ACTION_QUIT:
|
||||||
|
get_tree().get_root().propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
func __editor_save_board() -> void:
|
||||||
|
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||||
|
__save_board(ctx.settings.editor_data_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
func __editor_reload_board() -> void:
|
||||||
|
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||||
|
__action(ACTION_SAVE)
|
||||||
|
__open_board(ctx.settings.editor_data_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
func __editor_create_board() -> void:
|
||||||
|
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||||
|
__action(ACTION_SAVE)
|
||||||
|
__create_board()
|
||||||
|
__save_board(ctx.settings.editor_data_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
func __request_discard_changes(callback: Callable) -> void:
|
||||||
|
for connection in discard_changes_dialog.confirmed.get_connections():
|
||||||
|
discard_changes_dialog.confirmed.disconnect(connection["callable"])
|
||||||
|
discard_changes_dialog.confirmed.connect(callback)
|
||||||
|
discard_changes_dialog.popup_centered()
|
||||||
|
|
||||||
|
|
||||||
|
func __request_save(force_new_location: bool = false) -> void:
|
||||||
|
if not is_instance_valid(board_view):
|
||||||
|
return
|
||||||
|
if not force_new_location and not board_path.is_empty():
|
||||||
|
__save_board(board_path)
|
||||||
|
else:
|
||||||
|
file_dialog_save.popup_centered()
|
||||||
|
|
||||||
|
|
||||||
|
func __create_board() -> void:
|
||||||
|
var data := __BoardData.new()
|
||||||
|
|
||||||
|
data.layout = __LayoutData.new([
|
||||||
|
PackedStringArray([data.add_stage(__StageData.new("Todo"))]),
|
||||||
|
PackedStringArray([data.add_stage(__StageData.new("Doing"))]),
|
||||||
|
PackedStringArray([data.add_stage(__StageData.new("Done"))]),
|
||||||
|
])
|
||||||
|
data.add_category(
|
||||||
|
__CategoryData.new(
|
||||||
|
"Task",
|
||||||
|
get_editor_interface().get_base_control().
|
||||||
|
get_theme_color(&"accent_color", &"Editor")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
data.changed.connect(__on_board_changed)
|
||||||
|
|
||||||
|
__make_board_view_visible(data)
|
||||||
|
|
||||||
|
board_path = ""
|
||||||
|
board_changed = false
|
||||||
|
|
||||||
|
|
||||||
|
func __save_board(path: String) -> void:
|
||||||
|
if is_instance_valid(board_view):
|
||||||
|
__add_to_recent_files(path)
|
||||||
|
board_path = path
|
||||||
|
board_view.board_data.save(path)
|
||||||
|
board_changed = false
|
||||||
|
|
||||||
|
|
||||||
|
func __open_board(path: String) -> void:
|
||||||
|
var data := __BoardData.new()
|
||||||
|
data.load(path)
|
||||||
|
data.changed.connect(__on_board_changed)
|
||||||
|
|
||||||
|
__make_board_view_visible(data)
|
||||||
|
__add_to_recent_files(path)
|
||||||
|
|
||||||
|
board_path = path
|
||||||
|
board_changed = false
|
||||||
|
|
||||||
|
|
||||||
|
func __close_board() -> void:
|
||||||
|
board_view.queue_free()
|
||||||
|
board_view = null
|
||||||
|
board_path = ""
|
||||||
|
board_changed = false
|
||||||
|
start_view.show()
|
||||||
|
|
||||||
|
|
||||||
|
func __add_to_recent_files(path: String) -> void:
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return
|
||||||
|
|
||||||
|
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||||
|
var files = ctx.settings.recent_files
|
||||||
|
|
||||||
|
if path in files:
|
||||||
|
files.remove_at(files.find(path))
|
||||||
|
files.insert(0, path)
|
||||||
|
else:
|
||||||
|
files.insert(0, path)
|
||||||
|
files.resize(ctx.settings.recent_file_count)
|
||||||
|
|
||||||
|
ctx.settings.recent_files = files
|
||||||
|
|
||||||
|
|
||||||
|
func __on_board_changed() -> void:
|
||||||
|
board_changed = true
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
__request_save()
|
||||||
|
|
||||||
|
|
||||||
|
func __on_start_view_open_board(path: String) -> void:
|
||||||
|
if path.is_empty():
|
||||||
|
__action(ACTION_OPEN)
|
||||||
|
else:
|
||||||
|
__open_board(path)
|
||||||
|
|
||||||
|
|
||||||
|
func __make_board_view_visible(data: __BoardData) -> void:
|
||||||
|
if is_instance_valid(board_view):
|
||||||
|
board_view.queue_free()
|
||||||
|
board_view = __BoardView.instantiate()
|
||||||
|
board_view.show_documentation.connect(__action.bind(ACTION_DOCUMENTATION))
|
||||||
|
board_view.board_data = data
|
||||||
|
|
||||||
|
main_panel_frame.add_child(board_view)
|
||||||
|
start_view.hide()
|
||||||
|
|
||||||
|
|
||||||
|
func __save_settings() -> void:
|
||||||
|
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||||
|
var data := JSON.stringify(ctx.settings.to_json())
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
get_editor_interface().get_editor_settings().set_setting(
|
||||||
|
SETTINGS_KEY,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ProjectSettings.set_setting(
|
||||||
|
SETTINGS_KEY,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
save_project_settings()
|
||||||
|
|
||||||
|
|
||||||
|
func __load_settings() -> void:
|
||||||
|
var data: String = "{}"
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
var editor_settings = get_editor_interface().get_editor_settings()
|
||||||
|
if editor_settings.has_setting(SETTINGS_KEY):
|
||||||
|
data = editor_settings.get_setting(SETTINGS_KEY)
|
||||||
|
else:
|
||||||
|
if ProjectSettings.has_setting(SETTINGS_KEY):
|
||||||
|
data = ProjectSettings.get_setting(SETTINGS_KEY)
|
||||||
|
|
||||||
|
var json = JSON.new()
|
||||||
|
var err = json.parse(data)
|
||||||
|
if err != OK:
|
||||||
|
push_error(
|
||||||
|
"Error "
|
||||||
|
+ str(err)
|
||||||
|
+ " while parsing settings. At line "
|
||||||
|
+ str(json.get_error_line())
|
||||||
|
+ " the following problem occured:\n"
|
||||||
|
+ json.get_error_message()
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||||
|
ctx.settings.from_json(json.data)
|
||||||
|
ctx.settings.changed.connect(__save_settings)
|
||||||
23
addons/kanban_tasks/plugin_singleton/singletons.gd
Normal file
23
addons/kanban_tasks/plugin_singleton/singletons.gd
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
extends Object
|
||||||
|
|
||||||
|
## Allows the registration of anonymous singletons into the scene tree.
|
||||||
|
|
||||||
|
|
||||||
|
const HOLDER_NAME: String = "PluginSingletons"
|
||||||
|
|
||||||
|
|
||||||
|
static func instance_of(p_script: Script, requester: Node) -> Variant:
|
||||||
|
var holder: Node = requester.get_tree().get_root().get_node_or_null(HOLDER_NAME)
|
||||||
|
|
||||||
|
if not is_instance_valid(holder):
|
||||||
|
holder = Node.new()
|
||||||
|
holder.name = HOLDER_NAME
|
||||||
|
requester.get_tree().get_root().add_child(holder)
|
||||||
|
|
||||||
|
for child in holder.get_children():
|
||||||
|
if child.get_script() == p_script:
|
||||||
|
return child
|
||||||
|
|
||||||
|
var instance: Node = p_script.new()
|
||||||
|
holder.add_child(instance)
|
||||||
|
return instance
|
||||||
37
addons/kanban_tasks/standalone_plugin.gd
Normal file
37
addons/kanban_tasks/standalone_plugin.gd
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#@standalone
|
||||||
|
# The line above is needed for standalone detection. Do not modify it.
|
||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
## The StandalonePlugin implementation for in editor use.
|
||||||
|
##
|
||||||
|
## This file violates DRY. It should not contain references to other files
|
||||||
|
## of standalone plugin. This allows the independent use as editor plugin.
|
||||||
|
|
||||||
|
|
||||||
|
## Additional containers for add_control_to_container
|
||||||
|
# CONTAINER_TOOLBAR = 0,
|
||||||
|
# CONTAINER_SPATIAL_EDITOR_MENU = 1,
|
||||||
|
# CONTAINER_SPATIAL_EDITOR_SIDE_LEFT = 2,
|
||||||
|
# CONTAINER_SPATIAL_EDITOR_SIDE_RIGHT = 3,
|
||||||
|
# CONTAINER_SPATIAL_EDITOR_BOTTOM = 4,
|
||||||
|
# CONTAINER_CANVAS_EDITOR_MENU = 5,
|
||||||
|
# CONTAINER_CANVAS_EDITOR_SIDE_LEFT = 6,
|
||||||
|
# CONTAINER_CANVAS_EDITOR_SIDE_RIGHT = 7,
|
||||||
|
# CONTAINER_CANVAS_EDITOR_BOTTOM = 8,
|
||||||
|
# CONTAINER_INSPECTOR_BOTTOM = 9,
|
||||||
|
# CONTAINER_PROJECT_SETTING_TAB_LEFT = 10,
|
||||||
|
# CONTAINER_PROJECT_SETTING_TAB_RIGHT = 11,
|
||||||
|
const CONTAINER_LAUNCH_PAD := 12
|
||||||
|
|
||||||
|
|
||||||
|
func save_project_settings() -> int:
|
||||||
|
return ProjectSettings.save()
|
||||||
|
|
||||||
|
|
||||||
|
func add_menu(menu: PopupMenu) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func remove_menu(menu: PopupMenu) -> void:
|
||||||
|
pass
|
||||||
21
addons/kanban_tasks/uuid/LICENSE
Normal file
21
addons/kanban_tasks/uuid/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Xavier Sellier
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
41
addons/kanban_tasks/uuid/uuid.gd
Normal file
41
addons/kanban_tasks/uuid/uuid.gd
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Note: The code might not be as pretty it could be, since it's written
|
||||||
|
# in a way that maximizes performance. Methods are inlined and loops are avoided.
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
const MODULO_8_BIT = 256
|
||||||
|
|
||||||
|
static func getRandomInt():
|
||||||
|
# Randomize every time to minimize the risk of collisions
|
||||||
|
randomize()
|
||||||
|
|
||||||
|
return randi() % MODULO_8_BIT
|
||||||
|
|
||||||
|
static func uuidbin():
|
||||||
|
# 16 random bytes with the bytes on index 6 and 8 modified
|
||||||
|
return [
|
||||||
|
getRandomInt(), getRandomInt(), getRandomInt(), getRandomInt(),
|
||||||
|
getRandomInt(), getRandomInt(), ((getRandomInt()) & 0x0f) | 0x40, getRandomInt(),
|
||||||
|
((getRandomInt()) & 0x3f) | 0x80, getRandomInt(), getRandomInt(), getRandomInt(),
|
||||||
|
getRandomInt(), getRandomInt(), getRandomInt(), getRandomInt(),
|
||||||
|
]
|
||||||
|
|
||||||
|
static func v4():
|
||||||
|
# 16 random bytes with the bytes on index 6 and 8 modified
|
||||||
|
var b = uuidbin()
|
||||||
|
|
||||||
|
return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [
|
||||||
|
# low
|
||||||
|
b[0], b[1], b[2], b[3],
|
||||||
|
|
||||||
|
# mid
|
||||||
|
b[4], b[5],
|
||||||
|
|
||||||
|
# hi
|
||||||
|
b[6], b[7],
|
||||||
|
|
||||||
|
# clock
|
||||||
|
b[8], b[9],
|
||||||
|
|
||||||
|
# clock
|
||||||
|
b[10], b[11], b[12], b[13], b[14], b[15]
|
||||||
|
]
|
||||||
156
addons/kanban_tasks/view/board/board.gd
Normal file
156
addons/kanban_tasks/view/board/board.gd
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
@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):
|
||||||
|
match(what):
|
||||||
|
NOTIFICATION_THEME_CHANGED:
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
89
addons/kanban_tasks/view/board/board.tscn
Normal file
89
addons/kanban_tasks/view/board/board.tscn
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://c5dk4lnyiag3w"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" 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
|
||||||
44
addons/kanban_tasks/view/category/category_popup_menu.gd
Normal file
44
addons/kanban_tasks/view/category/category_popup_menu.gd
Normal file
@ -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))
|
||||||
227
addons/kanban_tasks/view/details/details.gd
Normal file
227
addons/kanban_tasks/view/details/details.gd
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
@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:
|
||||||
|
match(what):
|
||||||
|
NOTIFICATION_THEME_CHANGED:
|
||||||
|
if is_instance_valid(steps_panel_container):
|
||||||
|
steps_panel_container.add_theme_stylebox_override(&"panel", get_theme_stylebox(&"panel", &"Tree"))
|
||||||
|
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()
|
||||||
|
if is_instance_valid(__step_data):
|
||||||
|
for step in step_holder.get_step_entries():
|
||||||
|
if step.step_data == data:
|
||||||
|
step.grab_focus.call_deferred()
|
||||||
112
addons/kanban_tasks/view/details/details.tscn
Normal file
112
addons/kanban_tasks/view/details/details.tscn
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
[gd_scene load_steps=6 format=3 uid="uid://bwi22eyrmeeet"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" 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="Image" id="Image_fjijb"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_op8g0"]
|
||||||
|
image = SubResource("Image_fjijb")
|
||||||
|
|
||||||
|
[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"]
|
||||||
|
title = "Task Details"
|
||||||
|
size = Vector2i(916, 557)
|
||||||
|
ok_button_text = "Close"
|
||||||
|
script = ExtResource("1_gh7s6")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
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_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="VSplitContainer" type="VSplitContainer" parent="VBoxContainer/HSplitContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Description" type="TextEdit" parent="VBoxContainer/HSplitContainer/VSplitContainer"]
|
||||||
|
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_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Step Details:"
|
||||||
|
|
||||||
|
[node name="CloseStepDetails" type="Button" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Close"
|
||||||
|
icon = SubResource("ImageTexture_op8g0")
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="StepEdit" type="TextEdit" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
wrap_mode = 1
|
||||||
|
|
||||||
|
[node name="StepList" type="VBoxContainer" parent="VBoxContainer/HSplitContainer"]
|
||||||
|
custom_minimum_size = Vector2(200, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="CreateStepEdit" type="LineEdit" parent="VBoxContainer/HSplitContainer/StepList"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
placeholder_text = "Create Step"
|
||||||
|
right_icon = SubResource("ImageTexture_op8g0")
|
||||||
|
|
||||||
|
[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/HSplitContainer/StepList"]
|
||||||
|
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" instance=ExtResource("2_0ptaf")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
steps_focus_mode = 2
|
||||||
135
addons/kanban_tasks/view/details/step_entry.gd
Normal file
135
addons/kanban_tasks/view/details/step_entry.gd
Normal file
@ -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
|
||||||
184
addons/kanban_tasks/view/details/step_holder.gd
Normal file
184
addons/kanban_tasks/view/details/step_holder.gd
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
@tool
|
||||||
|
extends VBoxContainer
|
||||||
|
|
||||||
|
|
||||||
|
signal entry_action_triggered(entry: __StepEntry, action: __StepEntry.Actions)
|
||||||
|
signal entry_move_requesed(moved_entry: __StepEntry, target_entry: __StepEntry, move_after_target: bool)
|
||||||
|
|
||||||
|
const __StepData := preload("../../data/step.gd")
|
||||||
|
const __StepEntry := preload("../details/step_entry.gd")
|
||||||
|
|
||||||
|
@export var scrollable: bool = true:
|
||||||
|
set(value):
|
||||||
|
if value != scrollable:
|
||||||
|
scrollable = value
|
||||||
|
__update_children_settings()
|
||||||
|
|
||||||
|
@export var steps_can_be_removed: bool = true:
|
||||||
|
set(value):
|
||||||
|
if value != steps_can_be_removed:
|
||||||
|
steps_can_be_removed = value
|
||||||
|
__update_children_settings()
|
||||||
|
|
||||||
|
@export var steps_can_be_reordered: bool = true:
|
||||||
|
set(value):
|
||||||
|
if value != steps_can_be_reordered:
|
||||||
|
steps_can_be_reordered = value
|
||||||
|
__update_children_settings()
|
||||||
|
|
||||||
|
@export var steps_have_context_menu: bool = true:
|
||||||
|
set(value):
|
||||||
|
if value != steps_have_context_menu:
|
||||||
|
steps_have_context_menu = value
|
||||||
|
__update_children_settings()
|
||||||
|
|
||||||
|
@export var steps_focus_mode := FocusMode.FOCUS_NONE:
|
||||||
|
set(value):
|
||||||
|
if value != steps_focus_mode:
|
||||||
|
steps_focus_mode = value
|
||||||
|
__update_children_settings()
|
||||||
|
|
||||||
|
var __mouse_entered_step_list: bool = false
|
||||||
|
var __move_target_entry: __StepEntry = null
|
||||||
|
var __move_after_target: bool = false
|
||||||
|
|
||||||
|
@onready var __scroll_container: ScrollContainer = %ScrollContainer
|
||||||
|
@onready var __remove_separator: HSeparator = %RemoveSeparator
|
||||||
|
@onready var __step_list: VBoxContainer = %StepList
|
||||||
|
@onready var __remove_area: Button = %RemoveArea
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
__remove_area.icon = get_theme_icon(&"Remove", &"EditorIcons")
|
||||||
|
__step_list.draw.connect(__on_step_list_draw)
|
||||||
|
__step_list.mouse_exited.connect(__on_step_list_mouse_exited)
|
||||||
|
__step_list.mouse_entered.connect(__on_step_list_mouse_entered)
|
||||||
|
__update_children_settings()
|
||||||
|
|
||||||
|
|
||||||
|
func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
|
||||||
|
if not steps_can_be_removed and not steps_can_be_reordered:
|
||||||
|
return false
|
||||||
|
if data is __StepEntry:
|
||||||
|
if __remove_area.get_global_rect().has_point(get_global_transform() * at_position):
|
||||||
|
return true
|
||||||
|
__update_move_target(at_position)
|
||||||
|
return (__move_target_entry != null)
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _get_drag_data(at_position: Vector2) -> Variant:
|
||||||
|
if not steps_can_be_removed and not steps_can_be_reordered:
|
||||||
|
return null
|
||||||
|
for entry in get_step_entries():
|
||||||
|
if entry.get_global_rect().has_point(get_global_transform() * at_position):
|
||||||
|
var preview := Label.new()
|
||||||
|
preview.text = entry.step_data.details
|
||||||
|
set_drag_preview(preview)
|
||||||
|
return entry
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func _drop_data(at_position: Vector2, data: Variant) -> void:
|
||||||
|
if __move_target_entry != null:
|
||||||
|
entry_move_requesed.emit(data, __move_target_entry, __move_after_target)
|
||||||
|
__move_target_entry = null
|
||||||
|
if data is __StepEntry:
|
||||||
|
if __remove_area.get_global_rect().has_point(get_global_transform() * at_position):
|
||||||
|
data.__action(__StepEntry.Actions.DELETE)
|
||||||
|
|
||||||
|
|
||||||
|
func add_step(step: __StepData) -> void:
|
||||||
|
var entry = __StepEntry.new()
|
||||||
|
entry.step_data = step
|
||||||
|
entry.show_behind_parent = true
|
||||||
|
entry.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
__step_list.add_child(entry)
|
||||||
|
entry.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
entry.action_triggered.connect(__on_entry_action_triggered)
|
||||||
|
entry.context_menu_enabled = steps_have_context_menu
|
||||||
|
entry.focus_mode = steps_focus_mode
|
||||||
|
|
||||||
|
|
||||||
|
func clear_steps() -> void:
|
||||||
|
for step in get_step_entries():
|
||||||
|
__step_list.remove_child(step)
|
||||||
|
step.queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func get_step_entries() -> Array[__StepEntry]:
|
||||||
|
var step_entries: Array[__StepEntry] = []
|
||||||
|
if is_instance_valid(__step_list):
|
||||||
|
for child in __step_list.get_children():
|
||||||
|
if child is __StepEntry:
|
||||||
|
step_entries.append(child)
|
||||||
|
return step_entries
|
||||||
|
|
||||||
|
|
||||||
|
func __update_children_settings() -> void:
|
||||||
|
if is_instance_valid(__scroll_container):
|
||||||
|
__scroll_container.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO if scrollable else ScrollContainer.SCROLL_MODE_DISABLED
|
||||||
|
if is_instance_valid(__remove_separator):
|
||||||
|
__remove_separator.visible = steps_can_be_removed
|
||||||
|
if is_instance_valid(__remove_area):
|
||||||
|
__remove_area.visible = steps_can_be_removed
|
||||||
|
for entry in get_step_entries():
|
||||||
|
entry.context_menu_enabled = steps_have_context_menu
|
||||||
|
entry.focus_mode = steps_focus_mode
|
||||||
|
|
||||||
|
|
||||||
|
func __update_move_target(at_position: Vector2) -> void:
|
||||||
|
var at_global_position := get_global_transform() * at_position
|
||||||
|
# This __mouse_entered_step_list is needed here, as this seemed to be the only reliable solution, as:
|
||||||
|
# 1) something is NOK with transforming at_position to global and compare with step_list.global_rect
|
||||||
|
# 2) cannot decide what is the visible rect of the step_list
|
||||||
|
# 3) _can_drop_data was called even after mouse is outside the list (to the bottom direction)
|
||||||
|
if __mouse_entered_step_list:
|
||||||
|
var closes_entry: __StepEntry = null
|
||||||
|
var smallest_distance: float
|
||||||
|
var position_is_after_closes_entry: bool
|
||||||
|
for e in get_step_entries():
|
||||||
|
var entry_global_rect = e.get_global_rect()
|
||||||
|
var distance := abs(at_global_position.y - entry_global_rect.position.y)
|
||||||
|
if closes_entry == null or distance < smallest_distance:
|
||||||
|
closes_entry = e
|
||||||
|
smallest_distance = distance
|
||||||
|
position_is_after_closes_entry = false
|
||||||
|
distance = abs(at_global_position.y - entry_global_rect.end.y)
|
||||||
|
if closes_entry == null or distance < smallest_distance:
|
||||||
|
closes_entry = e
|
||||||
|
smallest_distance = distance
|
||||||
|
position_is_after_closes_entry = true
|
||||||
|
__move_target_entry = closes_entry
|
||||||
|
__move_after_target = position_is_after_closes_entry
|
||||||
|
else:
|
||||||
|
__move_target_entry = null
|
||||||
|
__step_list.queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
func __on_step_list_mouse_entered() -> void:
|
||||||
|
__mouse_entered_step_list = true
|
||||||
|
|
||||||
|
|
||||||
|
func __on_step_list_mouse_exited() -> void:
|
||||||
|
__mouse_entered_step_list = false
|
||||||
|
__update_move_target(get_local_mouse_position())
|
||||||
|
|
||||||
|
|
||||||
|
func __on_step_list_draw() -> void:
|
||||||
|
if __move_target_entry != null:
|
||||||
|
var target_rect := __step_list.get_global_transform().inverse() * __move_target_entry.get_global_rect()
|
||||||
|
var separation = __step_list.get_theme_constant(&"separation")
|
||||||
|
var preview_rect := Rect2(
|
||||||
|
Vector2(0, target_rect.end.y if __move_after_target else target_rect.position.y - separation),
|
||||||
|
Vector2(target_rect.size.x, separation)
|
||||||
|
)
|
||||||
|
if preview_rect.position.y < 0:
|
||||||
|
preview_rect.position.y = 0
|
||||||
|
if preview_rect.end.y > __step_list.size.y:
|
||||||
|
preview_rect.position.y -= (preview_rect.end.y - __step_list.size.y)
|
||||||
|
__step_list.draw_rect(preview_rect, get_theme_color(&"step_move_review_color"))
|
||||||
|
|
||||||
|
|
||||||
|
func __on_entry_action_triggered(entry, action) -> void:
|
||||||
|
entry_action_triggered.emit(entry, action)
|
||||||
45
addons/kanban_tasks/view/details/step_holder.tscn
Normal file
45
addons/kanban_tasks/view/details/step_holder.tscn
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://dwjg5vyxx4g48"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" 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"]
|
||||||
|
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_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_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="RemoveSeparator" type="HSeparator" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="RemoveArea" type="Button" parent="."]
|
||||||
|
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
|
||||||
78
addons/kanban_tasks/view/documentation/1.svg
Normal file
78
addons/kanban_tasks/view/documentation/1.svg
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
viewBox="0 0 26.458333 26.458333"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="1.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="2.8284271"
|
||||||
|
inkscape:cx="-102.00015"
|
||||||
|
inkscape:cy="63.462833"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1361"
|
||||||
|
inkscape:window-x="-9"
|
||||||
|
inkscape:window-y="-9"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid1049" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:#575b64;stroke:#575b64;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:round;paint-order:markers fill stroke;fill-opacity:1;stroke-opacity:1"
|
||||||
|
id="rect2968"
|
||||||
|
width="10.583333"
|
||||||
|
height="23.8125"
|
||||||
|
x="1.3229166"
|
||||||
|
y="1.3229166" />
|
||||||
|
<rect
|
||||||
|
style="fill:#626771;stroke:#626771;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:round;paint-order:markers fill stroke;fill-opacity:1;stroke-opacity:1"
|
||||||
|
id="rect2970"
|
||||||
|
width="10.583333"
|
||||||
|
height="23.8125"
|
||||||
|
x="14.552083"
|
||||||
|
y="1.3229166" />
|
||||||
|
<rect
|
||||||
|
style="fill:#179ceb;fill-opacity:1;stroke:#179ceb;stroke-width:1.05836;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect4591"
|
||||||
|
width="15.216529"
|
||||||
|
height="3.9785306"
|
||||||
|
x="2.3476388"
|
||||||
|
y="10.695463"
|
||||||
|
transform="matrix(0.99152896,-0.12988578,0.11857896,0.99294463,0,0)" />
|
||||||
|
<path
|
||||||
|
style="fill:#a4c1d3;fill-opacity:0.60851061;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 16.933333,10.847917 c -0.923401,0.09187 -1.777291,0.468265 -2.910417,1.161686 1.103691,-0.186089 2.064921,-0.191401 3.439584,0.16123 l -0.529167,-1.322916"
|
||||||
|
id="path12560"
|
||||||
|
sodipodi:nodetypes="cccc" />
|
||||||
|
<path
|
||||||
|
id="path12547"
|
||||||
|
style="color:#000000;fill:#d8d8d8;stroke-linejoin:round;-inkscape-stroke:none;fill-opacity:1"
|
||||||
|
d="M 16.930749 10.714591 C 16.914596 10.71489 16.89821 10.718029 16.88269 10.72441 C 16.83287 10.74494 16.800315 10.793523 16.800525 10.8474 L 16.800525 11.90625 C 16.80114 11.99606 16.889346 12.059309 16.974674 12.031307 L 17.202051 11.955859 L 17.252693 12.107788 A 0.1825 0.1825 0 0 0 17.48317 12.223026 A 0.1825 0.1825 0 0 0 17.598409 11.992549 L 17.548283 11.841138 L 17.769458 11.767757 C 17.861908 11.735907 17.889068 11.617968 17.820101 11.548649 L 17.027384 10.753865 C 17.001404 10.727637 16.966286 10.713935 16.930749 10.714591 z " />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
37
addons/kanban_tasks/view/documentation/1.svg.import
Normal file
37
addons/kanban_tasks/view/documentation/1.svg.import
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
[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/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
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=1
|
||||||
|
svg/scale=4.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
91
addons/kanban_tasks/view/documentation/2.svg
Normal file
91
addons/kanban_tasks/view/documentation/2.svg
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
viewBox="0 0 26.458333 26.458333"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="2.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="5.6568543"
|
||||||
|
inkscape:cx="-24.483572"
|
||||||
|
inkscape:cy="14.407301"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1361"
|
||||||
|
inkscape:window-x="-9"
|
||||||
|
inkscape:window-y="-9"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid1049" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:#575b64;stroke:#575b64;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:round;paint-order:markers fill stroke;fill-opacity:1;stroke-opacity:1"
|
||||||
|
id="rect2968"
|
||||||
|
width="10.583333"
|
||||||
|
height="23.8125"
|
||||||
|
x="1.3229166"
|
||||||
|
y="1.3229166" />
|
||||||
|
<rect
|
||||||
|
style="fill:#575b64;fill-opacity:1;stroke:#575b64;stroke-width:1.058;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:markers fill stroke;stroke-dasharray:none"
|
||||||
|
id="rect2970"
|
||||||
|
width="10.583333"
|
||||||
|
height="10.583331"
|
||||||
|
x="14.552083"
|
||||||
|
y="14.552083" />
|
||||||
|
<rect
|
||||||
|
style="fill:#575b64;fill-opacity:1;stroke:#575b64;stroke-width:1.05806874;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect16439"
|
||||||
|
width="10.583333"
|
||||||
|
height="10.583333"
|
||||||
|
x="14.552083"
|
||||||
|
y="1.3229166" />
|
||||||
|
<rect
|
||||||
|
style="fill:#179ceb;fill-opacity:1;stroke:#179ceb;stroke-width:1.05807;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect19474"
|
||||||
|
width="9.5251322"
|
||||||
|
height="1.3229165"
|
||||||
|
x="1.8519517"
|
||||||
|
y="2.9104166" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#106da5;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 2.1166666,3.6049483 H 10.054166"
|
||||||
|
id="path19478" />
|
||||||
|
<path
|
||||||
|
style="fill:#afb7c9;fill-opacity:0.84680849;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="M 8.4072969,3.5780708 8.1734362,2.7712509 8.7814743,3.4260613 9.1907307,3.0051116 9.295968,3.636536 9.7169177,3.8587037 9.1907307,3.928862 8.968563,4.3147324 8.6996231,3.917169 8.1266639,4.0691784 Z"
|
||||||
|
id="path19618" />
|
||||||
|
<path
|
||||||
|
style="fill:#afb7c9;fill-opacity:0.659574;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="M 8.2584693,3.4540478 8.0246086,2.6472279 8.6326467,3.3020383 9.0419031,2.8810886 9.1471404,3.512513 9.5680901,3.7346807 9.0419031,3.804839 8.8197354,4.1907094 8.5507955,3.793146 7.9778363,3.9451554 Z"
|
||||||
|
id="path19622" />
|
||||||
|
<path
|
||||||
|
id="path12547"
|
||||||
|
style="color:#000000;fill:#d8d8d8;fill-opacity:1;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="m 8.8614739,3.5711183 c -0.016153,2.99e-4 -0.032539,0.0034 -0.048059,0.0098 -0.04982,0.02053 -0.082375,0.06911 -0.082165,0.12299 v 1.05885 c 6.15e-4,0.08981 0.088821,0.153059 0.174149,0.125057 l 0.227377,-0.07545 0.050642,0.151929 a 0.1825,0.1825 0 0 0 0.230477,0.115238 0.1825,0.1825 0 0 0 0.115239,-0.230477 l -0.050126,-0.151411 0.221175,-0.07338 c 0.09245,-0.03185 0.11961,-0.149789 0.050643,-0.219108 l -0.792717,-0.794784 c -0.02598,-0.02623 -0.061098,-0.03993 -0.096635,-0.03927 z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
37
addons/kanban_tasks/view/documentation/2.svg.import
Normal file
37
addons/kanban_tasks/view/documentation/2.svg.import
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
[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/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
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=1
|
||||||
|
svg/scale=4.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
100
addons/kanban_tasks/view/documentation/3.svg
Normal file
100
addons/kanban_tasks/view/documentation/3.svg
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
viewBox="0 0 26.458333 26.458333"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="3.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="8"
|
||||||
|
inkscape:cx="36.3125"
|
||||||
|
inkscape:cy="35.1875"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1361"
|
||||||
|
inkscape:window-x="-9"
|
||||||
|
inkscape:window-y="-9"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid1049" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:#575b64;fill-opacity:1;stroke:#575b64;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect2968"
|
||||||
|
width="13.262239"
|
||||||
|
height="18.057812"
|
||||||
|
x="7.2098951"
|
||||||
|
y="4.0348959" />
|
||||||
|
<rect
|
||||||
|
style="fill:#179ceb;fill-opacity:1;stroke:#179ceb;stroke-width:1.05807;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect19474"
|
||||||
|
width="9.5251322"
|
||||||
|
height="1.3229165"
|
||||||
|
x="9.2602844"
|
||||||
|
y="22.489584" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#106da5;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 9.5250003,23.184123 H 17.462504"
|
||||||
|
id="path19478" />
|
||||||
|
<circle
|
||||||
|
style="fill:#106da5;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="path23344"
|
||||||
|
cx="18.654341"
|
||||||
|
cy="23.18438"
|
||||||
|
r="0.39464015" />
|
||||||
|
<path
|
||||||
|
style="fill:#afb7c9;fill-opacity:0.846809;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 18.36207,23.157246 -0.233861,-0.80682 0.608038,0.65481 0.409257,-0.42095 0.105237,0.631425 0.42095,0.222168 -0.526187,0.07016 -0.222168,0.385871 -0.26894,-0.397564 -0.572959,0.15201 z"
|
||||||
|
id="path19618" />
|
||||||
|
<path
|
||||||
|
id="path12547"
|
||||||
|
style="color:#000000;fill:#d8d8d8;fill-opacity:1;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="m 18.816247,23.150293 c -0.01615,2.99e-4 -0.03254,0.0034 -0.04806,0.0098 -0.04982,0.02053 -0.08237,0.06911 -0.08216,0.122991 v 1.05885 c 6.15e-4,0.08981 0.08882,0.153059 0.174149,0.125057 l 0.227377,-0.07545 0.05064,0.151929 a 0.1825,0.1825 0 0 0 0.230477,0.115238 0.1825,0.1825 0 0 0 0.11524,-0.230477 l -0.05013,-0.151411 0.221175,-0.07338 c 0.09245,-0.03185 0.11961,-0.149789 0.05064,-0.219108 l -0.792711,-0.794785 c -0.02598,-0.02623 -0.0611,-0.03993 -0.09664,-0.03927 z" />
|
||||||
|
<rect
|
||||||
|
style="fill:#646973;fill-opacity:1;stroke:#646973;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect27188"
|
||||||
|
width="12.336198"
|
||||||
|
height="1.1451006"
|
||||||
|
x="7.6729159"
|
||||||
|
y="4.6529698" />
|
||||||
|
<rect
|
||||||
|
style="fill:#646973;fill-opacity:1;stroke:#646973;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect27916"
|
||||||
|
width="8.2020836"
|
||||||
|
height="12.687549"
|
||||||
|
x="7.6729159"
|
||||||
|
y="7.8279738" />
|
||||||
|
<rect
|
||||||
|
style="fill:#646973;fill-opacity:1;stroke:#646973;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect27918"
|
||||||
|
width="2.2158854"
|
||||||
|
height="12.687549"
|
||||||
|
x="17.793228"
|
||||||
|
y="7.8279738" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.2 KiB |
37
addons/kanban_tasks/view/documentation/3.svg.import
Normal file
37
addons/kanban_tasks/view/documentation/3.svg.import
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
[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/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
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=1
|
||||||
|
svg/scale=4.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
64
addons/kanban_tasks/view/documentation/4.svg
Normal file
64
addons/kanban_tasks/view/documentation/4.svg
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
viewBox="0 0 26.458333 26.458333"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="4.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="8"
|
||||||
|
inkscape:cx="40.3125"
|
||||||
|
inkscape:cy="44.4375"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1361"
|
||||||
|
inkscape:window-x="-9"
|
||||||
|
inkscape:window-y="-9"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid1049" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#d0c268;fill-opacity:1;stroke:#d0c268;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="path32362"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
sodipodi:sides="5"
|
||||||
|
sodipodi:cx="7.8382812"
|
||||||
|
sodipodi:cy="7.9705729"
|
||||||
|
sodipodi:r1="8.6053162"
|
||||||
|
sodipodi:r2="4.6296601"
|
||||||
|
sodipodi:arg1="-1.5707963"
|
||||||
|
sodipodi:arg2="-0.94247777"
|
||||||
|
inkscape:rounded="0.11"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 7.8382814,-0.63474321 c 0.6126837,1e-8 2.2255746,4.49971601 2.7212456,4.85984251 0.495672,0.3601264 5.273567,0.5035881 5.462896,1.0862849 0.18933,0.5826968 -3.591744,3.5071357 -3.781073,4.0898325 -0.18933,0.5826968 1.15068,5.1710773 0.655009,5.5312033 -0.495672,0.360127 -4.4453943,-2.332187 -5.058078,-2.332187 -0.6126837,0 -4.5624065,2.692313 -5.058078,2.332187 C 2.2845315,14.572293 3.6245423,9.9839133 3.4352127,9.4012165 3.245883,8.8185197 -0.53519047,5.8940806 -0.34586078,5.3113838 -0.1565311,4.728687 4.6213638,4.5852256 5.1170353,4.2250992 5.6127068,3.8649727 7.2255977,-0.63474323 7.8382814,-0.63474321 Z"
|
||||||
|
inkscape:transform-center-y="-0.77805912"
|
||||||
|
transform="translate(5.3908937,6.0569817)" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
37
addons/kanban_tasks/view/documentation/4.svg.import
Normal file
37
addons/kanban_tasks/view/documentation/4.svg.import
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
[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/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
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=1
|
||||||
|
svg/scale=4.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
138
addons/kanban_tasks/view/documentation/documentation.tscn
Normal file
138
addons/kanban_tasks/view/documentation/documentation.tscn
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
[gd_scene load_steps=6 format=3 uid="uid://cwfixtyy5lpin"]
|
||||||
|
|
||||||
|
[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"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://phpa3kr3tjwu" path="res://addons/kanban_tasks/view/documentation/4.svg" id="4_ic0nh"]
|
||||||
|
|
||||||
|
[sub_resource type="LabelSettings" id="LabelSettings_cmnk1"]
|
||||||
|
font_size = 20
|
||||||
|
|
||||||
|
[node name="AcceptDialog" type="AcceptDialog"]
|
||||||
|
title = "Documentation"
|
||||||
|
size = Vector2i(1000, 600)
|
||||||
|
min_size = Vector2i(1000, 600)
|
||||||
|
theme_type_variation = &"EditorSettingsDialog"
|
||||||
|
ok_button_text = "Close"
|
||||||
|
|
||||||
|
[node name="Help" type="ScrollContainer" parent="."]
|
||||||
|
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"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
|
[node name="PanelContainer1" type="PanelContainer" parent="Help/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"Panel"
|
||||||
|
|
||||||
|
[node name="HBoxContainer1" type="HBoxContainer" parent="Help/VBoxContainer/PanelContainer1"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 50
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="Control" type="Control" parent="Help/VBoxContainer/PanelContainer1/HBoxContainer1"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Help/VBoxContainer/PanelContainer1/HBoxContainer1"]
|
||||||
|
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"]
|
||||||
|
custom_minimum_size = Vector2(200, 200)
|
||||||
|
layout_mode = 2
|
||||||
|
texture = ExtResource("1_inmy7")
|
||||||
|
expand_mode = 1
|
||||||
|
|
||||||
|
[node name="PanelContainer2" type="PanelContainer" parent="Help/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"Panel"
|
||||||
|
|
||||||
|
[node name="HBoxContainer2" type="HBoxContainer" parent="Help/VBoxContainer/PanelContainer2"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 50
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="TextureRect" type="TextureRect" parent="Help/VBoxContainer/PanelContainer2/HBoxContainer2"]
|
||||||
|
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"]
|
||||||
|
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"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="PanelContainer3" type="PanelContainer" parent="Help/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"Panel"
|
||||||
|
|
||||||
|
[node name="HBoxContainer3" type="HBoxContainer" parent="Help/VBoxContainer/PanelContainer3"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 50
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="Control" type="Control" parent="Help/VBoxContainer/PanelContainer3/HBoxContainer3"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Help/VBoxContainer/PanelContainer3/HBoxContainer3"]
|
||||||
|
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"]
|
||||||
|
custom_minimum_size = Vector2(200, 200)
|
||||||
|
layout_mode = 2
|
||||||
|
texture = ExtResource("3_e3hha")
|
||||||
|
expand_mode = 1
|
||||||
|
|
||||||
|
[node name="HBoxContainer4" type="HBoxContainer" parent="Help/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 50
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Control" type="Control" parent="Help/VBoxContainer/HBoxContainer4"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="Help/VBoxContainer/HBoxContainer4"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="TextureRect" type="TextureRect" parent="Help/VBoxContainer/HBoxContainer4/HBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 30)
|
||||||
|
layout_mode = 2
|
||||||
|
texture = ExtResource("4_ic0nh")
|
||||||
|
expand_mode = 3
|
||||||
|
|
||||||
|
[node name="LinkButton" type="LinkButton" parent="Help/VBoxContainer/HBoxContainer4/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
text = "Leave a star on Github"
|
||||||
|
uri = "https://github.com/HolonProduction/godot_kanban_tasks"
|
||||||
|
|
||||||
|
[node name="Control2" type="Control" parent="Help/VBoxContainer/HBoxContainer4"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
29
addons/kanban_tasks/view/edit_context.gd
Normal file
29
addons/kanban_tasks/view/edit_context.gd
Normal 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()
|
||||||
|
|
||||||
|
## 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()
|
||||||
14
addons/kanban_tasks/view/filter.gd
Normal file
14
addons/kanban_tasks/view/filter.gd
Normal 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
|
||||||
53
addons/kanban_tasks/view/settings/categories/categories.gd
Normal file
53
addons/kanban_tasks/view/settings/categories/categories.gd
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
@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
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
notification(NOTIFICATION_THEME_CHANGED)
|
||||||
|
randomizer.randomize()
|
||||||
|
add_category_button.pressed.connect(__add_category)
|
||||||
|
|
||||||
|
|
||||||
|
func _notification(what: int) -> void:
|
||||||
|
match what:
|
||||||
|
NOTIFICATION_THEME_CHANGED:
|
||||||
|
if is_instance_valid(add_category_button):
|
||||||
|
add_category_button.icon = get_theme_icon(&"Add", &"EditorIcons")
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
65
addons/kanban_tasks/view/settings/categories/categories.tscn
Normal file
65
addons/kanban_tasks/view/settings/categories/categories.tscn
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://b2likgss81t0s"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/kanban_tasks/view/settings/categories/categories.gd" id="1_n36ev"]
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_ig53p"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_w6wkd"]
|
||||||
|
image = SubResource("Image_ig53p")
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gsyou"]
|
||||||
|
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="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
|
||||||
|
icon = SubResource("ImageTexture_w6wkd")
|
||||||
|
|
||||||
|
[node name="PanelContainer" type="PanelContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_gsyou")
|
||||||
|
|
||||||
|
[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:
|
||||||
|
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()
|
||||||
141
addons/kanban_tasks/view/settings/general/general.gd
Normal file
141
addons/kanban_tasks/view/settings/general/general.gd
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
@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
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
172
addons/kanban_tasks/view/settings/general/general.tscn
Normal file
172
addons/kanban_tasks/view/settings/general/general.tscn
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://due07vdflx4o"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/kanban_tasks/view/settings/general/general.gd" id="1_8tblh"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7cqkc"]
|
||||||
|
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="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="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_7cqkc")
|
||||||
|
|
||||||
|
[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
|
||||||
|
button_pressed = true
|
||||||
|
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
|
||||||
|
button_pressed = true
|
||||||
|
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
|
||||||
|
button_pressed = true
|
||||||
|
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
|
||||||
|
item_count = 3
|
||||||
|
selected = 0
|
||||||
|
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
|
||||||
|
value = 2.0
|
||||||
|
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
|
||||||
|
item_count = 3
|
||||||
|
selected = 0
|
||||||
|
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 = "Save"
|
||||||
|
mode_overrides_title = false
|
||||||
28
addons/kanban_tasks/view/settings/settings.gd
Normal file
28
addons/kanban_tasks/view/settings/settings.gd
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
@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
|
||||||
|
|
||||||
|
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))
|
||||||
39
addons/kanban_tasks/view/settings/settings.tscn
Normal file
39
addons/kanban_tasks/view/settings/settings.tscn
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://dh1yunmhipirg"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" 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"]
|
||||||
|
title = "Settings"
|
||||||
|
size = Vector2i(800, 400)
|
||||||
|
visible = true
|
||||||
|
theme_type_variation = &"EditorSettingsDialog"
|
||||||
|
script = ExtResource("1_4eaw3")
|
||||||
|
|
||||||
|
[node name="Settings" type="TabContainer" parent="."]
|
||||||
|
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
|
||||||
|
|
||||||
|
[node name="General" parent="Settings" instance=ExtResource("1_dk7pg")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Categories" parent="Settings" instance=ExtResource("3_iycb0")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Stages" parent="Settings" instance=ExtResource("4_okolg")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
199
addons/kanban_tasks/view/settings/stages/stages.gd
Normal file
199
addons/kanban_tasks/view/settings/stages/stages.gd
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
@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 confirm_not_empty: ConfirmationDialog = %ConfirmNotEmpty
|
||||||
|
@onready var confirm_empty: ConfirmationDialog = %ConfirmEmpty
|
||||||
|
@onready var task_destination: OptionButton = %TaskDestination
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
func _notification(what) -> void:
|
||||||
|
match(what):
|
||||||
|
NOTIFICATION_THEME_CHANGED:
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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.changed.connect(__settings_changed)
|
||||||
144
addons/kanban_tasks/view/settings/stages/stages.tscn
Normal file
144
addons/kanban_tasks/view/settings/stages/stages.tscn
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://dapkpnkm8sow8"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/kanban_tasks/view/settings/stages/stages.gd" id="1_1yycq"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7cqkc"]
|
||||||
|
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
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_16p4g"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_frc8x"]
|
||||||
|
image = SubResource("Image_16p4g")
|
||||||
|
|
||||||
|
[node name="Stages" 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
|
||||||
|
script = ExtResource("1_1yycq")
|
||||||
|
|
||||||
|
[node name="Header" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Edit Stage Layout:"
|
||||||
|
|
||||||
|
[node name="WarnAboutEmptyDeletion" type="CheckBox" parent="Header"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
focus_mode = 0
|
||||||
|
text = "Warn about empty deletion."
|
||||||
|
|
||||||
|
[node name="PanelContainer" type="PanelContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_7cqkc")
|
||||||
|
|
||||||
|
[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="CenterContainer" type="CenterContainer" parent="PanelContainer/ScrollContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Grid" type="HBoxContainer" parent="PanelContainer/ScrollContainer/CenterContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 5
|
||||||
|
|
||||||
|
[node name="ColumnHolder" type="HBoxContainer" parent="PanelContainer/ScrollContainer/CenterContainer/Grid"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 5
|
||||||
|
|
||||||
|
[node name="AddColumn" type="VBoxContainer" parent="PanelContainer/ScrollContainer/CenterContainer/Grid"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 5
|
||||||
|
|
||||||
|
[node name="AddColumn" type="Button" parent="PanelContainer/ScrollContainer/CenterContainer/Grid/AddColumn"]
|
||||||
|
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"]
|
||||||
|
self_modulate = Color(1, 1, 1, 0)
|
||||||
|
custom_minimum_size = Vector2(40, 40)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+"
|
||||||
|
|
||||||
|
[node name="Warning" type="Control" parent="PanelContainer"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="WarningSign" type="Button" parent="PanelContainer/Warning"]
|
||||||
|
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
|
||||||
|
icon = SubResource("ImageTexture_frc8x")
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="ConfirmNotEmpty" type="ConfirmationDialog" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
title = "Delete Stage"
|
||||||
|
size = Vector2i(403, 159)
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="ConfirmNotEmpty"]
|
||||||
|
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"]
|
||||||
|
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_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="ConfirmEmpty" type="ConfirmationDialog" parent="."]
|
||||||
|
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."
|
||||||
120
addons/kanban_tasks/view/shortcuts.gd
Normal file
120
addons/kanban_tasks/view/shortcuts.gd
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
@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:
|
||||||
|
__setup_shortcuts()
|
||||||
|
|
||||||
|
|
||||||
|
func __setup_shortcuts() -> void:
|
||||||
|
# delete
|
||||||
|
var ev_delete := InputEventKey.new()
|
||||||
|
if OS.get_name() == "macOS":
|
||||||
|
ev_delete.keycode = KEY_BACKSPACE
|
||||||
|
ev_delete.meta_pressed = true
|
||||||
|
else:
|
||||||
|
ev_delete.keycode = KEY_DELETE
|
||||||
|
delete.events.append(ev_delete)
|
||||||
|
|
||||||
|
# duplicate
|
||||||
|
var ev_dupe := InputEventKey.new()
|
||||||
|
if OS.get_name() == "macOS":
|
||||||
|
ev_dupe.keycode = KEY_D
|
||||||
|
ev_dupe.meta_pressed = true
|
||||||
|
else:
|
||||||
|
ev_dupe.keycode = KEY_D
|
||||||
|
ev_dupe.ctrl_pressed = true
|
||||||
|
duplicate.events.append(ev_dupe)
|
||||||
|
|
||||||
|
# create
|
||||||
|
var ev_create := InputEventKey.new()
|
||||||
|
if OS.get_name() == "macOS":
|
||||||
|
ev_create.keycode = KEY_A
|
||||||
|
ev_create.meta_pressed = true
|
||||||
|
else:
|
||||||
|
ev_create.keycode = KEY_A
|
||||||
|
ev_create.ctrl_pressed = true
|
||||||
|
create.events.append(ev_create)
|
||||||
|
|
||||||
|
# rename
|
||||||
|
var ev_rename := InputEventKey.new()
|
||||||
|
ev_rename.keycode = KEY_F2
|
||||||
|
rename.events.append(ev_rename)
|
||||||
|
|
||||||
|
# search
|
||||||
|
var ev_search := InputEventKey.new()
|
||||||
|
if OS.get_name() == "macOS":
|
||||||
|
ev_search.keycode = KEY_F
|
||||||
|
ev_search.meta_pressed = true
|
||||||
|
else:
|
||||||
|
ev_search.keycode = KEY_F
|
||||||
|
ev_search.ctrl_pressed = true
|
||||||
|
search.events.append(ev_search)
|
||||||
|
|
||||||
|
# confirm
|
||||||
|
var ev_confirm := InputEventKey.new()
|
||||||
|
ev_confirm.keycode = KEY_ENTER
|
||||||
|
confirm.events.append(ev_confirm)
|
||||||
|
|
||||||
|
# undo
|
||||||
|
var ev_undo := InputEventKey.new()
|
||||||
|
if OS.get_name() == "macOS":
|
||||||
|
ev_undo.keycode = KEY_Z
|
||||||
|
ev_undo.meta_pressed = true
|
||||||
|
else:
|
||||||
|
ev_undo.keycode = KEY_Z
|
||||||
|
ev_undo.ctrl_pressed = true
|
||||||
|
undo.events.append(ev_undo)
|
||||||
|
|
||||||
|
# redo
|
||||||
|
var ev_redo := InputEventKey.new()
|
||||||
|
if OS.get_name() == "macOS":
|
||||||
|
ev_redo.keycode = KEY_Z
|
||||||
|
ev_redo.meta_pressed = true
|
||||||
|
ev_redo.shift_pressed = true
|
||||||
|
else:
|
||||||
|
ev_redo.keycode = KEY_Z
|
||||||
|
ev_redo.ctrl_pressed = true
|
||||||
|
ev_redo.shift_pressed = true
|
||||||
|
redo.events.append(ev_redo)
|
||||||
|
|
||||||
|
# save
|
||||||
|
var ev_save := InputEventKey.new()
|
||||||
|
if OS.get_name() == "macOS":
|
||||||
|
ev_save.keycode = KEY_S
|
||||||
|
ev_save.meta_pressed = true
|
||||||
|
else:
|
||||||
|
ev_save.keycode = KEY_S
|
||||||
|
ev_save.ctrl_pressed = true
|
||||||
|
save.events.append(ev_save)
|
||||||
|
|
||||||
|
# save as
|
||||||
|
var ev_save_as := InputEventKey.new()
|
||||||
|
if OS.get_name() == "macOS":
|
||||||
|
ev_save_as.keycode = KEY_S
|
||||||
|
ev_save_as.meta_pressed = true
|
||||||
|
ev_save_as.shift_pressed = true
|
||||||
|
else:
|
||||||
|
ev_save_as.keycode = KEY_S
|
||||||
|
ev_save_as.ctrl_pressed = true
|
||||||
|
ev_save_as.shift_pressed = true
|
||||||
|
save_as.events.append(ev_save_as)
|
||||||
266
addons/kanban_tasks/view/stage/stage.gd
Normal file
266
addons/kanban_tasks/view/stage/stage.gd
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
@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:
|
||||||
|
match(what):
|
||||||
|
NOTIFICATION_THEME_CHANGED:
|
||||||
|
if is_instance_valid(panel_container):
|
||||||
|
panel_container.add_theme_stylebox_override(&"panel", get_theme_stylebox(&"panel", &"Tree"))
|
||||||
|
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)
|
||||||
|
|
||||||
|
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():
|
||||||
|
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)
|
||||||
138
addons/kanban_tasks/view/stage/stage.tscn
Normal file
138
addons/kanban_tasks/view/stage/stage.tscn
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
[gd_scene load_steps=6 format=3 uid="uid://bjmtdjfx7iqgp"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/kanban_tasks/view/stage/stage.gd" id="1_i5556"]
|
||||||
|
[ext_resource type="Script" 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
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_kabwd"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_yu4gn"]
|
||||||
|
image = SubResource("Image_kabwd")
|
||||||
|
|
||||||
|
[node name="Stage" type="MarginContainer"]
|
||||||
|
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_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"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 5
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="Title" type="VBoxContainer" parent="Panel/VBoxContainer/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 31)
|
||||||
|
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_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Add task."
|
||||||
|
focus_mode = 0
|
||||||
|
toggle_mode = true
|
||||||
|
action_mode = 0
|
||||||
|
icon = SubResource("ImageTexture_yu4gn")
|
||||||
|
|
||||||
|
[node name="HSeparator" type="HSeparator" parent="Panel/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="ScrollContainer" type="ScrollContainer" parent="Panel/VBoxContainer"]
|
||||||
|
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"]
|
||||||
|
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_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"]
|
||||||
|
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_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"]
|
||||||
|
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
addons/kanban_tasks/view/start/start.gd
Normal file
59
addons/kanban_tasks/view/start/start.gd
Normal 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)
|
||||||
103
addons/kanban_tasks/view/start/start.tscn
Normal file
103
addons/kanban_tasks/view/start/start.tscn
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
[gd_scene load_steps=4 format=3 uid="uid://bemcl1rqpeqty"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" 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"
|
||||||
32
addons/kanban_tasks/view/task/autosize_label.gd
Normal file
32
addons/kanban_tasks/view/task/autosize_label.gd
Normal file
@ -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
|
||||||
439
addons/kanban_tasks/view/task/task.gd
Normal file
439
addons/kanban_tasks/view/task/task.gd
Normal file
@ -0,0 +1,439 @@
|
|||||||
|
@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
|
||||||
|
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:
|
||||||
|
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():
|
||||||
|
__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
|
||||||
108
addons/kanban_tasks/view/task/task.tscn
Normal file
108
addons/kanban_tasks/view/task/task.tscn
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
[gd_scene load_steps=8 format=3 uid="uid://ckqrwj5kxr6vl"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/kanban_tasks/view/task/task.gd" id="1_dslv8"]
|
||||||
|
[ext_resource type="Script" path="res://addons/kanban_tasks/edit_label/edit_label.gd" id="2_iitpi"]
|
||||||
|
[ext_resource type="Script" 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" path="res://addons/kanban_tasks/expand_button/expand_button.gd" id="5_sgwao"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3iasq"]
|
||||||
|
bg_color = Color(0.1, 0.1, 0.1, 0.6)
|
||||||
|
border_width_left = 8
|
||||||
|
|
||||||
|
[node name="Task" type="MarginContainer"]
|
||||||
|
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_name_in_owner = true
|
||||||
|
show_behind_parent = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_3iasq")
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="Panel"]
|
||||||
|
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"]
|
||||||
|
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"]
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/HBoxContainer/MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="CategoryButton" type="Button" parent="Panel/HBoxContainer/MarginContainer/VBoxContainer/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
focus_mode = 0
|
||||||
|
|
||||||
|
[node name="Title" type="VBoxContainer" parent="Panel/HBoxContainer/MarginContainer/VBoxContainer/HBoxContainer"]
|
||||||
|
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_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" 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
|
||||||
|
steps_focus_mode = null
|
||||||
|
|
||||||
|
[node name="ExpandButton" type="Button" parent="Panel/HBoxContainer/MarginContainer/VBoxContainer"]
|
||||||
|
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_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
focus_mode = 0
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="ContextMenu" type="PopupMenu" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
allow_search = false
|
||||||
|
|
||||||
|
[node name="Details" parent="." instance=ExtResource("3_2ol5j")]
|
||||||
|
unique_name_in_owner = true
|
||||||
47
addons/kanban_tasks/view/tooltip.gd
Normal file
47
addons/kanban_tasks/view/tooltip.gd
Normal 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
|
||||||
40
kanban_tasks_data.kanban
Normal file
40
kanban_tasks_data.kanban
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"uuid": "a76a310c-9417-4a0b-b02a-64864c7dc07a",
|
||||||
|
"title": "Task",
|
||||||
|
"color": "70bafa"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stages": [
|
||||||
|
{
|
||||||
|
"uuid": "7014ecb9-b7e4-4734-abd1-0897d32d6bc0",
|
||||||
|
"title": "Todo",
|
||||||
|
"tasks": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "386be904-378d-42e9-91c2-6cd2254eb98e",
|
||||||
|
"title": "Doing",
|
||||||
|
"tasks": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "80a2bc87-1518-4944-aeff-68be5c9fc264",
|
||||||
|
"title": "Done",
|
||||||
|
"tasks": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tasks": [],
|
||||||
|
"layout": {
|
||||||
|
"columns": [
|
||||||
|
[
|
||||||
|
"7014ecb9-b7e4-4734-abd1-0897d32d6bc0"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"386be904-378d-42e9-91c2-6cd2254eb98e"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"80a2bc87-1518-4944-aeff-68be5c9fc264"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,6 +21,10 @@ window/size/viewport_height=720
|
|||||||
window/stretch/mode="canvas_items"
|
window/stretch/mode="canvas_items"
|
||||||
window/handheld/orientation=1
|
window/handheld/orientation=1
|
||||||
|
|
||||||
|
[editor_plugins]
|
||||||
|
|
||||||
|
enabled=PackedStringArray("res://addons/kanban_tasks/plugin.cfg")
|
||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
renderer/rendering_method="mobile"
|
renderer/rendering_method="mobile"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user