GObject decorators
Decorators that wrap GObject.registerClass
.
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
Declaring properties 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>): void
These decorators take a single parameter which defines the type:
any class that has a registered
GType
. This includes the globally availableString
,Number
,Boolean
andObject
JavaScript constructors which are mapped to their relativeGObject.ParamSpec
.Object
:ParamSpec.jsobject
String
:ParamSpec.string
Number
:ParamSpec.double
Boolean
:ParamSpec.boolean
GObject.Object
and its subclasses
a function that produces a
ParamSpec
where 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 }
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, this 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::prop
The getter
decorator lets you declare a read-only property.
@register()
class MyObj extends GObject.Object {
@getter(String)
get readOnly() {
return "readonly value"
}
}
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?: {
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, {
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
}
}
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 {}
This decorator registers properties and signals defined with
decorators, so make sure to use this and not GObject.registerClass
if you define any.