Skip to content

Commit 4f3460f

Browse files
Merge visual tab into dev tab
1 parent 73d63f8 commit 4f3460f

2 files changed

Lines changed: 167 additions & 62 deletions

File tree

Template/addons/SetupPlugin/Scripts/Dock/DevTools/DevToolsTabLayout.gd

Lines changed: 81 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
@tool
22
# Constructs and owns all UI controls for the Dev Tools dock.
3-
# Provides three tab sections:
4-
# Dev – editor utility buttons (UID cleanup, nullable toggle, scene actions, etc.)
5-
# Visual – rendering settings (clear colour, anti-aliasing, hierarchy controls)
3+
# Provides two tab sections:
4+
# Dev – editor utilities, hierarchy controls, rendering settings, and IDE launch
65
# Update – template synchronisation buttons and backup reminder
76
extends VBoxContainer
87

@@ -15,9 +14,14 @@ const TAB_MARGIN_TOP_PX: int = 10
1514
const TAB_MARGIN_BOTTOM_PX: int = 0
1615
const ANTI_ALIASING_PATH_2D: String = "rendering/anti_aliasing/quality/msaa_2d"
1716
const DEFAULT_CLEAR_COLOR_PATH: String = "rendering/environment/defaults/default_clear_color"
17+
const EXTERNAL_EDITOR_AUTO: String = "auto"
18+
const EXTERNAL_EDITOR_VSCODE: String = "vscode"
19+
const EXTERNAL_EDITOR_VISUAL_STUDIO: String = "visual_studio"
20+
const EXTERNAL_EDITOR_RIDER: String = "rider"
1821

1922
var _status_label: Label
20-
var _visual_status_label: Label
23+
var _external_editor_options: OptionButton
24+
var _open_external_editor_button: Button
2125
var _cleanup_uids_button: Button
2226
var _nullable_button: Button
2327
var _remove_empty_folders_button: Button
@@ -56,12 +60,15 @@ func _create_controls() -> void:
5660
_status_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
5761
_status_label.text = " "
5862

59-
_visual_status_label = Label.new()
60-
_visual_status_label.autowrap_mode = TextServer.AutowrapMode.AUTOWRAP_WORD_SMART
61-
_visual_status_label.clip_text = false
62-
_visual_status_label.custom_minimum_size = Vector2(0, 22)
63-
_visual_status_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
64-
_visual_status_label.text = " "
63+
_external_editor_options = OptionButton.new()
64+
_external_editor_options.size_flags_horizontal = Control.SIZE_EXPAND_FILL
65+
_external_editor_options.custom_minimum_size = Vector2(220, 0)
66+
_add_external_editor_option("Auto (System Default)", EXTERNAL_EDITOR_AUTO)
67+
_add_external_editor_option("Visual Studio Code", EXTERNAL_EDITOR_VSCODE)
68+
_add_external_editor_option("Visual Studio", EXTERNAL_EDITOR_VISUAL_STUDIO)
69+
_add_external_editor_option("JetBrains Rider", EXTERNAL_EDITOR_RIDER)
70+
71+
_open_external_editor_button = _create_button("Open External Editor", 210)
6572

6673
_cleanup_uids_button = _create_button("Cleanup uids", 150)
6774
_remove_empty_folders_button = _create_button("Remove Empty Folders", 180)
@@ -137,7 +144,7 @@ func _create_controls() -> void:
137144
_update_feedback_timer.wait_time = UPDATE_FEEDBACK_DURATION
138145
_update_feedback_timer.one_shot = true
139146

140-
# Assembles the three-tab container and attaches it to this VBoxContainer.
147+
# Assembles the two-tab container and attaches it to this VBoxContainer.
141148
# Must be called after _create_controls.
142149
func _build_layout() -> void:
143150
var tabs: TabContainer = TabContainer.new()
@@ -148,7 +155,6 @@ func _build_layout() -> void:
148155
tabs.add_theme_stylebox_override("panel", no_margin_style)
149156
tabs.add_theme_stylebox_override("tabbar_background", no_margin_style)
150157
tabs.add_child(_build_dev_tab())
151-
tabs.add_child(_build_visual_tab())
152158
tabs.add_child(_build_update_tab())
153159

