Class Field

java.lang.Object
limn.scene.Widget<Field>
limn.components.Field

public final class Field extends Widget<Field>
A field of a form around one control: a caption above it, a line under it, and the two states a form has, required and invalid. The control is any widget: a TextField, a ComboBox, a date picker, a check box, or a row of an amount and its currency.

 TextField email = new TextField();
 Field field = new Field("Email", email)
         .setHelperText("We never share it")
         .setRequired(true);
 email.onChange(text -> field.setValidation(text.contains("@")
         ? Validation.NONE : Validation.ERROR, "Enter an address like ada@example.com"));
 

The caption names the control. It is drawn above the control in the step's label font, muted, and a screen reader hears it as the control's name, on the part the keyboard lands on (a date picker's field, an editable combo's text). An & in it marks the access letter that brings the keyboard to the control, as a caption bound with Label.setLabelFor(limn.scene.Widget<?>) does. A field built with no caption keeps everything else: a check box that must be ticked keeps its own words and gains a line and the states. A caption beside its control, rather than above, is a Grid(2) of a bound Label and a field with no caption.

The line under the control says one thing. While the state is not Validation.NONE and a message is set, it is the message, in the state's Tone and with the state's glyph before it, so the state never depends on colour alone; otherwise the helper text, muted; otherwise nothing. A message set with NONE is not shown, and counts as none. A control that refuses what it holds, a date field or a date picker holding a day out of range say, says why on the line, in the error tone, ahead of the helper text and of an Validation.INFO or Validation.SUCCESS message, which would otherwise say "looks good" over a value still refused; an Validation.ERROR or Validation.WARNING message the application set still takes the line, and the refusal returns to it when that message goes, while the control still refuses. A screen reader hears the line as the control's description, whichever it shows.

The line keeps its height. A field given helper text, or a state once, reserves its line from then on, so a form does not move when the first error appears or the last one is fixed. A field given neither reserves nothing.

The state is the application's, and a required field validates nothing. setRequired(boolean) draws an asterisk after the caption, in the danger colour, and tells a reader the field is required in the system's own words; whether an empty required field is an error, and when to say so, is the application's, set with setValidation(limn.components.Validation). The state colours the control's border where the control draws one (a text field, a text area, a combo box, a spinner, a date field and a date picker; a field around a date field or a picker that refuses a day stays red whatever state is set), and only Validation.ERROR reaches a reader as a state, as invalid.

