Phosphor::Widget(S, M)
Abstract base for app-specific widget wrappers.
A Widget sits between a reusable MockWidget and the app's Screen.
Its two jobs are:
on_event— translate raw terminalEvents into appMsgvalues. This is the only place in the framework that performsEvent→Msgtranslation.sync— translate the app's state (S) +app.themeintoMockWidget::Props. This is the only place in the framework that callsapp.theme.
Neither job bleeds into the other: on_event never touches the theme, and
sync never returns a Msg. The wrapped MockWidget never sees app types.
Generic over S, the same application state type parameter Screen(S, M)
uses — a Widget and the Screen that mounts it always share the same
S. Also generic over M, the app's own Msg type — on_event returns
M?, not the framework's built-in Phosphor::Msg specifically, so an app
can define its own message vocabulary instead of using the shipped one.
See Screen(S, M)'s own doc comment for why this is generic at all.
See DESIGN.md § "Widget" for the full rationale and a worked example.
Constructors
Instance methods
How often this widget needs a TickEvent to animate, or nil if it
never animates. View#animation_interval collects the minimum
non-nil value across every mounted widget so App#run knows how
long it can block before waking to synthesize a tick.
Defaults to nil. Widgets that wrap an animating MockWidget should
override this to delegate to @mock.animation_interval, the same way
#hit_test/#handle_cmd delegate.
Responds to cmd by mutating the wrapped MockWidget's own render
state. Used by View#dispatch to route mouse-wheel scroll commands
(Cmd::ScrollUp/Cmd::ScrollDown) by position, the same way
hit_test routes clicks — scrolling changes a MockWidget's cursor or
scroll offset directly, not the app's own state, so it bypasses Msg
and Screen#handle entirely.
Defaults to CmdResult::None. Widgets that wrap a MockWidget should
override this to delegate to @mock.handle_cmd(cmd); widgets with no
MockWidget need not override it.
Returns true if (x, y) falls within this widget's last rendered
bounds. Used by View#dispatch to route MouseEvents by position
instead of focus.
Defaults to false. Widgets that wrap a MockWidget should override
this to delegate to @mock.hit_test(x, y); widgets with no MockWidget
(e.g. a stub that renders nothing) are never hit.
This widget's own mount id (the Symbol passed to View#mount), or
nil before that call. Set alongside #view by View#mount. Backs
#register_zone, which passes it through as a zone's widget_id so
View#dispatch_touch can fall back to this widget when a zone's own
id doesn't match any mounted widget.
This widget's own mount id (the Symbol passed to View#mount), or
nil before that call. Set alongside #view by View#mount. Backs
#register_zone, which passes it through as a zone's widget_id so
View#dispatch_touch can fall back to this widget when a zone's own
id doesn't match any mounted widget.
Translates event into an application Msg, or returns nil if this
widget does not handle the event.
This is the only place that maps Event → Msg. Return nil for
every event the widget does not consume; View#dispatch collects non-nil
results and forwards them to Screen#handle.
This widget's preferred row count at the given width, or nil if
it has none. View#preferred_height sums this across every mounted
widget so Terminal#auto_resize can size an Inline session's block
to fit its content.
Defaults to nil. Widgets that wrap a MockWidget with a natural
height should override this to delegate to
@mock.preferred_height(width), the same way #hit_test/#handle_cmd
delegate.
Registers frame as a named touch target on the mounted View, under
id — a convenience so a widget can call this from its own render
after computing its frame, without reaching into View directly. A
no-op before #view/#mount_id are set (i.e. before View#mount has
run).
id need not match this widget's own mount id — it can name a
sub-region (e.g. "action_apply" within an ActionBarWidget mounted
as :action_bar). View#dispatch_touch tries id first and falls
back to this widget's own #mount_id, passed through here as the
zone's widget_id, when no widget is mounted under id itself. See
View#register_zone/#zone_at.
Writes this widget's cells into buf within the bounds of frame.
Delegates to the wrapped MockWidget#render. View#render_widget always
calls sync before render on each frame, so @mock.props is
up-to-date when this runs.
Rebuilds the wrapped MockWidget's Props from state and app.theme.
This is the only place that calls app.theme. All theme → Style
resolution happens here; the wrapped MockWidget only ever receives
already-resolved Style values.
The View this widget is mounted on, or nil before View#mount is
called. Set by View#mount, not #initialize — a Widget is
constructed before it's mounted, so there's no View to reference yet.
Backs #register_zone.
The View this widget is mounted on, or nil before View#mount is
called. Set by View#mount, not #initialize — a Widget is
constructed before it's mounted, so there's no View to reference yet.
Backs #register_zone.