154160
var content: VBoxContainer = VBoxContainer.new()
@@ -160,51 +166,70 @@ func _build_layout() -> void:
160166
add_child(content)
161167

162168
# Builds and returns the Dev tab content:
163-
# clipboard errors, uid cleanup, nullable toggle, hierarchy controls, scene actions.
169+
# external editor launch, project utilities, hierarchy tools, and rendering settings.
164170
func _build_dev_tab() -> VBoxContainer:
165171
var dev_tab: VBoxContainer = VBoxContainer.new()
166172
dev_tab.name = "Dev"
167173

168174
var content: VBoxContainer = VBoxContainer.new()
169175
content.add_theme_constant_override("separation", 8)
170-
content.add_child(_create_row([_cleanup_uids_button, _remove_empty_folders_button, _nullable_button, _close_all_scene_tabs_button, _restart_editor_button], 8))
171-
content.add_child(_status_label)
172176

173-
dev_tab.add_child(_wrap_with_tab_margin(content))
174-
return dev_tab
175-
176-
# Builds and returns the Visual tab content:
177-
# viewport clear colour picker, MSAA dropdown, and hierarchy depth controls.
178-
func _build_visual_tab() -> VBoxContainer:
179-
var visual_tab: VBoxContainer = VBoxContainer.new()
180-
visual_tab.name = "Visual"
177+
var top_row: HBoxContainer = HBoxContainer.new()
178+
top_row.add_theme_constant_override("separation", 16)
179+
top_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
181180

182-
var content: VBoxContainer = VBoxContainer.new()
183-
content.add_theme_constant_override("separation", 8)
181+
var left_column: VBoxContainer = VBoxContainer.new()
182+
left_column.add_theme_constant_override("separation", 8)
183+
left_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
184184

185-
var split_row: HBoxContainer = HBoxContainer.new()
186-
split_row.add_theme_constant_override("separation", 16)
185+
left_column.add_child(_create_section_label("External Editor"))
186+
var editor_row: HBoxContainer = HBoxContainer.new()
187+
editor_row.add_theme_constant_override("separation", 8)
188+
editor_row.add_child(_external_editor_options)
189+
editor_row.add_child(_open_external_editor_button)
190+
left_column.add_child(editor_row)
191+
192+
left_column.add_child(_create_section_label("Project Tools"))
193+
var tools_grid: GridContainer = GridContainer.new()
194+
tools_grid.columns = 2
195+
tools_grid.add_theme_constant_override("h_separation", 8)
196+
tools_grid.add_theme_constant_override("v_separation", 8)
197+
for tool_button in [_cleanup_uids_button, _remove_empty_folders_button, _nullable_button, _close_all_scene_tabs_button, _restart_editor_button]:
198+
tool_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
199+
tools_grid.add_child(tool_button)
200+
left_column.add_child(tools_grid)
187201

202+
var right_column: VBoxContainer = VBoxContainer.new()
203+
right_column.add_theme_constant_override("separation", 8)
204+
right_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
205+
206+
right_column.add_child(_create_section_label("Scene Hierarchy"))
207+
_expand_to_level_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
208+
_fully_expand_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
209+
_fully_collapse_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
210+
var hierarchy_row: HBoxContainer = HBoxContainer.new()
211+
hierarchy_row.add_theme_constant_override("separation", 8)
212+
hierarchy_row.add_child(_expand_to_level_button)
213+
hierarchy_row.add_child(_hierarchy_level_spinbox)
214+
hierarchy_row.add_child(_fully_expand_button)
215+
hierarchy_row.add_child(_fully_collapse_button)
216+
right_column.add_child(hierarchy_row)
217+
218+
right_column.add_child(_create_section_label("Rendering"))
188219
var rendering_column: VBoxContainer = VBoxContainer.new()
189220
rendering_column.add_theme_constant_override("separation", 8)
190-
rendering_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
191221
_add_labeled_control("Clear Color", _clear_color_picker, rendering_column)
192222
_add_labeled_control("Anti Aliasing", _anti_aliasing_options, rendering_column)
223+
right_column.add_child(rendering_column)
224+
225+
top_row.add_child(left_column)
226+
top_row.add_child(right_column)
227+
content.add_child(top_row)
228+
229+
content.add_child(_status_label)
193230

