godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Implement a way for EditorPlugins to get 2D Editor Grid Snap #10529

Open IntangibleMatter opened 3 weeks ago

IntangibleMatter commented 3 weeks ago

Describe the project you are working on

A plugin with a node that has custom handles for scaling/moving in the editor.

Describe the problem or limitation you are having in your project

There is no way to access grid snap to force the item to snap to the grid by default

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Implement some sort of get_grid_snap() function on EditorPlugin, EditorInterface, or the otherwise most relevant class.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

You would call a function to get the snapping intervals, as well as whether snap is enabled/disabled.

If this enhancement will not be used often, can it be worked around with a few lines of script?

Nope, you need to implement an entire grid system yourself, which is generally much more confusing to people using the plugin.

Is there a reason why this should be core and not an add-on in the asset library?

Core editor functionality.

KoBeWi commented 3 weeks ago

You can hack it around by finding the snap dialog in scene tree and reading its values.

IntangibleMatter commented 3 weeks ago

You can hack it around by finding the snap dialog in scene tree and reading its values.

This is a good temporary solution and I thank you for it, but it's still a needless hack considering how common this use case is for 2D plugins.

IntangibleMatter commented 3 weeks ago

For anyone who comes across this in future and wants to use this, here's a version of the code working in Godot 4.3.0

var snap_enabled: bool = false
var snap_dimensions: Vector2 = Vector2.ZERO
var snap_offset: Vector2 = Vector2.ZERO

func _ready() -> void:
    connect_to_snap()

func connect_to_snap() -> void:
    if not Engine.is_editor_hint():
        return

    var snap: Node = EditorInterface.get_editor_main_screen()\
        .get_child(0).get_child(2).get_child(0).get_child(0)

    # this is a terrible line of code but I don't have a better way to do it
    var snaptoggle: Button = EditorInterface.get_editor_main_screen().\
        get_child(0).get_child(0).get_child(0).get_child(0).get_child(12)

    snap_enabled = snaptoggle.button_pressed
    snaptoggle.toggled.connect(func(tog: bool) -> void: snap_enabled = tog)

    # grid offset x
    snap.get_child(1).value_changed.connect(func(val: float) -> void: snap_offset.x = val)
    # grid offset y
    snap.get_child(2).value_changed.connect(func(val: float) -> void: snap_offset.y = val)
    snap_offset = Vector2(snap.get_child(1).value, snap.get_child(2).value)

    # grid snap x
    snap.get_child(7).value_changed.connect(func(val: float) -> void: snap_dimensions.y = val)
    # grid snap y
    snap.get_child(8).value_changed.connect(func(val: float) -> void: snap_dimensions.y = val)
    snap_dimensions = Vector2(snap.get_child(7).value, snap.get_child(8).value)

This code will break if any of the relevant nodes are offset in the tree, but this is the only way I could find to do it without being dependent on using .filter() and searching for exact strings, which isn't friendly to using other languages in the editor. Again, terrible. This is why there should be an easier way to access these variables. Hope this helps anyone in the future!

KoBeWi commented 3 weeks ago

Finding base controls can be more reliable:

func nice_find_child(parent: Node, child: String) -> Node:
    return parent.find_child("*%s*" % child, true, false)

func connect_to_snap() -> void:
    var snap := nice_find_child(EditorInterface.get_base_control(), "SnapDialog") # SnapDialog
    snap = nice_find_child(snap_grid, "GridContainer") # SpinBox container.

    var snap_toggle := nice_find_child(EditorInterface.get_base_control(), "CanvasItemEditor") # 2D editor.
    snap_toggle = nice_find_child(snap_toggle, "HBoxContainer") # Toolbar.
    snap_toggle = snap_toggle.get_child(12) # Snap button.

You can also avoid get_child() by implementing a method that returns nth child of given type.

While it's still a hack, it's a bit more resilient to hierarchy changes.

IntangibleMatter commented 3 weeks ago

I actually started out by using find_child(), but it wouldn't find the nodes that I wanted it to do so (likely due to weird stuff with internal children or something), so I used this method instead, though I'll take another look.

Again, while it's good to have a method to do so, something this important for 2D editor plugins shouldn't be so hacky.