Skip to content

Commit a7ab6c0

Browse files
authored
Add Category Tabs Mode (#231)
1 parent 95b4145 commit a7ab6c0

File tree

3 files changed

+306
-15
lines changed

3 files changed

+306
-15
lines changed

addons/pandora/settings/pandora_settings.gd

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ const DEFAULT_PANDORA_DEFINITIONS_DIR: StringName = "res://pandora/"
2323
const SETTINGS_PANDORA_EXTENSIONS_DIR: StringName = CATEGORY_CONFIG + "/extensions"
2424
const DEFAULT_PANDORA_EXTENSIONS_DIR: Array[StringName] = ["res://pandora/extensions"]
2525

26+
const SETTING_USE_CATEGORY_TABS: StringName = CATEGORY_CONFIG + "/use_category_tabs"
27+
const DEFAULT_USE_CATEGORY_TABS: bool = false
28+
2629
static var extensions_models: Dictionary[String, RefCounted] = {}
2730
static var extensions_types: Dictionary[String, String] = {}
2831

@@ -56,6 +59,12 @@ static func initialize() -> void:
5659
PROPERTY_HINT_DIR
5760
)
5861

62+
init_setting(
63+
SETTING_USE_CATEGORY_TABS,
64+
DEFAULT_USE_CATEGORY_TABS,
65+
TYPE_BOOL
66+
)
67+
5968
static func init_setting(
6069
name: String,
6170
default: Variant,
@@ -106,14 +115,23 @@ static func set_definitions_dir(path: StringName) -> void:
106115

107116
static func get_extensions_dirs() -> Array:
108117
return ProjectSettings.get_setting(
109-
SETTINGS_PANDORA_EXTENSIONS_DIR,
118+
SETTINGS_PANDORA_EXTENSIONS_DIR,
110119
DEFAULT_PANDORA_EXTENSIONS_DIR
111120
)
112121

113122
static func set_extensions_dir(array: Array) -> void:
114123
ProjectSettings.set_setting(SETTINGS_PANDORA_EXTENSIONS_DIR, array)
115124
_check_new_extensions_models()
116125

126+
static func get_use_category_tabs() -> bool:
127+
return ProjectSettings.get_setting(
128+
SETTING_USE_CATEGORY_TABS,
129+
DEFAULT_USE_CATEGORY_TABS
130+
)
131+
132+
static func set_use_category_tabs(enabled: bool) -> void:
133+
ProjectSettings.set_setting(SETTING_USE_CATEGORY_TABS, enabled)
134+
117135
static func _check_new_extensions_models() -> void:
118136
var extensions_dirs = PandoraSettings.get_extensions_dirs()
119137
for extensions_dir in extensions_dirs:

addons/pandora/ui/editor/pandora_editor.gd

Lines changed: 245 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@ class_name PandoraEditor extends Control
1515
@onready var save_label = %SaveLabel
1616
@onready var import_dialog = %ImportDialog
1717
@onready var progress_bar = %ProgressBar
18+
@onready var category_tab_container: TabContainer = %CategoryTabContainer
19+
@onready var tree_scroll_container: ScrollContainer = %TreeScrollContainer
1820

1921
@onready var data_content = %DataContent
2022
@onready var error_content = %ErrorContent
2123

2224
var selected_entity: PandoraEntity
2325
var _load_error = false
26+
var _current_tab_category: PandoraCategory = null
27+
var tab_context_menu: PopupMenu
2428

2529

2630
func _ready() -> void:
@@ -59,6 +63,18 @@ func _ready() -> void:
5963
EditorInterface.get_file_system_dock().file_removed.connect(_handle_file_deleted)
6064
EditorInterface.get_file_system_dock().files_moved.connect(_handle_file_moved)
6165

66+
# Listen for settings changes
67+
ProjectSettings.settings_changed.connect(_on_settings_changed)
68+
69+
# Tab context menu
70+
tab_context_menu = PopupMenu.new()
71+
tab_context_menu.add_item("Rename", 0)
72+
tab_context_menu.add_separator()
73+
tab_context_menu.add_item("Delete", 1)
74+
tab_context_menu.id_pressed.connect(_on_tab_context_menu_pressed)
75+
add_child(tab_context_menu)
76+
77+
6278
func reattempt_load_on_error() -> void:
6379
if _load_error:
6480
_reset_to_saved_file()
@@ -94,15 +110,29 @@ func _on_inherited_property_selected(category_id: String, property_name: String)
94110

95111

96112
func _create_entity() -> void:
97-
if not selected_entity is PandoraCategory:
113+
var use_tabs = PandoraSettings.get_use_category_tabs()
114+
115+
if use_tabs and _current_tab_category != null:
116+
# In tab mode: create entity in the current tab's category
117+
Pandora.create_entity("New Entity", _current_tab_category)
118+
elif not selected_entity is PandoraCategory:
98119
return
99-
Pandora.create_entity("New Entity", selected_entity)
120+
else:
121+
# Default: create in selected category
122+
Pandora.create_entity("New Entity", selected_entity)
100123

101124

102125
func _create_category() -> void:
103-
if not selected_entity is PandoraCategory:
126+
var use_tabs = PandoraSettings.get_use_category_tabs()
127+
128+
if use_tabs and _current_tab_category != null:
129+
# In tab mode: create subcategory under current tab
130+
Pandora.create_category("New Category", _current_tab_category)
131+
elif not selected_entity is PandoraCategory:
132+
# No selection: create root category
104133
Pandora.create_category("New Category")
105134
else:
135+
# Default: create subcategory under selected
106136
Pandora.create_category("New Category", selected_entity)
107137

108138

@@ -124,15 +154,99 @@ func _populate_data() -> void:
124154

125155
var data: Array[PandoraEntity] = []
126156
data.assign(Pandora.get_all_roots())
127-
tree.set_data(data)
157+
158+
var use_tabs = PandoraSettings.get_use_category_tabs()
159+
160+
if use_tabs:
161+
await _populate_tabs(data)
162+
else:
163+
tree.set_data(data)
128164

129165
if not Pandora.data_loaded.is_connected(_populate_data):
130166
Pandora.data_loaded.connect(_populate_data)
131167

132-
create_entity_button.disabled = true
133168
create_category_button.disabled = false
134169
regenerate_id_button.disabled = true
135170
delete_button.disabled = true
171+
create_entity_button.disabled = not use_tabs
172+
173+
category_tab_container.visible = use_tabs
174+
create_category_button.visible = not use_tabs
175+
176+
177+
func _populate_tabs(root_categories: Array[PandoraEntity], select_tab_index: int = 0) -> void:
178+
if category_tab_container.tab_changed.is_connected(_on_tab_changed):
179+
category_tab_container.tab_changed.disconnect(_on_tab_changed)
180+
# Clear existing tabs
181+
for child in category_tab_container.get_children():
182+
child.queue_free()
183+
184+
await get_tree().process_frame
185+
186+
# Create tabs for root categories
187+
for i in range(root_categories.size()):
188+
var tab_content = Control.new()
189+
category_tab_container.add_child(tab_content)
190+
category_tab_container.set_tab_title(i, root_categories[i].get_entity_name())
191+
192+
# Add '+' tab for creating new root categories
193+
var add_tab_content = Control.new()
194+
add_tab_content.name = "+"
195+
category_tab_container.add_child(add_tab_content)
196+
197+
# Connect tab change signal to filter the tree
198+
if not category_tab_container.tab_changed.is_connected(_on_tab_changed):
199+
category_tab_container.tab_changed.connect(_on_tab_changed)
200+
201+
# Connect right-click on tab bar to show context menu
202+
var tab_bar = category_tab_container.get_tab_bar()
203+
if not tab_bar.gui_input.is_connected(_on_tab_bar_input):
204+
tab_bar.gui_input.connect(_on_tab_bar_input)
205+
206+
# Store root categories
207+
category_tab_container.set_meta("root_categories", root_categories)
208+
209+
# Select tab
210+
if root_categories.size() > 0:
211+
category_tab_container.current_tab = select_tab_index
212+
_on_tab_changed(select_tab_index)
213+
214+
215+
func _on_tab_changed(tab_index: int) -> void:
216+
var root_categories = category_tab_container.get_meta("root_categories", []) as Array
217+
218+
# Create new root category if '+' tab is selected
219+
if tab_index >= root_categories.size():
220+
_create_root_category()
221+
return
222+
223+
if tab_index < 0:
224+
return
225+
226+
# Show selected category
227+
var selected_category = root_categories[tab_index]
228+
_current_tab_category = selected_category
229+
230+
# Filter tree to show only entities in the selected category
231+
var filtered_data: Array[PandoraEntity] = []
232+
if selected_category is PandoraCategory:
233+
filtered_data.assign(selected_category._children)
234+
235+
tree.set_data(filtered_data)
236+
237+
# Show the parent category's properties in the property editor
238+
property_editor.set_entity(selected_category)
239+
selected_entity = selected_category
240+
241+
242+
func _create_root_category() -> void:
243+
Pandora.create_category("New Category")
244+
245+
var data: Array[PandoraEntity] = []
246+
data.assign(Pandora.get_all_roots())
247+
248+
# Select the newly created tab
249+
_populate_tabs(data, data.size() - 1)
136250

137251

138252
func _save() -> void:
@@ -155,7 +269,17 @@ func _reset_to_saved_file() -> void:
155269
Pandora.load_data()
156270
var data: Array[PandoraEntity] = []
157271
data.assign(Pandora.get_all_roots())
158-
tree.set_data(data)
272+
273+
var use_tabs = PandoraSettings.get_use_category_tabs()
274+
if use_tabs:
275+
category_tab_container.visible = true
276+
create_category_button.visible = false
277+
await _populate_tabs(data)
278+
else:
279+
category_tab_container.visible = false
280+
create_category_button.visible = true
281+
tree.set_data(data)
282+
159283
create_entity_button.disabled = true
160284
create_category_button.disabled = false
161285
regenerate_id_button.disabled = true
@@ -207,7 +331,7 @@ func _handle_file_moved(old_path: String, new_path: String) -> void:
207331
for entity in entities:
208332
if entity.get_icon_path() == old_path:
209333
entity._icon_path = new_path
210-
334+
211335
# Handle properties which we can't automatically update,
212336
# like strings. We don't need to update resources as they
213337
# are automatically updated by the engine.
@@ -219,12 +343,124 @@ func _handle_file_moved(old_path: String, new_path: String) -> void:
219343
if property.get_property_type()._type_name == "string" && property._default_value == old_path:
220344
property._default_value = new_path
221345
await Engine.get_main_loop().process_frame
222-
346+
223347
Pandora.save_data()
224348
_populate_data.call_deferred()
225349
for property in property_editor.property_list.get_children():
226350
property._refresh()
227351

228352
func _handle_file_deleted(file: String) -> void:
229353
await _handle_file_moved(file, "")
230-
354+
355+
356+
func _on_tab_bar_input(event: InputEvent) -> void:
357+
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
358+
var tab_bar = category_tab_container.get_tab_bar()
359+
var clicked_tab = tab_bar.get_tab_idx_at_point(event.position)
360+
361+
if clicked_tab >= 0:
362+
var root_categories = category_tab_container.get_meta("root_categories", []) as Array
363+
364+
# Don't show menu for '+' tab
365+
if clicked_tab >= root_categories.size():
366+
return
367+
368+
# Store which tab was clicked
369+
category_tab_container.set_meta("context_menu_tab", clicked_tab)
370+
371+
# Show context menu
372+
tab_context_menu.position = tab_bar.get_screen_position() + event.position
373+
tab_context_menu.popup()
374+
375+
376+
func _on_tab_context_menu_pressed(id: int) -> void:
377+
var clicked_tab = category_tab_container.get_meta("context_menu_tab", -1)
378+
if clicked_tab < 0:
379+
return
380+
381+
var root_categories = category_tab_container.get_meta("root_categories", []) as Array
382+
if clicked_tab >= root_categories.size():
383+
return
384+
385+
var category = root_categories[clicked_tab]
386+
387+
match id:
388+
0: # Rename
389+
_show_rename_dialog(category, clicked_tab)
390+
1: # Delete
391+
_show_delete_confirmation(category, clicked_tab)
392+
393+
394+
func _show_rename_dialog(category: PandoraCategory, tab_index: int) -> void:
395+
var dialog = AcceptDialog.new()
396+
dialog.title = "Rename Category"
397+
dialog.dialog_text = "Enter new name:"
398+
399+
var line_edit = LineEdit.new()
400+
line_edit.text = category.get_entity_name()
401+
line_edit.select_all()
402+
line_edit.custom_minimum_size = Vector2i(300, 0)
403+
dialog.add_child(line_edit)
404+
405+
add_child(dialog)
406+
407+
dialog.confirmed.connect(func():
408+
var new_name = line_edit.text.strip_edges()
409+
if new_name != "":
410+
category.set_entity_name(new_name)
411+
category_tab_container.set_tab_title(tab_index, new_name)
412+
413+
if selected_entity == category:
414+
property_editor.set_entity(category)
415+
416+
dialog.queue_free()
417+
)
418+
419+
dialog.canceled.connect(func():
420+
dialog.queue_free()
421+
)
422+
423+
dialog.popup_centered()
424+
line_edit.grab_focus()
425+
426+
# Submit on Enter - just press the OK button
427+
line_edit. text_submitted.connect(func(_text):
428+
dialog.get_ok_button().pressed.emit()
429+
)
430+
431+
432+
func _show_delete_confirmation(category: PandoraCategory, tab_index: int) -> void:
433+
var dialog = ConfirmationDialog.new()
434+
dialog.dialog_text = "Delete category '%s' and all its contents?" % category.get_entity_name()
435+
dialog.title = "Confirm Deletion"
436+
437+
add_child(dialog)
438+
439+
dialog.confirmed.connect(func():
440+
# Delete the category
441+
Pandora.delete_entity(category)
442+
443+
# Refresh tabs
444+
var data: Array[PandoraEntity] = []
445+
data.assign(Pandora.get_all_roots())
446+
447+
if data.size() > 0:
448+
# Select first tab or the one before deleted
449+
var new_tab = min(tab_index, data.size() - 1)
450+
_populate_tabs(data, new_tab)
451+
else:
452+
# No categories left
453+
_populate_tabs(data, 0)
454+
455+
dialog.queue_free()
456+
)
457+
458+
dialog.canceled.connect(func():
459+
dialog.queue_free()
460+
)
461+
462+
dialog.popup_centered()
463+
464+
465+
func _on_settings_changed() -> void:
466+
_populate_data()

0 commit comments

Comments
 (0)