GObject decorators
Decorators that wrap GObject.registerClass.
Required TypeScript settings
Make sure experimentalDecorators is set to false and target is less than or equal to ES2020 in tsconfig.json.
{
"compilerOptions": {
"experimentalDecorators": false,
"target": "ES2020"
}
}Example Usage
import GObject, { register, property, signal } from "gnim/gobject"
@register({ GTypeName: "MyObj" })
class MyObj extends GObject.Object {
@property(String) myProp = ""
@signal(String, GObject.TYPE_UINT)
mySignal(a: string, b: number) {
// default handler
}
}What it (roughly) transpiles to
const priv = Symbol("private props")
class MyObj extends GObject.Object {
[priv] = { "my-prop": "" }
constructors() {
super()
Object.defineProperty(this, "myProp", {
enumerable: true,
configurable: false,
set(value) {
if (this[priv]["my-prop"] !== value) {
this[priv]["my-prop"] = v
this.notify("my-prop")
}
},
get() {
return this[priv]["my-prop"]
},
})
}
mySignal(a, b) {
return this.emit("my-signal", a, b)
}
on_my_signal(a, b) {
// default handler
}
}
GObject.registerClass(
{
GTypeName: "MyObj",
Properties: {
"my-prop": GObject.ParamSpec.string(
"my-prop",
"",
"",
GObject.ParamFlags.READWRITE,
"",
),
},
Signals: {
"my-signal": {
param_types: [String.$gtype, GObject.TYPE_UINT],
},
},
},
MyObj,
)NOTE
Property accessors are defined on the object instance and not the prototype. This might change in the future. Stage 3 decorators are adding a new keyword accessor for declaring properties, which marks properties to expand as get and set methods on the prototype. The accessor keyword is currently not supported by these decorators.
Property decorator
Property declarations are split into three decorators:
type PropertyTypeDeclaration<T> =
| ((name: string, flags: ParamFlags) => ParamSpec<T>)
| { $gtype: GType<T> }
function property<T>(typeDeclaration: PropertyTypeDeclaration<T>): void
function setter<T>(typeDeclaration: PropertyTypeDeclaration<T>): void
function getter<T>(typeDeclaration: PropertyTypeDeclaration<T>): voidThese decorators take a single parameter that defines the type:
any class that has a registered
GType. This includes the globally availableString,Number,BooleanandObjectJavaScript constructors, which are mapped to their relativeGObject.ParamSpec.Object:ParamSpec.jsobjectString:ParamSpec.stringNumber:ParamSpec.doubleBoolean:ParamSpec.booleanGObject.Objectand its subclasses
a function that produces a
ParamSpecwhere the passed name is a kebab-cased name of the property (for examplemyProp->my-prop), and flags is one of:ParamFlags.READABLE,ParamFlags.WRITABLE,ParamFlags.READWRITE.tsconst Percent = (name: string, flags: ParamFlags) => GObject.ParamSpec.double(name, "", "", flags, 0, 1, 0) @register() class MyObj extends GObject.Object { @property(Percent) percent = 0 }
property
The property decorator lets you declare a read-write property.
@register()
class MyObj extends GObject.Object {
@property(String) myProp = ""
}This will create a getter and setter for the property and will also emit the notify signal when the value is set to a new value.
WARNING
The value is checked by reference, which is important if your property is an object type.
const dict = obj.prop
dict["key"] = 0
obj.prop = dict // This will not emit notify::prop
obj.prop = { ...dict } // This will emit notify::propWhen using custom subclasses as properties, you might want to annotate its $gtype.
@register()
class DeepProp extends GObject.Object {
declare static $gtype: GObject.GType<DeepProp>
}
@register()
class MyClass extends GObject.Object {
@property(DeepProp) prop: DeepProp
}getter
The getter decorator lets you declare a read-only property.
@register()
class MyObj extends GObject.Object {
@getter(String)
get readOnly() {
return "readonly value"
}
}setter
The setter decorator lets you declare a write-only property.
@register()
class MyObj extends GObject.Object {
#prop = ""
@setter(String)
set myProp(value: string) {
if (value !== this.#prop) {
this.#prop = value
this.notify("my-prop")
}
}
}NOTE
When using setter you will have to explicitly emit the notify signal.
TIP
You can use the setter and getter decorators in combination to declare a read-write property.
Signal decorator
function signal(
params: Array<GType>,
returnType?: GType,
options?: {
default?: default
flags?: SignalFlags
accumulator?: AccumulatorType
},
)
function signal(...params: Array<GType>)You can apply the signal decorator to a method where the method is the default handler of the signal.
@register()
class MyObj extends GObject.Object {
@signal([String, Number], Boolean, {
default: true,
accumulator: GObject.AccumulatorType.FIRST_WINS,
})
myFirstHandledSignal(str: string, n: number): boolean {
return false
}
@signal(String, GObject.TYPE_STRING)
mySignal(a: string, b: string): void {
// default signal handler
}
}TIP
It is required to provide a function implementation which becomes the default signal handler. In case you don't want to implement a default handler you can set the default option to false.
class {
@signal([], Boolean, {
default: false,
})
withoutDefaultImpl(): boolean {
throw "this never runs"
}
}You can emit the signal by calling the signal method or using emit.
const obj = new MyObj()
obj.connect("my-signal", (obj, a: string, b: string) => {})
obj.mySig("a", "b")
obj.emit("my-signal", "a", "b")TIP
To make the connect method aware of signals, you can override it.
interface MyObjSignals extends GObject.Object.SignalSignatures {
"my-signal": MyObj["mySignal"]
}
@register()
class MyObj extends GObject.Object {
declare $signals: MyObjSignals // this makes signals inferable in JSX
override connect<S extends keyof MyObjSignals>(
signal: S,
callback: GObject.SignalCallback<this, MyObjSignals[S]>,
): number {
return super.connect(signal, callback)
}
}Register decorator
Every GObject.Object subclass has to be registered. You can pass the same options to this decorator as you would to GObject.registerClass.
@register({ GTypeName: "MyObj" })
class MyObj extends GObject.Object {}TIP
This decorator registers properties and signals defined with decorators, so make sure to use this and not GObject.registerClass if you define any.