class TUI::ScrollableField(T)

Overview

Adapts any Scrollable with a #value : String getter (TextEdit, MarkdownEdit, or any future syntax-specific TextEdit subclass) into a FormField, so a form field can reuse a Scrollable's existing scrolling multi-line editor instead of InputField's single-line-only edit state. Reuses 100% of the wrapped widget's editing logic — this adds only the same small amount of glue Window already does to host a Scrollable (own a Scroller, fabricate a ScrollControl each call, blit a scratch buffer into the region FormField#render was given), just at FormField's smaller scale instead of Widget's.

T is deliberately unconstrained (Crystal has no clean "includes Scrollable and has #value" bound) — a T missing #value or the Scrollable methods this class calls simply fails with Crystal's ordinary "undefined method" compile error at the call site, the same tradeoff TUI::Form.define's own macro already accepts for a model property typo.

Esc always commits (there's no discard-in-place gesture, matching InputField's/BoolField's stated Esc convention — this is a free-text editing kind, not a picker). Esc is intercepted by this wrapper itself, never passed to @content: TextEdit#handle_key has no Esc case today and must not gain one just for this, since standalone full-screen TextEdit/MarkdownEdit (Window.full_screen) has no commit concept and must keep behaving exactly as before. Enter is NOT intercepted — it flows through unchanged and inserts a newline, exactly like TextEdit's existing standalone behavior.

Defined in:

tui/form/scrollable_field.cr

Constructors

Instance Method Summary

Instance methods inherited from class TUI::FormField

handle_key(ev : KeyEvent) : Symbol | Nil handle_key, render(buffer : Buffer, y : Int32, x : Int32, width : Int32, height : Int32 = 1, focused : Bool = true) : Nil render, start(current_value : String) : Nil start, status_hint : String status_hint, value : String value

Constructor Detail

def self.new(build : String -> T) #

[View source]

Instance Method Detail

def handle_key(ev : KeyEvent) : Symbol | Nil #
Description copied from class TUI::FormField

Returns :commit or :cancel once the edit finishes; nil while it's still in progress (the key was consumed either way).


[View source]
def render(buffer : Buffer, y : Int32, x : Int32, width : Int32, height : Int32 = 1, focused : Bool = true) : Nil #

height bounds how many buffer rows this field may draw into, same contract as every other FormField#render. Always reserves the last column of width for a scrollbar track — same convention Window follows for its own border's right column (see Window#render, which reserves inner_width = width - 2*inset unconditionally, scrollbar or not) — rather than sizing content to the full width when nothing needs to scroll and to width - 1 when it does: since content_size depends on the width content wraps against, a conditional width would make "does this need a scrollbar" and "how wide is the content" mutually dependent. Reserving unconditionally keeps content_size stable across renders and #handle_key calls, both of which must agree on the same ScrollControl visible/width. Buffer#scrollbar itself no-ops (leaving the column blank) when #fraction is nil, so a field that never overflows just shows a blank last column, not a misleading always-on track.

Builds a scratch Buffer sized to the remaining content width since Scrollable#render_content expects one sized to exactly its own content area (it has no notion of drawing into a sub-rectangle of a larger buffer), then blits every cell into buffer at the given (y, x) offset — this is Window#blit_inner's body, copied at field scale since Buffer has no sub-region blit primitive to call into directly and Window#blit_inner is private/Window-shaped.


[View source]
def scrollbar_style : Style #

Applied to the scrollbar track/thumb drawn by #render.


[View source]
def scrollbar_style=(scrollbar_style : Style) #

Applied to the scrollbar track/thumb drawn by #render.


[View source]
def start(current_value : String) : Nil #

T has no reseed method (no TextEdit#value=), so this replaces @content wholesale with a freshly built instance — mirrors every other FormField getting a fresh instance per edit session, just done one level down (Form::Host's field.build.call only constructs this wrapper once; #start is the only hook that ever sees the wire value).


[View source]
def status_hint : String #
Description copied from class TUI::FormField

Fragment of a host's status-bar hint describing this field's own key bindings while it's being edited — owned here (rather than left for a host to re-derive per kind) so the bindings shown to the user can never drift from what #handle_key actually does.


[View source]
def value : String #
Description copied from class TUI::FormField

The composed value after a :commit result.


[View source]