194-
var hierarchy_column: VBoxContainer = VBoxContainer.new()
195-
hierarchy_column.add_theme_constant_override("separation", 8)
196-
hierarchy_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
197-
var hierarchy_label: Label = Label.new()
198-
hierarchy_label.text = "Hierarchy"
199-
hierarchy_column.add_child(hierarchy_label)
200-
hierarchy_column.add_child(_create_row([_expand_to_level_button, _hierarchy_level_spinbox, _fully_expand_button, _fully_collapse_button], 8))
201-
202-
split_row.add_child(rendering_column)
203-
split_row.add_child(hierarchy_column)
204-
content.add_child(split_row)
205-
content.add_child(_visual_status_label)
206-
visual_tab.add_child(_wrap_with_tab_margin(content))
207-
return visual_tab
231+
dev_tab.add_child(_wrap_with_tab_margin(content))
232+
return dev_tab
208233

209234
# Builds and returns the Update tab content:
210235
# update-from-main/release buttons and a backup reminder label.
@@ -348,4 +373,17 @@ func _create_checkbox(text: String, pressed: bool) -> CheckButton:
348373
checkbox.button_pressed = pressed
349374
checkbox.alignment = HORIZONTAL_ALIGNMENT_LEFT
350375
checkbox.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
351-
return checkbox
376+
return checkbox
377+
378+
func _add_external_editor_option(label: String, key: String) -> void:
379+
var index: int = _external_editor_options.item_count
380+
_external_editor_options.add_item(label)
381+
_external_editor_options.set_item_metadata(index, key)
382+
383+
func _create_section_label(text: String) -> Label:
384+
var label: Label = Label.new()
385+
label.text = text
386+
label.modulate = Color(0.82, 0.82, 0.82)
387+
label.add_theme_font_size_override("font_size", 14)
388+
label.autowrap_mode = TextServer.AutowrapMode.AUTOWRAP_WORD_SMART
389+
return label

Template/addons/SetupPlugin/Scripts/Dock/DevToolsTab.gd

Lines changed: 86 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ extends "res://addons/SetupPlugin/Scripts/Dock/DevToolsTabLayout.gd"
88

