375 lines
11 KiB
Markdown
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`.
|