Build instructions
This commit is contained in:
374
codex/FEATURES.md
Normal file
374
codex/FEATURES.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user