Terminality couples its widget tree to application logic through two complementary mechanisms:Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Rikitav/Terminality/llms.txt
Use this file to discover all available pages before exploring further.
Event<Args...> for broadcasting notifications to multiple subscribers, and Property<TOwner, T> for values that automatically invalidate the layout pipeline when they change. Together they eliminate the need for manual Invalidate() calls in most situations.
Events
Event<Args…>
Event is a variadic template that holds a map of typed callbacks. Each handler is a std::function<void(Args...)>.
Subscribing with operator+=
The simplest way to attach a handler is operator+=. The handler stays connected for the lifetime of the event owner. There is no way to disconnect it later.
Subscribing with Connect()
Connect() returns an EventConnection that you can store. When the EventConnection is destroyed, the handler is automatically removed. This is the preferred pattern inside classes where handler lifetime must match object lifetime.
Disconnecting manually
CallDisconnect() on the EventConnection object at any time to remove the handler before the connection goes out of scope:
EventConnection is move-only — it cannot be copied. Move assignment disconnects the previous connection before taking ownership of the new one.
Emitting events
CallEmit(args...) to invoke all registered handlers. Emit snapshots the handler map first, so handlers that add or remove connections during dispatch do not cause iterator invalidation:
Properties
Property<TOwner, T>
Property wraps a value and ties it to its owner’s invalidation pipeline. Every built-in ControlBase property uses this template:
Reading
Use implicit conversion,Get(), or operator-> for struct access:
Writing
Assign withoperator= or call Set(). Both are no-ops when the value has not changed:
ApplyInvalidation() and then OnPropertyChanged() on the owner.
InvalidationKind
InvalidationKind controls which part of the layout pipeline is re-run after a property changes:
| Value | Effect |
|---|---|
None | No layout pass triggered; only PropertyChanged fires |
Visual | Calls InvalidateVisual() — the control redraws on the next frame |
Arrange | Calls InvalidateArrange() — positions are recomputed before the next draw |
Measure | Calls InvalidateMeasure() — full measure + arrange + render on the next frame |
Measure is the most expensive because it bubbles up the tree and restarts the entire layout pipeline for that subtree. Use Visual for colour or text-content changes where size does not change.
How invalidation flows
Assignment detected
Property::operator= compares the new value to the stored one. If they differ, it continues.ApplyInvalidation
owner->ApplyInvalidation(invalidation_) calls InvalidateVisual(), InvalidateArrange(), or InvalidateMeasure() as appropriate. Each of those sets a dirty flag and calls parent->OnChildInvalidated(), propagating up the tree.OnPropertyChanged
owner->OnPropertyChanged(name_) is called with the property name string. The base ControlBase implementation handles "ExpSize" by synchronising MinSize and MaxSize. Override this in your own control to react to property changes.ExpSize shorthand
SettingExpSize to a Size simultaneously sets both MinSize and MaxSize to that same value, making the control a fixed size. This is handled in OnPropertyChanged:
Keyboard events and hotkeys
ControlBase fires KeyDown and KeyUp events for every key event received while the control is focused. You can subscribe to them like any other event:
OnHotkey registers a dedicated callback. It throws std::runtime_error if the combination is already registered on that control:
ControlBase::OnKeyDown. If a hotkey matches, the handler runs and the event is consumed (returns true).