the symptom, in your words

"Building a draggable slider with click-to-jump and drag-to-scrub"

✓ verified on 26.07.08e
lane Building UIposted

▸ SYMPTOM

You need a slider that supports both click-to-jump (click anywhere on the track to set the value) and drag-to-scrub (hold and drag to continuously update) in a PanelComponent, but there's no built-in drag helper you can drop into a RenderFragment.

▸ CAUSE

The engine's UI system doesn't expose a high-level drag API for Razor panels. MousePanelEvent has no .Type or event-name field, so you can't distinguish mousedown from mousemove by inspecting the event object itself. You need a different approach to branch between jump and scrub behavior.

▸ FIX

The engine ships the exact pattern in its own SliderControl.razor (and ColorHueControl.cs) under addons/base/code/UI/Controls/. The approach:

Value calculation: frac = Clamp(LocalPosition.x / This.Box.Rect.Width, 0, 1), mouse position relative to the track panel, normalized to 0–1.

Jump vs scrub branching: since MousePanelEvent has no event-type field, pass a bool per binding:

  • onmousedown → always act (this is the jump)
  • onmousemove → act only if This.PseudoClass.HasFlag(PseudoClass.Active) (true while pressed, this is the scrub)

Snap to step: MathF.Round(v / step) * step or the engine's float.SnapToGrid(step).

Key SCSS rules for the track:

snippet
.track {
  pointer-events: all;
  height: 14px;  /* tall enough to be grabbable */
}

.fill {
  pointer-events: none;  /* events always target the track */
}

Setting pointer-events: none on the fill ensures events always target the track, keeping LocalPosition track-relative. Also drop any transition: width on the fill or scrub will visually lag behind the cursor.

A bare click automatically works as jump-to-position with this design: no special handling needed.

▸ WHY IT WORKS

By routing all pointer events to the track (via pointer-events: none on the fill), LocalPosition is always relative to the track panel. The PseudoClass.Active flag (which is true while the mouse button is held down) provides the scrub gate without needing to identify event types. This matches the engine's own slider implementation and avoids inventing a custom drag state machine.

Verified on engine 26.07.08e: seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?

Want to know when new guides or fixes drop? Join the community to help build this out. Report gotchas, flag outdated fixes, or just lurk.

Join the Discord