Class PasswordField
TextField that renders every character as a mask dot, with an optional reveal toggle.
While masked, copy/cut are blocked (the clipboard never sees the secret); paste and all editing
behave like a TextField.
The dot is drawn, not typeset; see DOT_DIAMETER. Everything else (geometry,
padding, the animated border) is inherited from TextField: this class declares no extent
of its own beyond the dot's two ratios.
The content is never shaped. shapeDisplay(java.lang.String, limn.graphics.Font) builds the masked line from
ShapedText.uniform(java.lang.String, limn.graphics.Font, float, limn.graphics.TextMetrics, long), which is one multiplication and no glyphs, so the secret never
reaches a shaper nor the memo a shaper keeps — and the caret, the click mapping, the selection
band and the painted dots then all come out of that one piece of arithmetic instead of two that
have to be kept in agreement.
What an assistive technology is told follows the same toggle. Masked, the node is a password
field carrying the mask — one bullet per painted dot, with the caret and the selection
in the mask's own offsets — and revealed it is a plain text field carrying the value under the
model's. The role and the masked state move together, so no platform ever sees a password role
over cleartext; see onAccessibility(limn.accessibility.Accessibility).
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprotected booleanSecure text entry keeps the platform IME off (the convention ofNSSecureTextField/ES_PASSWORD): composition would echo the secret in cleartext between the mask characters, surface it in the OS candidate window, and feed it to the IME's learning dictionary.protected booleanWhether copy/cut may export the content (password fields say no).protected booleanWhile masked, word-wise caret jumps and deletes would let an observer count the words and their lengths inside the secret; modifier+arrows degrade to per-character moves until revealed.booleanWhether the characters are shown instead of the mask.protected voidThe same nodeTextFieldpublishes, said to be masked and carrying the mask in place of the contents — one bullet per painted dot, with the caret and the selection in the mask's own offsets.protected booleanonAccessibilityAction(Accessible.Action action, Accessible.Argument arg) Translates a caret or a selection back out of the mask's offsets before the field places it, and refuses one the mask has no stop for.protected voidpaintDisplayText(Canvas canvas, ShapedText display, float x, float baseline, TextMetrics metrics, SizeTokens t, Color ink) Paints the mask as circles on the ink box's centre line.Refused: a password is not formatted.setRevealed(boolean newRevealed) Shows/hides the real text (the optional "reveal" toggle).protected ShapedTextshapeDisplay(String text, Font font) The masked line: one dot cell per grapheme cluster, built by arithmetic and not by a shaper.Methods inherited from class limn.components.TextField
baselineOffset, caretPosition, caretRect, composingText, handleUserChange, insertText, invalidateDisplayLine, isMaskComplete, mask, onCanEdit, onChange, onCharTyped, onEdit, onFocusGained, onFocusLost, onKeyEvent, onMeasure, onMouseEvent, onPaint, onPreedit, onSubmit, onSyntheticAction, placeholder, select, selectAll, selectedText, selectionEnd, selectionStart, setCaretPosition, setError, setLeadingIcon, setLeadingIcon, setPlaceholder, setPlaceholder, setPreferredWidth, setText, setTrailingButton, setTrailingButton, setTrailingButton, setTrailingButton, setValidation, showContextMenu, showContextMenuForFocus, text, trailingButtonName, unmaskedText, validationMethods 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, onLayout, onPaintOverlay, 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
-
PasswordField
public PasswordField()
-
-
Method Details
-
isRevealed
public boolean isRevealed()Whether the characters are shown instead of the mask. -
setRevealed
Shows/hides the real text (the optional "reveal" toggle). -
shapeDisplay
The masked line: one dot cell per grapheme cluster, built by arithmetic and not by a shaper.ShapedText.uniform(java.lang.String, limn.graphics.Font, float, limn.graphics.TextMetrics, long)is what makes the whole class two overrides instead of a pair of parallel measurements. It gives a line whose index space is the model's, so the caret, the selection range and a click need no translation from source offsets to mask offsets — the count-preserving substitution the old mask string had to guarantee is not a thing that can go wrong any more, because there is no second string. And the content does not reachshape, which is the reason this override exists: a shaper resolves faces, memoizes what it was asked, and would be a place a secret comes to rest.Cells are grapheme clusters, so one dot stands for one user-perceived character and the caret can never land at a fractional dot: an astral character is one dot, not two, which also keeps the mask from leaking that a character was astral.
- Overrides:
shapeDisplayin classTextField- Parameters:
text- the model's text, which is the index space of the resultfont- the font the line is drawn in- Returns:
- the shaped display line; never null
-
paintDisplayText
protected void paintDisplayText(Canvas canvas, ShapedText display, float x, float baseline, TextMetrics metrics, SizeTokens t, Color ink) Paints the mask as circles on the ink box's centre line.The centre is
baseline - ascent + height/2, the middle of the same band the selection fill and the caret span, so the dots stay centred against any face the field's own text would use, at any step, with no per-font vertical fudge. (A dot has no baseline of its own to sit on: this is why the vertical anchor is the band and not the baseline.)- Overrides:
paintDisplayTextin classTextField- Parameters:
canvas- where to drawdisplay- the shaped display line, asTextField.shapeDisplay(java.lang.String, limn.graphics.Font)produced itx- left edge of the line, in this widget's coordinatesbaseline- the text baseline, in this widget's coordinatesmetrics- the line's vertical bandt- the size row resolved for this passink- the colour to draw in
-
allowClipboardCopy
protected boolean allowClipboardCopy()Description copied from class:TextFieldWhether copy/cut may export the content (password fields say no).- Overrides:
allowClipboardCopyin classTextField
-
allowsWordJumps
protected boolean allowsWordJumps()While masked, word-wise caret jumps and deletes would let an observer count the words and their lengths inside the secret; modifier+arrows degrade to per-character moves until revealed.- Overrides:
allowsWordJumpsin classTextField
-
acceptsTextInput
protected boolean acceptsTextInput()Secure text entry keeps the platform IME off (the convention ofNSSecureTextField/ES_PASSWORD): composition would echo the secret in cleartext between the mask characters, surface it in the OS candidate window, and feed it to the IME's learning dictionary. The scene also drops preedit events for widgets that refuse text input, so no composition can reach this field even mid-teardown. Direct key/char input (including paste) is unaffected.- Overrides:
acceptsTextInputin classTextField
-
onAccessibility
The same nodeTextFieldpublishes, said to be masked and carrying the mask in place of the contents — one bullet per painted dot, with the caret and the selection in the mask's own offsets.The role and the
PASSWORDstate follow the toggle, and they move together. Stating both flat and conditioning only the facet on the flag would contradict itself:State.PASSWORDsays in its own words that the text is masked and must never be spoken, which is not a thing to publish beside the cleartext the facet carries when the field is revealed. Which of the two follows the flag is then the real question, and it is the role as well, because a platform's refusal to speak is keyed on the role and not on the state — AT-SPI's password role, AppKit's secure-field class,Role.PASSWORD_FIELD's own definition as "a text field whose content is masked". A revealed field left under that role would be the one control on screen a blind user cannot read after deliberately asking to, which is the whole purpose of the toggle. Moving the two together is what keeps any platform from ever seeing a password role over cleartext, or a plain field over a mask.The role is written after the super call and cannot move before it, for the reason
SearchFieldrecords: a role is a plain store into the node's slot, the base writesTEXT_FIELDinto it, and it lands on this widget's node rather than on the trailing button's only because the base closes every synthetic child it opened.Everything else is inherited and deliberately not restated: the placeholder names the node,
EDITABLEand the context menu are the base's, and a trailing button — the natural place to hang a reveal control — is the base's synthetic child with the base's box.READ_ONLYis never set: a masked field is editable, and masking is not read-only. There is no node for the reveal toggle itself, because the toggle is a public setter an application drives from a control of its own and this widget draws nothing for it, and a synthetic child has to be something its owner draws.The facet is republished here in both branches, over whatever the base wrote, because the two branches have to share one witness;
publishedTextcarries the whole of that reasoning. The base's write while revealed is a dead set of stores this hook overwrites in full, which costs nothing.- Overrides:
onAccessibilityin classTextField- Parameters:
a- the node being described
-
onAccessibilityAction
Translates a caret or a selection back out of the mask's offsets before the field places it, and refuses one the mask has no stop for.This is the half the forward mapping leaves out, and it is where the bug is. Publishing maps a model offset forward to a mask ordinal for the facet and stops there. The verbs come back the other way: a client holds the published string, so it asks in mask ordinals, and the inherited handler consumes model
charoffsets. Left alone, a reader asking for the caret after the second dot of a value whose first character is astral lands inside the surrogate pair, silently — the shorter buffer's own bounds check passes, which is the same shape of failure the base refuses an offset-carrying verb over during an IME composition.An ordinal outside the mask is refused and never clamped, because
ShapedText.caretIndex(int)clamps: without the check a client asking for dot forty of a three-dot field would be quietly answered with the end of the value, which is the mistake the base states its own reason for refusing rather than hiding.Everything else goes to
superand must. The context menu is still the only route a reader has to Cut, Copy, Paste and Select All, and the two the mask must not let out are already greyed byallowClipboardCopy()rather than by suppressing the menu. The whole-value set carries no offsets at all, needs no translation, and is how a password manager fills the field.- Overrides:
onAccessibilityActionin classTextField- Parameters:
action- what is being askedarg- the text for a whole-value set, and the mask's own range for the other two- Returns:
- whether this widget did it
-
setMask
Refused: a password is not formatted. A pattern's literals and sections would be drawn as dots like everything else, and its "masked" would mean two things on one field, so a password field takes no input mask.null, which takes a mask away, is accepted and does nothing.TextField.unmaskedText()is therefore the password as typed.- Overrides:
setMaskin classTextField- Parameters:
pattern-null- Returns:
- this field
- Throws:
UnsupportedOperationException- for any pattern
-