Class ColorPickerButton
ColorPicker in a dialog when clicked:
the control an application reaches for when it needs a colour from the user but cannot
give up a panel to a whole picker.
ColorPickerButton accent = new ColorPickerButton(Color.rgb(0xF59E0B));
accent.onChange(colour -> { shape.setFill(colour); shape.invalidate(); });
Cancel is a change, not a silence. The picker updates the chip and calls
onChange on every move, so an application sees the colour live; dismissing the
dialog puts the previous colour back and reports that as a change too. The
button's own color() is therefore always the answer, and a caller that simply
applies what it is handed is correct with no bookkeeping of its own, which is the whole
reason the listener fires on the way back. Keep the listener cheap: it runs on every
frame of a drag.
The caption defaults to the colour's hex and follows it. setText(java.lang.String) replaces it
with anything else, including nothing: a button with an empty caption is chrome and a
chip, which is the form a dense inspector column wants. It is then square, as a
Button holding only an icon is: as wide as it is tall, with the chip in the middle, and
Widget.setMinWidth(float) or Widget.setMaxWidth(float) make it wider or narrower. In a wider box the chip
keeps the leading edge, as far from it as from the top of the square; in a narrower one it is in
the middle; in a taller one, which a row stretching its children makes, it is as far from the
leading edge as in the square and in the middle top to bottom.
Not a Button subclass: what it draws is a value, not a label, and the two
disagree about almost every line of paint. It behaves like one (hover, focus ring,
Enter and Space) because a control that opens a dialog should.
Reading right to left, the chip and its caption swap sides together: the chip is the leading item of the pair and the caption follows it inwards. Nothing else moves. The box is the same size in both directions, the chip is the same square, and the colour inside it has no reading axis of its own.
-
Constructor Summary
ConstructorsConstructorDescriptionA button on opaque white.ColorPickerButton(Color initial) A button oninitial. -
Method Summary
Modifier and TypeMethodDescriptionprotected floatThe baseline BASELINE rows align on, the expressiononPaint(limn.graphics.Canvas)draws with.color()The colour on the chip.protected voidhandleUserChange(Change.Aspect aspect) Runs this widget's application handler for a change it has just announced.booleanWhether the alpha line is offered; when off,color()is always opaque.booleanWhether the picker dialog is up; a second click while it is does nothing.protected voidWhat this button is to an assistive technology: oneAccessible.Role.BUTTONcarrying a popup, offering a press, and named by whichever of its two captions it is painting.protected booleanRaises the picker an assistive technology asked for, throughopenPicker()— the same call a left click and an Enter or Space release make, so the dialog, its Cancel and OK, and the revert that reports the previous colour throughonChangeare all exactly what the pointer would have got.The application's response to the user moving the colour: on every move of the picker, and once more with the previous colour if the dialog is dismissed.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) What the shaper should fall back to for a caption with nothing strong in it, given the direction this pass already resolved.protected voidonMouseEvent(MouseEvent event) Mouse events (bubbling); callevent.consume()when handled.protected voidWidget's own background/content, in local coordinates.voidRaises the picker, as a click would.protected floatThe focus ring is drawn outside the box; damage has to know.setAlphaEnabled(boolean enabled) Whether the picker offers alpha at all (defaulttrue).Sets the colour: a caller's write, announced asVALUE/CODEand reaching no handler, so a listener that writes back does not loop.setDialogTitle(I18nString value) The dialog's title;"Colour"in the UI language unless set.Asks for the picker to be raised in a window of its own or as an overlay inside this button's window; seeDisplayMode.Replaces the caption with a fixed string;""leaves chrome and the chip.setText(I18nString value) Replaces the caption with a value that follows the UI language.Puts the caption back to the colour's hex, which is where it starts.text()The caption as it currently reads: the colour's hex unless one was set.Methods inherited from class limn.scene.Widget
acceptsTextInput, accessibleLabelAccessKey, accessibleLabelTarget, accessibleLabelText, add, add, announceChange, backdropReach, 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, onAccessibilityChild, onAccessibilityChildAction, onAccessibilityChildIdentity, onAttached, onCanEdit, onCharTyped, onDetached, onDragExit, onDragOver, onDragStart, onDrop, onEdit, onHitTest, onLayout, onPaintOverlay, onPreedit, onSyntheticAction, 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
-
ColorPickerButton
public ColorPickerButton()A button on opaque white. -
ColorPickerButton
A button oninitial.
-
-
Method Details
-
color
The colour on the chip. Opaque unlesssetAlphaEnabled(boolean)says otherwise. -
setColor
Sets the colour: a caller's write, announced asVALUE/CODEand reaching no handler, so a listener that writes back does not loop. The picker's moves and a cancelled dialog's restore are the user's and take the other origin through the same seam. -
setAlphaEnabled
Whether the picker offers alpha at all (defaulttrue). Turning it off makescolor()opaque immediately, and drops the alpha line from the dialog, for the many things being coloured that cannot be translucent. -
isAlphaEnabled
public boolean isAlphaEnabled()Whether the alpha line is offered; when off,color()is always opaque. -
onChange
The application's response to the user moving the colour: on every move of the picker, and once more with the previous colour if the dialog is dismissed. Never forsetColor(limn.graphics.Color); to hear every change whatever caused it, watch the button instead.- Parameters:
listener- the handler, ornullto clear the slot- Returns:
- this button
- 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<ColorPickerButton>- Parameters:
aspect- what changed; the origin isUSERor this is not called
-
text
The caption as it currently reads: the colour's hex unless one was set. -
setText
Replaces the caption with a fixed string;""leaves chrome and the chip. -
setText
Replaces the caption with a value that follows the UI language. -
setTextFromColor
Puts the caption back to the colour's hex, which is where it starts. -
setDialogTitle
The dialog's title;"Colour"in the UI language unless set. -
isPickerOpen
public boolean isPickerOpen()Whether the picker dialog is up; a second click while it is does nothing. -
openPicker
public void openPicker()Raises the picker, as a click would. Public so a menu item or a keyboard shortcut elsewhere can open the same dialog this button opens.- Throws:
IllegalStateException- if this button is not in a scene
-
setPickerDisplayMode
Asks for the picker to be raised in a window of its own or as an overlay inside this button's window; seeDisplayMode. DefaultDisplayMode.NATIVE_WINDOW.The reason to choose
IN_SCENEhere is usually not the platform. A colour picker is the one dialog whose whole job is to change what is behind it, so an application that shows the result live (a theme editor, a drawing tool) wants the picker and the thing it is recolouring in one window and one screenshot. A native window is also invisible to anything that captures the owner window, which is why the toolkit's own gallery raises this one in scene.A preference, not a guarantee, and the dialog's own: over a fullscreen window the picker is drawn in scene whatever was asked, for the reasons
DisplayModegives: an owner fullscreen at a named mode is minimized when the picker's window takes the focus, one at the mode the display is in already covers the screen, and on a Mac without a GPU the picker held the UI for 1.4 to 3.7 seconds as a window, against 0.6 to 0.7 in scene. A picker already up in a window moves into the scene when the owner goes fullscreen, the colour it holds unchanged.pickerDisplayMode()answers what happened.Takes effect on the next
openPicker(); a picker already up is not moved by it.- See Also:
-
pickerDisplayMode
- Returns:
- how the picker is presented: while it is up, what its dialog actually chose,
including a fullscreen owner forcing
IN_SCENEover a request for a window, at the open or later. While it is not, what the next one will be asked for, since the nextopenPicker()decides afresh over the window as it is then.
-
onMeasure
What the shaper should fall back to for a caption with nothing strong in it, given the direction this pass already resolved.A fallback and not an imposition: the first-strong rule still decides everything a strong character can decide, so the default caption — a hex, which is Latin and digits — still reads left to right inside a right-to-left form, and only where it is placed changes. What this settles is the caption that says nothing either way, whose right answer is the direction of the interface around it.
Takes the resolved direction rather than reading it again, so that one paint cannot place the caption for one direction and shape it for the other.
- Specified by:
onMeasurein classWidget<ColorPickerButton>
-
baselineOffset
protected float baselineOffset()The baseline BASELINE rows align on, the expressiononPaint(limn.graphics.Canvas)draws with.- Overrides:
baselineOffsetin classWidget<ColorPickerButton>
-
paintOutset
protected float paintOutset()The focus ring is drawn outside the box; damage has to know. SeeButton.- Overrides:
paintOutsetin classWidget<ColorPickerButton>
-
onPaint
Description copied from class:WidgetWidget's own background/content, in local coordinates.- Overrides:
onPaintin classWidget<ColorPickerButton>
-
onMouseEvent
Description copied from class:WidgetMouse events (bubbling); callevent.consume()when handled.- Overrides:
onMouseEventin classWidget<ColorPickerButton>
-
onKeyEvent
Description copied from class:WidgetKey events (focused widget first, then ancestors).- Overrides:
onKeyEventin classWidget<ColorPickerButton>
-
onFocusGained
protected void onFocusGained()Description copied from class:WidgetCalled when this widget takes keyboard focus. Default: nothing.- Overrides:
onFocusGainedin classWidget<ColorPickerButton>
-
onFocusLost
protected void onFocusLost()Description copied from class:WidgetCalled when this widget loses keyboard focus. Default: nothing.- Overrides:
onFocusLostin classWidget<ColorPickerButton>
-
onAccessibility
What this button is to an assistive technology: oneAccessible.Role.BUTTONcarrying a popup, offering a press, and named by whichever of its two captions it is painting.A button and not a colour chooser. The chooser is the
ColorPickerthe press raises, which has a node of its own in whichever tree it is mounted in. What this widget does is open a dialog, which is what a button with a popup does.The name is the caption, and the caption is two different things. A caption set through
setText(java.lang.String)is anI18nStringthis widget holds, so it is handed over by reference and the walk compares the reference, the language and the translation epoch. The default caption is the colour's hex, which has no source to compare because it is formatted, so it is handed over fromhex()with the counter that memo was filled against. Both areAccessible.NameFrom.CONTENT: on the default path that is exact, since the hex is literally the painted caption, and with an empty caption it is the closest of the five, the same divergence the picker's own swatch and the split pane's divider already record. One provenance for both branches also keeps asetText("")and asetTextFromColor()from churning the attribute one platform writes the name into.An empty caption is still named, by the colour.
setText("")is a supported configuration — chrome and a chip, which is what a dense inspector column wants — and the widget still paints the chip there, so the colour is its whole content. A focusable node with no name at all is the one thing the gallery check refuses, and this is the answer that costs no application a line.The hex moves to the description when a caption displaced it, and is published exactly once either way. What that costs, said plainly: a captioned button with a tooltip loses the tooltip, because the walk gives a tooltip to the description only when nothing else took it. The trade is deliberate — a caption already identifies the control, and the colour is published nowhere else in this widget's tree, so without it a reader of a captioned colour well can never learn the colour. An application that disagrees calls
setAccessibleDescription, which is applied after this hook and wins.Accessible.State.HAS_POPUPalways, and not only while the picker is up. It is the bit that tells a reader this is not a plain button before anything has happened, and a bit that moved with the dialog would republish this node twice per picker.No facet of any kind, and the survey's row is wrong to want one. "Value text is the hex" cannot be implemented:
Accessibility.valueText(java.lang.String, long)writes the text and leaves the node without a number, the publish step builds a value facet only where a number is present, and the difference raises a value event only when a number moves — so such a text is dropped in silence on all three platforms. And there is no honest number to pair with it, because a colour has no scalar. The hex belongs in the name, which is where it is.No relation. Presented in scene, the dialog's overlay is a parentless root whose inheritance host is this button, so the walk publishes the popup relation there and its mirror here; declaring it again would put two of the same relation on this node, since relations are not de-duplicated. Presented in a window of its own — the default — the picker is in another scene and another tree, and the
Dialogthe survey's row names is not a widget at all, so a relation naming it could only ever be dropped.Declared here is all a reader is told. The box, the language, the enabled, visible, showing, focusable and focused bits and the focus and scroll-into-view verbs are the walk's;
paintOutset()is how far the focus ring's ink reaches for damage and is never the operable rectangle. The armed visual is not published, forButton's reasons: no platform maps it, it would republish the tree twice per click, and a press from an assistive technology never arms. The chip is not a node either — the whole box is one hit target, a node for it would say what the name already says, and leaving it out keeps this hook free ofWidget.layoutDirection()entirely while the box mirrors for free.- Overrides:
onAccessibilityin classWidget<ColorPickerButton>- Parameters:
a- the node being described
-
onAccessibilityAction
Raises the picker an assistive technology asked for, throughopenPicker()— the same call a left click and an Enter or Space release make, so the dialog, its Cancel and OK, and the revert that reports the previous colour throughonChangeare all exactly what the pointer would have got.No enabled check of its own, unlike
Button's. The node acted on here is this widget, so the scene's own gate has already walked this button and every ancestor forisEnabled(), checked that it is showing, that the window is not modal-blocked and that it is inside the layer that owns input. This widget's click and key paths rely on exactly that gate and carry no guard either, and a reader's press that was stricter than the pointer's would be a second rule to keep in step.The refusal while a picker is already up is about truthfulness rather than safety:
openPicker()returns silently in that case, and atruehere is published as an invocation, so acknowledging a press that did nothing would tell a reader something happened. The scene's own reachability test refuses first in both presentations — in scene the dialog's overlay owns input, natively the owner window is modal-blocked — so this is the second line and not the first.- Overrides:
onAccessibilityActionin classWidget<ColorPickerButton>- Parameters:
verb- what was askedarg- ignored; a press carries none- Returns:
- whether this widget did it
-