Add useSignal
Some checks failed
Lint / lint (push) Failing after 9s

This commit is contained in:
Julien Valverdé
2026-01-01 23:25:05 +01:00
parent d37c5e1405
commit fae11b2024
8 changed files with 73 additions and 21 deletions

View File

@@ -0,0 +1,5 @@
declare module "godot" {
interface SceneNodes {
"src/TestUi2.tscn": {};
}
}

View File

@@ -0,0 +1,6 @@
import TestUi2 from "../../../src/TestUi2";
declare module "godot" {
interface ResourceTypes {
"res://src/TestUi2.tscn": PackedScene<TestUi2>;
}
}

View File

@@ -8,4 +8,8 @@ export default class TestUi1 extends Control {
_ready(): void { _ready(): void {
Renderer.renderComponent(this, TestUi1Component) Renderer.renderComponent(this, TestUi1Component)
} }
_exit_tree(): void {
console.log("exit tree")
}
} }

View File

@@ -1,5 +1,5 @@
import Godot, { Vector2 } from "godot" import Godot, { Vector2 } from "godot"
import { useEffect, useState } from "react" import { useState } from "react"
import { Component } from "react-godot-renderer" import { Component } from "react-godot-renderer"
@@ -11,26 +11,16 @@ const Button = Component.make(Godot.Button)
export function TestUi1Component() { export function TestUi1Component() {
const [text, setText] = useState("Default text") const [text, setText] = useState("Default text")
const textEditRef = TextEdit.useUnsafeRef() const textEditRef = TextEdit.useRef()
const buttonRef = Button.useUnsafeRef() Component.useSignal(textEditRef, "text_changed", function(this) {
setText(this.text)
})
useEffect(() => { const buttonRef = Button.useRef()
const onTextChanged = Godot.Callable.create(() => { Component.useSignal(buttonRef, "pressed", () => {
setText(textEditRef.current.text) console.log("Pressed!")
}) })
textEditRef.current.text_changed.connect(onTextChanged)
return () => { textEditRef.current.text_changed.disconnect(onTextChanged) }
}, [textEditRef.current])
useEffect(() => {
const onPressed = Godot.Callable.create(() => {
console.log("Pressed!")
})
buttonRef.current.pressed.connect(onPressed)
return () => { buttonRef.current.pressed.disconnect(onPressed) }
}, [buttonRef.current])
return ( return (
<HFlowContainer <HFlowContainer

View File

@@ -0,0 +1,9 @@
import { Control } from "godot"
export default class TestUi2 extends Control {
// Called when the node enters the scene tree for the first time.
_ready(): void {
}
}

View File

@@ -0,0 +1 @@
uid://bm1qao8uj6b1f

View File

@@ -0,0 +1,12 @@
[gd_scene load_steps=2 format=3 uid="uid://b17syqj60kwx1"]
[ext_resource type="Script" uid="uid://bm1qao8uj6b1f" path="res://src/TestUi2.ts" id="1_1lkkw"]
[node name="TestUi2" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_1lkkw")

View File

@@ -1,4 +1,4 @@
import type * as Godot from "godot" import Godot from "godot"
import * as React from "react" import * as React from "react"
@@ -9,7 +9,7 @@ export type Props<T extends Godot.Node<Godot.NodePathMap>> = {
readonly ref?: React.RefObject<T | null> readonly ref?: React.RefObject<T | null>
} & { } & {
// biome-ignore lint/complexity/noBannedTypes: using Function here is completely fine // biome-ignore lint/complexity/noBannedTypes: using Function here is completely fine
[K in keyof T as T[K] extends Function ? never : K]?: T[K] [K in keyof T as T[K] extends Function | Godot.Signal ? never : K]?: T[K]
} }
export interface Prototype<T extends Godot.Node<Godot.NodePathMap>> { export interface Prototype<T extends Godot.Node<Godot.NodePathMap>> {
@@ -36,3 +36,28 @@ export const make = <T extends Godot.Node<Godot.NodePathMap>>(
), ),
Prototype, Prototype,
) )
export declare namespace useSignal {
export type SignalNames<T extends Godot.Node<Godot.NodePathMap>> = {
[K in keyof T & string]: T[K] extends Godot.Signal ? K : never
}[keyof T & string]
export type Function<T extends Godot.Node<Godot.NodePathMap>, N extends useSignal.SignalNames<T>> = (
T[N] extends Godot.Signal<infer F>
? (this: T, ...args: Parameters<F>) => ReturnType<F>
: never
)
}
export const useSignal = <T extends Godot.Node<Godot.NodePathMap>, N extends useSignal.SignalNames<T>>(
ref: React.RefObject<T | null>,
name: N,
f: useSignal.Function<T, N>,
// biome-ignore lint/correctness/useExhaustiveDependencies: "f" is non-reactive
): void => React.useEffect(() => {
if (!ref.current) return
const signal = ref.current[name] as Godot.Signal
const callable = Godot.Callable.create(ref.current, f)
signal.connect(callable)
return () => { signal.disconnect(callable) }
}, [ref.current])