Files
dpg-gauges/codex/FEATURES.md
2026-07-02 12:40:20 +02:00

375 lines
11 KiB
Markdown

# dpg-gauges Feature Specification
This document defines the public behaviour and API contract for `dpg-gauges`.
`dpg-gauges` is a Python package providing easy-to-use, high-performance Dear PyGui gauge
widgets for vehicle telemetry, dyno displays, dashboards, and general numeric/status data.
It should be usable as:
```python
import dpg_gauges as dpgg
```
The first release must be practical to use quickly, but still built cleanly enough to avoid a
rewrite after the first real dashboard.
## Product goals
- Provide a healthy set of common gauges out of the box.
- Feel natural in Dear PyGui code and layout containers.
- Support high-rate telemetry updates from the GUI thread without flicker.
- Smooth or animate rapidly changing values so humans can read them.
- Keep public API stable once introduced.
- Be configurable enough for vehicle and dyno dashboards without forcing users to subclass.
- Remain display-only for the first release.
## Non-goals for the first release
Do not implement these in the first release:
- Non-Dear PyGui backends
- Arbitrary image/icon rendering inside gauges
- Historical traces or sparklines
- Gauge-driven threshold callbacks or alarms
- Editable knobs/sliders
- Complex custom shader/GPU renderers
- Theme engines that replace Dear PyGui theming
- Background-thread Dear PyGui updates
## Core requirements
### 1. Dear PyGui-native usage
Gauge widgets should behave like normal Dear PyGui widgets as much as possible:
```python
with dpgg.analog_gauge(tag="rpm", label="RPM", min_value=0, max_value=8000, unit="rpm"):
pass
dpgg.set_value("rpm", 3250)
```
Creation functions are context managers so users can write layouts naturally:
```python
with dpg.window(label="Dashboard"):
with dpgg.gauge_panel(label="Engine"):
dpgg.analog_gauge(tag="rpm", label="RPM", max_value=8000, redline_start=6500)
dpgg.digital_gauge(tag="speed", label="Speed", unit="km/h")
```
### 2. GUI-thread update contract
Dear PyGui item operations must happen on the GUI thread. Public creation functions are
GUI-thread-only. Runtime updates are also intended for the GUI thread unless a function is
explicitly documented otherwise.
High-rate data may be produced in worker threads, but the app should marshal the latest values to
the GUI thread before calling `dpgg.set_value(...)` or `dpgg.update_gauge(...)`.
The package should still be robust against high-rate GUI-thread calls:
- value updates should be cheap
- repeated value updates may coalesce to the latest value before draw
- animation/smoothing should be optional
- static gauge geometry should be reused where practical
- dynamic draw layers should be rebuilt without flickering through empty states
### 3. Gauge catalogue
Required MVP gauge types:
- `digital_gauge`: large numeric readout with label, unit, precision, optional sign, and state color
- `analog_gauge`: circular or arc dial with ticks, labels, needle, thresholds, target markers, and redline
- `bar_gauge`: horizontal or vertical fill bar with thresholds and optional target marker
- `level_gauge`: tank/level style gauge, useful for fuel, battery, fluids, and percentages
- `status_light`: LED/status indicator with text, colors, blink/pulse option, and simple states
- `segmented_gauge`: segmented bar or LED strip, useful for RPM shift lights or battery cells
- `compass_gauge`: circular heading display for degrees, direction labels, and optional bearing marker
- `thermometer_gauge`: vertical temperature-style display with colored bands
- `battery_gauge`: battery-specific gauge with percentage/value display and charge/discharge state styling
- `multi_value_gauge`: compact grouped numeric readout for related values, such as AFR/lambda/fuel pressure
Nice-to-have after MVP stability:
- `shift_light_gauge`: specialized segmented RPM/shift light strip
- `delta_gauge`: positive/negative centered bar for lap delta, trim, or correction values
- `mini_dial_gauge`: small dial optimized for dense dashboards
- `radial_progress_gauge`: donut-style progress/value indicator
### 4. Configuration model
Common gauge configuration should cover most needs without requiring later public API churn.
Shared parameters should include where applicable:
```python
tag: str | int | None = None
label: str | None = None
value: float | int | bool | str | None = None
min_value: float = 0.0
max_value: float = 100.0
unit: str = ""
precision: int = 0
width: int = 0
height: int = 0
autosize_x: bool = False
autosize_y: bool = False
show: bool = True
show_label: bool = True
show_value: bool = True
show_unit: bool = True
tooltip: str | None = None
callback: Callable | None = None
user_data: Any = None
theme: str | GaugeTheme | None = None
style: GaugeStyle | None = None
animation: AnimationConfig | bool | None = None
smoothing: SmoothingConfig | bool | None = None
clamp: bool = True
format_value: Callable[[float], str] | None = None
```
Visual configuration should include where applicable:
```python
background_color: Color | None = None
frame_color: Color | None = None
value_color: Color | None = None
text_color: Color | None = None
muted_text_color: Color | None = None
border_color: Color | None = None
needle_color: Color | None = None
tick_color: Color | None = None
thresholds: Sequence[ThresholdBand] | None = None
markers: Sequence[GaugeMarker] | None = None
redline_start: float | None = None
redline_color: Color = (255, 45, 45, 255)
```
Analog-specific configuration should include:
```python
start_angle: float = 225.0
end_angle: float = -45.0
major_ticks: int = 8
minor_ticks: int = 4
show_tick_labels: bool = True
needle_length: float = 0.82
needle_width: float = 3.0
center_cap_radius: float = 7.0
arc_width: float = 8.0
maintain_aspect: bool = True
```
Bar/level-specific configuration should include:
```python
orientation: Literal["horizontal", "vertical"] = "horizontal"
fill_mode: Literal["clamp", "centered"] = "clamp"
corner_radius: float = 4.0
show_scale: bool = True
stretch: bool = True
```
Status-specific configuration should include:
```python
state: str | bool | int = False
states: Mapping[Any, StatusState] | None = None
blink: bool = False
pulse: bool = False
```
### 5. Thresholds and markers
Thresholds are visual bands only. They must not trigger callbacks or business logic.
```python
@dataclass(frozen=True)
class ThresholdBand:
start: float
end: float
color: Color
label: str | None = None
@dataclass(frozen=True)
class GaugeMarker:
value: float
color: Color
label: str | None = None
thickness: float = 2.0
```
Rules:
- Thresholds and markers are clamped to the gauge range for drawing.
- Invalid threshold ordering raises during configuration.
- Overlapping thresholds are allowed and draw in given order.
- `redline_start` is convenience syntax for a threshold from `redline_start` to `max_value`.
### 6. Value behaviour
- Numeric values clamp to `min_value` and `max_value` by default.
- Displayed value should match the clamped visual value.
- Non-finite numeric values should enter an invalid display state rather than crash.
- `None` should show an empty/unknown state if supported by the gauge.
- Precision and formatting must be consistent across digital text and visual geometry.
### 7. Animation and smoothing
Animation should be optional per gauge and globally configurable.
```python
@dataclass(frozen=True)
class AnimationConfig:
enabled: bool = True
duration: float = 0.15
easing: str = "linear"
@dataclass(frozen=True)
class SmoothingConfig:
enabled: bool = False
alpha: float = 0.35
max_step: float | None = None
```
Rules:
- Animation changes what is drawn, not the logical target value.
- Smoothing is for readability, not data processing.
- Users can disable animation for exact instantaneous displays.
- High-rate updates should converge toward the newest target value without queue buildup.
### 8. Layout and sizing
Gauges must support common Dear PyGui size behaviour:
- `width=0` and `height=0` preserve Dear PyGui default behaviour.
- `width=-1` fills available width.
- `height=-1` fills available height where Dear PyGui allows it.
- Positive dimensions are respected.
- `autosize_x` and `autosize_y` are supported where practical.
- Analog/compass/radial gauges maintain square aspect by default.
- Digital/bar/segmented gauges can stretch horizontally.
- Widgets should work in windows, groups, child windows, tabs, tables, and hidden/show-later layouts.
### 9. Dashboard helpers
Provide light helpers, not a full layout engine:
```python
with dpgg.gauge_panel(label="Engine", width=-1):
...
with dpgg.gauge_grid(columns=3, width=-1):
...
```
Dashboard helpers should use normal Dear PyGui containers internally and not block users from using
native Dear PyGui layouts directly.
### 10. Public API
Required exports:
```python
configure
GaugeTheme
GaugeStyle
AnimationConfig
SmoothingConfig
ThresholdBand
GaugeMarker
StatusState
GaugeValue
GaugeStats
analog_gauge
digital_gauge
bar_gauge
level_gauge
status_light
segmented_gauge
compass_gauge
thermometer_gauge
battery_gauge
multi_value_gauge
gauge_panel
gauge_grid
set_value
get_value
update_gauge
configure_gauge
set_thresholds
set_markers
set_show
delete_gauge
get_gauge_debug_state
```
### 11. Global configuration
```python
dpgg.configure(
*,
default_theme: str | GaugeTheme | None = None,
animation: AnimationConfig | bool | None = None,
smoothing: SmoothingConfig | bool | None = None,
default_width: int | None = None,
default_height: int | None = None,
frame_rate_limit: float | None = None,
debug: bool = False,
) -> None
```
### 12. Runtime API
```python
dpgg.set_value(tag, value: GaugeValue) -> None
dpgg.get_value(tag) -> GaugeValue
dpgg.update_gauge(
tag,
*,
value: GaugeValue | None = None,
label: str | None = None,
unit: str | None = None,
min_value: float | None = None,
max_value: float | None = None,
thresholds: Sequence[ThresholdBand] | None = None,
markers: Sequence[GaugeMarker] | None = None,
show: bool | None = None,
) -> None
dpgg.configure_gauge(tag, **config) -> None
dpgg.set_thresholds(tag, thresholds: Sequence[ThresholdBand]) -> None
dpgg.set_markers(tag, markers: Sequence[GaugeMarker]) -> None
dpgg.set_show(tag, show: bool) -> None
dpgg.delete_gauge(tag) -> None
```
Runtime functions are GUI-thread functions unless explicitly documented otherwise.
### 13. Acceptance tests
Before the first usable release:
1. Every gauge type can be created in a basic example.
2. Analog, digital, bar, level, status, segmented, compass, thermometer, battery, and multi-value gauges render without terminal errors.
3. Gauges update from GUI-thread frame callbacks at normal telemetry rates.
4. Stress examples tolerate producer rates above 60 Hz by using latest-value marshalling to the GUI thread.
5. Animation and smoothing can be enabled and disabled.
6. Thresholds and markers render correctly and do not trigger callbacks.
7. Out-of-range values clamp and display min/max.
8. Hidden tab examples do not permanently collapse gauge size.
9. Analog gauges maintain square aspect by default.
10. Bar/digital gauges can stretch horizontally.
11. Dashboard helpers work with grouped gauges.
12. Local editable install works with `uv add --editable ../dpg-gauges`.