Class DateField
Two starts and one knob decide the shape: a field starts at the year or at the hour, and
setGranularity(limn.components.date.DateField.Granularity) says how fine it goes.
new DateField() // 31/12/2026 -- a date
new DateField().setGranularity(MONTH) // 12/2026 -- a month
new DateField().setGranularity(MINUTE) // 31/12/2026 14:30
DateField.ofTime() // 14:30 -- a time of day
DateField.ofTime().setGranularity(SECOND) // 14:30:45
new DatePicker() // the first, with a calendar to pick from
The segments and the separators are the locale's. They come from the language's own short pattern, so a Brazilian field reads day, month, year, an American one month, day, year, a Japanese one year, month, day, and a Korean one keeps the full stop after the last of them. A two-digit year in a pattern is widened to four here, because the field is edited and a two-digit year in something a person types is an ambiguity the toolkit would be creating on purpose.
The keyboard. Up and Down adjust the focused segment and roll over its bounds; Left and
Right move between segments; Home and End go to the first and last; Delete and Backspace clear
the focused segment. Typing digits fills the focused segment and moves on to the next when it can
hold no more, which is what makes a date typable as a run of digits: 31122026 in a
day-month-year language commits the last day of 2026 without a separator being typed. A separator
typed anyway moves on, so a person who types the slashes is not fighting the field either.
Ctrl/Cmd+C copies the whole value as shown and Ctrl/Cmd+V parses what is on the
clipboard, which is where 2026-12-31 pasted out of a spreadsheet is understood.
An incomplete field has no value. date() answers null until every
segment of the date is filled, and the field publishes itself invalid meanwhile. It never guesses
the missing part: guessing is what makes a form submit a date nobody typed.
Bounds are held, not snapped. A date outside setMinDate(java.time.LocalDate)/setMaxDate(java.time.LocalDate) or
refused by setDateFilter(java.util.function.Predicate<java.time.LocalDate>) stays on screen, the field publishes VALIDITY with a
message saying which of the three it broke, and date() still answers what was typed.
Snapping a typed date to the nearest legal one throws away what the person wrote and tells them
nothing.
To a screen reader it is a group of spin buttons, one per editable segment, which is
the shape Spinner publishes for its two arrows and for the same reason: the caret is in
one segment at a time, and a single text field publishing the whole date gives a reader no way to
say which part that is.
Reading right to left the segments do not reorder. A date is a run of numbers, and a run
of numbers keeps its own left-to-right order inside a right-to-left line: that is the
bidirectional algorithm's rule and not a convention of this toolkit, and it is the same reading
Spinner documents for its clock. What mirrors is which side of the box the run sits
against.
Like every widget, it is touched from the UI thread only: each method that changes it checks, and throws on any other thread.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic enumHow fine a field goes: which segment is its last. -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intThe value forsetTwoDigitYearWindow(int)that turns the guess off: a two-digit year, pasted into the field or typed and left, is left blank and the field stays incomplete. -
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.protected floatDistance from this widget's top edge to its first text baseline, in logical points: the alignment reference forFlex.CrossAlignment.BASELINE.protected @Nullable RectThe box of the segment the caret is in, in scene coordinates: the band the paint fills behind it, which is this field's caret.clear()Clears every segment.@Nullable LocalDatedate()@Nullable LocalDateTimedateTime()intprotected voidhandleUserChange(Change.Aspect aspect) Runs this widget's application handler for a change it has just announced.booleanhasDate()booleanhasTime()booleanisEmpty()booleanisValid()@Nullable LocalDatemaxDate()@Nullable LocalDateminDate()static DateFieldofTime()A field that edits a time of day, on the clock the language keeps: twenty-four hours in Portuguese and German, twelve and a day period in English and Korean.protected voidA group of spin buttons, one per editable segment.protected booleanonAccessibilityAction(Accessible.Action action, Accessible.Argument arg) The calendar's open and close, for a field inside a picker: exactly the two verbs the tree publishes, one at a time by state, so a reader's Expand on an open field is refused rather than reported done while nothing happened.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 moving the value: the date, or the time of day in a field that has one.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 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)).protected booleanonSyntheticAction(long key, Accessible.Action action, Accessible.Argument arg) A segment's step or set, through the same path the keyboard takes: the verbs are the two keys they stand for, so an assistive technology rolls over the segment's bounds exactly as Up and Down do, and reaches the application's handler through the sameUSERseam.protected floatHow far beyond this widget's bounds its painting may reach, in logical points: its own painting and any descendant no ancestor clips (a drop shadow, a glow, a child positioned outside the parent's box).setChronology(@Nullable Chronology chronology) Shows the segments in a calendar system of the application's choosing rather than the language's; the value exchanged stays ISO either way.Where this field reads today and now from, for the one thing it asks them: what an empty segment becomes on its first step.Sets the date, or clears it.setDateFilter(@Nullable Predicate<LocalDate> filter) Which dates are acceptable beyond the two bounds.setDateTime(@Nullable LocalDateTime value) Sets both halves at once, which is one announcement rather than two: a caller writing a timestamp is making one change, and a watcher that saw two would see a moment that never existed between them.How fine this field goes: which segment is its last.setMaxDate(@Nullable LocalDate date) The latest acceptable date; the mirror ofsetMinDate(java.time.LocalDate).setMinDate(@Nullable LocalDate date) The earliest acceptable date.Sets the time of day, or clears it.setTwoDigitYearWindow(int yearsBack) Which century a two-digit year means.text()The whole value as it is drawn: whatCtrl+Ccopies.@Nullable LocalTimetime()int@Nullable String@Nullable I18nStringMethods 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, 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
-
Field Details
-
REFUSE_TWO_DIGIT_YEARS
public static final int REFUSE_TWO_DIGIT_YEARSThe value forsetTwoDigitYearWindow(int)that turns the guess off: a two-digit year, pasted into the field or typed and left, is left blank and the field stays incomplete.- See Also:
-
-
Constructor Details
-
DateField
public DateField()A field that edits a date, down to the day.
-
-
Method Details
-
ofTime
A field that edits a time of day, on the clock the language keeps: twenty-four hours in Portuguese and German, twelve and a day period in English and Korean. Down to the minute untilsetGranularity(limn.components.date.DateField.Granularity)says otherwise.- Returns:
- the field
-
hasDate
public boolean hasDate()- Returns:
- whether this field carries date segments: it starts at the year
-
hasTime
public boolean hasTime()- Returns:
- whether this field carries time segments: it goes down to at least the hour
-
granularity
- Returns:
- how fine this field goes;
DateField.Granularity.DAYfor a date,DateField.Granularity.MINUTEfor a time
-
setGranularity
How fine this field goes: which segment is its last.A field that starts at the year takes any level:
DateField.Granularity.MONTHmakes it a month picker with no day,DateField.Granularity.MINUTEadds a clock after the date. A field that starts at the hour takesDateField.Granularity.HOUR,DateField.Granularity.MINUTEorDateField.Granularity.SECONDand refuses the three date levels: it has no date to edit down to, and silently accepting the call would leave a field that shows nothing.Segments the new level no longer holds are emptied; the value follows (a date field made a month field answers the first of its month), and the change is announced as
VALUEfrom code when it moved.- Parameters:
level- how fine to go- Returns:
- this
- Throws:
IllegalArgumentException- for a date level on a field built byofTime()
-
date
- Returns:
- the date, or
nullif this field has no date part or its segments are not all filled. A date outside the bounds is still answered here: the field holds what was typed and says separately that it is not acceptable (seeisValid()). At aDateField.Granularitycoarser than a day this is the first day of the period named — the last day for the end field of aDatePicker.ofRange()
-
time
- Returns:
- the time of day, or
nullif this field has no time part or it is incomplete. AtDateField.Granularity.HOURthe minute is zero, and for the end field of a period the last one of the hour, with the seconds to match
-
dateTime
- Returns:
- both halves as one value, or
nullunless this field carries both and both are complete. Never invented: a date-only field answersnullhere rather than midnight
-
setDate
Sets the date, or clears it.- Parameters:
date- the date, ornullto empty the date segments- Returns:
- this
-
setTime
Sets the time of day, or clears it.- Parameters:
time- the time, ornullto empty the time segments- Returns:
- this
-
setDateTime
Sets both halves at once, which is one announcement rather than two: a caller writing a timestamp is making one change, and a watcher that saw two would see a moment that never existed between them.- Parameters:
value- the moment, ornullto empty every segment- Returns:
- this
-
clear
Clears every segment. -
minDate
- Returns:
- the earliest acceptable date, or
nullfor no lower bound
-
setMinDate
The earliest acceptable date. A typed date before it is held and published invalid, never snapped. A field coarser than a day names a month or a year, which is acceptable when any day of it is: a month field bounded at 15 September accepts September.- Parameters:
date- the bound, ornullfor none- Returns:
- this
-
maxDate
- Returns:
- the latest acceptable date, or
nullfor no upper bound
-
setMaxDate
The latest acceptable date; the mirror ofsetMinDate(java.time.LocalDate).- Parameters:
date- the bound, ornullfor none- Returns:
- this
-
dateFilter
- Returns:
- the filter, or
nullif every date inside the bounds is acceptable
-
setDateFilter
Which dates are acceptable beyond the two bounds. Unlike the calendar's, this one is called when the value changes rather than once per painted cell, so it may be as expensive as an application's own validation is.- Parameters:
filter- answers whether a date is acceptable, ornullto accept every date- Returns:
- this
-
isValid
public boolean isValid()- Returns:
- whether what the field holds is complete and acceptable. An empty field is valid: a form decides whether a date is required, and a field that called itself invalid the moment it was created would put an error on every blank form
-
validationMessage
- Returns:
- why the field is not valid, as it reads in the field's own language, or
nullwhen it is. This is what a screen reader is told after "invalid", and what an application shows beside the field if it does not have a message of its own; seevalidationMessageSource()for the value behind it
-
validationMessageSource
- Returns:
- the localizable value behind
validationMessage(), ornullwhen the field is valid
-
isEmpty
public boolean isEmpty()- Returns:
- whether every segment is empty
-
chronology
- Returns:
- the calendar system the segments are shown in
-
setChronology
Shows the segments in a calendar system of the application's choosing rather than the language's; the value exchanged stays ISO either way.- Parameters:
chronology- the calendar, ornullto follow the language- Returns:
- this
-
onChange
The application's response to the user moving the value: the date, or the time of day in a field that has one. The handler reads what it needs,date(),time()ordateTime(); the date isnullwhile the segments are incomplete, which is the state a person is in for most of the time they are typing.The user alone:
setDate(java.time.LocalDate)from code never reaches it.- Parameters:
listener- what to run, ornullto clear the slot- Returns:
- this
- 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<DateField>- Parameters:
aspect- what changed; the origin isUSERor this is not called
-
text
The whole value as it is drawn: whatCtrl+Ccopies. A reader is not handed this string; it is told the segments one at a time, each with its own value.- Returns:
- the field's text, with its separators and its dashes
-
setClock
Where this field reads today and now from, for the one thing it asks them: what an empty segment becomes on its first step. Defaultnull, the system clock in the default zone read at each call, asLocalDate.now()andLocalTime.now()are.- Parameters:
newClock- the clock, ornullfor the system's- Returns:
- this
-
twoDigitYearWindow
public int twoDigitYearWindow()- Returns:
- how many years back the window for a two-digit year starts; 80 unless changed
-
setTwoDigitYearWindow
Which century a two-digit year means. A year written with two digits — pasted as31/12/26, or typed as26and left with a Right or a Home — resolves into the hundred years that startyearsBackyears before today by the field's clock: by default 80 back and 19 ahead, so in 202626is 2026 and85is 1985. A calendar whose years carry their era (Reiwa 8) is not windowed: there a two-digit year is the whole year.REFUSE_TWO_DIGIT_YEARSturns the guess off: a two-digit year, pasted or typed and left, is left blank and the field stays incomplete. A typed one stayed what was typed until 2026-09-14, which called "31/12/26" the valid year 26.- Parameters:
yearsBack- how far back the window starts,0to99, orREFUSE_TWO_DIGIT_YEARS- Returns:
- this
-
focusedSegment
public int focusedSegment()- Returns:
- which segment the caret is in, counted over the editable segments in reading order
-
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()Description copied from class:WidgetDistance from this widget's top edge to its first text baseline, in logical points: the alignment reference forFlex.CrossAlignment.BASELINE. Defaultheight(): align on the bottom edge, the correct fallback for a widget with no text. Text-bearing components override with the expression they already paint with; a single-line control's istextRuler().centredBaseline(font, height()), which stands the line's capitals in the middle of the box.Valid only once this widget has been given a box.
Flexguarantees that: it lays every child of a BASELINE line out at cross position 0 first, reads the baselines, then repositions withWidget.moveChild(limn.scene.Widget<?>, int), which moves without re-running layout.- Overrides:
baselineOffsetin classWidget<DateField>
-
paintOutset
protected float paintOutset()Description copied from class:WidgetHow far beyond this widget's bounds its painting may reach, in logical points: its own painting and any descendant no ancestor clips (a drop shadow, a glow, a child positioned outside the parent's box). Partial rendering uses it both ways:Widget.invalidate()damage grows by it, and a repaint pass skips this subtree only when bounds + outset miss the pass region; painting farther than declared leaves stale pixels.The work is divided by who knows the number. A widget declares what it paints itself outside its box:
Label's icon overhang, the focus ringButton,CheckboxandRadioButtondraw outside theirs. A widget that lays out children and does not clip them declares how far they may paint past it, because a ring on a child at its edge hangs past it too, and a pass landing only in that band would otherwise skip it and never reach the child. Every layout the toolkit ships does that by default with the reach the theme gives a focus ring, and takes another number through its publicpaintOverflow, which sets what this hook answers; a layout an application writes inherits both by extendinglimn.scene.layout.Container, and one that extends this class directly overrides this hook itself. A widget that clips its children declares only its own painting, since the clip bounds theirs.Asked of every widget a repaint pass considers, and on every
invalidate(), so an override answers in constant time without allocating: a number held or looked up, never a walk over the children. Default0: everything stays inside the bounds, which is true for most leaves.- Overrides:
paintOutsetin classWidget<DateField>
-
onPaint
Description copied from class:WidgetWidget's own background/content, in local coordinates. -
onFocusGained
protected void onFocusGained()Description copied from class:WidgetCalled when this widget takes keyboard focus. Default: nothing.- Overrides:
onFocusGainedin classWidget<DateField>
-
onFocusLost
protected void onFocusLost()Description copied from class:WidgetCalled when this widget loses keyboard focus. Default: nothing.- Overrides:
onFocusLostin classWidget<DateField>
-
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<DateField>
-
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
The box of the segment the caret is in, in scene coordinates: the band the paint fills behind it, which is this field's caret. The platform puts an input method's candidate window under this rectangle, so a digit composed through a Japanese or Chinese input method has its list of candidates under the segment it will land in, and follows the caret to the next segment when a full one moves it on. Without it the list stayed wherever the platform last had it: the window's corner, or the caret of whichever text field had the focus before.The field draws no composition, so there is no caret inside the segment to point at while one is up: the segment is where the committed digits go, and it is the whole answer.
Walks the same composed run the paint, a click and the accessible tree walk, so reading right to left it moves with the run to the trailing edge, and the segment a reader is told the caret is in is the one the candidate window sits under.
-
onMouseEvent
Description copied from class:WidgetMouse events (bubbling); callevent.consume()when handled.- Overrides:
onMouseEventin classWidget<DateField>
-
onCharTyped
Description copied from class:WidgetCommitted text input (focused widget first, then ancestors).- Overrides:
onCharTypedin classWidget<DateField>
-
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<DateField>
-
onAccessibility
A group of spin buttons, one per editable segment.The group carries no value (the comment below says why) and the validity message when it has one; each segment carries its own name, its own bounds, its own value against its own range — or the empty word when nothing is typed there — and the two step verbs, which reach the same private path Up and Down reach.
The group names itself with nothing: what a date field is for is the question the form asks, which only the application has, and this is a public widget an application names with
setAccessibleName, a bound caption or a tooltip.- Overrides:
onAccessibilityin classWidget<DateField>- Parameters:
a- the node being described; write into it, and never keep it
-
onAccessibilityAction
The calendar's open and close, for a field inside a picker: exactly the two verbs the tree publishes, one at a time by state, so a reader's Expand on an open field is refused rather than reported done while nothing happened.- Overrides:
onAccessibilityActionin classWidget<DateField>- Parameters:
action- what was asked; one of the seventeen, including the four that carry an argumentarg- the argument, orAccessible.Argument.NONE- Returns:
- whether this widget did it.
falseis the honest answer for a verb it does not offer.
-
onSyntheticAction
A segment's step or set, through the same path the keyboard takes: the verbs are the two keys they stand for, so an assistive technology rolls over the segment's bounds exactly as Up and Down do, and reaches the application's handler through the sameUSERseam.- Overrides:
onSyntheticActionin classWidget<DateField>- Parameters:
key- the key this widget gave that child when it declared itaction- what was askedarg- the argument, orAccessible.Argument.NONE- Returns:
- whether this widget did it
-