enhancement
major
minor
major
minor
major
#29546
TL Views: colors for enumeration literals and classifier objects rendered as pills, and a progress control
Problem
1. Status-like values look like any other text
A ticket status, a priority, a severity, a label: values whose meaning a user recognizes by color in every issue tracker. In the view layer (com.top_logic.layout.view) such a value was displayed as plain text wherever it appeared: the read-only value of ReactDropdownSelectControl in table cells and view-mode form fields, a ReactResourceCellControl, a <text>. The model had no way to attach a color to an enumeration literal or to compute the color of an object from its state, and the React controls had no pill rendering for a value that carries one.
2. Progress had no display
"Closed 7 of 12 planned tickets" of a milestone, the completion of a project, the fraction of a quota: a number pair or a fraction shown as a bar with a label. No React control displayed that.
3. A color was typed as a hex string
A configuration property of type java.awt.Color (the color of an annotation, a theme setting, a default value) was edited as text: #04a38d typed by hand, although a color chooser exists.
Solution
A color for values, rendered as a pill
- Model (com.top_logic.model.annotate.ui, tl-core): <color value="#04a38d"/> or <color token="support-success"/> on an enumeration literal (TLColor) names its color as a literal or as a design token of the UI theme, so a token follows the active theme. A type whose instances have a color annotates the algorithm computing it: <dynamic-color> (TLDynamicColor, inherited by specializations) holds a ValueColorProvider, in the shape <dynamic-icon> and <label> use for icons and labels. tl-model-search states that algorithm as a TL-Script function over the object: <dynamic-color><color-by-expression color="t -> $t.get(`Ticket#status)"/></dynamic-color>` (ColorByExpression); a color value as the result is the color itself, an enumeration literal is colored by its own <color>, any other result leaves the object uncolored. AnnotationValueColorProvider.INSTANCE.colorOf(value) answers the ValueColor of a classifier, an object (through the provider cached per type in TLModelOperations.getColorProvider) or a color value, and null for everything else; ValueColor.cssValue() is the CSS to apply it with.
- Rendering. Wherever a model value with a color is displayed read-only, it is rendered as a pill: the label on a background tinted from the color with readable text, composed with color-mix() against the theme's surface and text tokens so it reads on light and dark themes. The server names the color in one wire field (ReactValueColor.COLOR), the client renders it through one component (TLPill) with the color as an inline custom property, no class per color. Sites: the read-only value, the options and the selected chips of ReactDropdownSelectControl (reference and enumeration attributes in <table> cells and <form> fields), ReactResourceCellControl, and <text> over a colored channel value. A <text> follows a new channel value and a change of the object on the channel (through the shared ChannelObjectObserver, #29533), so the pill of an object whose state was edited changes color without being selected anew.
The color chooser for every color property
A configuration property of type java.awt.Color is edited with the color chooser, by type and without a per-property annotation: the declarative form editor (ValueEditor installs ColorControlProvider) and the React configuration editor (ConfigControlService maps java.awt.Color to ColorInputConfigProvider, which builds the ReactColorInputControl). An explicit @ControlProvider / @ConfigControl annotation still wins. The value of a <color> annotation is thus picked from a palette in the model editor.
A progress control
- ReactProgressControl displays a fraction clamped to 0..1 as a bar with an optional label, colored from the theme tokens.
- <progress> (ProgressElement): <progress input="…" fraction="x -> …"/> or <progress input="…" done="x -> …" total="x -> …"/> (mutually exclusive, a configuration error otherwise; two counts label the bar 3 / 7 unless label="x -> …" says otherwise), the input channel optional; the bar follows a new channel value, a change of the object on it, and changes of the observed-types through the shared ChannelObjectObserver (#29533).
- As a table cell, a CellRenderer returns CellContent.Raw with a CellControlFactory building a ReactProgressControl, the existing route for bespoke cells; the FAQ documents it.
Documentation and demo
docs/faq/react-view-layer.md, sections "Colored values" and "Progress". com.top_logic.demo.react, view "Object list": the ticket status literals carry <color token> annotations and show as pills in the status column, in the detail form and in the dropdown while editing; the Ticket type takes its color from its status through <dynamic-color>, and a <text input="ticket"/> shows the selected ticket as a pill that follows Close and Reopen; a <progress> above the tables shows closed of all tickets and follows the Close, Reopen and create commands. Tests: TestValueColorProvider, TestEditorFactory (tl-core), TestColorByExpression (tl-model-search), TestConfigControlService (tl-layout-configedit), TestReactValueColor, TestReactProgressControl (tl-layout-react), TestProgressElement, TestTextElementObservation (tl-layout-view).