Tryst::UI::Signal(*T)
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
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.