class

Tryst::UI::Signal(*T)

Inherits Reference < Object

One typed event, Qt/GObject style, and the only public event mechanism this DSL offers. EventBus lives on internally (see its own doc comment) for Document's build-time hooks - nothing outside Document should reach for it.

file_saved = Tryst::UI::Signal(String, Int64).new file_saved.connect { |path, bytes| status.value = "Saved #{path} (#{bytes} bytes)" } file_saved.emit(path, bytes)

A typo'd #connect/#emit is a compile error (undefined variable, or no overload matches), not a silent no-op; a listener's block params are the real payload types, not casts out of an Array(T) union. The cost is ownership - each event is its own object, so an app with several wants a holder:

class Events getter file_saved = Signal(String, Int64).new getter row_picked = Signal(Int32).new end

one declaration per event, plumbed explicitly. Real ceremony for a 50-line script, but the only public option now - see the class comment above for why.

*T is a splat, not a single T: Signal(String, Int64) needs its listener block to destructure two named, independently-typed params, which a single T (even T = {String, Int64}) can't give a block signature - it'd hand back one Tuple argument, not two.

Zero-payload events (the most common kind - "saved", "closed") are NOT a separate class. *T splats to an empty tuple just fine: Signal().new, #connect { puts "saved" }, #emit with no args. Only the fully bare Signal.new, with no type args at all, fails to compile (T is unconstrained) - the empty parens are required.

#connect/#emit/#disconnect are main-thread-only: emitting from inside a BackgroundWork work block corrupts the listener array rather than raising - emit from on_progress instead.

Constructors

Instance methods

connect

Subscribe. Returns the block, to pass to a later #disconnect.

Source
disconnect(listener : Proc(*T, Nil)) : Nil

Unsubscribe a specific listener.

Source
emit(*args : *T) : Nil

Emit to every current subscriber, in subscription order.

Runs a snapshot of the listener Array, not the live one - a listener that calls #disconnect on itself mid-call (the natural way to write "fire once") would otherwise shrink the same Array #each is walking and skip whoever occupied the shifted index. Each listener runs inside its own rescue, so one raising doesn't stop its neighbors; the first exception seen is re-raised once every listener has had its turn, rather than swallowed. Same dispatch semantics as EventBus#emit - decided once there, applied here rather than re-litigated.

Source