A field takes no focus and handles no key: Tab goes to the control. Reading right to left, the caption and the line start at the right edge, the asterisk follows the caption's end and the glyph leads the message.

  • Constructor Details

    • Field

      public Field(I18nString label, Widget<?> control)
      A field whose caption is label, around control.
      Parameters:
      label - the caption, which follows the language
      control - what is named and laid out
    • Field

      public Field(String label, Widget<?> control)
      A field whose caption is a fixed string, around control.
      Parameters:
      label - the caption
      control - what is named and laid out
    • Field

      public Field(@Nullable I18nString label, Widget<?> control, Widget<?> placed)
      A field around a control that sits inside a wrapper: a field beside a switch, a sized box. The caption names control and the states are published on it, while placed is what is laid out under the caption.
      Parameters:
      label - the caption, or null for none
      control - what is named, described and given the states
      placed - what is laid out: control itself, or a widget that holds it
      Throws:
      IllegalArgumentException - when placed does not hold control
  • Method Details

    • control

      public Widget<?> control()
      The control the caption names and the states are published on.
    • label

      public @Nullable String label()
      The caption as it currently reads, or null for none: its text in the language in effect, an access letter's & mark taken out ("&Name" reads "Name"); see labelSource() for the value behind it.
    • labelSource

      public @Nullable I18nString labelSource()
      The localizable value behind label(), or null for none.
    • setLabel

      public Field setLabel(@Nullable I18nString newLabel)
      Replaces the caption; null takes it away, and the control keeps whatever names it otherwise (a check box its own words). UI thread only.
      Parameters:
      newLabel - the caption, or null
      Returns:
      this field
    • setLabel

      public Field setLabel(String newLabel)
      setLabel(I18nString) with a fixed string.
    • clearLabel

      public Field clearLabel()
      Takes the caption away, as setLabel(null) would: that call does not compile, because null fits more than one of the overloads.
      Returns:
      this field
    • helperText

      public @Nullable String helperText()
      The helper text in the language in effect, or null for none; see helperTextSource() for the value behind it.
    • helperTextSource

      public @Nullable I18nString helperTextSource()
      The localizable value behind helperText(), or null for none.
    • setHelperText

      public Field setHelperText(@Nullable I18nString text)
      Sets the line under the control while no message stands in its place: how to fill the field, what it expects ("Eleven digits, as on the card"). Shown with the focus elsewhere too, which a text field's placeholder and a mask's guide are not, and read to a screen reader as the control's description. From the first helper text on, the line keeps its height. UI thread only.
      Parameters:
      text - the helper text, or null for none
      Returns:
      this field
    • setHelperText

      public Field setHelperText(String text)
      setHelperText(I18nString) with a fixed string.
    • clearHelperText

      public Field clearHelperText()
      Takes the helper text away, as setHelperText(null) would: that call does not compile, because null fits more than one of the overloads.
      Returns:
      this field
    • isRequired

      public boolean isRequired()
      Whether the field is required; see setRequired(boolean).
    • setRequired

      public Field setRequired(boolean newRequired)
      Marks the field as one that must be filled in: an asterisk after the caption, in the danger colour, and the required state on the control, which each system's reader says in its own words. Nothing is validated: an empty required field is an error when the application says so. UI thread only.
      Parameters:
      newRequired - whether the field is required
      Returns:
      this field
    • validation

      public Validation validation()
      The state the application set; see setValidation(Validation, I18nString).
    • validationMessage

      public @Nullable String validationMessage()
      The message about the control, as the line under the control says it: the one the application set with Validation.ERROR or Validation.WARNING; else the control's own reason while it refuses what it holds (a date field that refuses a day); else the one the application set with Validation.INFO or Validation.SUCCESS; null when there is none of them. A message set with Validation.NONE is not shown, and is not answered here either. It reads in the control's language, the one the control declares, else the one it takes from this field and the widgets above it, so a Portuguese date field in an English window is described in Portuguese, on the line and here. See validationMessageSource() for the value behind it.
    • validationMessageSource

      public @Nullable I18nString validationMessageSource()
      The localizable value behind validationMessage(): the application's message with Validation.ERROR or Validation.WARNING, else the control's own reason, else the application's message with Validation.INFO or Validation.SUCCESS, else null.
    • setValidation

      public Field setValidation(Validation state)
      Sets the state alone, with no message: the border and the reader's state follow it, and the line shows the helper text; see setValidation(Validation, I18nString).
      Parameters:
      state - the state
      Returns:
      this field
    • setValidation

      public Field setValidation(Validation state, @Nullable I18nString newMessage)
      Sets what the application decided about the value the control holds, and what to say about it. The state colours the control's border where it draws one; the message, while the state is not Validation.NONE, takes the line under the control in the state's tone, with the state's glyph, except that an Validation.INFO or Validation.SUCCESS message waits while the control refuses what it holds (a date field holding a day it refuses), whose reason the line shows instead; Validation.ERROR is the invalid state to a screen reader, which hears the line as the control's description. From the first state on, the line keeps its height. Called from the application's handler or watcher, when it chooses: on change, on leaving the field, on submitting. UI thread only.
      Parameters:
      state - the state
      newMessage - what to say, or null for nothing: the helper text stays on the line. With Validation.NONE it is neither shown nor read, nor answered by validationMessage()
      Returns:
      this field
    • setValidation

      public Field setValidation(Validation state, String newMessage)
    • clearValidation

      public Field clearValidation()
      Takes the state and the message away: the border is the control's own again, and the line shows the helper text, or stays empty at its height. UI thread only.
      Returns:
      this field
    • onAttached

      protected void onAttached()
      Description copied from class: Widget
      Called when this widget enters a scene (attached to the tree). Widget.scene() is the scene it just joined. Fires top-down, so a parent runs before its children. Default no-op.
      Overrides:
      onAttached in class Widget<Field>
    • onDetached

      protected void onDetached()
      Description copied from class: Widget
      Called when this widget leaves the scene: detached from the tree, moved into another scene (told this before the Widget.onAttached() of the scene it joins), or still in it when the scene's window closes, which detaches every widget the scene has. What was taken in Widget.onAttached() from something that outlives the window (a listener's place in a list of the process's, a resource of the system's) is given back here.

      Widget.scene() still answers the scene being left, and becomes null once this returns, so releasing something the scene owns needs no field of your own to remember it by. Fires bottom-up, so children run before their parent, and every one of them can still reach the scene.

      Release resources here, but GPU resources must be handed to Scene.disposeLater(limn.graphics.GpuSurface) (disposal needs the owning GL context, which is only current inside a frame). Not called on Widget.setVisible(boolean). Default no-op.

      Overrides:
      onDetached in class Widget<Field>
    • paintOutset

      protected float paintOutset()
      How far the control may paint past this field's box: the reach of the theme's focus ring, since the control is laid out to the field's own edges.
      Overrides:
      paintOutset in class Widget<Field>
    • onMeasure

      protected Size onMeasure(Constraints constraints)
      Description copied from class: Widget
      Reports the size this widget wants within constraints. 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 ControlSize and the LayoutDirection once each here and thread them down; never read either in a constructor. The locale needs no threading: it is in scope, and I18n.locale() answers it wherever text is resolved.

      Specified by:
      onMeasure in class Widget<Field>
    • onLayout

      protected void onLayout()
      Description copied from class: Widget
      Containers position children here (measure + Widget.layoutBox(float, float, float, float) per child).
      Overrides:
      onLayout in class Widget<Field>