Skip to content

Instantly share code, notes, and snippets.

@Qubus0
Last active February 26, 2023 21:42
Show Gist options
  • Save Qubus0/0b4688f946bf195ca005b9c52bf82a95 to your computer and use it in GitHub Desktop.
Save Qubus0/0b4688f946bf195ca005b9c52bf82a95 to your computer and use it in GitHub Desktop.
Godot Plugins: Good looking tabbed dock (3.5)

Making a tabbed EditorPlugin dock in Godot (3.5.x)

...seems simple enough:

Make a dock scene, load it, add it, done. Like any other dock, right?

extends EditorPlugin

var dock: Control

func _enter_tree():
    dock = preload("res://addons/tab_dock/dock.tscn").instance()

    add_control_to_bottom_panel(dock, 'Tab Dock')

Perfect!

no changes debugger

Wait, why do we have a border around everything and the debugger doesn't?

To investigate, I've used the editor debugger addon. It can show the editor nodes in the inspector, so you can have a good look at all of their properties.

Let's have a look how Godot does it. Ah, here we have it - github.com/godotengine/godot/blob/.../editor/editor_node.cpp

void EditorNode::_bottom_panel_switch(bool p_enable, int p_idx) {
    // other stuff
    if (p_enable) { // <- ah, when they are enabling it
        if (EditorDebuggerNode::get_singleton() == bottom_panel_items[p_idx].control) {
            // This is the debug panel which uses tabs, so the top section should be smaller.
            bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("BottomPanelDebuggerOverride"), SNAME("EditorStyles")));
        } else {
            bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("BottomPanel"), SNAME("EditorStyles")));
        }
    }
}

So, they are using a specific override panel there and they set it when the tab switches. Cool, we can do that too!

Let's get the base theme first. We can get that in the EditorPlugin script and just pass it to the dock

func _enter_tree():
    dock = preload("res://addons/tab_dock/dock.tscn").instance()
    dock.base_theme = get_editor_interface().get_base_control().theme
    add_control_to_bottom_panel(dock, 'Tab Dock')

Then we take it in the dock script and just override it when the visibility changes "it" in this case means two nodes above our dock, since the dock is not the panel itself. I called that parent tab_parent_bottom_panel

var base_theme: Theme     # passed from the EditorPlugin
var tab_parent_bottom_panel: PanelContainer

func _ready() -> void:
    tab_parent_bottom_panel = get_parent().get_parent() as PanelContainer
    
    
func _on_dock_visibility_changed() -> void:
    if not visible or not base_theme or not tab_parent_bottom_panel:
        return

    var panel_box: StyleBoxFlat = base_theme.get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")
    tab_parent_bottom_panel.add_stylebox_override("panel", panel_box)

Hold up. Why does this not work? It seems as if the method to change the override is called right after ours and just overrides it back to the default? How dare they. But - we can just wait a frame and then override the override with what we want.

    yield(get_tree(), "idle_frame")
    tab_parent_bottom_panel.add_stylebox_override("panel", panel_box)

Good. What does it look like now?

parent panel overridden

Okay, we can handle that. Looks like the style box has content margins on the left and right, we can just remove those.

    panel_box.content_margin_left = 0
    panel_box.content_margin_right = 0
margins set debugger bugged

Now the debugger overflows. Undo.

Godot placed the debugger inside a Control node with left and right margins set to -10 and 10 to compensate for the content margin of the panel. Don't ask me why.

Okay, we copy that structure.

node layout

And, looks good for both now!

margins set

Wait, there is a little edge left. That stems from the tab panel stylebox it seems. Ah, they also have an override panel for that, let's use it.

func _ready() -> void:
    if base_theme:
        var tab_panel: StyleBoxFlat = base_theme.get_stylebox("DebuggerPanel", "EditorStyles")
        $TabContainer.add_stylebox_override("panel", tab_panel)

Now we are done. What a journey!

panel overridden

Full code

res://addons/tab_dock/plugin.gd

extends EditorPlugin

var dock: Control

func _enter_tree():
    dock = preload("res://addons/tab_dock/dock.tscn").instance()
    dock.base_theme = get_editor_interface().get_base_control().theme

    add_control_to_bottom_panel(dock, 'Tab Dock')

res://addons/tab_dock/dock.gd

var base_theme: Theme     # passed from the EditorPlugin
var tab_parent_bottom_panel: PanelContainer


func _ready() -> void:
    tab_parent_bottom_panel = get_parent().get_parent() as PanelContainer
    if base_theme:
        $TabContainer.add_stylebox_override("panel", base_theme.get_stylebox("DebuggerPanel", "EditorStyles"))

# replicates the behaviour for the debugger tab styles
# for the full setup of this, the TabContainer needs to be the child of a
# full rect Control and have a margin_left of -10 and a margin_right of 10
# this is to offset the 10px content margins that are still present in the
# BottomPanelDebuggerOverride stylebox for some reason. It's how Godot does it.
func _on_ModToolsDock_visibility_changed() -> void:
    if not visible or not base_theme or not tab_parent_bottom_panel:
        return

    # the panel style is overridden by godot after this method is called
    # make sure our override-override is applied after that
    yield(get_tree(), "idle_frame")

    var panel_box: StyleBoxFlat = base_theme.get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")
    tab_parent_bottom_panel.add_stylebox_override("panel", panel_box)

Now you know! If you have more questions, feel free to ask.

If you want to support me, you can

Buy Me A Coffee
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment