Class TextArea
setSoftWrap(boolean) breaks them at the text column's edge instead, at the
opportunities the UI language's line-break rule finds, and the horizontal
axis goes quiet.
Colors come from the Theme, metrics from the SizeTokens row of the
ControlSize step resolved on this widget, and every stroke from
Strokes.
The content inset is anisotropic, and deliberately so. Horizontally it is
fieldPadH, the same token TextField insets its text by, so a field and an
area stacked in a form put their first character on the same column. Vertically it is
areaPad: a text margin rather than a vertical-centring pad, which is why it is
its own token and nothing outside this widget has to agree with it.
The preferred box is derived per step to hold a roughly constant amount of visible content rather than a constant box: an editor is where character count matters most.
Every geometric question is asked of a shaped line, one per visible line. Caret x, hit
testing and the selection band all come from the ShapedText of the line they concern
(shapedRow(int, limn.components.SizeTokens)) rather than from the measured width of a prefix of it, because with a
shaper in the pipeline the width of the first n characters of a line is not the width of
those characters inside it: they join, ligate, kern and reorder differently. The
consequences a user sees are that a click lands on the character under the pointer in mixed text,
that a selection crossing a direction boundary paints as the two or more boxes it really covers,
and that Left and Right move the caret by what is next on the screen.
Left and Right are visual; everything else is logical. Home, End, word movement,
Backspace, Delete, Page and Up/Down all step through the string, because each has to
name a contiguous range: Shift+Home makes a selection and a selection is one range of the
buffer. So in right-to-left text Left and Ctrl+Left move the caret in opposite directions, which
is what Windows and GTK do and the lesser of the two evils.
Soft wrap is a row map, never a second copy of the document. Wrapping, each hard
line holds the char offsets its rows start at (ints), a prefix sum turns a global row into a
line and back, and every geometry question — caret, click, selection band, scroll extent
— is asked of the row under it, through the same shaped-line window the unwrapped mode
paints from (unwrapped, a line simply is its one row). Building the map costs one
shaping per line and is paid when the text is replaced or the column width, font, ruler epoch
or locale moves; an edit re-wraps only the lines it touched, told apart by
TextEditModel.lineDamage(). That seam exists because re-deriving per-line state from
textVersion() alone means re-shaping the document per keystroke — the cliff
ADR 031 §8.2 measured at 22 ms per character typed — and a soft wrap
that reintroduced it would be a regression dressed as a feature.
The scrollbars do not follow the step, so the overlay bars stay 15 pt wide while the pads shrink at the small steps and the bars float over more live text there. An accepted cost: at XSMALL, prefer a trailing right margin in the surrounding layout if the last column matters.
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprotected booleanWhether this widget edits text and should drive the platform input method (IME): the scene enables the IME while such a widget holds focus and disables it otherwise, so composition never intercepts keys meant for non-text UI.Whether the scrollbars overlay the text or reserve a gutter.protected floatThe first line's baseline, the very expressiononPaint(limn.graphics.Canvas)draws line 0 with, taken atscrollY == 0.intprotected @Nullable RectThe caret rectangle in scene coordinates (logical points) used to place the IME candidate window;nullwhen there is no caret to anchor (or the widget is not laid out yet).protected voidhandleUserChange(Change.Aspect aspect) Runs this widget's application handler for a change it has just announced.insertText(@Nullable String text) Insertstextat the cursor (replacing any selection): a caller's write, announced as aTEXTedit atCODE.booleanWhether long lines wrap at the text column's edge instead of scrolling.protected voidOne multi-line editor: what it is, what is in it, where the caret and the selection are, how far its two axes have scrolled, and that it raises a menu.protected booleanonAccessibilityAction(Accessible.Action action, Accessible.Argument arg) Raises the context menu, replaces the contents, moves the caret or sets the selection, each through the path the user's own gesture takes; the rules areTextAccessibility's.protected booleanonCanEdit(EditCommand command) Whether this widget can do an editing command now: copy with something selected, paste with something to paste, undo with something to take back.The application's response to the user editing the text: typing, pasting, undo and redo, an IME commit, an assistive technology's set.protected voidonCharTyped(CharEvent event) Committed text input (focused widget first, then ancestors).protected booleanonEdit(EditCommand command) Does an editing command, as the user's own act.protected voidCalled when this widget takes keyboard focus.protected voidCalled when this widget loses keyboard focus.protected voidonKeyEvent(KeyEvent event) Key events (focused widget first, then ancestors).protected voidonLayout()Containers position children here (measure +Widget.layoutBox(float, float, float, float)per child).protected SizeonMeasure(Constraints constraints) Reports the size this widget wants withinconstraints.protected voidonMouseEvent(MouseEvent event) Mouse events (bubbling); callevent.consume()when handled.protected voidWidget's own background/content, in local coordinates.protected voidonPreedit(PreeditEvent event) In-progress IME composition ("preedit") for this widget while it is focused: the still-composing text, shown inline but not yet committed (the commit later arrives asWidget.onCharTyped(limn.scene.event.CharEvent)).scrollBy(float dx, float dy) Scrolls by a delta in logical points (clamped to the content).floatHorizontal scroll offset in logical points,0at the leading edge: the left edge in a left-to-right subtree and the right edge in a right-to-left one.floatVertical scroll offset in logical points,0at the first line.select(int anchor, int caret) Selects fromanchortocaret, and the caret ends atcaret, so acaretbefore the anchor is a selection made backwards, as Shift+Left makes one.Selects the whole text, the caret at its end.intintsetBarLayout(ScrollGutters.Layout layout) Sets whether the bars float over the text or reserve strips of their own (defaultScrollGutters.Layout.OVERLAY, which is what prose wants).setCaretPosition(int offset) Puts the caret at an offset and selects nothing.setError(boolean error) Convenience:ERRORwhen true,NONEwhen false.setPreferredSize(float width, float height) Overrides the step'sareaWidth/areaHeight.setSoftWrap(boolean wrap) Turns soft wrap on or off (default off: long lines scroll).Replaces the contents, clearing the selection and undo history.setValidation(Validation state) Sets the validation state; colors the border danger/warning/success.protected voidshowContextMenu(float localX, float localY) Raises the Cut/Copy/Paste/Select All menu at a point in this widget's own coordinates: the same menu, and the same enabled rules, asTextField's.protected voidRaises the same menu for a request that carries no point: the Menu key or Shift+F10, where the caret is the only place the user can have meant.text()The full contents, lines joined by\n.The current validation state, which drives the border's colour, as a text field's does.Methods inherited from class limn.scene.Widget
accessibleLabelAccessKey, accessibleLabelTarget, accessibleLabelText, add, add, announceChange, backdropReach, baselineOffsetOf, children, clipboard, clipHeight, clipsChildren, clipWidth, clipX, clipY, controlSize, cursor, declaredControlSize, declaredLayoutDirection, declaredLocale, focusArrivedBackward, focusArrivedByTraversal, height, hitTest, hitTestChild, id, imageCursor, inheritanceHost, invalidate, invalidate, invalidateAccessible, isEnabled, isFocusable, isFocused, isRightToLeft, isSelfEnabled, isShowing, isVisible, isVisibleByItsOwnFlag, layoutBox, layoutDirection, locale, localToScene, localToSceneBounds, markNeedsContainedLayout, markNeedsLayout, markNeedsLayoutInPlace, maxHeight, maxWidth, measure, minHeight, minWidth, moveChild, moveChild, moveChild, neutralBase, notifyChange, notifyTextEdit, observeChanges, onAccessibilityChild, onAccessibilityChildAction, onAccessibilityChildIdentity, onAttached, onDetached, onDragExit, onDragOver, onDragStart, onDrop, onHitTest, onPaintOverlay, onSyntheticAction, overlayPassesPointer, paintChildren, paintOutset, paintsDecoration, paintsFromBackdrop, paintWidget, parent, remove, remove, requestFocus, requestFocus, revealInView, revealInView, runHandler, scene, sceneNanos, sceneToLocal, self, setAccessibleDescribedBy, setAccessibleDescription, setAccessibleDescription, setAccessibleIgnored, setAccessibleLabelledBy, setAccessibleName, setAccessibleName, setAccessibleRole, setControlSize, setCursor, setEnabled, setFocusable, setId, setImageCursor, setInheritanceHost, setLayoutDirection, setLocale, setMaxHeight, setMaxWidth, setMinHeight, setMinWidth, setPresentedElsewhere, setTooltip, setTooltip, setVisible, shapeText, textRuler, tooltip, tooltipSource, width, x, y
-
Constructor Details
-
TextArea
public TextArea()An empty editor.
-
-
Method Details
-
text
The full contents, lines joined by\n. -
setText
Replaces the contents, clearing the selection and undo history. Announces aTEXTedit asCODEwhen the text moved, and reaches no handler; the undo history and the caret are reset even when it is handed the string it already holds. UI thread only. -
setSoftWrap
Turns soft wrap on or off (default off: long lines scroll). Wrapping, a line wider than the text column breaks at the opportunitiesBreakIteratorfinds under the UI language — the same walkLabelwraps with, so a paragraph breaks in an editor exactly where it breaks in the prose beside it, unspaced CJK and dictionary-segmented Thai included — and the horizontal axis goes quiet: nothing overflows it,scrollXOffset()pins at0, and the horizontal bar shows no thumb. Reading right to left every row is flush against the edge reading starts from, the right one, exactly as unwrapped lines are.Wrapped, Up, Down and Page move by visual row on a sticky goal x, where unwrapped they move by hard line on the model's sticky goal column: a column is the right invariant while a line is one row and meaningless once it is several. Left and Right step onto the neighbouring row when the current one runs out, exactly as they already step onto the neighbouring line. Home, End and Shift+Home stay hard-line and logical, as everywhere else in the toolkit, so a selection is always one range of the buffer.
A caret whose index sits exactly on a soft break is two places on screen — the end of one row and the start of the next — and the caret's side says which, the same side that already disambiguates a direction boundary. The whitespace a break drops is not deleted: it hangs past the margin, undrawn, and a caret inside it shows at the row's drawn end.
Toggling resets the horizontal scroll, because the axis changes meaning; the vertical offset keeps its value and re-clamps against the new extent. UI thread only.
-
isSoftWrap
public boolean isSoftWrap()Whether long lines wrap at the text column's edge instead of scrolling. -
onChange
The application's response to the user editing the text: typing, pasting, undo and redo, an IME commit, an assistive technology's set. Never forsetText(java.lang.String)orinsertText(java.lang.String), which are a caller's writes; to hear every edit whatever caused it, watch the area, which also hands over the edit's offsets.- Parameters:
listener- the handler, ornullto clear the slot- Returns:
- this area
- Throws:
IllegalStateException- if a handler is already registered
-
handleUserChange
Description copied from class:WidgetRuns this widget's application handler for a change it has just announced. Called byWidget.runHandler(limn.scene.Change), and so byWidget.notifyChange(limn.scene.Change), only for aUSERorigin and after every watcher has run.A component overrides it once and switches on the aspect, reading its own accessors for the payload: a slider passes
value(), a combo boxselectedIndex(), a text fieldtext(). A component with no handler does not override it.An override that does not recognise the aspect must call
super, because a subclass of a component that has a handler inherits that component's dispatch through this method: aSearchFieldthat overrode this for its own aspect and did not chain would silently deleteTextField's. The ordering lives in the base class, this is the one link in it a subclass can break, and the contract test drives every subclass through its parent's gestures to catch one that does.- Overrides:
handleUserChangein classWidget<TextArea>- Parameters:
aspect- what changed; the origin isUSERor this is not called
-
insertText
Insertstextat the cursor (replacing any selection): a caller's write, announced as aTEXTedit atCODE. UI thread. -
setValidation
Sets the validation state; colors the border danger/warning/success. -
setError
Convenience:ERRORwhen true,NONEwhen false. -
validation
The current validation state, which drives the border's colour, as a text field's does. -
setPreferredSize
Overrides the step'sareaWidth/areaHeight. A negative value on either axis restores that axis's token, sosetPreferredSize(-1, 150)pins the height and lets the width follow the step. -
caretPosition
public int caretPosition()- Returns:
- where the caret is, as a
charoffset intotext()
-
selectionStart
public int selectionStart()- Returns:
- where the selection starts, or the caret's offset when nothing is selected
-
selectionEnd
public int selectionEnd()- Returns:
- where the selection ends, or the caret's offset when nothing is selected
-
selectedText
- Returns:
- the selected text, empty when nothing is selected
-
setCaretPosition
Puts the caret at an offset and selects nothing. Seeselect(int, int).- Parameters:
offset- acharoffset, from 0 to the text's length- Returns:
- this
TextArea - Throws:
IndexOutOfBoundsException- whenoffsetis outside the text
-
select
Selects fromanchortocaret, and the caret ends atcaret, so acaretbefore the anchor is a selection made backwards, as Shift+Left makes one. An offset inside a grapheme cluster (a combining mark, an emoji sequence) moves to the start of that cluster, because a caret is never drawn inside one. AnnouncesSELECTIONasCODEwhen the caret or the selection moved, and reaches no handler. UI thread only.- Parameters:
anchor- where the selection starts, acharoffset from 0 to the text's lengthcaret- where it ends and the caret goes, likewise- Returns:
- this
TextArea - Throws:
IndexOutOfBoundsException- when either offset is outside the text
-
selectAll
Selects the whole text, the caret at its end. Seeselect(int, int).- Returns:
- this
TextArea
-
scrollXOffset
public float scrollXOffset()Horizontal scroll offset in logical points,0at the leading edge: the left edge in a left-to-right subtree and the right edge in a right-to-left one. The range is[0, maxScrollX]in both, so "scrolled to the start" is0either way and a reset on a content change needs no branch. Under soft wrap nothing overflows this axis and the offset stays0. -
scrollYOffset
public float scrollYOffset()Vertical scroll offset in logical points,0at the first line. -
scrollBy
Scrolls by a delta in logical points (clamped to the content). UI thread only. -
setBarLayout
Sets whether the bars float over the text or reserve strips of their own (defaultScrollGutters.Layout.OVERLAY, which is what prose wants). -
barLayout
Whether the scrollbars overlay the text or reserve a gutter. -
onMeasure
Description copied from class:WidgetReports the size this widget wants withinconstraints. Called once per layout pass, and the result is cached against the constraints and the resolved axes (size step, layout direction, locale), so it must be a pure function of them and of this widget's own state.Resolve the
ControlSizeand theLayoutDirectiononce each here and thread them down; never read either in a constructor. The locale needs no threading: it is in scope, andI18n.locale()answers it wherever text is resolved. -
baselineOffset
protected float baselineOffset()The first line's baseline, the very expressiononPaint(limn.graphics.Canvas)draws line 0 with, taken atscrollY == 0. Deliberately not scroll-dependent: the scroll offset is view state, and a BASELINE row that re-aligned itself as the user scrolled would jitter.No empty-text guard here, unlike the single-line controls. An editor always paints a line box (the caret sits in it) whether or not the model holds text, so falling back to
super.baselineOffset()(the bottom edge) would make typing the first character jump a whole ~140 pt row.- Overrides:
baselineOffsetin classWidget<TextArea>
-
onLayout
protected void onLayout()Description copied from class:WidgetContainers position children here (measure +Widget.layoutBox(float, float, float, float)per child). -
onPaint
Description copied from class:WidgetWidget's own background/content, in local coordinates. -
onMouseEvent
Description copied from class:WidgetMouse events (bubbling); callevent.consume()when handled.- Overrides:
onMouseEventin classWidget<TextArea>
-
onCanEdit
Description copied from class:WidgetWhether this widget can do an editing command now: copy with something selected, paste with something to paste, undo with something to take back.Scene.canEdit(limn.scene.EditCommand)asks the widget that has the keyboard and then each of its ancestors, and a menu's item of an editing role is enabled by the answer.A widget answers for the commands it takes from the keyboard, and does for the command what it does for the key.
-
onEdit
Description copied from class:WidgetDoes an editing command, as the user's own act. Called on the first widget, from the one with the keyboard upward, whoseWidget.onCanEdit(limn.scene.EditCommand)answeredtrue. -
onKeyEvent
Description copied from class:WidgetKey events (focused widget first, then ancestors).- Overrides:
onKeyEventin classWidget<TextArea>
-
showContextMenu
protected void showContextMenu(float localX, float localY) Raises the Cut/Copy/Paste/Select All menu at a point in this widget's own coordinates: the same menu, and the same enabled rules, asTextField's. -
showContextMenuForFocus
protected void showContextMenuForFocus()Raises the same menu for a request that carries no point: the Menu key or Shift+F10, where the caret is the only place the user can have meant. Protected for the same reason as its pointer twin, and a subclass suppressing one has to suppress both. -
onCharTyped
Description copied from class:WidgetCommitted text input (focused widget first, then ancestors).- Overrides:
onCharTypedin classWidget<TextArea>
-
acceptsTextInput
protected boolean acceptsTextInput()Description copied from class:WidgetWhether this widget edits text and should drive the platform input method (IME): the scene enables the IME while such a widget holds focus and disables it otherwise, so composition never intercepts keys meant for non-text UI. Text widgets override this totrue. Default:false.- Overrides:
acceptsTextInputin classWidget<TextArea>
-
composingText
- Returns:
- the text currently being composed by the IME (empty when not composing)
-
onPreedit
Description copied from class:WidgetIn-progress IME composition ("preedit") for this widget while it is focused: the still-composing text, shown inline but not yet committed (the commit later arrives asWidget.onCharTyped(limn.scene.event.CharEvent)). Only widgets that accept text input receive it. Default: ignored. -
caretRect
Description copied from class:WidgetThe caret rectangle in scene coordinates (logical points) used to place the IME candidate window;nullwhen there is no caret to anchor (or the widget is not laid out yet). Consulted by the scene only while this widget is focused andWidget.acceptsTextInput()is true. -
onAccessibility
One multi-line editor: what it is, what is in it, where the caret and the selection are, how far its two axes have scrolled, and that it raises a menu.No name of its own, and that is a fact about the widget rather than an omission here. This class holds no
I18nStringof any kind — there is no placeholder to fall back on the way aTextFieldhas one — so the only names an area can have are the walk's tooltip default and an application'ssetAccessibleName. A focusable node with neither is a real gap, and it is the widget's, not the tree's.No description either: what
setValidation(limn.components.Validation)holds is a colour, and the message beside an area is aLabelthe application owns.MULTI_LINEis unconditional and is not about wrapping. It is a fact about the model this widget holds —new TextEditModel(false), whose sanitize keeps every newline — and about Enter inserting one. Soft wrap decides how a line is drawn and says nothing about how many lines there are.The two
ScrollBarchildren are real widgets and publish themselves through the ordinary walk, so nothing is declared about them here. Each ignores itself while its axis fits, which is why an area whose text fits is a childless leaf and one that overflows grows one or twoSCROLL_BARchildren.- Overrides:
onAccessibilityin classWidget<TextArea>- Parameters:
a- the node being described
-
onAccessibilityAction
Raises the context menu, replaces the contents, moves the caret or sets the selection, each through the path the user's own gesture takes; the rules areTextAccessibility's.- Overrides:
onAccessibilityActionin classWidget<TextArea>- Parameters:
action- what is being askedarg- the text forSET_TEXTand the range for the other two- Returns:
- whether this widget did it
-
onFocusGained
protected void onFocusGained()Description copied from class:WidgetCalled when this widget takes keyboard focus. Default: nothing.- Overrides:
onFocusGainedin classWidget<TextArea>
-
onFocusLost
protected void onFocusLost()Description copied from class:WidgetCalled when this widget loses keyboard focus. Default: nothing.- Overrides:
onFocusLostin classWidget<TextArea>
-