Skip to content

Implement <DragValue> tag (egui::DragValue) #32

@ZhukMax

Description

@ZhukMax

Add a declarative <DragValue> numeric input with a controlled binding. Support drag-to-change with configurable speed, optional clamping to a range, formatting (prefix/suffix, decimals), enable/disable, and tooltips. Emit onChange during updates and optionally onRelease when the user stops dragging/commits.

  • Covers a precise numeric input pattern common in tooling, inspectors, and property panels.
  • Reduces imperative glue around egui::DragValue while keeping a controlled-value model.
  • Complements <Slider> for “fine-grained” adjustments and typed entry.

Scope

  • Tag: <DragValue>

  • Required: value={number_expr}

  • Optional attributes:

    • Behavior: speed=number (default sensible), clampToRange=bool (default true)
    • Range: min=number, max=number or range="a..=b" (mutually exclusive with min/max)
    • Formatting: prefix="...", suffix="...", minDecimals=u8, maxDecimals=u8, fixedDecimals=u8 (mutually exclusive with minDecimals/maxDecimals)
    • Quantization (optional): step=number (snap to multiples inside range after change)
    • Common: enabled=bool, tooltip="...", id="...", width=f32
  • Events:
    onChange={|v| ...} — fires when value changes;
    onRelease={|v| ...} — fires when drag ends (commit-like).

Non-goals (here): custom formatter callbacks; exponential/logarithmic scaling.


Proposed API (sketch)

Float with range, prefix/suffix

efx!(ui, r#"
<DragValue value={props.price} range="0.0..=9999.0" speed="0.25"
           prefix="$" fixedDecimals="2" onChange={|v| props.price = v}/>
"#);

Integer with min/max, quantization and release event

efx!(ui, r#"
<DragValue value={model.count} min="0" max="1000" speed="1.0" step="5"
           suffix=" pcs" onRelease={|v| persist(v)}/>
"#);

Conceptual mapping to egui

let mut v = /* bound number */;
let mut dv = egui::DragValue::new(&mut v).speed(speed);

// Clamp range
if let Some(r) = clamp_range { dv = dv.clamp_range(r); }
if !clamp_to_range { dv = dv.clamp_to_range(false); }

// Formatting
if let Some(p) = prefix { dv = dv.prefix(p); }
if let Some(s) = suffix { dv = dv.suffix(s); }
if let Some(n) = fixed_decimals { dv = dv.fixed_decimals(n); }
else {
    if let Some(n) = min_decimals { dv = dv.min_decimals(n); }
    if let Some(n) = max_decimals { dv = dv.max_decimals(n); }
}

// Width hint
if let Some(w) = width { dv = dv.width(w); }

let resp = if enabled { ui.add(dv) } else { ui.add_enabled(false, dv) };
if let Some(tip) = tooltip { resp.on_hover_text(tip); }

// Optional quantization
let v_out = if let Some(step) = step { quantize(v, step, clamp_range) } else { v };

let changed = resp.changed() /* and v_out != old */;
if changed { /* write back bound value */ /* emit onChange(v_out) */ }
if resp.drag_released() { /* emit onRelease(v_out) */ }

Tasks

  • AST & parsing

    • Add <DragValue> node; parse value, speed, min/max or range, clampToRange, formatting attrs, step, and common attrs.
    • Enforce attr exclusivity: range xor (min & max); fixedDecimals xor (minDecimals/maxDecimals).
    • Validate numeric types and that min ≤ max.
  • Codegen

    • Bind value={expr} via local copy → egui::DragValue::new(&mut v) → write back on change.
    • Map range to clamp_range(..); respect clampToRange=false.
    • Apply formatting (prefix, suffix, decimals) and width.
    • Implement optional step quantization (snap to nearest multiple within range) before emitting events.
    • Emit onChange(v) and onRelease(v) appropriately.
  • Diagnostics

    • Missing value or bad type → clear error (expects numeric).
    • Conflicting attrs (range vs min/max; fixed vs min/max decimals) → actionable message.
    • min > max or malformed range → compile error with example.
    • Unknown attrs → list allowed attributes for <DragValue>.
  • Examples & docs

    • examples/drag_value_price.rs (float with currency, fixed decimals).
    • examples/drag_value_int.rs (int with step snapping and onRelease).
    • Cookbook snippet + entry in docs/tags.md.
  • Tests

    • trybuild UI tests: missing/invalid attrs, conflicts, type errors.
    • Unit tests: quantization helper; decimals precedence rules.
    • Ensure examples build for wasm32-unknown-unknown (no runtime launch).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions