Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 202 additions & 13 deletions addons/inspector_tabs/inspector_tabs.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const KEY_TAB_LAYOUT = "inspector_tabs/tab_layout"
const KEY_TAB_STYLE = "inspector_tabs/tab_style"
const KEY_TAB_PROPERTY_MODE = "inspector_tabs/tab_property_mode"
const KEY_MERGE_ABSTRACT_CLASS_TABS = "inspector_tabs/merge_abstract_class_tabs"
const KEY_TAB_CLIP_MODE = "inspector_tabs/tab_clip_mode"

enum TabStyles{
TEXT_ONLY,
Expand All @@ -23,7 +24,7 @@ var tabs = [] # All tabs in the inspector

var categories_finish = false # Finish adding categories

var tab_bar:TabBar # Inspector Tabs
var tab_bar:InspectorTabBar # Inspector Tabs
var base_control = EditorInterface.get_base_control()
var settings = EditorInterface.get_editor_settings()

Expand All @@ -35,6 +36,7 @@ var vertical_tab_side = 1 # 0:left; 1:Right;
var tab_style:TabStyles
var property_mode:TabPropertyModes
var merge_abstract_class_tabs:bool
var tab_clip_mode:InspectorTabBar.ClipMode

## path to the editor inspector list of properties
var property_container = EditorInterface.get_inspector().get_child(0).get_child(2)
Expand Down Expand Up @@ -123,6 +125,7 @@ func parse_begin(object: Object) -> void:
func process(delta) -> void:
# Reposition UI
if vertical_mode:
tab_bar.size.y = 0
tab_bar.size.x = EditorInterface.get_inspector().size.y
if vertical_tab_side == 0:#Left side
tab_bar.global_position = EditorInterface.get_inspector().global_position+Vector2(0,tab_bar.size.x)
Expand Down Expand Up @@ -170,6 +173,7 @@ func start() -> void:
tab_style = settings.get("inspector_tabs/tab_style")
property_mode = settings.get("inspector_tabs/tab_property_mode")
merge_abstract_class_tabs = settings.get("inspector_tabs/merge_abstract_class_tabs")
tab_clip_mode = settings.get(KEY_TAB_CLIP_MODE)
settings.settings_changed.connect(settings_changed)

var tab_pos = settings.get("inspector_tabs/tab_layout")
Expand Down Expand Up @@ -317,7 +321,9 @@ func tab_selected(tab):
func tab_resized():
if not vertical_mode:
if tabs.size() != 0:
tab_bar.max_tab_width = tab_bar.get_parent().get_rect().size.x/tabs.size()
pass
#print_debug("FIX max_tab_width")
#tab_bar.max_tab_width = tab_bar.get_parent().get_rect().size.x/tabs.size()



Expand All @@ -328,17 +334,15 @@ func change_vertical_mode(mode:bool = vertical_mode):
tab_bar.queue_free()
vertical_mode = vertical_mode

tab_bar = TabBar.new()
tab_bar = InspectorTabBar.new(tab_clip_mode, vertical_mode, vertical_tab_side == 0)
tab_bar.custom_minimum_size = Vector2(10,10)
tab_bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL
tab_bar.clip_tabs = true
#tab_bar.clip_tabs = true
tab_bar.rotation = PI/2
tab_bar.mouse_filter =Control.MOUSE_FILTER_PASS
var panel = Panel.new()
tab_bar.add_child(panel)
panel.anchor_right = 1
panel.anchor_bottom = 1
panel.show_behind_parent = true
panel.mouse_filter = Control.MOUSE_FILTER_IGNORE

#panel.anchor_right = 1
#panel.anchor_bottom = 1

var inspector = EditorInterface.get_inspector().get_parent()

Expand All @@ -357,9 +361,11 @@ func change_vertical_mode(mode:bool = vertical_mode):
viewer_container.size_flags_horizontal = Control.SIZE_SHRINK_END
tab_bar.top_level = true
if vertical_tab_side == 0:
tab_bar.layout_direction =Control.LAYOUT_DIRECTION_RTL
else:
tab_bar.layout_direction =Control.LAYOUT_DIRECTION_LTR
(tab_bar._tab_container as HBoxContainer).layout_direction = Control.LAYOUT_DIRECTION_RTL
else:
tab_bar.layout_direction =Control.LAYOUT_DIRECTION_RTL
(tab_bar._tab_container as HBoxContainer).layout_direction = Control.LAYOUT_DIRECTION_LTR
else:
property_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
favorite_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
Expand Down Expand Up @@ -393,8 +399,14 @@ func settings_changed() -> void:
if merge_class != null:
if merge_abstract_class_tabs != merge_class:
merge_abstract_class_tabs = merge_class
var clip_mode = settings.get(KEY_TAB_CLIP_MODE)
if clip_mode != null:
if tab_clip_mode != clip_mode:
tab_clip_mode = clip_mode
change_vertical_mode()

if tab_pos != null and style != null and prop_mode != null and merge_class != null:

if tab_pos != null and style != null and prop_mode != null and merge_class != null and clip_mode != null:

#Save settings
var config = ConfigFile.new()
Expand All @@ -403,6 +415,7 @@ func settings_changed() -> void:
config.set_value("Settings", "tab style", style)
config.set_value("Settings", "tab property mode", prop_mode)
config.set_value("Settings", "merge abstract class tabs", merge_abstract_class_tabs)
config.set_value("Settings", "tab clip mode", clip_mode)

# Save it to a file (overwrite if already exists).
var err = config.save(EditorInterface.get_editor_paths().get_config_dir()+"/InspectorTabsPluginSettings.cfg")
Expand Down Expand Up @@ -526,3 +539,179 @@ func load_gdextension_config(path: String) -> Dictionary:
data[section][key] = config.get_value(section, key)

return data

class InspectorTabBar extends MarginContainer:
signal tab_clicked(tab : int)
signal tab_selected(tab : int)

var _buttons : Array[Button] = []
var _tab_container : Container

var current_tab:int = 0:
set(v):
if current_tab != v:
current_tab = v
_update_buttons()

var tab_count:int = 0

enum ClipMode {
SCROLL,
CLIP_TEXT,
FLOW
}

func _init(mode:ClipMode, vertical:bool, on_left_side:bool) -> void:
self.name = "InspectorTabBar"

var panel = Panel.new()
add_child(panel)
panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
panel.clip_children = CanvasItem.CLIP_CHILDREN_AND_DRAW

var add_scroll_container := func():
_tab_container = HBoxContainer.new()
_tab_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var scroll_container := InspectorScrollContainer.new(vertical)
panel.add_child(scroll_container)
scroll_container.add_child(_tab_container)
scroll_container.minimum_size_changed.connect(func(): panel.custom_minimum_size.y = scroll_container.size.y)
scroll_container.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE)


