@@ -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
2224var selected_entity : PandoraEntity
2325var _load_error = false
26+ var _current_tab_category : PandoraCategory = null
27+ var tab_context_menu : PopupMenu
2428
2529
2630func _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+
6278func 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
96112func _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
102125func _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
138252func _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
228352func _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