└── addons └── script-tabs ├── LICENSE ├── plugin.cfg └── plugin.gd /addons/script-tabs/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Maxim Kovkel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/script-tabs/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="script-tabs" 4 | description="Move scripts list to tabs." 5 | author="kovkel" 6 | version="1.0.0" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/script-tabs/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | const HIDE_NATIVE_LIST = true 5 | 6 | var _scripts_tab_container: TabContainer 7 | var _scripts_tab_bar: TabBar 8 | var _scripts_item_list: ItemList 9 | var _prev_state := TabContainerState.new() 10 | var _last_tab_selected = -1 11 | var _last_tab_hovered = -1 12 | 13 | 14 | func _enter_tree() -> void: 15 | var script_editor = get_editor_interface().get_script_editor() 16 | _scripts_tab_container = first_or_null(script_editor.find_children( 17 | "*", "TabContainer", true, false 18 | ) 19 | ) 20 | _scripts_item_list = first_or_null(script_editor.find_children( 21 | "*", "ItemList", true, false 22 | )) 23 | if _scripts_tab_container: 24 | _scripts_tab_bar = get_tab_bar_of(_scripts_tab_container) 25 | _prev_state.save(_scripts_tab_container, _scripts_tab_bar) 26 | _scripts_tab_container.tabs_visible = true 27 | _scripts_tab_container.drag_to_rearrange_enabled = true 28 | _scripts_tab_container.sort_children.connect(_update_tabs) 29 | if _scripts_tab_bar: 30 | _scripts_tab_bar.tab_close_display_policy = TabBar.CLOSE_BUTTON_SHOW_ACTIVE_ONLY 31 | _scripts_tab_bar.select_with_rmb = true 32 | _scripts_tab_bar.drag_to_rearrange_enabled = true 33 | _scripts_tab_bar.tab_close_pressed.connect(_on_tab_close) 34 | _scripts_tab_bar.tab_rmb_clicked.connect(_on_tab_rmb) 35 | _scripts_tab_bar.tab_selected.connect(_on_tab_selected) 36 | _scripts_tab_bar.tab_hovered.connect(_on_tab_hovered) 37 | _scripts_tab_bar.mouse_exited.connect(_on_tab_bar_mouse_exited) 38 | _scripts_tab_bar.active_tab_rearranged.connect(_on_active_tab_rearranged) 39 | _scripts_tab_bar.gui_input.connect(_on_scripts_tab_bar_gui_input) 40 | if _scripts_item_list: 41 | if HIDE_NATIVE_LIST: 42 | _scripts_item_list.get_parent().visible = false 43 | _scripts_item_list.property_list_changed.connect(_on_item_list_property_list_changed) 44 | _update_tabs() 45 | 46 | 47 | func _exit_tree() -> void: 48 | if _scripts_tab_container: 49 | _scripts_tab_bar = get_tab_bar_of(_scripts_tab_container) 50 | _prev_state.restore(_scripts_tab_container, _scripts_tab_bar) 51 | _scripts_tab_container.sort_children.disconnect(_update_tabs) 52 | if _scripts_item_list: 53 | if HIDE_NATIVE_LIST: 54 | _scripts_item_list.get_parent().visible = true 55 | _scripts_item_list.property_list_changed.disconnect(_on_item_list_property_list_changed) 56 | if _scripts_tab_bar: 57 | _scripts_tab_bar.mouse_exited.disconnect(_on_tab_bar_mouse_exited) 58 | _scripts_tab_bar.gui_input.disconnect(_on_scripts_tab_bar_gui_input) 59 | _scripts_tab_bar.tab_close_pressed.disconnect(_on_tab_close) 60 | _scripts_tab_bar.tab_rmb_clicked.disconnect(_on_tab_rmb) 61 | _scripts_tab_bar.tab_selected.disconnect(_on_tab_selected) 62 | _scripts_tab_bar.tab_hovered.disconnect(_on_tab_hovered) 63 | _scripts_tab_bar.active_tab_rearranged.disconnect(_on_active_tab_rearranged) 64 | 65 | 66 | func _on_tab_bar_mouse_exited(): 67 | _last_tab_hovered = -1 68 | 69 | 70 | func _on_tab_hovered(idx): 71 | _last_tab_hovered = idx 72 | 73 | 74 | func _on_scripts_tab_bar_gui_input(event: InputEvent): 75 | if event is InputEventMouseMotion: 76 | var tab_control = _scripts_tab_container.get_tab_control(_last_tab_hovered) 77 | var path = '' 78 | if tab_control: 79 | path = tab_control.get("metadata/_edit_res_path") 80 | _scripts_tab_bar.tooltip_text = '' if path == null else path 81 | if _last_tab_hovered == -1: return 82 | if event is InputEventMouseButton: 83 | if event.is_pressed() and event.button_index == MOUSE_BUTTON_MIDDLE: 84 | _simulate_item_clicked(_last_tab_hovered, MOUSE_BUTTON_MIDDLE) 85 | 86 | 87 | func _on_active_tab_rearranged(_idx_to): 88 | var control = _scripts_tab_container.get_tab_control(_last_tab_selected) 89 | if not control: 90 | return 91 | _scripts_tab_container.move_child(control, _idx_to) 92 | _scripts_tab_container.current_tab = _scripts_tab_container.current_tab 93 | _trigger_script_editor_update_script_names() 94 | 95 | 96 | func _on_tab_selected(tab_idx): 97 | _last_tab_selected = tab_idx 98 | var item_idx = _find_list_item_idx_by_tab_idx(tab_idx) 99 | if item_idx != -1: 100 | if not _scripts_item_list.is_selected(item_idx): 101 | var select_scripts_item = func(): 102 | _scripts_item_list.select(item_idx) 103 | _scripts_item_list.item_selected.emit(item_idx) 104 | select_scripts_item.call_deferred() 105 | 106 | 107 | func _on_tab_rmb(tab_idx): 108 | _simulate_item_clicked(tab_idx, MOUSE_BUTTON_RIGHT) 109 | 110 | 111 | func _on_tab_close(tab_idx): 112 | _simulate_item_clicked(tab_idx, MOUSE_BUTTON_MIDDLE) 113 | 114 | 115 | func _on_item_list_property_list_changed(): 116 | _update_tabs.call_deferred() 117 | 118 | 119 | func _simulate_item_clicked(tab_idx, mouse_idx): 120 | if _scripts_item_list: 121 | var item_idx = _find_list_item_idx_by_tab_idx(tab_idx) 122 | if item_idx != -1: 123 | _scripts_item_list.item_clicked.emit( 124 | item_idx, 125 | _scripts_item_list.get_local_mouse_position(), 126 | mouse_idx 127 | ) 128 | 129 | 130 | func _update_tabs(): 131 | _update_tab_names() 132 | _update_tab_icons() 133 | 134 | 135 | func _update_tab_names(): 136 | if not _scripts_tab_container or not _scripts_item_list: 137 | return 138 | 139 | for item_idx in _scripts_item_list.item_count: 140 | var tab_idx = _get_item_list_tab_idx(item_idx) 141 | if tab_idx != -1: 142 | _scripts_tab_container.set_tab_title( 143 | tab_idx, _scripts_item_list.get_item_text(item_idx) 144 | ) 145 | 146 | 147 | func _update_tab_icons(): 148 | if not _scripts_tab_container or not _scripts_item_list: 149 | return 150 | 151 | for item_idx in _scripts_item_list.item_count: 152 | var tab_idx = _get_item_list_tab_idx(item_idx) 153 | if tab_idx != -1: 154 | _scripts_tab_container.set_tab_icon( 155 | tab_idx, _scripts_item_list.get_item_icon(item_idx) 156 | ) 157 | 158 | 159 | func _get_item_list_tab_idx(item_idx) -> int: 160 | var metadata = _scripts_item_list.get_item_metadata(item_idx) 161 | if not metadata is int: 162 | return -1 163 | else: 164 | return metadata 165 | 166 | 167 | func _find_list_item_idx_by_tab_idx(tab_idx) -> int: 168 | for i in _scripts_item_list.item_count: 169 | if _scripts_item_list.get_item_metadata(i) == tab_idx: 170 | return i 171 | return -1 172 | 173 | 174 | func _trigger_script_editor_update_script_names(): 175 | var script_editor = get_editor_interface().get_script_editor() 176 | # for now it is the only way to trigger script_edtior._update_script_names 177 | script_editor.notification(Control.NOTIFICATION_THEME_CHANGED) 178 | 179 | 180 | static func first_or_null(arr): 181 | if len(arr) == 0: 182 | return null 183 | return arr[0] 184 | 185 | 186 | static func get_tab_bar_of(src) -> TabBar: 187 | for c in src.get_children(true): 188 | if c is TabBar: 189 | return c 190 | return null 191 | 192 | 193 | class TabContainerState: 194 | var _tabs_visible 195 | var _drag_to_rearrange_enabled 196 | var _tab_close_display_policy 197 | var _select_with_rmb 198 | 199 | func save(src: TabContainer, tab_bar: TabBar): 200 | if src: 201 | _tabs_visible = src.tabs_visible 202 | if tab_bar: 203 | _drag_to_rearrange_enabled = tab_bar.drag_to_rearrange_enabled 204 | _tab_close_display_policy = tab_bar.tab_close_display_policy 205 | _select_with_rmb = tab_bar.select_with_rmb 206 | 207 | func restore(src: TabContainer, tab_bar: TabBar): 208 | if src: 209 | src.tabs_visible = _tabs_visible 210 | if tab_bar: 211 | tab_bar.drag_to_rearrange_enabled = _drag_to_rearrange_enabled 212 | tab_bar.tab_close_display_policy = _tab_close_display_policy 213 | tab_bar.select_with_rmb = _select_with_rmb 214 | --------------------------------------------------------------------------------