add kanban

This commit is contained in:
algodoogle
2026-07-16 15:52:34 +01:00
parent 329bb1f984
commit 5e0b58a984
94 changed files with 5384 additions and 1 deletions
+225
View File
@@ -0,0 +1,225 @@
@tool
extends AcceptDialog
const __BoardData := preload("../../data/board.gd")
const __StepData := preload("../../data/step.gd")
const __StepEntry := preload("../details/step_entry.gd")
const __Singletons := preload("../../plugin_singleton/singletons.gd")
const __EditContext := preload("../edit_context.gd")
var board_data: __BoardData
var data_uuid: String
var __step_data: __StepData
@onready var category_select: OptionButton = %Category
@onready var h_split_container: HSplitContainer = %HSplitContainer
@onready var description_edit: TextEdit = %Description
@onready var step_holder: VBoxContainer = %StepHolder
@onready var steps_panel_container: PanelContainer = %PanelContainer
@onready var create_step_edit: LineEdit = %CreateStepEdit
@onready var step_details: VBoxContainer = %StepDetails
@onready var close_step_details_button: Button = %CloseStepDetails
@onready var step_edit: TextEdit = %StepEdit
func _ready() -> void:
about_to_popup.connect(__on_about_to_popup)
create_step_edit.text_submitted.connect(__create_step)
close_step_details_button.pressed.connect(__close_step_details)
notification(NOTIFICATION_THEME_CHANGED)
step_holder.entry_action_triggered.connect(__on_step_action_triggered)
step_holder.entry_move_requesed.connect(__step_move_requesed)
visibility_changed.connect(__save_internal_state)
func _notification(what: int) -> void:
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
if is_instance_valid(steps_panel_container):
steps_panel_container.add_theme_stylebox_override(&"panel", get_theme_stylebox(&"panel", &"Panel"))
if is_instance_valid(create_step_edit):
create_step_edit.right_icon = get_theme_icon(&"Add", &"EditorIcons")
if is_instance_valid(close_step_details_button):
close_step_details_button.icon = get_theme_icon(&"Close", &"EditorIcons")
func update() -> void:
if description_edit.text_changed.is_connected(__on_description_changed):
description_edit.text_changed.disconnect(__on_description_changed)
if description_edit.text != board_data.get_task(data_uuid).description:
description_edit.text = board_data.get_task(data_uuid).description
description_edit.text_changed.connect(__on_description_changed)
title = "Task Details: " + board_data.get_task(data_uuid).title
if category_select.item_selected.is_connected(__on_category_selected):
category_select.item_selected.disconnect(__on_category_selected)
category_select.clear()
for uuid in board_data.get_categories():
var i = Image.create(16, 16, false, Image.FORMAT_RGB8)
i.fill(board_data.get_category(uuid).color)
var t = ImageTexture.create_from_image(i)
category_select.add_icon_item(t, board_data.get_category(uuid).title)
category_select.set_item_metadata(-1, uuid)
if uuid == board_data.get_task(data_uuid).category:
category_select.select(category_select.item_count - 1)
category_select.item_selected.connect(__on_category_selected)
step_holder.clear_steps()
for step in board_data.get_task(data_uuid).steps:
step_holder.add_step(step)
for entry in step_holder.get_step_entries():
entry.being_edited = (entry.step_data == __step_data)
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
step_details.visible = is_instance_valid(__step_data)
description_edit.visible = not (ctx.settings.edit_step_details_exclusively and is_instance_valid(__step_data))
if is_instance_valid(__step_data):
if step_edit.text_changed.is_connected(__on_step_details_changed):
step_edit.text_changed.disconnect(__on_step_details_changed)
step_edit.text = __step_data.details
step_edit.text_changed.connect(__on_step_details_changed)
# Workaround for godotengine/godot#70451
func popup_centered_ratio_no_fullscreen(ratio: float = 0.8) -> void:
var viewport: Viewport = get_parent().get_viewport()
popup(Rect2i(Vector2(viewport.position) + viewport.size / 2.0 - viewport.size * ratio / 2.0, viewport.size * ratio))
func edit_step_details(step: __StepData) -> void:
if is_instance_valid(__step_data):
__step_data.changed.disconnect(update)
__step_data = step
__step_data.changed.connect(update)
update()
step_edit.set_caret_line(step_edit.get_line_count())
step_edit.set_caret_column(len(step_edit.get_line(step_edit.get_line_count() - 1)))
step_edit.grab_focus.call_deferred()
func move_step_up(step: __StepData) -> void:
var steps = board_data.get_task(data_uuid).steps
if step in steps and steps[0] != step:
var index = steps.find(step)
steps.erase(step)
steps.insert(index - 1, step)
board_data.get_task(data_uuid).steps = steps
update()
func move_step_down(step: __StepData) -> void:
var steps = board_data.get_task(data_uuid).steps
if step in steps and steps[-1] != step:
var index = steps.find(step)
steps.erase(step)
steps.insert(index + 1, step)
board_data.get_task(data_uuid).steps = steps
update()
func delete_step(step: __StepData) -> void:
close_step_details(step)
var steps = board_data.get_task(data_uuid).steps
if step in steps:
steps.erase(step)
board_data.get_task(data_uuid).steps = steps
update()
func close_step_details(step: __StepData) -> void:
if __step_data == step:
__close_step_details()
func __on_step_action_triggered(entry: __StepEntry, action: __StepEntry.Actions) -> void:
match action:
__StepEntry.Actions.EDIT_HARD:
edit_step_details(entry.step_data)
__StepEntry.Actions.EDIT_SOFT:
if is_instance_valid(__step_data):
edit_step_details(entry.step_data)
__StepEntry.Actions.CLOSE:
close_step_details(entry.step_data)
__StepEntry.Actions.DELETE:
delete_step(entry.step_data)
__StepEntry.Actions.MOVE_UP:
move_step_up(entry.step_data)
__StepEntry.Actions.MOVE_DOWN:
move_step_down(entry.step_data)
func __step_move_requesed(moved_entry: __StepEntry, target_entry: __StepEntry, move_after_target: bool) -> void:
var steps = board_data.get_task(data_uuid).steps
var moved_idx = steps.find(moved_entry.step_data)
var target_idx = steps.find(target_entry.step_data)
if moved_idx < 0 or target_idx < 0 or moved_idx == target_idx:
return
steps.erase(moved_entry.step_data)
if moved_idx < target_idx:
target_idx -= 1
if move_after_target:
steps.insert(target_idx + 1, moved_entry.step_data)
else:
steps.insert(target_idx, moved_entry.step_data)
board_data.get_task(data_uuid).steps = steps
update()
func __load_internal_state() -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
if ctx.settings.internal_states.has("details_editor_step_holder_width"):
h_split_container.split_offset = ctx.settings.internal_states["details_editor_step_holder_width"]
func __save_internal_state() -> void:
if not visible:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
ctx.settings.set_internal_state("details_editor_step_holder_width", h_split_container.split_offset)
func __close_step_details() -> void:
__step_data.changed.disconnect(update)
__step_data = null
update()
func __on_step_details_changed() -> void:
if __step_data.changed.is_connected(update):
__step_data.changed.disconnect(update)
__step_data.details = step_edit.text
__step_data.changed.connect(update)
func __on_about_to_popup() -> void:
if is_instance_valid(__step_data):
__close_step_details()
update()
__load_internal_state()
if board_data.get_task(data_uuid).description.is_empty():
description_edit.grab_focus.call_deferred()
func __on_description_changed() -> void:
board_data.get_task(data_uuid).description = description_edit.text
func __on_category_selected(index: int) -> void:
board_data.get_task(data_uuid).category = category_select.get_item_metadata(index)
func __create_step(text: String) -> void:
if text.is_empty():
return
var task = board_data.get_task(data_uuid)
var data = __StepData.new(text)
task.add_step(data)
create_step_edit.text = ""
update()
for step in step_holder.get_step_entries():
if step.step_data == data:
step.grab_focus.call_deferred()
@@ -0,0 +1 @@
uid://cfuka8bfmcwog
@@ -0,0 +1,100 @@
[gd_scene format=3 uid="uid://bwi22eyrmeeet"]
[ext_resource type="Script" uid="uid://cfuka8bfmcwog" path="res://addons/kanban_tasks/view/details/details.gd" id="1_gh7s6"]
[ext_resource type="PackedScene" uid="uid://dwjg5vyxx4g48" path="res://addons/kanban_tasks/view/details/step_holder.tscn" id="2_0ptaf"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g2k57"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 5.0
bg_color = Color(0.1, 0.1, 0.1, 0.6)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[node name="Details" type="AcceptDialog" unique_id=537054767]
oversampling_override = 1.0
title = "Task Details"
position = Vector2i(0, 36)
size = Vector2i(916, 557)
ok_button_text = "Close"
script = ExtResource("1_gh7s6")
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1730906135]
custom_minimum_size = Vector2(900, 500)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
grow_horizontal = 2
grow_vertical = 2
[node name="Category" type="OptionButton" parent="VBoxContainer" unique_id=879562296]
unique_name_in_owner = true
layout_mode = 2
[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer" unique_id=1738893748]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[node name="VSplitContainer" type="VSplitContainer" parent="VBoxContainer/HSplitContainer" unique_id=1951989716]
layout_mode = 2
size_flags_horizontal = 3
[node name="Description" type="TextEdit" parent="VBoxContainer/HSplitContainer/VSplitContainer" unique_id=2088816932]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
wrap_mode = 1
[node name="StepDetails" type="VBoxContainer" parent="VBoxContainer/HSplitContainer/VSplitContainer" unique_id=1125161346]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails" unique_id=2007120957]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails/HBoxContainer" unique_id=609616893]
layout_mode = 2
size_flags_horizontal = 3
text = "Step Details:"
[node name="CloseStepDetails" type="Button" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails/HBoxContainer" unique_id=1389055250]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Close"
flat = true
[node name="StepEdit" type="TextEdit" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails" unique_id=2005028969]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
wrap_mode = 1
[node name="StepList" type="VBoxContainer" parent="VBoxContainer/HSplitContainer" unique_id=1447925866]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
[node name="CreateStepEdit" type="LineEdit" parent="VBoxContainer/HSplitContainer/StepList" unique_id=479211446]
unique_name_in_owner = true
layout_mode = 2
placeholder_text = "Create Step"
[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/HSplitContainer/StepList" unique_id=1566602760]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_g2k57")
[node name="StepHolder" parent="VBoxContainer/HSplitContainer/StepList/PanelContainer" unique_id=128405745 instance=ExtResource("2_0ptaf")]
unique_name_in_owner = true
layout_mode = 2
steps_focus_mode = 2
@@ -0,0 +1,135 @@
@tool
extends HBoxContainer
## Visual representation of a step.
signal action_triggered(entry: __StepEntry, action: Actions)
const __EditLabel := preload("../../edit_label/edit_label.gd")
const __StepData := preload("../../data/step.gd")
const __Singletons := preload("../../plugin_singleton/singletons.gd")
const __Shortcuts := preload("../shortcuts.gd")
const __StepEntry := preload("step_entry.gd")
enum Actions {
DELETE,
MOVE_UP,
MOVE_DOWN,
EDIT_HARD, ## Forces the step details to open.
EDIT_SOFT, ## Only switches to this step if the details are opened.
CLOSE,
}
@export var context_menu_enabled: bool = true
var done: CheckBox
var title_label: Label
var focus_box: StyleBoxFlat
var context_menu: PopupMenu
var step_data: __StepData
var being_edited := false
func _ready() -> void:
set_h_size_flags(SIZE_EXPAND_FILL)
context_menu = PopupMenu.new()
context_menu.id_pressed.connect(__action)
add_child(context_menu)
done = CheckBox.new()
done.focus_mode = Control.FOCUS_NONE
done.toggled.connect(__set_done)
add_child(done)
title_label = Label.new()
title_label.set_h_size_flags(SIZE_EXPAND_FILL)
title_label.text = step_data.details
title_label.max_lines_visible = 1
title_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
add_child(title_label)
focus_box = StyleBoxFlat.new()
focus_box.bg_color = Color(1, 1, 1, 0.1)
notification(NOTIFICATION_THEME_CHANGED)
step_data.changed.connect(update)
update()
func _shortcut_input(event: InputEvent) -> void:
if not __Shortcuts.should_handle_shortcut(self):
return
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
if not event.is_echo() and event.is_pressed():
if shortcuts.rename.matches_event(event):
get_viewport().set_input_as_handled()
__action(Actions.EDIT_HARD)
elif shortcuts.confirm.matches_event(event):
get_viewport().set_input_as_handled()
done.button_pressed = not done.button_pressed
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT:
accept_event()
if context_menu_enabled:
__update_context_menu()
context_menu.position = get_global_mouse_position()
if not get_window().gui_embed_subwindows:
context_menu.position += get_window().position
context_menu.popup()
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed() and event.is_double_click():
__action(Actions.EDIT_HARD)
func _notification(what) -> void:
match(what):
NOTIFICATION_DRAW:
if has_focus() or being_edited:
focus_box.draw(get_canvas_item(), Rect2(Vector2.ZERO, get_rect().size))
NOTIFICATION_FOCUS_ENTER:
__action(Actions.EDIT_SOFT)
func update() -> void:
tooltip_text = step_data.details
done.set_pressed_no_signal(step_data.done)
title_label.text = step_data.details
func __action(what: Actions) -> void:
action_triggered.emit(self, what)
func __update_context_menu() -> void:
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
context_menu.clear()
context_menu.size = Vector2.ZERO
if being_edited:
context_menu.add_icon_item(get_theme_icon(&"Close", &"EditorIcons"), "Close", Actions.CLOSE)
else:
context_menu.add_icon_item(get_theme_icon(&"Rename", &"EditorIcons"), "Edit", Actions.EDIT_HARD)
context_menu.set_item_shortcut(context_menu.get_item_index(Actions.EDIT_HARD), shortcuts.rename)
context_menu.add_icon_item(get_theme_icon(&"MoveUp", &"EditorIcons"), "Move Up", Actions.MOVE_UP)
context_menu.set_item_disabled(context_menu.get_item_index(Actions.MOVE_UP), get_index() == 0)
context_menu.add_icon_item(get_theme_icon(&"MoveDown", &"EditorIcons"), "Move Down", Actions.MOVE_DOWN)
context_menu.set_item_disabled(context_menu.get_item_index(Actions.MOVE_DOWN), get_index() == get_parent().get_child_count() - 1)
context_menu.add_separator()
context_menu.add_icon_item(get_theme_icon(&"Remove", &"EditorIcons"), "Delete", Actions.DELETE)
func __set_done(done: bool) -> void:
__action(Actions.CLOSE)
step_data.done = done
@@ -0,0 +1 @@
uid://cdyn8h04v5qqh
@@ -0,0 +1,190 @@
@tool
extends VBoxContainer
signal entry_action_triggered(entry: __StepEntry, action: __StepEntry.Actions)
signal entry_move_requesed(moved_entry: __StepEntry, target_entry: __StepEntry, move_after_target: bool)
const __StepData := preload("../../data/step.gd")
const __StepEntry := preload("../details/step_entry.gd")
@export var scrollable: bool = true:
set(value):
if value != scrollable:
scrollable = value
__update_children_settings()
@export var steps_can_be_removed: bool = true:
set(value):
if value != steps_can_be_removed:
steps_can_be_removed = value
__update_children_settings()
@export var steps_can_be_reordered: bool = true:
set(value):
if value != steps_can_be_reordered:
steps_can_be_reordered = value
__update_children_settings()
@export var steps_have_context_menu: bool = true:
set(value):
if value != steps_have_context_menu:
steps_have_context_menu = value
__update_children_settings()
@export var steps_focus_mode := FocusMode.FOCUS_NONE:
set(value):
if value != steps_focus_mode:
steps_focus_mode = value
__update_children_settings()
var __mouse_entered_step_list: bool = false
var __move_target_entry: __StepEntry = null
var __move_after_target: bool = false
@onready var __scroll_container: ScrollContainer = %ScrollContainer
@onready var __remove_separator: HSeparator = %RemoveSeparator
@onready var __step_list: VBoxContainer = %StepList
@onready var __remove_area: Button = %RemoveArea
func _ready() -> void:
__step_list.draw.connect(__on_step_list_draw)
__step_list.mouse_exited.connect(__on_step_list_mouse_exited)
__step_list.mouse_entered.connect(__on_step_list_mouse_entered)
notification(NOTIFICATION_THEME_CHANGED)
__update_children_settings()
func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
if not steps_can_be_removed and not steps_can_be_reordered:
return false
if data is __StepEntry:
if __remove_area.get_global_rect().has_point(get_global_transform() * at_position):
return true
__update_move_target(at_position)
return (__move_target_entry != null)
return false
func _get_drag_data(at_position: Vector2) -> Variant:
if not steps_can_be_removed and not steps_can_be_reordered:
return null
for entry in get_step_entries():
if entry.get_global_rect().has_point(get_global_transform() * at_position):
var preview := Label.new()
preview.text = entry.step_data.details
set_drag_preview(preview)
return entry
return null
func _drop_data(at_position: Vector2, data: Variant) -> void:
if __move_target_entry != null:
entry_move_requesed.emit(data, __move_target_entry, __move_after_target)
__move_target_entry = null
if data is __StepEntry:
if __remove_area.get_global_rect().has_point(get_global_transform() * at_position):
data.__action(__StepEntry.Actions.DELETE)
func _notification(what: int) -> void:
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
if is_instance_valid(__remove_area):
__remove_area.icon = get_theme_icon(&"Remove", &"EditorIcons")
func add_step(step: __StepData) -> void:
var entry = __StepEntry.new()
entry.step_data = step
entry.show_behind_parent = true
entry.size_flags_horizontal = Control.SIZE_EXPAND_FILL
__step_list.add_child(entry)
entry.size_flags_horizontal = Control.SIZE_EXPAND_FILL
entry.action_triggered.connect(__on_entry_action_triggered)
entry.context_menu_enabled = steps_have_context_menu
entry.focus_mode = steps_focus_mode
func clear_steps() -> void:
for step in get_step_entries():
__step_list.remove_child(step)
step.queue_free()
func get_step_entries() -> Array[__StepEntry]:
var step_entries: Array[__StepEntry] = []
if is_instance_valid(__step_list):
for child in __step_list.get_children():
if child is __StepEntry:
step_entries.append(child)
return step_entries
func __update_children_settings() -> void:
if is_instance_valid(__scroll_container):
__scroll_container.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO if scrollable else ScrollContainer.SCROLL_MODE_DISABLED
if is_instance_valid(__remove_separator):
__remove_separator.visible = steps_can_be_removed
if is_instance_valid(__remove_area):
__remove_area.visible = steps_can_be_removed
for entry in get_step_entries():
entry.context_menu_enabled = steps_have_context_menu
entry.focus_mode = steps_focus_mode
func __update_move_target(at_position: Vector2) -> void:
var at_global_position := get_global_transform() * at_position
# This __mouse_entered_step_list is needed here, as this seemed to be the only reliable solution, as:
# 1) something is NOK with transforming at_position to global and compare with step_list.global_rect
# 2) cannot decide what is the visible rect of the step_list
# 3) _can_drop_data was called even after mouse is outside the list (to the bottom direction)
if __mouse_entered_step_list:
var closes_entry: __StepEntry = null
var smallest_distance: float
var position_is_after_closes_entry: bool
for e in get_step_entries():
var entry_global_rect = e.get_global_rect()
var distance := abs(at_global_position.y - entry_global_rect.position.y)
if closes_entry == null or distance < smallest_distance:
closes_entry = e
smallest_distance = distance
position_is_after_closes_entry = false
distance = abs(at_global_position.y - entry_global_rect.end.y)
if closes_entry == null or distance < smallest_distance:
closes_entry = e
smallest_distance = distance
position_is_after_closes_entry = true
__move_target_entry = closes_entry
__move_after_target = position_is_after_closes_entry
else:
__move_target_entry = null
__step_list.queue_redraw()
func __on_step_list_mouse_entered() -> void:
__mouse_entered_step_list = true
func __on_step_list_mouse_exited() -> void:
__mouse_entered_step_list = false
__update_move_target(get_local_mouse_position())
func __on_step_list_draw() -> void:
if __move_target_entry != null:
var target_rect := __step_list.get_global_transform().inverse() * __move_target_entry.get_global_rect()
var separation = __step_list.get_theme_constant(&"separation")
var preview_rect := Rect2(
Vector2(0, target_rect.end.y if __move_after_target else target_rect.position.y - separation),
Vector2(target_rect.size.x, separation)
)
if preview_rect.position.y < 0:
preview_rect.position.y = 0
if preview_rect.end.y > __step_list.size.y:
preview_rect.position.y -= (preview_rect.end.y - __step_list.size.y)
__step_list.draw_rect(preview_rect, get_theme_color(&"step_move_review_color"))
func __on_entry_action_triggered(entry, action) -> void:
entry_action_triggered.emit(entry, action)
@@ -0,0 +1 @@
uid://dw1afc1ntflyw
@@ -0,0 +1,45 @@
[gd_scene format=3 uid="uid://dwjg5vyxx4g48"]
[ext_resource type="Script" uid="uid://dw1afc1ntflyw" path="res://addons/kanban_tasks/view/details/step_holder.gd" id="1_exd17"]
[sub_resource type="Theme" id="Theme_1hs0w"]
StepHolder/base_type = &"VBoxContainer"
StepHolder/colors/step_move_review_color = Color(0.439216, 0.729412, 0.980392, 0.501961)
[node name="StepHolder" type="VBoxContainer" unique_id=1736962155]
offset_right = 326.0
offset_bottom = 500.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme = SubResource("Theme_1hs0w")
theme_type_variation = &"StepHolder"
script = ExtResource("1_exd17")
[node name="ScrollContainer" type="ScrollContainer" parent="." unique_id=1852819234]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
horizontal_scroll_mode = 0
metadata/_edit_use_anchors_ = true
[node name="StepList" type="VBoxContainer" parent="ScrollContainer" unique_id=643748775]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="RemoveSeparator" type="HSeparator" parent="." unique_id=106663436]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/separation = 0
[node name="RemoveArea" type="Button" parent="." unique_id=1184154065]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 40)
layout_mode = 2
size_flags_vertical = 8
focus_mode = 0
mouse_filter = 2
button_mask = 0
flat = true
icon_alignment = 1