99
const PROJECT_ROOT_PATH: String = "res://"
1010
const ANTI_ALIASING_PATH_3D: String = "rendering/anti_aliasing/quality/msaa_3d"
11+
const SOLUTION_FILE_NAME: String = "Template.sln"
1112
const DevToolsUidCleanupScript = preload("DevToolsUidCleanup.gd")
1213
const EditorSceneActionsScript = preload("EditorSceneActions.gd")
1314
const NullableProjectSettingsScript = preload("NullableProjectSettings.gd")
@@ -58,7 +59,7 @@ func _project_root() -> String:
5859
func _register_events() -> void:
5960
if _events_registered:
6061
return
61-
for pair in [[_cleanup_uids_button, _on_cleanup_uids_pressed], [_nullable_button, _on_nullable_pressed], [_remove_empty_folders_button, _on_remove_empty_folders_pressed], [_close_all_scene_tabs_button, _on_close_all_scene_tabs_pressed], [_restart_editor_button, _on_restart_editor_pressed], [_expand_to_level_button, _on_expand_to_level_pressed], [_fully_expand_button, _on_fully_expand_pressed], [_fully_collapse_button, _on_fully_collapse_pressed], [_update_from_main_button, _on_update_from_main_pressed], [_update_from_release_button, _on_update_from_release_pressed], [_check_updates_button, _on_check_updates_pressed], [_reset_update_cache_button, _on_reset_update_cache_pressed], [_view_template_repo_button, _on_view_template_repo_pressed], [_link_to_commits_button, _on_link_to_commits_pressed], [_link_to_release_notes_button, _on_link_to_release_notes_pressed]]:
62+
for pair in [[_open_external_editor_button, _on_open_external_editor_pressed], [_cleanup_uids_button, _on_cleanup_uids_pressed], [_nullable_button, _on_nullable_pressed], [_remove_empty_folders_button, _on_remove_empty_folders_pressed], [_close_all_scene_tabs_button, _on_close_all_scene_tabs_pressed], [_restart_editor_button, _on_restart_editor_pressed], [_expand_to_level_button, _on_expand_to_level_pressed], [_fully_expand_button, _on_fully_expand_pressed], [_fully_collapse_button, _on_fully_collapse_pressed], [_update_from_main_button, _on_update_from_main_pressed], [_update_from_release_button, _on_update_from_release_pressed], [_check_updates_button, _on_check_updates_pressed], [_reset_update_cache_button, _on_reset_update_cache_pressed], [_view_template_repo_button, _on_view_template_repo_pressed], [_link_to_commits_button, _on_link_to_commits_pressed], [_link_to_release_notes_button, _on_link_to_release_notes_pressed]]:
6263
pair[0].pressed.connect(pair[1])
6364
_check_updates_on_startup_checkbox.toggled.connect(_on_check_updates_on_startup_toggled)
6465
_clear_color_picker.color_changed.connect(_on_clear_color_changed)
@@ -72,7 +73,7 @@ func _unregister_events() -> void:
7273
if not _events_registered:
7374
return
7475
_events_registered = false
75-
for pair in [[_cleanup_uids_button, "_on_cleanup_uids_pressed"], [_nullable_button, "_on_nullable_pressed"], [_remove_empty_folders_button, "_on_remove_empty_folders_pressed"], [_close_all_scene_tabs_button, "_on_close_all_scene_tabs_pressed"], [_restart_editor_button, "_on_restart_editor_pressed"], [_expand_to_level_button, "_on_expand_to_level_pressed"], [_fully_expand_button, "_on_fully_expand_pressed"], [_fully_collapse_button, "_on_fully_collapse_pressed"], [_update_from_main_button, "_on_update_from_main_pressed"], [_update_from_release_button, "_on_update_from_release_pressed"], [_check_updates_button, "_on_check_updates_pressed"], [_reset_update_cache_button, "_on_reset_update_cache_pressed"], [_view_template_repo_button, "_on_view_template_repo_pressed"], [_link_to_commits_button, "_on_link_to_commits_pressed"], [_link_to_release_notes_button, "_on_link_to_release_notes_pressed"]]:
76+
for pair in [[_open_external_editor_button, "_on_open_external_editor_pressed"], [_cleanup_uids_button, "_on_cleanup_uids_pressed"], [_nullable_button, "_on_nullable_pressed"], [_remove_empty_folders_button, "_on_remove_empty_folders_pressed"], [_close_all_scene_tabs_button, "_on_close_all_scene_tabs_pressed"], [_restart_editor_button, "_on_restart_editor_pressed"], [_expand_to_level_button, "_on_expand_to_level_pressed"], [_fully_expand_button, "_on_fully_expand_pressed"], [_fully_collapse_button, "_on_fully_collapse_pressed"], [_update_from_main_button, "_on_update_from_main_pressed"], [_update_from_release_button, "_on_update_from_release_pressed"], [_check_updates_button, "_on_check_updates_pressed"], [_reset_update_cache_button, "_on_reset_update_cache_pressed"], [_view_template_repo_button, "_on_view_template_repo_pressed"], [_link_to_commits_button, "_on_link_to_commits_pressed"], [_link_to_release_notes_button, "_on_link_to_release_notes_pressed"]]:
7677
_disconnect_signal(pair[0], "pressed", pair[1])
7778
_disconnect_signal(_check_updates_on_startup_checkbox, "toggled", "_on_check_updates_on_startup_toggled")
7879
_disconnect_signal(_clear_color_picker, "color_changed", "_on_clear_color_changed")
@@ -94,17 +95,6 @@ func _set_status(text: String) -> void:
9495
_status_label.text = text
9596
_status_label.modulate = Color(0.6, 0.95, 0.6)
9697

