GUI

All info i learned about building graphical user interfaces in Godot.

Nodes

Here is a list of common Control nodes with their name next to them:

control_gallery

Most used nodes

Unhandled input

Once an InputEvent didn't interact with the GUI, it is forwarded to the _unhandled_input method:

func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_pressed("lmb"):
        on_lmb_pressed.emit( get_mouse_position_ray_result() )
    if event.is_action_released("lmb"):
        on_lmb_released.emit( get_mouse_position_ray_result() )

How does it work?

Tipps and Tricks

GUI Tipps and Tricks

Split container

HSplitContainer and VSplitContainer allow a pretty flexible layout.

From Reddit

golfgame-split-containers

split_style.gd

extends SplitContainer

var draggers: Array[Control]
var idx_cur: int = 0
var is_dragging: bool = false
var is_mouse_enter: bool = false

func _ready() -> void:
    theme_type_variation = &"ScSplitSection"
    drag_started.connect(on_drag_started)
    drag_ended.connect(on_drag_ended)
    deffered_ready.call_deferred()

func deffered_ready() -> void:
    var idx: int = 0
    for child in get_children(true):
        if child.get_class() == "SplitContainerDragger":
            draggers.push_back(child)
            child.mouse_entered.connect(on_mouse_entered.bind(idx))
            child.mouse_exited.connect(on_mouse_exited)
            idx += 1
    update_view()

func update_view() -> void:
    for dragger: Control in draggers:
        dragger.self_modulate.a = 0.0
    if is_dragging:
        draggers[idx_cur].self_modulate.a = 1.0
    elif is_mouse_enter:
        draggers[idx_cur].self_modulate.a = 0.36

func on_drag_started() -> void:
    is_dragging = true
    update_view()
func on_drag_ended() -> void:
    is_dragging = false
    if GlobalMng.is_touch:
        is_mouse_enter = false
    update_view()

func on_mouse_entered(idx: int) -> void:
    if !is_dragging:
        idx_cur = idx
    is_mouse_enter = true
    update_view()
func on_mouse_exited() -> void:
    is_mouse_enter = false
    update_view()

split_with_parent.gd

extends "res://scene/shot_analysis/sections/split_style.gd"


var is_left: bool = true
var parent_split: SplitContainer = get_parent().get_parent() as SplitContainer
var idx_near: int = 0 if is_left else 1
var idx_far: int = 1 if is_left else 0
var ori_mouse_pos: float
var ori_offset: int
var ori_parent_offsets: PackedInt32Array = [0,0]


func _ready() -> void:
    super()
    drag_started.connect(on_drag_started2)
    await get_tree().process_frame


func on_drag_started2() -> void:
    ori_mouse_pos = get_global_mouse_position()[1 if vertical else 0] 
    ori_offset = split_offsets[0]
    ori_parent_offsets[0] = parent_split.split_offsets[0]
    ori_parent_offsets[1] = parent_split.split_offsets[1]


func _input(event: InputEvent) -> void:
    if !(is_dragging && event is InputEventMouseMotion):
        return
    get_viewport().set_input_as_handled()
    var par_diff: float = parent_split.split_offsets[idx_near] - ori_parent_offsets[idx_near]
    var mouse_delta: float = event.global_position[1 if vertical else 0] - ori_mouse_pos
    var desired_offset: int = ori_offset + int(mouse_delta+par_diff*0.5)
    split_offsets[0] = desired_offset
    clamp_split_offset(0)
    var overflow_near: int = desired_offset - split_offsets[0]
    if (overflow_near<0) == is_left:
        overflow_near = 0
    var next_near_offset: int = ori_parent_offsets[idx_near] + overflow_near
    var overflow_far: int = next_near_offset - ori_parent_offsets[idx_far]
    if (overflow_far<0) == is_left:
        overflow_far = 0
    parent_split.split_offsets[idx_near] = next_near_offset
    parent_split.split_offsets[idx_far] = ori_parent_offsets[idx_far] + overflow_far