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

Haiku BStatusBar: Progress Accounting and Responsive Updates

Report Haiku task progress accurately with BStatusBar, separating completed units from labels, resetting state deliberately, and updating on the UI looper.

BStatusBar is an Interface Kit view for communicating bounded progress. Its API exposes a maximum value, current value, incremental Update(), absolute SetTo(), and Reset(), along with primary and trailing labels. The view is useful when work has a meaningful total, but it can mislead users if the application reports guessed percentages, updates it from the wrong thread, or treats a visual progress value as proof that the underlying task succeeded.

The public API reference currently documents several BStatusBar methods only minimally, so implementation-specific details should be checked against the Haiku source version you target. The current implementation adds the delta passed to Update() to the current value, preserves existing text when a text argument is NULL, clamps values set through SetTo() between zero and the maximum, and resets the maximum to 100 in Reset(). Verify these details on the release you ship rather than treating undocumented behavior as a permanent guarantee.

Define the unit before constructing progress

Choose a maximum that maps to a real unit: bytes, records, files, phases, or another bounded quantity. If the work has no known total, do not display a percentage that implies precision. Show an indeterminate activity indicator or a status message instead. A bar that remains at 87% for a long time is not useful simply because the denominator was guessed at startup.

The progress API stores its value and maximum as float, so it is not an exact counter for very large workloads. Keep authoritative byte or record totals in an integer or higher-precision model, calculate a bounded display fraction, and format exact counts separately in the trailing text. Define the zero-work case explicitly instead of dividing by a zero maximum, and reject non-finite estimates before they reach the view. In the current Haiku source, SetMaxValue() assigns the maximum without calling Invalidate(); configure the maximum before showing progress, and verify redraw behavior if it must change on a visible bar.

void ImportWindow::BeginImport()
{
	fStatusBar->Reset("Preparing", "0 / 240 files");
	fStatusBar->SetMaxValue(240.0f);
}

void ImportWindow::FileCompleted(int32 completed)
{
	BString trailing;
	trailing.SetToFormat("%ld / 240 files", static_cast<long>(completed));
	fStatusBar->SetTo(static_cast<float>(completed),
		"Importing", trailing.String());
}

This example assumes the total is known and the callback runs on the window’s looper. Check the exact method signatures for the Haiku headers in use, and keep the formatted string alive through the call if the API copies it as expected. If the total changes after work begins, explain the new estimate rather than silently moving the bar backward or changing the denominator without context.

The primary label should describe the operation, such as “Indexing” or “Copying,” while the trailing text can show a count or current item. Avoid including a full path or sensitive value in a status bar unless it is safe to display and log. Make the same progress visible in diagnostics using structured event fields rather than relying on the pixels in the UI.

Choose Update() or SetTo() deliberately

Update(delta, text, trailingText) is appropriate when each event reports a completed increment. It adds the delta to the current progress value. This is easy to use for a sequence of equal-sized tasks, but unsafe if the same completion event can be processed twice: a duplicate increment overstates progress. Use an idempotent absolute SetTo(completedCount, ...) when the application has a reliable current count.

SetTo() is useful when progress is computed from a reconciled model or when work can complete out of order. Keep the source of the count authoritative. If workers run concurrently, aggregate results under a synchronized model and publish a snapshot to the UI; do not let each worker independently increment the view. This avoids races, double-counting, and drawing calls from arbitrary threads.

Reset() starts a new operation and replaces its labels. In the current implementation it clears text fields, resets the current value to zero, and resets the maximum to 100. Code that uses a custom maximum should set the maximum after reset. If a new operation reuses a previous bar but forgets this ordering, the display can show an incorrect fraction or stale label.

Passing NULL text to Update() is documented by the implementation to preserve existing text rather than clear it. Use an explicit empty string when the label should be cleared, and verify this behavior on the target build because the public reference’s method descriptions are sparse. Keep labels in one owner that can decide which text remains valid across increments.

Keep UI updates on the owning looper

BStatusBar is a view and must be updated through the Interface Kit’s thread and window-locking rules. A worker thread that performs file I/O should send progress data to the window’s looper using a message or messenger, then let the window update the bar. Do not hold the window lock while waiting for a network response or while the worker is copying a large file.

Coalesce progress events when work produces them faster than the UI can display them. A small queue of thousands of one-byte updates wastes time and can make the window feel less responsive. Publish at meaningful increments or a bounded rate, preserve the latest completed count, and ensure completion or cancellation always produces a final state update. If the UI closes while work continues, cancel or detach the progress target safely rather than posting to a stale handler.

Do not calculate the UI value on one thread and mutate the underlying operation state on another without a clear consistency model. Progress is observational. The worker’s completion state, cancellation state, and error result must remain authoritative. At 100%, the operation may still need flush, validation, rename, or cleanup steps; report “Finishing” until those steps succeed rather than announcing success too early.

Make cancellation and failure visible

A progress bar alone does not explain whether the task can be cancelled. Provide a separate cancel command when cancellation is supported and make it cooperative: stop scheduling new work, allow in-flight operations to reach a safe boundary, and report partial output or rollback behavior. If cancellation is not safe after a commit point, disable the action with a clear explanation rather than pretending that closing the window reverses the operation.

On failure, preserve the error status and identify the completed portion. Do not leave the bar at a plausible percentage with no textual indication that work stopped. Use the owning view to show a resumable, retryable, or failed state. If a retry starts a new attempt, decide whether to reset progress or show aggregate progress across attempts; do not mix the two silently.

When the total is indeterminate, use text such as “Scanning files” and show counts without dividing by an unknown estimate. If an estimate is presented, label it as an estimate and update it from observed work. Avoid repeatedly changing the maximum, which can make the bar appear to go backward and undermine trust.

Test timing, duplicates, and lifecycle

Test zero work, one unit, total completion, a duplicate completion notification, out-of-order worker results, a changing total, cancellation, failure, and window closure during background work. Confirm each task has one authoritative progress value and that repeated messages do not advance it twice. Verify that the maximum is restored after a reset and that stale text is cleared intentionally.

Profile update frequency with a realistic workload. Confirm that UI messages remain bounded, drawing does not occur from worker threads, and the final status follows data flush and validation. Test on the target Haiku build because some BStatusBar behavior is underdocumented in the generated reference and may need source-level confirmation.

Progress reporting is a contract between work and user expectation. Define a meaningful unit, use absolute state when events can repeat, marshal updates to the UI looper, and keep success or failure separate from the bar’s visual position.

Related:

Sources:

Comments