Skip to content
Haiku OSDeep Dive Published Updated 7 min readViews unavailable

Haiku BColorControl: RGB Selection, Messages, and Layout

Build Haiku color-selection UI with BColorControl's RGB value, layout, invocation messages, persistence, and accessibility checks.

BColorControl is Haiku’s native color-selection control. It is a BControl, so its current setting is an integer control value and user interaction follows the Interface Kit invocation/message model. The convenience method ValueAsColor() converts that value to an rgb_color, while SetValue(rgb_color) performs the reverse conversion. Treat this as an opaque UI representation at the boundary: store the actual RGB channels in the application model, and do not make a packed control integer your file format or network protocol.

The visible selector also depends on the display environment. The current implementation has a palette-oriented path for an 8-bit screen color space and a channel-oriented path otherwise. Thus, this class is not a promise that every machine presents the same palette or pixel-identical selector. Persist the selected color, not the current visual cell or screen palette index.

Construct it as a normal control

The current public constructor takes a starting point, a color_control_layout, a cell size, a name, an invocation message, and an optional offscreen-rendering flag. The five layout constants describe grid arrangements from B_CELLS_4x64 through B_CELLS_64x4. They are layout choices, not changes to the color data model. The control also supports archive construction from a BMessage.

#include <ColorControl.h>
#include <Message.h>

static const uint32 kColorChanged = 'clch';

BMessage* changeMessage = new BMessage(kColorChanged);
BColorControl* picker = new BColorControl(
    BPoint(0, 0), B_CELLS_16x16, 8.0f, "foreground-color",
    changeMessage);
picker->SetValue(rgb_color{0x33, 0x66, 0x99, 0xff});

The message pointer is handed to the control’s BInvoker base, which owns and deletes its stored message. Allocate a dedicated message for the control as in the example; do not pass a stack object or a pointer that another owner will free. The example uses an opaque RGB color. The implementation’s ValueAsColor() reports alpha as 255, and alpha is not a fourth editable channel in this control. If the rest of the application supports transparency, keep alpha in the model separately and do not imply that the selector edits it.

The control is a view and must be attached to a live window before normal interaction. Add it through the window’s layout rather than assuming the constructor’s starting point creates a responsive layout. Call SetCellSize() or SetLayout() only when the product has a reason to change the preferred geometry; both methods resize the control to its preferred size. A parent layout should own the resulting placement and respond to localization, font metrics, and window resizing.

Translate between the control and model deliberately

Use ValueAsColor() at a clear model boundary. A robust update path is: receive an invocation, read the current RGB value, update the application model, persist according to the product’s commit policy, and send a normalized model change to other views. When an external model update arrives, call SetValue(rgb_color) to refresh the selector. Do not write the model from Draw() or from layout callbacks; presentation should be a projection of state, not a second source of truth.

BColorControl::SetValue(int32) is inherited in meaning from the BControl value contract but its current implementation interprets the packed bytes as red in bits 31-24, green in bits 23-16, blue in bits 15-8, and returns alpha 255 from ValueAsColor(). Prefer the typed SetValue(rgb_color) / ValueAsColor() pair so callers do not repeat bit shifts or accidentally assume another channel order. If older data stores the integer form, write a migration that explicitly decodes that historical representation and test it with known colors such as red, green, blue, white, and black.

Never persist an in-memory pointer, a control’s archive as the only durable business record, or a monitor-specific palette slot when the desired data is an RGB color. A settings schema can store named byte channels, a documented color string, or another stable format with a schema version. On load, validate the presence and range of every channel before constructing rgb_color; if the record is malformed, choose a documented fallback and preserve enough diagnostic context to repair or report it.

Treat invocations as UI events, not implicit commits

The constructor accepts the message that the control invokes. Its target can be a BHandler, BMessenger, or the control’s normal target setup, consistent with BControl. The message should express a user action such as “color selection changed”; the receiver can read the control’s current typed color or the message data that the application deliberately added. Avoid placing a raw view pointer into a persisted or cross-process message.

A color drag or channel edit can produce several changes in a short time. Decide whether those changes are previews or commits. For an expensive operation such as generating an image, updating a remote setting, or rewriting a configuration file, update a lightweight swatch immediately and debounce or commit on the final invocation. If a preview is sent asynchronously, include a monotonically increasing request identifier so an old render completion cannot replace a newer color. A control message does not guarantee that downstream work completed, and a message target must remain valid under the normal looper/handler lifetime rules.

Likewise, prevent feedback loops. A model refresh can call SetValue(), and a user action can invoke a model write; if application code reacts to both paths identically, the refresh may trigger another write. Track the origin at the controller/model boundary or compare normalized color values before publishing. Do not disable notifications globally just to hide a poorly separated state flow.

Layout, focus, and visual communication

The cell-grid constants change the shape of the selector and can affect width and height substantially. A wide 64-by-4 layout is a poor default in a narrow settings pane; a tall arrangement may be easier on a phone-sized window but require substantial scrolling. Let the layout system choose the final position and test preferred size at multiple window widths. Test translated labels and enlarged system fonts because the RGB fields and selector can require more room than an English desktop screenshot suggests.

The class is keyboard-navigable as a BControl, but the actual color-selection workflow should be tested with keyboard focus, assistive technology, and high-contrast settings. A color alone is not a sufficient signal for an important state: show a text label, numeric value, or other redundant indication when the selected color carries meaning. Ensure that a focus ring and disabled state remain visible, and that the user can distinguish the currently chosen value without relying solely on hue.

If the product needs alpha, color profiles, wide-gamut values, or a perceptual color space, model those capabilities outside BColorControl. The control’s rgb_color path is an RGB selection convenience, not a complete color-management pipeline. Conversion to a display color space, profile-aware rendering, and preservation of transparency belong to explicit APIs and model fields. Do not silently discard those values when round-tripping settings through this control.

Verify behavior and compatibility

Test the five layout modes in a live window, both with a normal color display and, when available, an indexed/palette display. Verify that SetValue(rgb_color) followed by ValueAsColor() preserves the three intended channels and returns opaque alpha. Exercise a user interaction and verify that the configured message reaches the intended target exactly through the expected control path. Test an external model update, a rapid sequence of changes, and a stale asynchronous response.

For persistence, round-trip several edge colors and malformed historical records through the application’s own serializer, not through a screenshot. Confirm that no channel swap or low-byte assumption enters the file format. Resize the parent window, switch UI scale/font settings, navigate using only a keyboard, and inspect screen-reader naming. If supporting an older Haiku release or a BeOS-compatible build, compile against that SDK’s header and verify the constructor and message semantics there rather than treating current master as a release guarantee.

The contract is intentionally modest: this is a native RGB-oriented picker with a BControl value and message lifecycle. A stable application remains responsible for model ownership, alpha/profile information, persistence, asynchronous work, and the surrounding responsive/accessibility behavior.

Related:

Sources:

Comments