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.events`. Import this package when working with widgets: it contains `ActionEvent`, `KeyEvent`, `MouseEvent`, `ResizeEvent`, `CloseEvent`, `PaintEvent`, and the GUI facade `GuiEvent`.

Payloads tied to one concrete backend live in that backend package. For example, `TreeNodeEvent`, `TreeNodeContextEvent`, and `TableCellEvent` are Windows GUI payloads and live in `klyn.gui.windows.events`.

    import klyn.events        # base event payload infrastructure
    import klyn.gui.events    # GUI event payloads
    import klyn.gui.windows.events
    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, spinBox 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.events` for common GUI payloads, backend-specific packages for specialized 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)
    spinBox = SpinBox(value=50, minimum=0, maximum=100)
    label = Label()
    model = SliderStatus(label)

    Binding.twoWay(slider::value, spinBox::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.events
    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`, `SpinBox.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.updated`, `FileWatcher.deleted`: generic events carrying `FileWatcherEvent` 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)

Terminal applications use the same event syntax as window applications. Resize, mouse and page changes are exposed as events, not callback properties.

    terminal = Terminal.instance

    terminal.resized += lambda(e: ResizeEvent): print(e.size)
    terminal.mouseReceived += lambda(e: MouseReceivedEvent): print(e.mouse.x)
    terminal.pageChanged += lambda(e: PageChangeEvent): print(e.current.title)

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 spin boxes.