Class CalendarView
A widget in its own right and not a part of DatePicker, because a scheduling screen
wants a calendar sitting in a panel rather than hidden in a popup. The picker uses this one; so
can an application, with the same three lines.
CalendarView calendar = new CalendarView()
.setMinDate(LocalDate.now())
.setDateFilter(day -> day.getDayOfWeek() != DayOfWeek.SATURDAY
&& day.getDayOfWeek() != DayOfWeek.SUNDAY)
.onSelect(() -> booking.setStart(calendar.selectedDate()));
The value is ISO and the calendar drawn is the reader's. Everything this class hands
out and takes in is a LocalDate; the month names, the year number and the length of a
month come from the chronology resolved for the widget's effective locale, which is ISO for every
language until one carries a u-ca extension or an application calls
setChronology(java.time.chrono.Chronology). A Thai user driving a Buddhist calendar picks a day out of a grid headed
with a year 543 greater, and the application stores the ISO date it asked for.
Always six rows. A month needs four to six depending on where it starts, and a grid that changed height as it paged would resize the popup under the pointer and move the button the user was about to press. The leading and trailing cells are filled with the neighbouring months' days, drawn muted; they are selectable, and picking one pages the grid to that month.
The keyboard cursor is not the selection. Arrows move a cursor, Enter and Space commit it. A grid where arrowing selected would fire a form's handler seven times crossing a week, and would make a range impossible to build with the keyboard at all. Left and Right move by a day and mirror with the layout; Up and Down move by a week and never mirror; Home and End are the first and last day of the week; PageUp and PageDown page the month, and with Shift the year.
Bounds are enforced, not snapped. A day outside
setMinDate(java.time.LocalDate)/setMaxDate(java.time.LocalDate), or refused by setDateFilter(java.util.function.Predicate<java.time.LocalDate>), is drawn disabled,
refuses a click and Enter, and is published disabled without a select verb. The cursor
stops on it rather than skipping it: a reader arrowing across the month then hears that
the day is unavailable, where a skip would have left a hole nobody was told about.
To a screen reader this is a table: TABLE over ROWs of CELLs
under a row of COLUMN_HEADERs, which are the four roles already mapped on all three
platforms for the Table widget. A day cell is named with the whole date and not the bare
number, because a cell heard on its own has to say what it is.
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 enumWhat a click does.static enumWhat the grid is showing. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescription@Nullable LocalDateWhere the keyboard is, which is not the selection: arrows move this and Enter commits it, so a grid can be walked without firing a form's handler at every step.protected voidhandleUserChange(Change.Aspect aspect) The handler, reached for a day inSINGLEand a closed period inRANGE.booleanisSelectable(@Nullable LocalDate day) Whether a day may be picked: inside both bounds and not refused by the filter.@Nullable LocalDatemaxDate()@Nullable LocalDateminDate()protected voidThe grid as a table, which is what it is:TABLEcarrying six rows and seven columns (eight with week numbers), a header row ofCOLUMN_HEADERs, oneROWper week and oneCELLper day, plus the two paging buttons and the title, aBUTTONthat climbs to the month and year choosers — the sameTABLE, three rows of four months or six of four years, each cell carryingSELECTandFOCUSwhere it leads somewhere (describeChooser(limn.accessibility.Accessibility, boolean)).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) A square day cell at the step's control height, so a calendar and a text field at the same density agree about how tall a row of anything is, and seven of them plus the week column is the natural width.protected voidonMouseEvent(MouseEvent event) Mouse events (bubbling); callevent.consume()when handled.protected voidWidget's own background/content, in local coordinates.The application's response to the user picking: a day inCalendarView.SelectionMode.SINGLE, a closed period inCalendarView.SelectionMode.RANGE.protected booleanonSyntheticAction(long key, Accessible.Action action, Accessible.Argument arg) Picking a day and paging the month, through the same private paths a click takes: an assistive technology's select reachespick(java.time.LocalDate, limn.scene.Change.Origin)exactly as a click does, so it refuses an unselectable day the same way, pages the same way and reaches the 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).@Nullable LocalDate@Nullable DateRangesetChronology(@Nullable Chronology chronology) Draws the grid in a calendar system of the application's choosing rather than the language's.Where this calendar reads today from: the ring on today's cell, the "today" a reader hears on it, and the month it opens on while nobody has chosen one.setDateFilter(@Nullable Predicate<LocalDate> filter) Which days may be picked, beyond the two bounds: weekends out, blackout dates out, only the days a resource is free.setDayMarks(@Nullable Function<LocalDate, @Nullable DayMark> marks) What to draw under a day beyond its number: a holiday, a deadline, a day with appointments.setFirstDayOfWeek(@Nullable DayOfWeek day) Starts the week on a day of the application's choosing rather than the language's: Monday in Germany, Sunday in Brazil and Saturday in Egypt are what the locale answers, and an application whose own week is fixed by regulation says so here.setGranularity(CalendarView.View level) What this calendar picks: a day, a month or a year.setMaxDate(@Nullable LocalDate day) The latest selectable day; the mirror ofsetMinDate(java.time.LocalDate).setMinDate(@Nullable LocalDate day) The earliest selectable day.setSelectedDate(@Nullable LocalDate day) Selects a day, or clears the selection.setSelectedRange(@Nullable DateRange range) Selects a period, or clears it.Whether this calendar picks a day, a period, or nothing.setShowWeekNumbers(boolean show) Shows a leading column of week numbers, in the numbering this language uses — which is not ISO's everywhere, and deliberately so: Germany counts a week as belonging to the year holding four of its days and the United States to the year holding its first, so the same week at the turn of a year has two right answers and this shows each reader theirs.setView(CalendarView.View wanted) Shows days, months or years.setVisibleMonth(LocalDate day) Pages the grid to the month holding a day.booleanview()Methods inherited from class limn.scene.Widget
acceptsTextInput, accessibleLabelAccessKey, accessibleLabelTarget, accessibleLabelText, add, add, announceChange, backdropReach, baselineOffset, baselineOffsetOf, caretRect, 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, onAccessibilityAction, onAccessibilityChild, onAccessibilityChildAction, onAccessibilityChildIdentity, onAttached, onCanEdit, onCharTyped, onDetached, onDragExit, onDragOver, onDragStart, onDrop, onEdit, onHitTest, onPaintOverlay, onPreedit, 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
-
Constructor Details
-
CalendarView
public CalendarView()A calendar showing this month, with nothing selected."This month" is read once, from the system clock, at construction. A calendar is not a clock and does not repaint at midnight; an application that leaves one on screen across a day boundary and cares calls
setVisibleMonth(java.time.LocalDate)when it notices.
-
-
Method Details
-
selectedDate
- Returns:
- the selected day, or
nullif none is; alwaysnullin RANGE mode
-
setSelectedDate
Selects a day, or clears the selection.Pages the grid to the month holding it, because a selection the user cannot see is a selection they have to go looking for. Selecting the day already selected changes nothing and announces nothing, which is the early return that keeps two calendars bound to each other from recursing.
- Parameters:
day- the day, ornullto clear- Returns:
- this
-
selectedRange
- Returns:
- the selected period, or
nullif none is complete. A period being built — one end chosen and the other not — is not a period and is not answered here
-
setSelectedRange
Selects a period, or clears it. Pages the grid to the month holding its start.- Parameters:
range- the period, ornullto clear- Returns:
- this
-
focusedDate
Where the keyboard is, which is not the selection: arrows move this and Enter commits it, so a grid can be walked without firing a form's handler at every step.Public because it is announced: the
ACTIVEaspect says the cursor moved, and a widget may not announce a state no accessor answers.- Returns:
- the day the cursor is on, or
nullbefore the grid has had focus
-
selectionMode
- Returns:
- what a click does;
CalendarView.SelectionMode.SINGLEunless it was changed
-
setSelectionMode
Whether this calendar picks a day, a period, or nothing.Changing the mode drops whatever the other mode had selected, because a day is not a period and carrying one across as the other would be inventing a selection the user never made.
- Parameters:
mode- the mode- Returns:
- this
-
view
- Returns:
- whether the grid is showing days, months or years
-
setView
Shows days, months or years. An application opens a birth-date picker onCalendarView.View.YEARSand saves its user four hundred presses.- Parameters:
wanted- what to show- Returns:
- this
- Throws:
IllegalArgumentException- for a view finer than thegranularity(): a month picker has no days to show
-
granularity
- Returns:
- the finest view this calendar picks in;
CalendarView.View.DAYSunless it was changed
-
setGranularity
What this calendar picks: a day, a month or a year.A
CalendarView.View.MONTHScalendar is a month picker: it opens on the twelve months, a pick there is the selection (the first day of the month, as an ISO date) rather than a step down to days it cannot pick, and the title climbs only to the years and back. ACalendarView.View.YEARScalendar is a year picker the same way. A period at either level runs from the first day of its first month or year to the last day of its last, so a range of March to June answers 1 March to 30 June.Takes a
CalendarView.Viewrather than the field's finer list on purpose: an hour is nothing a grid can show, so an hour granularity here does not compile.DatePickerconverts.Changing the level drops the selection, for
setSelectionMode(limn.components.date.CalendarView.SelectionMode)'s reason: a day is not a month, and carrying one across as the other would be inventing a choice. A view finer than the new level moves up to it, announced asVALUEwith the originADJUSTMENT: the calendar moved it, because the level moved.- Parameters:
level- the finest view- Returns:
- this
-
visibleMonth
- Returns:
- the ISO date of the first day of the month currently drawn, in the calendar being drawn: for a Hijri grid that is the first of the Hijri month, which is most often the middle of an ISO one
-
setVisibleMonth
Pages the grid to the month holding a day.- Parameters:
day- any day in the wanted month- Returns:
- this
-
setClock
Where this calendar reads today from: the ring on today's cell, the "today" a reader hears on it, and the month it opens on while nobody has chosen one.Default
null: the system clock in the default zone, read at each call, exactly asLocalDate.now(). A fixed clock is for whatever has to look the same tomorrow as it does today -- a capture, a test, a screenshot in a document -- and for an application that keeps its own idea of the date, a server's or a simulated one.A month already chosen, by
setVisibleMonth(java.time.LocalDate),setSelectedDate(java.time.LocalDate)or the person paging, stays where it is; only the month construction guessed from the wall clock follows the new one.- Parameters:
newClock- the clock, ornullfor the system's- Returns:
- this
-
minDate
- Returns:
- the earliest selectable day, or
nullfor no lower bound
-
setMinDate
The earliest selectable day. A day before it is drawn disabled, refuses a click and carries no select verb; it is not hidden, because a month with half its days missing reads as a broken grid rather than as a bounded one.- Parameters:
day- the bound, ornullfor none- Returns:
- this
-
maxDate
- Returns:
- the latest selectable day, or
nullfor no upper bound
-
setMaxDate
The latest selectable day; the mirror ofsetMinDate(java.time.LocalDate).- Parameters:
day- the bound, ornullfor none- Returns:
- this
-
dateFilter
- Returns:
- the filter, or
nullif every day inside the bounds may be picked
-
setDateFilter
Which days may be picked, beyond the two bounds: weekends out, blackout dates out, only the days a resource is free.Called during paint, once per visible cell. It must be cheap and it must be pure: a filter that queries a database is a filter that stalls a frame, and one that answers differently for the same day makes the grid flicker between two pictures of itself.
Announced as
RANGE/CODEwhen it is a different filter; handing the same one again is no change. Two different filters that happen to answer alike are still announced: the calendar cannot know that without asking every day there is.- Parameters:
filter- answers whether a day may be picked, ornullto allow every day- Returns:
- this
-
setDayMarks
What to draw under a day beyond its number: a holiday, a deadline, a day with appointments.Called during paint, once per visible cell, under the same two obligations
setDateFilter(java.util.function.Predicate<java.time.LocalDate>)carries.- Parameters:
marks- answers aDayMarkfor a day, ornullfor none; may itself returnnullfor a day with nothing to say- Returns:
- this
-
showsWeekNumbers
public boolean showsWeekNumbers()- Returns:
- whether the week-number column is shown
-
setShowWeekNumbers
Shows a leading column of week numbers, in the numbering this language uses — which is not ISO's everywhere, and deliberately so: Germany counts a week as belonging to the year holding four of its days and the United States to the year holding its first, so the same week at the turn of a year has two right answers and this shows each reader theirs. A language named without a region ("de") is read for the region it most likely means, and a first day set withsetFirstDayOfWeek(java.time.DayOfWeek)is where the numbered weeks start too.- Parameters:
show- whether to show the column- Returns:
- this
-
chronology
- Returns:
- the calendar system being drawn, resolved from the locale unless one was declared
-
setChronology
Draws the grid in a calendar system of the application's choosing rather than the language's.Rarely wanted, and worth having: an application whose domain is a calendar — an Islamic finance ledger, a Japanese government form — needs the calendar its documents are written in whatever language the interface is set to. The value exchanged is ISO either way.
- Parameters:
chronology- the calendar to draw, ornullto follow the language again- Returns:
- this
-
firstDayOfWeek
- Returns:
- the day the drawn week starts on
-
setFirstDayOfWeek
Starts the week on a day of the application's choosing rather than the language's: Monday in Germany, Sunday in Brazil and Saturday in Egypt are what the locale answers, and an application whose own week is fixed by regulation says so here.- Parameters:
day- the first day of a week, ornullto follow the language- Returns:
- this
-
onSelect
The application's response to the user picking: a day inCalendarView.SelectionMode.SINGLE, a closed period inCalendarView.SelectionMode.RANGE. The first click of a period reaches nothing: a range with one end is not a range. The handler reads what it needs,selectedDate()orselectedRange(), as every list-shaped widget's does.The user alone: a
setSelectedDate(java.time.LocalDate)from code never reaches it. Code that wants every change, whoever made it, watches the widget withWidget.observeChanges(limn.scene.ChangeObserver).- Parameters:
listener- what to run, ornullto clear the slot- Returns:
- this
- Throws:
IllegalStateException- if a handler is already registered
-
handleUserChange
The handler, reached for a day inSINGLEand a closed period inRANGE.Chains to
superfor every other aspect: that call is the link a subclass must not break.- Overrides:
handleUserChangein classWidget<CalendarView>- Parameters:
aspect- what changed; the origin isUSERor this is not called
-
isSelectable
Whether a day may be picked: inside both bounds and not refused by the filter.- Parameters:
day- the day- Returns:
- whether a click on it would do anything
-
onMeasure
A square day cell at the step's control height, so a calendar and a text field at the same density agree about how tall a row of anything is, and seven of them plus the week column is the natural width. The title is measured too: a long month name at a large step is wider than seven cells in several languages, and a header clipped to the grid would cut it.- Specified by:
onMeasurein classWidget<CalendarView>
-
onLayout
protected void onLayout()Description copied from class:WidgetContainers position children here (measure +Widget.layoutBox(float, float, float, float)per child).- Overrides:
onLayoutin classWidget<CalendarView>
-
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<CalendarView>
-
onPaint
Description copied from class:WidgetWidget's own background/content, in local coordinates.- Overrides:
onPaintin classWidget<CalendarView>
-
onFocusGained
protected void onFocusGained()Description copied from class:WidgetCalled when this widget takes keyboard focus. Default: nothing.- Overrides:
onFocusGainedin classWidget<CalendarView>
-
onFocusLost
protected void onFocusLost()Description copied from class:WidgetCalled when this widget loses keyboard focus. Default: nothing.- Overrides:
onFocusLostin classWidget<CalendarView>
-
onMouseEvent
Description copied from class:WidgetMouse events (bubbling); callevent.consume()when handled.- Overrides:
onMouseEventin classWidget<CalendarView>
-
onKeyEvent
Description copied from class:WidgetKey events (focused widget first, then ancestors).- Overrides:
onKeyEventin classWidget<CalendarView>
-
onAccessibility
The grid as a table, which is what it is:TABLEcarrying six rows and seven columns (eight with week numbers), a header row ofCOLUMN_HEADERs, oneROWper week and oneCELLper day, plus the two paging buttons and the title, aBUTTONthat climbs to the month and year choosers — the sameTABLE, three rows of four months or six of four years, each cell carryingSELECTandFOCUSwhere it leads somewhere (describeChooser(limn.accessibility.Accessibility, boolean)).Every role here was already mapped for the
Tablewidget, three weeks before this widget existed, and that is the whole accessibility cost of a calendar: no role is added to the model, no facet, and no bridge code inlimn-backend-lwjgl, where the three bridges live.A cell is named with the whole date. "9 September 2026" and not "9": a reader arrowing into a cell hears the cell, and the column head says only which weekday it is under. The strings come from the memo the layout filled, so a frame damaged by a hover allocates nothing here — except the full dates, which are built in this hook because they are needed nowhere else and building 42 of them per layout would be worse.
Today carries a word of its own, because today is drawn as a ring and nothing else says so, which makes it exactly the kind of fact that reaches everyone who looks and nobody who listens.
The pointer's hover is deliberately not published: a fact derived from it would republish the whole tree on every mouse move.
- Overrides:
onAccessibilityin classWidget<CalendarView>- Parameters:
a- the node being described; write into it, and never keep it
-
onSyntheticAction
Picking a day and paging the month, through the same private paths a click takes: an assistive technology's select reachespick(java.time.LocalDate, limn.scene.Change.Origin)exactly as a click does, so it refuses an unselectable day the same way, pages the same way and reaches the handler through the sameUSERseam.FOCUSon a day or a chooser cell takes the path an arrow takes instead, and moves only the cursor.No enabled guard of its own beyond the two the paths already carry: the scene's dispatcher has walked this widget and every ancestor for the enabled flag, refused an owner that is not showing and refused a modal-blocked window before this runs.
- Overrides:
onSyntheticActionin classWidget<CalendarView>- 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
-