match mode:
ClipMode.FLOW:
_tab_container = FlowContainer.new()
panel.add_child(_tab_container)
_tab_container.minimum_size_changed.connect(func(): panel.custom_minimum_size.y = _tab_container.get_minimum_size().y)
ClipMode.SCROLL:
add_scroll_container.call()
ClipMode.CLIP_TEXT:
add_scroll_container.call()

_tab_container.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_tab_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_tab_container.size_flags_vertical = Control.SIZE_EXPAND_FILL


func _tab_clicked(tab: int) -> void:
current_tab = tab
tab_clicked.emit(tab)
_update_buttons()
func _update_buttons():
for i in _buttons.size():
if i != current_tab:
_buttons[i].button_pressed = false
else:
_buttons[i].button_pressed = true

func clear_tabs() -> void:
for button in _buttons:
button.queue_free()
_buttons.clear()
tab_count = 0

func add_tab(title:String, icon:Texture2D) -> void:
#print("ADD T")
var button := Button.new()
button.toggle_mode = true
_tab_container.add_child(button)
button.pressed.connect(_tab_clicked.bind(_buttons.size()))
_buttons.append(button)
button.text = title
button.icon = icon
tab_count += 1
#printerr("add_tab() not implemented")

func set_tab_tooltip(tab_idx:int, tooltip:String) -> void:
_buttons[tab_idx].tooltip_text = tooltip

class InspectorScrollContainer extends Control:

const PAGE_DIVISOR = 8

var _scroll_bar : ScrollBar
var _child : Control

var reverse := false

func _init(reverse:bool) -> void:
self.reverse = reverse
#var vbox := VBoxContainer.new()
#vbox.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
#add_child(vbox)
#vbox.minimum_size_changed.connect(func(): custom_minimum_size.y = vbox.get_minimum_size().y)
#vbox.clip_contents = false
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

#vbox.add_child(_tab_container)


