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

Haiku BSlider: Value Mapping, Keyboard Steps, and Updates

Design Haiku sliders with deliberate integer ranges, accessible increments, bounded modification notifications, stable text labels, and explicit commit behavior.

BSlider presents an integer-valued control with a movable thumb, optional hash marks, endpoint labels, a message target, and keyboard interaction. Its value is an int32; it is not inherently a percentage, physical unit, or continuously precise floating-point setting. Applications should define a deliberate mapping from the integer range to the domain value and should decide whether dragging previews a change or commits it.

Haiku’s current implementation distinguishes modification notifications during movement from the final invocation. As the slider changes, it can send its modification message with B_CONTROL_MODIFIED; when the user completes the interaction, the normal invocation path sends the final message. An application that treats every intermediate movement as an irreversible commit can produce excessive writes, network requests, or device reconfiguration.

Choose a range that reflects the model

The constructor takes minimum and maximum int32 values. Use a range that expresses the actual precision the product needs. A volume slider could represent a bounded integer scale and then map to gain; a zoom slider could represent discrete steps whose corresponding factors are calculated by the application. Do not use an enormous arbitrary range to simulate continuous precision unless the user experience and mapping justify it.

Map values with explicit units. Keep the slider’s integer as the UI value and convert at one well-defined boundary. Use sufficiently wide intermediate arithmetic to avoid overflow when computing min + position * range. Clamp values to the declared limits before applying them to a model or hardware device. Store physical settings in a versioned model rather than persisting only a control pointer or view archive.

SetPosition() uses a normalized floating position, while Position() reports a normalized value. ValueForPoint() maps a view coordinate into the range. If the application uses a nonlinear scale such as logarithmic volume or exponential zoom, do not mistake the control’s uniform integer/position relationship for a perceptual scale. Implement the domain mapping outside the view and label it clearly.

Separate intermediate and final messages

The control can use a modification message while tracking the thumb and a normal invocation message at completion. The target should treat them differently if the operation is expensive. For example, update a label immediately for preview but defer writing a configuration file until the final invocation. If a live preview must reach hardware, debounce or coalesce updates in the application layer and always apply the final value.

The current implementation’s default SnoozeAmount() is 20,000 microseconds for synchronous mouse tracking. SetSnoozeAmount() takes microseconds. This does not mean every slider configuration sends one message exactly every 20 ms: asynchronous-control mode uses a different mouse tracking path, and keyboard interactions also have their own behavior. Use the value as a mechanism in the API, not as a real-time guarantee.

Messages can arrive after model state changes or after a request is superseded. Attach a generation/request ID to expensive asynchronous work and ignore stale completions. Do not perform a blocking save or device I/O directly in a window’s MessageReceived() method; update the UI promptly and send work to a managed worker.

Keyboard interaction and focus

BSlider is keyboard-navigable by default in its constructors. The current API exposes SetKeyIncrementValue() and KeyIncrementValue() to control the keyboard step. Choose an increment that is useful in the domain and keep arrow-key changes bounded by the same minimum/maximum rules as pointer movement.

The slider should have a meaningful label, focus indication, and useful endpoint labels. SetLimitLabels() supplies text at the minimum and maximum; avoid labels such as “low” and “high” when the values represent units the user needs to understand. If the current numeric value is important, override UpdateText() or place a synchronized textual readout next to the slider. Make sure the visual label and keyboard focus state remain understandable at large font sizes.

Do not make color the only indication of current value. The thumb position, numeric readout, and endpoint labels can communicate the state redundantly. Test keyboard-only operation, focus order, and screen magnification/large fonts rather than assuming pointer dragging is the only way to use the control.

Hash marks and ticks

SetHashMarkCount() and SetHashMarks() define visible marks and their location. Hash marks are a visual aid; they do not automatically quantize values to those mark positions. If the application requires discrete steps, enforce the discrete mapping in the model or configure a range/increment that actually represents the allowed values. Do not display 10 hash marks and assume that only 10 values can be selected.

For a discrete selector, snap the domain mapping deliberately and show the selected value. For a continuous setting, excessive hash marks clutter the bar without improving precision. Test the slider at narrow widths because ticks and endpoint labels can collide or become unreadable.

Text updates and style customization

The slider provides UpdateText() and UpdateTextChanged() for custom text. Keep formatting cheap because the value can update repeatedly. Avoid allocating a new string on every movement if a small reusable buffer or cached formatting path suffices. The displayed units should match what the model actually applies, including any nonlinear transformation.

Subclassing DrawSlider(), DrawBar(), or DrawThumb() can customize appearance, but increases responsibility for state, focus, clipping, and future API changes. Prefer standard Haiku drawing unless a specific product requirement justifies a custom look. If customizing, preserve native interaction cues and test normal/disabled/pressed/focused states.

Layout and value lifecycle

Place the slider in a responsive layout and let it request a preferred size. Do not construct a fixed coordinate bar that becomes too short when localized text or system font settings change. Recompute value-to-pixel relationships after resize; use the control’s mapping APIs where appropriate rather than duplicating internal coordinate math.

When the model changes externally, update the slider with SetValue() without causing a recursive action loop. If the change originated from the slider, distinguish it from a remote update using an explicit state flag or message field. Normalize out-of-range persisted values to a safe valid value and surface a diagnostic for corrupted configuration.

Verification plan

Test min, max, midpoint, keyboard steps, pointer drag, click on the bar, disabled state, and resizing. Verify the integer-to-domain mapping at boundaries and around any nonlinear transition. Observe modification and final invocation messages separately, including asynchronous control mode if the application enables it.

Use a test target that records message type, value, and timestamp. Confirm that an expensive action is coalesced or deferred and that a stale async reply cannot overwrite a newer slider value. Test long labels, accessibility/keyboard navigation, localization, and values restored from older settings.

For a mapping that converts an integer slider value v in [min, max] to a normalized fraction, compute the denominator only after confirming max > min; a single-value setting should use a separate non-interactive or explicit state path rather than divide by zero. When converting that fraction to a domain value, clamp at both ends and document rounding. For a logarithmic scale, validate that the domain endpoints are positive and finite before taking logarithms. These checks prevent a mathematically plausible UI range from producing undefined or out-of-range model values.

Treat message handling as a small transaction. A modification message can update a transient preview, while final invocation persists or commits the setting. If the user cancels an interaction or the model changes from another source, restore or synchronize the preview from the authoritative model instead of retaining a stale thumb value. Include the originating interaction or request generation in app-owned asynchronous work so a delayed completion cannot overwrite a newer selection. Verify the displayed readout after every external update, not only after pointer input.

BSlider is a precise value control only after the application defines the value model. The integer range, continuous position, keyboard increment, modification notifications, final invocation, and displayed text are separate contracts. Designing them together keeps the UI responsive, accessible, and faithful to the setting the user intends to change.

Related:

Sources:

Comments