97-
# Updates the status label shown at the bottom of the Visual tab.
98-
func _set_visual_status(text: String) -> void:
99-
if _visual_status_label == null:
100-
return
101-
if text.is_empty():
102-
_visual_status_label.text = " "
103-
_visual_status_label.modulate = Color(0.75, 0.75, 0.75)
104-
return
105-
_visual_status_label.text = text
106-
_visual_status_label.modulate = Color(0.6, 0.95, 0.6)
107-
10898
# Updates the status label in the Update tab only.
10999
func _set_update_status(text: String) -> void:
110100
_set_update_feedback(text)
@@ -119,7 +109,6 @@ func _refresh_editor_filesystem() -> void:
119109
# Clears the status label once the feedback display duration elapses.
120110
func _on_feedback_timer_timeout() -> void:
121111
_set_status("")
122-
_set_visual_status("")
123112

124113
# Updates the green update feedback label. An empty string hides its text.
125114
func _set_update_feedback(text: String) -> void:
@@ -171,35 +160,113 @@ func _on_restart_editor_pressed() -> void:
171160
_feedback_timer.start()
172161
_editor_scene_actions.restart_editor(true)
173162

163+
# Opens the selected external editor, falling back to the system default.
164+
func _on_open_external_editor_pressed() -> void:
165+
var project_root: String = _project_root()
166+
var solution_path: String = project_root.path_join(SOLUTION_FILE_NAME)
167+
var open_target: String = solution_path if FileAccess.file_exists(solution_path) else project_root
168+
var selection: String = _selected_external_editor_key()
169+
var opened: bool = false
170+
171+
match selection:
172+
EXTERNAL_EDITOR_VSCODE:
173+
opened = _open_in_vscode(project_root)
174+
EXTERNAL_EDITOR_VISUAL_STUDIO:
175+
opened = _open_in_visual_studio(open_target)
176+
EXTERNAL_EDITOR_RIDER:
177+
opened = _open_in_rider(open_target)
178+
_:
179+
opened = _open_with_default(open_target)
180+
181+
_set_status("Opened external editor." if opened else "Could not open external editor. Check your IDE install or PATH.")
182+
_feedback_timer.start()
183+
184+
func _selected_external_editor_key() -> String:
185+
if _external_editor_options == null:
186+
return EXTERNAL_EDITOR_AUTO
187+
var index: int = _external_editor_options.selected
188+
var meta: Variant = _external_editor_options.get_item_metadata(index)
189+
if meta is String and not (meta as String).is_empty():
190+
return str(meta)
191+
return EXTERNAL_EDITOR_AUTO
192+
193+
func _open_in_vscode(project_root: String) -> bool:
194+
var os_name: String = OS.get_name()
195+
if os_name == "macOS":
196+
if _try_launch("open", ["-a", "Visual Studio Code", project_root]):
197+
return true
198+
if _try_launch("code", ["-r", project_root]):
199+
return true
200+
if _try_launch("code-insiders", ["-r", project_root]):
201+
return true
202+
var uri_path: String = project_root.replace(" ", "%20")
203+
return OS.shell_open("vscode://file/%s" % uri_path) == OK
204+
205+
func _open_in_visual_studio(solution_path: String) -> bool:
206+
var os_name: String = OS.get_name()
207+
if os_name == "Windows":
208+
if _try_launch("devenv", [solution_path]):
209+
return true
210+
if _try_launch("devenv.exe", [solution_path]):
211+
return true
212+
elif os_name == "macOS":
213+
if _try_launch("open", ["-a", "Visual Studio", solution_path]):
214+
return true
215+
return _open_with_default(solution_path)
216+
217+
func _open_in_rider(solution_path: String) -> bool:
218+
var os_name: String = OS.get_name()
219+
if os_name == "Windows":
220+
for exe in ["rider64.exe", "rider.exe", "rider64", "rider"]:
221+
if _try_launch(exe, [solution_path]):
222+
return true
223+
elif os_name == "macOS":
224+
if _try_launch("open", ["-a", "Rider", solution_path]):
225+
return true
226+
if _try_launch("open", ["-a", "JetBrains Rider", solution_path]):
227+
return true
228+
else:
229+
for exe in ["rider", "rider64", "rider.sh", "jetbrains-rider"]:
230+
if _try_launch(exe, [solution_path]):
231+
return true
232+
return _open_with_default(solution_path)
233+
234+
func _open_with_default(target_path: String) -> bool:
235+
return OS.shell_open(target_path) == OK
236+
237+
func _try_launch(executable: String, args: Array[String]) -> bool:
238+
var pid: int = OS.create_process(executable, args, false)
239+
return pid != -1
240+
174241
# Expands the Scene dock hierarchy to the depth set in the SpinBox.
175242
func _on_expand_to_level_pressed() -> void:
176243
var level: int = int(_hierarchy_level_spinbox.value)
177244
var changed_count: int = _scene_hierarchy_actions.expand_to_level(level)
178-
_set_visual_status("Expanded hierarchy to level %d" % level if changed_count > 0 else "No scene hierarchy available")
245+
_set_status("Expanded hierarchy to level %d" % level if changed_count > 0 else "No scene hierarchy available")
179246
_feedback_timer.start()
180247