_scroll_bar = HScrollBar.new()
add_child(_scroll_bar)
_scroll_bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_scroll_bar.set_anchors_and_offsets_preset.call_deferred(Control.PRESET_BOTTOM_WIDE)
_scroll_bar.owner = self
_scroll_bar.scrolling.connect(_on_scroll_bar_scrolling)
resized.connect(_update_scroll_bar)
if reverse:
_scroll_bar.scale.x = -1
#_scroll_bar.

custom_minimum_size.y = _scroll_bar.get_minimum_size().y
child_entered_tree.connect(_on_child_entered_tree)

func _on_child_entered_tree(node: Node) -> void:
if _child: return
if node != _scroll_bar:
if node is Control:
_child = node
node.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE)
node.minimum_size_changed.connect(func():
_update_scroll_bar())
custom_minimum_size.y = _scroll_bar.get_minimum_size().y + node.get_minimum_size().y
#panel.resized.connect(func():
#scroll_bar.page = panel.size.x;
#scroll_bar.custom_minimum_size.x = panel.size.x)

func _update_scroll_bar() -> void:
if _child:
_scroll_bar.max_value = _child.get_minimum_size().x
custom_minimum_size.y = _scroll_bar.get_minimum_size().y + _child.get_minimum_size().y;

_scroll_bar.page = size.x;
#_scroll_bar.custom_minimum_size.x = size.x
_scroll_bar.scrolling.emit()

if _scroll_bar.page == _scroll_bar.max_value:
_scroll_bar.visible = false
if _child:
custom_minimum_size.y = _child.get_minimum_size().y;
else:
_scroll_bar.visible = true
if _child:
custom_minimum_size.y = _scroll_bar.get_minimum_size().y + _child.get_minimum_size().y;
_scroll_bar.position.y = size.y - _scroll_bar.size.y

if reverse:
_scroll_bar.position.x = size.x

func _on_scroll_bar_scrolling() -> void:
if _child:
if not reverse:
_child.position.x = -_scroll_bar.value
else:
_child.position.x = size.x + _scroll_bar.value - _child.size.x

func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton:
if event.button_index == MouseButton.MOUSE_BUTTON_WHEEL_DOWN and event.is_pressed():
# Taken from godot source code.
var change : float = ( _scroll_bar.page / PAGE_DIVISOR if (_scroll_bar.page != 0.0) else (_scroll_bar.max_value - _scroll_bar.min_value) / 16.0) * event.factor;
_scroll_bar.value += max(change,_scroll_bar.step)
_scroll_bar.scrolling.emit()
if event.button_index == MouseButton.MOUSE_BUTTON_WHEEL_UP and event.is_pressed():
# Taken from godot source code.
var change : float = ( _scroll_bar.page / PAGE_DIVISOR if (_scroll_bar.page != 0.0) else (_scroll_bar.max_value - _scroll_bar.min_value) / 16.0) * event.factor;
_scroll_bar.value -= max(change,_scroll_bar.step)
_scroll_bar.scrolling.emit()
13 changes: 13 additions & 0 deletions addons/inspector_tabs/plugin.gd
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func _exit_tree():
settings.set(plugin.KEY_TAB_STYLE, null)
settings.set(plugin.KEY_TAB_PROPERTY_MODE, null)
settings.set(plugin.KEY_MERGE_ABSTRACT_CLASS_TABS, null)
settings.set(plugin.KEY_TAB_CLIP_MODE, null)

plugin.exit()
remove_inspector_plugin(plugin)
Expand Down Expand Up @@ -78,6 +79,18 @@ For example, [b]PhysicsBody3D[/b] category will be in the [b]RigidBody3D[/b] tab
config,
)

_load_setting(INSPECTOR_TAB.KEY_TAB_CLIP_MODE,
"How the tabs in the tab bar are clipped when they are overflowed.\n
[b]Scroll:[/b] A scroll bar will be shown when the tabs overflowed. \n
[b]Clip Text:[/b] If [b]Tab Style[/b] is set to [b]Text Only[/b] or [b]Text and Icon[/b], the text in the tab will be clipped when overflowed. \n
[b]Flow:[/b] The tabs will be moved to a new row when overflowed.",
TYPE_INT,
PROPERTY_HINT_ENUM,
"Scroll,Clip Text,Flow",
"tab clip mode",
0,
config,
)



Expand Down