Skip to content

GObject decorators

Decorators that wrap GObject.registerClass.

Example Usage

ts
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
js
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:

ts
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 available String, Number, Boolean and Object JavaScript constructors which are mapped to their relative GObject.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 example myProp -> my-prop), and flags is one of: ParamFlags.READABLE, ParamFlags.WRITABLE, ParamFlags.READWRITE.

    ts
    const 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.

ts
@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.

ts
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.

ts
@register()
class MyObj extends GObject.Object {
  @getter(String)
  get readOnly() {
    return "readonly value"
  }
}

The setter decorator lets you declare a write-only property.

ts
@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

ts
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.

ts
@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.

ts
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

ts
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

ts
@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.