181248
# Fully expands every node in the Scene dock hierarchy.
182249
func _on_fully_expand_pressed() -> void:
183-
_set_visual_status("Fully expanded hierarchy" if _scene_hierarchy_actions.fully_expand() > 0 else "No scene hierarchy available")
250+
_set_status("Fully expanded hierarchy" if _scene_hierarchy_actions.fully_expand() > 0 else "No scene hierarchy available")
184251
_feedback_timer.start()
185252

186253
# Collapses the Scene dock hierarchy to just the root level.
187254
func _on_fully_collapse_pressed() -> void:
188-
_set_visual_status("Fully collapsed hierarchy" if _scene_hierarchy_actions.fully_collapse() > 0 else "No scene hierarchy available")
255+
_set_status("Fully collapsed hierarchy" if _scene_hierarchy_actions.fully_collapse() > 0 else "No scene hierarchy available")
189256
_feedback_timer.start()
190257

191258
# Persists the chosen viewport clear colour to project settings immediately.
192259
func _on_clear_color_changed(color: Color) -> void:
193260
ProjectSettings.set_setting(DEFAULT_CLEAR_COLOR_PATH, color)
194261
ProjectSettings.save()
195-
_set_visual_status("Updated clear color.")
262+
_set_status("Updated clear color.")
196263
_feedback_timer.start()
197264

198265
# Writes the chosen MSAA level to both 2D and 3D project settings.
199266
func _on_anti_aliasing_item_selected(index: int) -> void:
200267
ProjectSettings.set_setting(ANTI_ALIASING_PATH_2D, index)
201268
ProjectSettings.set_setting(ANTI_ALIASING_PATH_3D, index)
202-
_set_visual_status("Anti-aliasing updated.")
269+
_set_status("Anti-aliasing updated.")
203270
_feedback_timer.start()
204271

205272
# Reads the current nullable state from Template.csproj and sets the button

0 commit comments

Comments
 (0)