Class ColorPicker

java.lang.Object
limn.scene.Widget
limn.components.ColorPicker

public final class ColorPicker extends Widget
Picks a colour the way a graphics application does: a saturation/value field for the current hue, a hue ramp beside it, a before/after swatch, a hex field, and numeric channels in whichever model the user thinks in: RGB, HSV or CMYK. Every channel is a line of its own: the letter, a rail showing what that channel does, and the number. Alpha, when it is offered, is one more such line.

Hue and saturation live on the widget rather than being re-derived from the colour. Grey has no hue and black has no saturation, so a colour alone cannot say where the cursor was: dragging the value down to black and back up would otherwise return red. What was last chosen survives the trip.

Alpha is a mode. setAlphaEnabled(boolean) removes the whole alpha line, rail and number together, and color() then always returns an opaque colour. Alpha is the last line, so losing it shortens the picker rather than rearranging it.

Two opposite answers about direction live in this one widget, and unifying them would be wrong. The rails are value axes laid along the reading axis, so their sweep, their thumb, their pointer and their horizontal arrows all turn round together when the picker reads right to left. The saturation/value field and the hue ramp do not turn round: the first is a colour space, whose white corner is a convention every artist has met and not a side of the page, and the second is vertical, which no direction touches. The ramp still changes sides, because the row holding it is what places it.

The answer is a display-range Color. An application authoring light rather than pixels needs a multiplier beside this widget, not inside it: the swatch cannot show what such a multiplier does, since normalising an over-range colour for display divides it straight back out.

Not a dialog; put it in one with Dialog.setContent(limn.scene.Widget), or inline it in a panel. final, unlike most components here: it is a composite of four painted parts wired to one model, and a subclass would be overriding paint and layout it does not own. Compose one instead.

  • Constructor Details

    • ColorPicker

      public ColorPicker()
      A picker starting on opaque white, with the alpha line offered.
  • Method Details

    • color

      public Color color()
      The chosen colour. Opaque unless setAlphaEnabled(boolean) says otherwise.
    • setInitialColor

      public ColorPicker setInitialColor(Color color)
      Sets the colour and the "before" swatch: this is what opening the picker on an existing colour means. Use setColor(limn.graphics.Color) to move the selection without moving the comparison.
    • setColor

      public ColorPicker setColor(Color color)
      Moves the selection. Hue and saturation are taken from color only when it has them: a grey has no hue to read, and overwriting the current one with zero would swing the field to red for no reason the artist can see.
    • setAlphaEnabled

      public ColorPicker setAlphaEnabled(boolean enabled)
      Whether the picker offers alpha at all (default true). Turning it off hides the ramp and the field and makes color() opaque, 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.
    • setFormat

      public ColorPicker setFormat(ColorPicker.Format format)
      Which numeric model the channel rows show. The visual field is always HSV.

      The one entry point, whichever end it came from: a tab the user clicked arrives here through TabbedPane.onSelect(java.util.function.Consumer<java.lang.Integer>), and this pushes the selection back the other way for a caller that set it. That round trip is why the guard exists: unlike a segmented control, a tabbed pane reports a programmatic selection too, so without it one switch would run syncFields() twice: once from the notification and once here.

    • format

      public ColorPicker.Format format()
      The notation the channel rows are currently showing.
    • onChange

      public ColorPicker onChange(Consumer<Color> listener)
      Fires on every move: a picker shows its answer live, it does not wait for OK. That makes a delivery a preview: see onCommit for the one that is a decision.
      Throws:
      NullPointerException - if listener is null, as everywhere else in this set
    • onCommit

      public ColorPicker onCommit(Consumer<Color> listener)
      Fires once with the settled colour when a drag ends, the difference between a preview and a decision, and the same split Slider.onChange/Slider.onCommit makes. This is where an undo entry is closed, or a value written to a document.
      Throws:
      NullPointerException - if listener is null
    • 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
    • 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
    • onKeyEvent

      protected void onKeyEvent(KeyEvent event)
      Description copied from class: Widget
      Key events (focused widget first, then ancestors).
      Overrides:
      onKeyEvent in class Widget