diff --git a/addons/script-ide/icon/pin.svg b/addons/script-ide/icon/pin.svg
new file mode 100644
index 0000000..cc1be70
--- /dev/null
+++ b/addons/script-ide/icon/pin.svg
@@ -0,0 +1 @@
+
diff --git a/addons/script-ide/icon/pin.svg.import b/addons/script-ide/icon/pin.svg.import
new file mode 100644
index 0000000..4653360
--- /dev/null
+++ b/addons/script-ide/icon/pin.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://khcndpjwfvkc"
+path="res://.godot/imported/pin.svg-7a38377e68dd470181b224010de04380.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/script-ide/icon/pin.svg"
+dest_files=["res://.godot/imported/pin.svg-7a38377e68dd470181b224010de04380.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/script-ide/icon/unpin.svg b/addons/script-ide/icon/unpin.svg
new file mode 100644
index 0000000..3d5d175
--- /dev/null
+++ b/addons/script-ide/icon/unpin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/script-ide/icon/unpin.svg.import b/addons/script-ide/icon/unpin.svg.import
new file mode 100644
index 0000000..70346f5
--- /dev/null
+++ b/addons/script-ide/icon/unpin.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://4816ua8c10kn"
+path="res://.godot/imported/unpin.svg-325c68c89e8429070a58abe0dfe905c2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/script-ide/icon/unpin.svg"
+dest_files=["res://.godot/imported/unpin.svg-325c68c89e8429070a58abe0dfe905c2.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/script-ide/plugin.gd b/addons/script-ide/plugin.gd
index 3a7d39f..ba26006 100644
--- a/addons/script-ide/plugin.gd
+++ b/addons/script-ide/plugin.gd
@@ -20,6 +20,9 @@ const INLINE: StringName = &"@"
const BUILT_IN_SCRIPT: StringName = &"::GDScript"
+const CustomTabBar := preload("tabbar/custom_tab_bar.gd")
+const CustomTabContainer := preload("tabbar/custom_tab_container.gd")
+
#region Settings and Shortcuts
## Editor setting path
const SCRIPT_IDE: StringName = &"plugin/script_ide/"
@@ -38,6 +41,8 @@ const SCRIPT_LIST_VISIBLE: StringName = SCRIPT_IDE + &"script_list_visible"
const SCRIPT_TABS_VISIBLE: StringName = SCRIPT_IDE + &"script_tabs_visible"
## Editor setting to control where the script tabs should be.
const SCRIPT_TAB_POSITION_TOP: StringName = SCRIPT_IDE + &"script_tab_position_top"
+## Editor setting to control multiline tabs.
+const SCRIPT_MULTILINE_TABS: StringName = SCRIPT_IDE + &"script_multiline_tabs"
## Editor setting for the 'Open Outline Popup' shortcut
const OPEN_OUTLINE_POPUP: StringName = SCRIPT_IDE + &"open_outline_popup"
@@ -78,6 +83,7 @@ var hide_private_members: bool = false
var is_auto_navigate_in_fs: bool = true
var is_script_tabs_visible: bool = true
var is_script_tabs_top: bool = true
+var is_script_multiline_tabs: bool = false
var outline_order: PackedStringArray
var open_outline_popup_shc: Shortcut
@@ -95,6 +101,8 @@ var scripts_tab_bar: TabBar
var script_filter_txt: LineEdit
var scripts_item_list: ItemList
var panel_container: VSplitContainer
+var custom_tab_bar: CustomTabBar
+var custom_tab_container: CustomTabContainer
var split_container: HSplitContainer
var old_outline: ItemList
@@ -167,6 +175,18 @@ func _enter_tree() -> void:
# Make tab container visible.
scripts_tab_container = find_or_null(script_editor.find_children("*", "TabContainer", true, false))
scripts_tab_bar = scripts_tab_container.get_tab_bar()
+ scripts_tab_bar.visible = false
+
+ custom_tab_container = CustomTabContainer.new()
+ custom_tab_container.scripts_tab_container = scripts_tab_container
+ custom_tab_container.scripts_item_list = scripts_item_list
+ scripts_tab_container.get_parent().add_theme_constant_override(&"separation", 0)
+ scripts_tab_container.get_parent().add_child(custom_tab_container)
+ scripts_tab_container.get_parent().move_child(custom_tab_container, 0)
+ custom_tab_bar = custom_tab_container.custom_tab_bar
+ custom_tab_container.visible = is_script_tabs_visible and is_script_multiline_tabs
+
+ scripts_tab_container.tabs_visible = false
# Save old tab state to restore later.
tab_state = TabStateCache.new()
@@ -176,7 +196,7 @@ func _enter_tree() -> void:
create_set_scripts_popup()
# Configure tab container and bar.
- scripts_tab_container.tabs_visible = is_script_tabs_visible
+ scripts_tab_container.tabs_visible = is_script_tabs_visible and not is_script_multiline_tabs
scripts_tab_container.drag_to_rearrange_enabled = true
scripts_tab_container.auto_translate_mode = Node.AUTO_TRANSLATE_MODE_DISABLED
update_tabs_position()
@@ -275,6 +295,9 @@ func _exit_tree() -> void:
scripts_tab_container.pre_popup_pressed.disconnect(prepare_scripts_popup)
scripts_tab_container.set_popup(null)
+ scripts_tab_container.get_parent().remove_child(custom_tab_container)
+ custom_tab_container.set_popup(null)
+ custom_tab_container.free()
scripts_popup.free()
if (scripts_tab_bar != null):
@@ -381,6 +404,7 @@ func init_settings():
is_script_list_visible = get_setting(SCRIPT_LIST_VISIBLE, is_script_list_visible)
is_auto_navigate_in_fs = get_setting(AUTO_NAVIGATE_IN_FS, is_auto_navigate_in_fs)
is_script_tabs_visible = get_setting(SCRIPT_TABS_VISIBLE, is_script_tabs_visible)
+ is_script_multiline_tabs = get_setting(SCRIPT_MULTILINE_TABS, is_script_multiline_tabs)
is_script_tabs_top = get_setting(SCRIPT_TAB_POSITION_TOP, is_script_tabs_top)
init_outline_order()
@@ -573,6 +597,9 @@ func create_set_scripts_popup():
scripts_tab_container.pre_popup_pressed.connect(prepare_scripts_popup)
scripts_tab_container.set_popup(scripts_popup)
+ custom_tab_container.pre_popup_pressed.connect(prepare_scripts_popup)
+ custom_tab_container.set_popup(scripts_popup)
+
func prepare_scripts_popup():
scripts_popup.size.x = outline.size.x
scripts_popup.size.y = panel_container.size.y - scripts_tab_bar.size.y
@@ -922,13 +949,21 @@ func sync_settings():
if (new_script_tabs_visible != is_script_tabs_visible):
is_script_tabs_visible = new_script_tabs_visible
- scripts_tab_container.tabs_visible = is_script_tabs_visible
+ scripts_tab_container.tabs_visible = is_script_tabs_visible and not is_script_multiline_tabs
+ custom_tab_container.visible = is_script_tabs_visible and is_script_multiline_tabs
elif (setting == SCRIPT_TAB_POSITION_TOP):
var new_script_tabs_top: bool = get_setting(SCRIPT_TAB_POSITION_TOP, is_script_tabs_top)
if (new_script_tabs_top != is_script_tabs_top):
is_script_tabs_top = new_script_tabs_top
update_tabs_position()
+ elif (setting == SCRIPT_MULTILINE_TABS):
+ var new_script_multiline_tabs: bool = get_setting(SCRIPT_MULTILINE_TABS, is_script_multiline_tabs)
+ if (new_script_multiline_tabs != is_script_multiline_tabs):
+ is_script_multiline_tabs = new_script_multiline_tabs
+
+ scripts_tab_container.tabs_visible = is_script_tabs_visible and not is_script_multiline_tabs
+ custom_tab_container.visible = is_script_tabs_visible and is_script_multiline_tabs
elif (setting == AUTO_NAVIGATE_IN_FS):
is_auto_navigate_in_fs = get_setting(AUTO_NAVIGATE_IN_FS, is_auto_navigate_in_fs)
elif (setting == OPEN_OUTLINE_POPUP):
@@ -966,7 +1001,7 @@ func get_shortcut(property: StringName) -> Shortcut:
return get_editor_settings().get_setting(property)
func on_tab_changed(index: int):
- selected_tab = index;
+ selected_tab = index
if (old_script_editor_base != null):
old_script_editor_base.edited_script_changed.disconnect(update_selected_tab)
@@ -1302,3 +1337,21 @@ class TabStateCache:
tab_bar.drag_to_rearrange_enabled = drag_to_rearrange_enabled
tab_bar.tab_close_display_policy = tab_close_display_policy
tab_bar.select_with_rmb = select_with_rmb
+
+
+func _get_window_layout(configuration: ConfigFile) -> void:
+ var tabs: Array
+ for tab in custom_tab_bar.get_children():
+ if tab.pinned:
+ tabs.append(tab.get_tab_path())
+ configuration.set_value("script-ide", "pinned_tabs", tabs)
+
+
+func _set_window_layout(configuration: ConfigFile) -> void:
+ var tabs: Array = configuration.get_value("script-ide", "pinned_tabs", [])
+ custom_tab_container.synced.connect(_update_pinned_tabs.bind(tabs), CONNECT_ONE_SHOT)
+
+
+func _update_pinned_tabs(pinned_tabs: Array) -> void:
+ for tab in pinned_tabs:
+ custom_tab_bar.pin_tab(tab)
diff --git a/addons/script-ide/tabbar/custom_tab.gd b/addons/script-ide/tabbar/custom_tab.gd
new file mode 100644
index 0000000..c127c65
--- /dev/null
+++ b/addons/script-ide/tabbar/custom_tab.gd
@@ -0,0 +1,181 @@
+@tool
+extends Button
+
+signal tab_pinned(idx: int, pinned: bool)
+signal tab_close_pressed(idx: int)
+
+var container: HBoxContainer = HBoxContainer.new()
+var texture_rect: TextureRect = TextureRect.new()
+var label: Label = Label.new()
+var pin_button: Button = Button.new()
+var close_button: Button = Button.new()
+
+var pinned: bool = false:
+ set(value):
+ pinned = value
+ _update_buttons()
+
+var title: String:
+ set(value):
+ label.text = value
+ get():
+ return label.text
+
+var tab_icon: Texture2D:
+ set(value):
+ texture_rect.texture = value
+ get():
+ return texture_rect.texture
+
+var pin_icon: Texture2D:
+ set(value):
+ pin_icon = value
+ if pinned: pin_button.icon = value
+
+var unpin_icon: Texture2D:
+ set(value):
+ unpin_icon = value
+ if not pinned: pin_button.icon = value
+
+var close_icon: Texture2D:
+ set(value):
+ close_button.icon = value
+
+var font_unselected_color: Color
+var font_hovered_color: Color
+var font_selected_color: Color
+var icon_color: Color:
+ set(value):
+ texture_rect.self_modulate = value
+ get():
+ return texture_rect.self_modulate
+
+func _ready() -> void:
+ toggle_mode = true
+ action_mode = ACTION_MODE_BUTTON_PRESS
+ toggled.connect(_update_buttons.unbind(1))
+ mouse_entered.connect(_update_buttons, CONNECT_DEFERRED)
+ mouse_exited.connect(_update_buttons, CONNECT_DEFERRED)
+
+ var separator := Control.new()
+ separator.custom_minimum_size.x = 12 * EditorInterface.get_editor_scale()
+ separator.mouse_filter = Control.MOUSE_FILTER_PASS
+ container.add_child(separator)
+
+ texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ container.add_child(texture_rect)
+
+ label.add_theme_color_override(&"font_color", Color.WHITE)
+ container.add_child(label)
+
+ pin_button.flat = true
+ pin_button.focus_mode = Control.FOCUS_NONE
+ pin_button.mouse_filter = Control.MOUSE_FILTER_PASS
+ pin_button.add_theme_color_override(&"icon_disabled_color", Color.TRANSPARENT)
+ pin_button.pressed.connect(_on_pinned_pressed)
+ container.add_child(pin_button)
+
+ close_button.flat = true
+ close_button.focus_mode = Control.FOCUS_NONE
+ close_button.mouse_filter = Control.MOUSE_FILTER_PASS
+ close_button.add_theme_color_override(&"icon_disabled_color", Color.TRANSPARENT)
+ close_button.pressed.connect(_on_close_pressed)
+ container.add_child(close_button)
+
+ container.add_theme_constant_override(&"separation", 0)
+ container.minimum_size_changed.connect(_on_container_item_rect_changed, CONNECT_DEFERRED)
+ add_child(container)
+
+ _update_buttons()
+
+
+func get_tab_path() -> String:
+ return tooltip_text.trim_suffix(" Class Reference")
+
+
+func _gui_input(event: InputEvent) -> void:
+ if event is InputEventMouseButton:
+ if event.button_index == MOUSE_BUTTON_MIDDLE and event.pressed:
+ _on_close_pressed()
+
+
+func _on_pinned_pressed() -> void:
+ pinned = not pinned
+ _update_buttons()
+ tab_pinned.emit(get_index(), pinned)
+
+
+func _on_close_pressed() -> void:
+ tab_close_pressed.emit(get_index())
+
+
+func _update_buttons() -> void:
+ pin_button.disabled = not (is_hovered() or button_pressed or pinned)
+ close_button.disabled = not (is_hovered() or button_pressed)
+ pin_button.icon = pin_icon if pinned else unpin_icon
+ label.self_modulate = font_selected_color if button_pressed else (font_hovered_color if is_hovered() else font_unselected_color)
+ z_index = 0
+ draw_drop_mark = false
+
+
+func _on_container_item_rect_changed() -> void:
+ custom_minimum_size = container.size
+
+
+#region Drag'n'Drop
+
+var drop_mark_color: Color = EditorInterface.get_editor_theme().get_color(&"drop_mark_color", &"TabBar")
+
+var drop_mark_width: float = 6.0 * EditorInterface.get_editor_scale()
+var drop_mark_offset: float = drop_mark_width / 2.0
+
+var draw_drop_mark: bool = false
+var is_drop_mark_left: bool = false
+
+func _draw() -> void:
+ if draw_drop_mark:
+ draw_rect(Rect2(-drop_mark_offset + (0 if is_drop_mark_left else size.x), 0, drop_mark_width, size.y), drop_mark_color)
+
+
+func _get_drag_data(at_position: Vector2) -> Variant:
+ var hbox := HBoxContainer.new()
+ hbox.z_index = 2
+ var icon := TextureRect.new()
+ icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ icon.texture = tab_icon
+ icon.self_modulate = icon_color
+ hbox.add_child(icon)
+
+ var label := Label.new()
+ label.text = title
+ hbox.add_child(label)
+
+ set_drag_preview(hbox)
+ set_meta("__tab", true)
+ return self
+
+
+func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
+ var can_drop: bool = data.get_meta("__tab", false)
+ if can_drop:
+ is_drop_mark_left = at_position.x <= size.x / 2
+ z_index = 1
+ draw_drop_mark = true
+ queue_redraw()
+ return can_drop
+
+
+func _drop_data(at_position: Vector2, data: Variant) -> void:
+ if data == self:
+ return
+ var is_left := at_position.x <= size.x / 2
+ var from: int = data.get_index()
+ var to: int
+ if from > get_index():
+ to = get_index() + (0 if is_left else 1)
+ else:
+ to = get_index() - (1 if is_left else 0)
+ if from != to:
+ get_parent().move_tab(from, to)
+
+#endregion
diff --git a/addons/script-ide/tabbar/custom_tab_bar.gd b/addons/script-ide/tabbar/custom_tab_bar.gd
new file mode 100644
index 0000000..c88b288
--- /dev/null
+++ b/addons/script-ide/tabbar/custom_tab_bar.gd
@@ -0,0 +1,168 @@
+@tool
+extends HFlowContainer
+
+signal tab_changed(idx: int)
+signal tab_pinned(idx: int, pinned: bool)
+signal tab_close_pressed(idx: int)
+signal tab_rearranged(from: int, to: int)
+
+const Tab := preload("custom_tab.gd")
+const pin_icon: Texture2D = preload("../icon/pin.svg")
+const unpin_icon: Texture2D = preload("../icon/unpin.svg")
+
+var style_tab_selected: StyleBoxFlat = EditorInterface.get_editor_theme().get_stylebox(&"tab_selected", &"TabBar").duplicate(true)
+var style_tab_unselected: StyleBoxFlat = EditorInterface.get_editor_theme().get_stylebox(&"tab_unselected", &"TabBar").duplicate(true)
+var style_tab_hovered: StyleBoxFlat = EditorInterface.get_editor_theme().get_stylebox(&"tab_hovered", &"TabBar").duplicate(true)
+
+var current_tab: int = -1:
+ set(value):
+ current_tab = value
+ if current_tab >= 0 and current_tab < get_child_count():
+ get_child(current_tab).button_pressed = true
+
+var button_group: ButtonGroup = ButtonGroup.new()
+
+var scripts_item_list: ItemList
+var scripts_tab_container: TabContainer
+
+var tabs: Dictionary
+
+func _ready() -> void:
+ add_theme_constant_override(&"h_separation", 0)
+ add_theme_constant_override(&"v_separation", 0)
+
+ button_group.pressed.connect(_on_tab_changed)
+
+
+func clear_tabs() -> void:
+ tabs.clear()
+ if get_child_count() == 0:
+ return
+ for i in range(get_child_count() - 1, -1, -1):
+ var child := get_child(i)
+ remove_child(child)
+ child.queue_free()
+
+
+func get_tab(idx: int) -> Tab:
+ if idx >= 0 and idx < get_child_count():
+ return get_child(idx)
+ return null
+
+
+func get_tab_count() -> int:
+ return get_child_count()
+
+
+func add_tab(title: String, icon: Texture2D = null) -> Tab:
+ var tab := Tab.new()
+ tab.title = title
+ tab.tab_icon = icon
+
+ tab.button_group = button_group
+
+ tab.pin_icon = pin_icon
+ tab.unpin_icon = unpin_icon
+ tab.close_icon = EditorInterface.get_editor_theme().get_icon(&"Close", &"EditorIcons")
+
+ tab.add_theme_stylebox_override(&"normal", style_tab_unselected)
+ tab.add_theme_stylebox_override(&"pressed", style_tab_selected)
+ tab.add_theme_stylebox_override(&"focus", style_tab_selected)
+ tab.add_theme_stylebox_override(&"hover", style_tab_hovered)
+ tab.font_hovered_color = EditorInterface.get_editor_theme().get_color(&"font_hovered_color", &"TabBar")
+ tab.font_unselected_color = EditorInterface.get_editor_theme().get_color(&"font_unselected_color", &"TabBar")
+ tab.font_selected_color = EditorInterface.get_editor_theme().get_color(&"font_selected_color", &"TabBar")
+
+ tab.tab_pinned.connect(_on_tab_pinned)
+ tab.tab_close_pressed.connect(_on_tab_close_pressed)
+ add_child(tab)
+ return tab
+
+
+func set_tab_title(idx: int, text: String) -> void:
+ var tab := get_tab(idx)
+ if tab:
+ tab.title = text
+
+
+#func get_tab_title(idx: int) -> String:
+ #var tab := get_tab(idx)
+ #if tab:
+ #return tab.title
+ #return ""
+
+
+func pin_tab(path: String) -> void:
+ var tab: Tab = tabs.get(path, null)
+ if tab:
+ tab._on_pinned_pressed()
+
+#func get_tab_icon(idx: int) -> Texture2D:
+ #var tab := get_tab(idx)
+ #if tab:
+ #return tab.tab_icon
+ #return null
+
+
+#func get_tab_icon_color(idx: int) -> Color:
+ #var tab := get_tab(idx)
+ #if tab:
+ #return tab.icon_color
+ #return Color.WHITE
+
+
+func set_tab_tooltip(idx: int, text: String) -> void:
+ var tab := get_tab(idx)
+ if tab:
+ tab.tooltip_text = text
+
+
+func _on_tab_close_pressed(idx: int) -> void:
+ tab_close_pressed.emit(idx)
+ var tab := get_tab(idx)
+ if tab:
+ remove_child(tab)
+ tab.queue_free()
+
+
+func _on_tab_changed(button: BaseButton) -> void:
+ if not button:
+ return
+ var idx := button.get_index()
+ if current_tab == idx:
+ return
+ current_tab = idx
+ tab_changed.emit(idx)
+
+
+func _on_tab_pinned(idx: int, pinned: bool) -> void:
+ var tab: Tab = get_tab(idx)
+ if pinned:
+ for i in range(0, get_child_count()):
+ var child: Tab = get_tab(i)
+ if child == tab:
+ break
+ if not child.pinned:
+ move_tab(idx, i)
+ break
+ else:
+ for i in range(get_child_count() - 1, -1, -1):
+ var child: Tab = get_tab(i)
+ if child == tab:
+ break
+ if child.pinned:
+ move_tab(idx, i)
+ break
+
+
+func move_tab(from: int, to: int, with_signal: bool = true) -> void:
+ move_child(get_child(from), to)
+ if with_signal:
+ tab_rearranged.emit(from, to)
+
+ var right_tab := get_tab(to + 1)
+ var left_tab := get_tab(to - 1)
+ if right_tab and right_tab.pinned:
+ get_tab(to).pinned = true
+ elif left_tab and not left_tab.pinned:
+ get_tab(to).pinned = false
diff --git a/addons/script-ide/tabbar/custom_tab_container.gd b/addons/script-ide/tabbar/custom_tab_container.gd
new file mode 100644
index 0000000..83d646d
--- /dev/null
+++ b/addons/script-ide/tabbar/custom_tab_container.gd
@@ -0,0 +1,134 @@
+@tool
+extends PanelContainer
+
+signal synced()
+signal pre_popup_pressed()
+
+const CustomTabBar := preload("custom_tab_bar.gd")
+
+var custom_tab_bar: CustomTabBar = CustomTabBar.new()
+var popup_button: Button = Button.new()
+var popup_panel: PopupPanel
+
+var scripts_tab_container: TabContainer
+var scripts_item_list: ItemList
+var active_script_editor: ScriptEditorBase
+
+func _ready() -> void:
+ add_theme_stylebox_override(&"panel", EditorInterface.get_editor_theme().get_stylebox(&"tabbar_background", &"TabContainer"))
+
+ var hsplit := HSplitContainer.new()
+ hsplit.add_theme_constant_override(&"separation", 0)
+ hsplit.dragger_visibility = SplitContainer.DRAGGER_HIDDEN_COLLAPSED
+ add_child(hsplit)
+
+ custom_tab_bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ hsplit.add_child(custom_tab_bar)
+
+ var texture_rect: TextureRect = TextureRect.new()
+ texture_rect.texture = EditorInterface.get_editor_theme().get_icon(&"menu", &"TabContainer")
+ texture_rect.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
+ popup_button.add_child(texture_rect)
+
+ popup_button.focus_mode = Control.FOCUS_NONE
+ popup_button.custom_minimum_size = Vector2(16, 24) * EditorInterface.get_editor_scale()
+ popup_button.size_flags_horizontal = Control.SIZE_SHRINK_END
+ popup_button.pressed.connect(_on_popup_button_pressed)
+ hsplit.add_child(popup_button)
+
+ scripts_tab_container.tab_changed.connect(_on_script_tab_changed)
+ scripts_tab_container.child_order_changed.connect(_on_script_tab_rearranged, CONNECT_DEFERRED | CONNECT_ONE_SHOT)
+ scripts_tab_container.child_entered_tree.connect(_queue_sync.unbind(1))
+ scripts_tab_container.child_exiting_tree.connect(_queue_sync.unbind(1))
+ custom_tab_bar.tab_changed.connect(_on_tab_changed)
+ custom_tab_bar.tab_close_pressed.connect(_on_tab_close_pressed)
+ custom_tab_bar.tab_rearranged.connect(_on_tab_rearranged)
+
+ EditorInterface.get_resource_filesystem().filesystem_changed.connect(sync_tab_names)
+ EditorInterface.get_script_editor().editor_script_changed.connect(_on_editor_script_changed)
+ if active_script_editor:
+ active_script_editor.edited_script_changed.connect(_on_edited_script_changed)
+
+ sync_tabs()
+
+
+func sync_tab_names() -> void:
+ await get_tree().process_frame
+ await get_tree().process_frame
+ for i in scripts_tab_container.get_tab_count():
+ custom_tab_bar.set_tab_title(i, scripts_tab_container.get_tab_title(i))
+
+
+func _on_editor_script_changed(script: Script) -> void:
+ if active_script_editor:
+ active_script_editor.edited_script_changed.disconnect(_on_edited_script_changed)
+ active_script_editor = EditorInterface.get_script_editor().get_current_editor()
+ if active_script_editor:
+ active_script_editor.edited_script_changed.connect(_on_edited_script_changed)
+
+
+func _on_edited_script_changed() -> void:
+ var idx := scripts_tab_container.current_tab
+ custom_tab_bar.set_tab_title(idx, scripts_tab_container.get_tab_title(idx))
+
+
+var is_syncing: bool = false
+func _queue_sync() -> void:
+ if is_syncing:
+ return
+ is_syncing = true
+ sync_tabs.call_deferred()
+
+
+func sync_tabs() -> void:
+ custom_tab_bar.clear_tabs()
+
+ for i in scripts_item_list.item_count:
+ var tab := custom_tab_bar.add_tab(scripts_item_list.get_item_text(i), scripts_item_list.get_item_icon(i))
+ tab.icon_color = scripts_item_list.get_item_icon_modulate(i)
+ tab.tooltip_text = scripts_item_list.get_item_tooltip(i)
+ custom_tab_bar.tabs[tab.get_tab_path()] = tab
+
+ custom_tab_bar.current_tab = scripts_tab_container.current_tab
+
+ is_syncing = false
+
+ synced.emit()
+
+
+func sync_current_tab(idx: int, custom: bool) -> void:
+ if custom:
+ scripts_tab_container.current_tab = idx
+ custom_tab_bar.current_tab = idx
+
+
+func set_popup(popup: PopupPanel) -> void:
+ popup_panel = popup
+
+func _on_script_tab_changed(idx: int) -> void:
+ sync_current_tab(idx, false)
+
+
+func _on_script_tab_rearranged() -> void:
+ custom_tab_bar.move_tab(custom_tab_bar.current_tab, scripts_tab_container.current_tab, false)
+ sync_current_tab(scripts_tab_container.current_tab, true)
+ scripts_tab_container.child_order_changed.connect(_on_script_tab_rearranged, CONNECT_DEFERRED | CONNECT_ONE_SHOT)
+
+
+func _on_tab_changed(idx: int) -> void:
+ sync_current_tab(idx, true)
+
+
+func _on_tab_close_pressed(idx: int) -> void:
+ scripts_tab_container.remove_child(scripts_tab_container.get_child(idx))
+
+
+func _on_tab_rearranged(from: int, to: int) -> void:
+ scripts_tab_container.move_child(scripts_tab_container.get_child(from), to)
+ sync_current_tab(to, true)
+
+
+func _on_popup_button_pressed() -> void:
+ pre_popup_pressed.emit()
+ popup_panel.position = popup_button.get_screen_position() - Vector2(popup_panel.size.x, 0)
+ popup_panel.popup()