GUI Signals Events Binding

Signals vs Events

Klyn separates observable state from UI actions. Use signal property for a value that can be bound or watched, and use event ... as T for a rich action that carries an event payload and accepts several handlers.

Mental Model

A signal property is still a property. It has a getter and a setter, stores state, and is read or written like any other property. The only extra rule is that changes can notify the common binding engine. The notification channel is deliberately not exposed as a public valueChanged member.

An event is not state. It is a multicast action stream. A button click, timer tick, menu action, or terminal button activation is something that happens at a point in time, possibly with a rich payload. Events are subscribed with += and unsubscribed with -=.

Packages

The base event infrastructure lives in klyn.events. It provides the base payload Event, compiler-created dispatchers, and callback invocation helpers used by every event source, including non-GUI libraries such as klyn.io.FileWatcher.

GUI-specific payloads live in klyn.gui.event. Import this package when working with widgets: it contains ActionEvent, KeyEvent, MouseEvent, ResizeEvent, CloseEvent, PaintEvent, and the GUI facade GuiEvent.

import klyn.events        # base event payload infrastructure
import klyn.gui.event    # GUI event payloads
import klyn.gui.windows
Comparison
Aspect signal property event ... as T
Purpose Observable state. Rich UI action.
Declaration public signal property value as Int public event clicked as ActionEvent
Access Read and write through the property name. Register handlers with += and -=.
Payload The current property value, read through the property or passed to a binding watcher. A typed event object such as ActionEvent.
Best use Text field content, slider value, spinner value, selected index, checkbox state. Button click, timer timeout, menu action, explicit selection action.
Common API Binding.oneWay, Binding.twoWay, Binding.watch. +=, -=, emit this.eventName(...).
Package klyn.binding. klyn.events for base payloads, klyn.gui.event for GUI payloads.
Signal Property

Use a signal property when the value itself is what matters. A slider does not expose a public valueChanged event. Instead, value is observable and can be watched or bound.

import klyn.binding
import klyn.gui.windows

class SliderStatus:

    private _label as Label

    public SliderStatus(label as Label):
        this._label = label

    public updateLabel(value as Int) as Void:
        this._label.text = "Value = " + value

slider = Slider(value=50, minimum=0, maximum=100)
spinner = Spinner(value=50, minimum=0, maximum=100)
label = Label()
model = SliderStatus(label)

Binding.twoWay(slider::value, spinner::value)
Binding.watch(slider::value, model::updateLabel)

The :: operator creates a typed member reference for the binding engine. This keeps the public API compact: consumers bind to slider::value, not to a separate notification object.

Declaring Signal Properties

Declare signal property on observable state. The compiler emits the notification automatically when the effective value is different after the setter has run.

class Counter:

    private _value as Int = 0

    public signal property value as Int:
        get:
            return this._value
        set:
            if value == this._value:
                return
            this._value = value

The compiler compares the value before and after the setter. If the value is unchanged, no notification is sent; this prevents useless repaints and avoids feedback loops in two-way bindings.

Event

Use an event when the application needs to react to an action, not mirror state. Events are multicast, so several independent handlers can observe the same action.

import klyn.gui.event
import klyn.gui.windows

class Controller:

    public save(action as ActionEvent) as Void:
        print("save requested")
        print(action.source)

controller = Controller()
button = Button("Save")
button.clicked += controller::save
button.clicked += lambda(e: ActionEvent): print("audit: clicked")
button.clicked -= controller::save

Declare events with the payload type directly: public event clicked as ActionEvent. The compiler creates the hidden dispatcher and emit this.clicked(...) constructs the payload.

Choosing Correctly
Rule of thumb

If the name sounds like a value, use a signal property. If the name sounds like something that happened, use an event.

  • Slider.value, Spinner.value, TextBox.text, CheckBox.isChecked: signal properties.
  • Button.clicked, Button.pressed, Button.released, Timer.timeout, ComboBox.selectionChanged: events.
  • Widget.keyPressed, Widget.mouseMoved, Widget.resized, Window.closed, Widget.paintRequested: rich GUI events.
  • FileWatcher.changed, FileWatcher.created, FileWatcher.modified, FileWatcher.deleted: generic events carrying FileWatchEvent payloads.
  • Do not create valueChanged or textChanged events for ordinary widget state.
  • Do not use a signal property for one-shot actions that have no stable value to read back.
Window and Terminal

The binding and event packages are shared by klyn.gui.windows and klyn.gui.terminal. The same Binding calls can connect state across both widget families, and the same ActionEvent model is used for button-like actions.

import klyn.binding
import klyn.gui.terminal

input = LineEdit("ready")
status = StatusLabel()

Binding.oneWay(input::text, status::text)

This common model keeps application code portable: the rendering backend changes, but state binding and action subscription keep the same semantics.

Next Step

Continue with GUI Themes and KSS to control the visual layer, or open samples/gui/ColorChooser.kn for a compact example using two-way binding between sliders and spinners.