Build instructions
This commit is contained in:
41
codex/AGENTS.md
Normal file
41
codex/AGENTS.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Current status
|
||||||
|
|
||||||
|
Project instruction set initialized.
|
||||||
|
|
||||||
|
## Completed steps
|
||||||
|
|
||||||
|
None yet.
|
||||||
|
|
||||||
|
## Current step
|
||||||
|
|
||||||
|
Step 1 - Public API contract and pure core.
|
||||||
|
|
||||||
|
## Design decisions
|
||||||
|
|
||||||
|
- Package is managed with uv.
|
||||||
|
- Public import is `import dpg_gauges as dpgg`.
|
||||||
|
- Package target is Python 3.11+.
|
||||||
|
- The package is Dear PyGui-only, but pure configuration/state helpers should avoid Dear PyGui imports where practical.
|
||||||
|
- Widget creation and Dear PyGui item operations are GUI-thread-only.
|
||||||
|
- Runtime value/configuration updates are intended to be called from the GUI thread.
|
||||||
|
- High-frequency updates should be smoothed/coalesced so gauges remain readable and flicker-free.
|
||||||
|
- Out-of-range values clamp to min/max and display the clamped value.
|
||||||
|
- Gauges are display-only for the first release.
|
||||||
|
- Thresholds and redline zones are visual only; they do not trigger events.
|
||||||
|
- The public API should feel like normal Dear PyGui context-manager usage.
|
||||||
|
|
||||||
|
## Known issues
|
||||||
|
|
||||||
|
None yet.
|
||||||
|
|
||||||
|
## Commands used
|
||||||
|
|
||||||
|
- Read `codex-old/README.md`, `codex-old/FEATURES.md`, `codex-old/ARCHITECTURE.md`, `codex-old/STEPS.md`, and `codex-old/AGENTS.md`.
|
||||||
|
- Inspected `/home/hector/projects/dpg-map` package, examples, tests, docs, and pyproject structure.
|
||||||
|
- Created the initial `codex/` instruction documents for `dpg-gauges`.
|
||||||
|
|
||||||
|
## Next action
|
||||||
|
|
||||||
|
Implement Step 1.
|
||||||
444
codex/ARCHITECTURE.md
Normal file
444
codex/ARCHITECTURE.md
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
# dpg-gauges Architecture
|
||||||
|
|
||||||
|
This document defines the internal architecture for `dpg-gauges`.
|
||||||
|
|
||||||
|
The central rule is:
|
||||||
|
|
||||||
|
> Dear PyGui item and draw operations happen on the GUI thread. Gauge state is kept separately
|
||||||
|
> from draw commands so high-rate value updates can redraw only the dynamic parts of a gauge.
|
||||||
|
|
||||||
|
`dpg-gauges` does not need the full thread-safe command architecture used by `dpg-map`; gauge
|
||||||
|
updates are intended to be made from the GUI thread. The package still needs a clean state/render
|
||||||
|
split so animation, smoothing, sizing, and high-rate updates stay stable.
|
||||||
|
|
||||||
|
## High-level architecture
|
||||||
|
|
||||||
|
```text
|
||||||
|
Public API
|
||||||
|
↓
|
||||||
|
Validation and configuration normalization
|
||||||
|
↓
|
||||||
|
Gauge registry and GaugeState
|
||||||
|
↓
|
||||||
|
Renderer on GUI thread
|
||||||
|
↓
|
||||||
|
Dear PyGui child/group + drawlist/text/items
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical invariants
|
||||||
|
|
||||||
|
### Threading invariants
|
||||||
|
|
||||||
|
1. Dear PyGui APIs are called only on the GUI thread.
|
||||||
|
2. Widget creation functions are GUI-thread-only.
|
||||||
|
3. Runtime updates are GUI-thread-only unless explicitly documented otherwise.
|
||||||
|
4. Worker threads may produce raw data, but application code must marshal values to the GUI thread.
|
||||||
|
5. Locks should not be needed for normal gauge updates in the first release.
|
||||||
|
|
||||||
|
### State invariants
|
||||||
|
|
||||||
|
1. Every gauge has one `GaugeState` stored in the registry.
|
||||||
|
2. Public tags identify logical gauges and should align with Dear PyGui behaviour where practical.
|
||||||
|
3. Raw target value, clamped value, and displayed animated value are separate concepts.
|
||||||
|
4. Thresholds and markers are configuration, not business logic.
|
||||||
|
5. Out-of-range values clamp before rendering.
|
||||||
|
6. Non-finite values enter an invalid display state instead of crashing.
|
||||||
|
|
||||||
|
### Rendering invariants
|
||||||
|
|
||||||
|
1. Static geometry and dynamic geometry should be separate where practical.
|
||||||
|
2. Value updates should not rebuild static ticks, labels, threshold bands, or frames unless needed.
|
||||||
|
3. Configuration changes mark the smallest reasonable dirty set.
|
||||||
|
4. Redraws must not flicker through an empty state.
|
||||||
|
5. Hidden or zero-size layouts must not permanently collapse a gauge.
|
||||||
|
|
||||||
|
### Sizing invariants
|
||||||
|
|
||||||
|
1. Requested size and measured size are stored separately.
|
||||||
|
2. Drawlists use concrete measured dimensions.
|
||||||
|
3. A measured size of zero while hidden does not overwrite the last useful non-zero size.
|
||||||
|
4. Square gauges use the smaller dimension by default.
|
||||||
|
5. Stretchable gauges can use the full available width/height.
|
||||||
|
|
||||||
|
## Package layout
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/dpg_gauges/
|
||||||
|
__init__.py
|
||||||
|
api.py
|
||||||
|
widget.py
|
||||||
|
state.py
|
||||||
|
renderer.py
|
||||||
|
draw_layers.py
|
||||||
|
sizing.py
|
||||||
|
animation.py
|
||||||
|
smoothing.py
|
||||||
|
scales.py
|
||||||
|
themes.py
|
||||||
|
gauges.py
|
||||||
|
diagnostics.py
|
||||||
|
types.py
|
||||||
|
exceptions.py
|
||||||
|
examples/
|
||||||
|
basic_analog.py
|
||||||
|
basic_digital.py
|
||||||
|
basic_bar.py
|
||||||
|
basic_level.py
|
||||||
|
basic_status.py
|
||||||
|
basic_segmented.py
|
||||||
|
basic_compass.py
|
||||||
|
basic_thermometer.py
|
||||||
|
basic_battery.py
|
||||||
|
basic_multi_value.py
|
||||||
|
thresholds_markers.py
|
||||||
|
animations.py
|
||||||
|
dashboard.py
|
||||||
|
sizing.py
|
||||||
|
live_high_rate.py
|
||||||
|
live_worst_case.py
|
||||||
|
tests/
|
||||||
|
docs/
|
||||||
|
GETTING_STARTED.md
|
||||||
|
EXAMPLES.md
|
||||||
|
API_REFERENCE.md
|
||||||
|
codex/
|
||||||
|
README.md
|
||||||
|
FEATURES.md
|
||||||
|
ARCHITECTURE.md
|
||||||
|
STEPS.md
|
||||||
|
AGENTS.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module responsibilities
|
||||||
|
|
||||||
|
### `__init__.py`
|
||||||
|
|
||||||
|
Exports the public API only.
|
||||||
|
|
||||||
|
No expensive Dear PyGui initialization should happen here.
|
||||||
|
|
||||||
|
### `api.py`
|
||||||
|
|
||||||
|
Contains thin public wrappers:
|
||||||
|
|
||||||
|
- resolve gauge tags
|
||||||
|
- validate and normalize simple arguments
|
||||||
|
- call creation helpers
|
||||||
|
- call state update helpers
|
||||||
|
- expose stable runtime functions
|
||||||
|
|
||||||
|
`api.py` should not contain detailed drawing code.
|
||||||
|
|
||||||
|
### `types.py`
|
||||||
|
|
||||||
|
Defines shared types and dataclasses:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Tag = str | int
|
||||||
|
Color = tuple[int, int, int] | tuple[int, int, int, int]
|
||||||
|
GaugeValue = float | int | bool | str | None
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ThresholdBand: ...
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class GaugeMarker: ...
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AnimationConfig: ...
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SmoothingConfig: ...
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class GaugeStats: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### `exceptions.py`
|
||||||
|
|
||||||
|
Public exceptions:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class DpgGaugesError(Exception): ...
|
||||||
|
class GaugeNotFoundError(DpgGaugesError): ...
|
||||||
|
class GaugeConfigError(DpgGaugesError): ...
|
||||||
|
class GaugeValueError(DpgGaugesError): ...
|
||||||
|
class GaugeTypeError(DpgGaugesError): ...
|
||||||
|
class ThreadingError(DpgGaugesError): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### `state.py`
|
||||||
|
|
||||||
|
Owns global configuration, the gauge registry, and `GaugeState`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class DpgGaugesConfig:
|
||||||
|
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
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GaugeState:
|
||||||
|
tag: Tag
|
||||||
|
gauge_type: str
|
||||||
|
container_tag: Tag
|
||||||
|
drawlist_tag: Tag | None
|
||||||
|
text_tags: dict[str, Tag]
|
||||||
|
config: GaugeConfig
|
||||||
|
target_value: GaugeValue
|
||||||
|
clamped_value: float | None
|
||||||
|
displayed_value: float | None
|
||||||
|
previous_displayed_value: float | None
|
||||||
|
value_revision: int = 0
|
||||||
|
config_revision: int = 0
|
||||||
|
measured_width: int = 0
|
||||||
|
measured_height: int = 0
|
||||||
|
last_nonzero_width: int = 0
|
||||||
|
last_nonzero_height: int = 0
|
||||||
|
dirty: DirtyFlags = DirtyFlags.FULL
|
||||||
|
renderer: GaugeRenderer | None = None
|
||||||
|
```
|
||||||
|
|
||||||
|
### `gauges.py`
|
||||||
|
|
||||||
|
Defines normalized configuration dataclasses for gauge families:
|
||||||
|
|
||||||
|
- `GaugeConfig`
|
||||||
|
- `AnalogGaugeConfig`
|
||||||
|
- `DigitalGaugeConfig`
|
||||||
|
- `BarGaugeConfig`
|
||||||
|
- `LevelGaugeConfig`
|
||||||
|
- `StatusLightConfig`
|
||||||
|
- `SegmentedGaugeConfig`
|
||||||
|
- `CompassGaugeConfig`
|
||||||
|
- `ThermometerGaugeConfig`
|
||||||
|
- `BatteryGaugeConfig`
|
||||||
|
- `MultiValueGaugeConfig`
|
||||||
|
|
||||||
|
This module should be mostly pure and easy to test.
|
||||||
|
|
||||||
|
### `widget.py`
|
||||||
|
|
||||||
|
Creates Dear PyGui items for gauges.
|
||||||
|
|
||||||
|
Typical internal structure:
|
||||||
|
|
||||||
|
```text
|
||||||
|
group or child_window
|
||||||
|
drawlist
|
||||||
|
optional text/value items
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Creation functions enter a context manager when appropriate.
|
||||||
|
- The logical gauge is registered before yielding.
|
||||||
|
- Raw internal draw item tags are not required as public API.
|
||||||
|
- Callback and tooltip wiring happens here or in a small helper.
|
||||||
|
|
||||||
|
### `renderer.py`
|
||||||
|
|
||||||
|
GUI-thread-only renderer.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
- measure gauge size
|
||||||
|
- apply animation/smoothing progression
|
||||||
|
- rebuild dirty static layer
|
||||||
|
- rebuild dirty dynamic layer
|
||||||
|
- update text/value items
|
||||||
|
- avoid unnecessary static redraws
|
||||||
|
- expose debug state
|
||||||
|
|
||||||
|
Gauge rendering should prefer Dear PyGui drawlists for shapes, arcs, needles, bars, and custom
|
||||||
|
visuals. Plain Dear PyGui text items may be used when that is simpler and more reliable.
|
||||||
|
|
||||||
|
### `draw_layers.py`
|
||||||
|
|
||||||
|
Tracks draw item tags by purpose:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class DrawLayer:
|
||||||
|
name: str
|
||||||
|
item_tags: set[Tag] = field(default_factory=set)
|
||||||
|
```
|
||||||
|
|
||||||
|
Suggested layers:
|
||||||
|
|
||||||
|
- background
|
||||||
|
- static scale
|
||||||
|
- thresholds
|
||||||
|
- dynamic value
|
||||||
|
- markers
|
||||||
|
- text/debug
|
||||||
|
|
||||||
|
The renderer can delete and rebuild only selected layers.
|
||||||
|
|
||||||
|
### `sizing.py`
|
||||||
|
|
||||||
|
Provides helpers for measured dimensions and aspect decisions:
|
||||||
|
|
||||||
|
- track requested vs measured size
|
||||||
|
- preserve last non-zero size
|
||||||
|
- calculate square gauge rect
|
||||||
|
- calculate stretch rect
|
||||||
|
- detect hidden-to-visible transitions
|
||||||
|
|
||||||
|
### `animation.py`
|
||||||
|
|
||||||
|
Provides animation interpolation:
|
||||||
|
|
||||||
|
- linear interpolation
|
||||||
|
- simple easing names
|
||||||
|
- target/displayed value separation
|
||||||
|
- frame scheduling rules
|
||||||
|
|
||||||
|
Avoid an overbuilt animation framework in the first release.
|
||||||
|
|
||||||
|
### `smoothing.py`
|
||||||
|
|
||||||
|
Provides optional readability smoothing:
|
||||||
|
|
||||||
|
- exponential moving average via `alpha`
|
||||||
|
- optional `max_step`
|
||||||
|
- reset behaviour when value jumps or gauge is reconfigured
|
||||||
|
|
||||||
|
### `scales.py`
|
||||||
|
|
||||||
|
Pure math helpers:
|
||||||
|
|
||||||
|
- clamp and normalize values
|
||||||
|
- map value to angle
|
||||||
|
- map value to bar fill fraction
|
||||||
|
- generate tick values
|
||||||
|
- format values
|
||||||
|
- resolve threshold geometry
|
||||||
|
|
||||||
|
No Dear PyGui imports.
|
||||||
|
|
||||||
|
### `themes.py`
|
||||||
|
|
||||||
|
Lightweight presets and style normalization.
|
||||||
|
|
||||||
|
This should complement Dear PyGui theming, not replace it.
|
||||||
|
|
||||||
|
Built-in presets can include:
|
||||||
|
|
||||||
|
- `default`
|
||||||
|
- `dark`
|
||||||
|
- `minimal`
|
||||||
|
- `motorsport`
|
||||||
|
- `industrial`
|
||||||
|
|
||||||
|
### `diagnostics.py`
|
||||||
|
|
||||||
|
Provides:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_gauge_debug_state(tag) -> dict[str, Any]: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
- gauge type
|
||||||
|
- target value
|
||||||
|
- clamped value
|
||||||
|
- displayed value
|
||||||
|
- measured size
|
||||||
|
- last non-zero size
|
||||||
|
- dirty flags
|
||||||
|
- value/config revisions
|
||||||
|
- animation state
|
||||||
|
- smoothing state
|
||||||
|
|
||||||
|
## Dirty flag model
|
||||||
|
|
||||||
|
Use bit flags:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class DirtyFlags(IntFlag):
|
||||||
|
NONE = 0
|
||||||
|
VALUE = auto()
|
||||||
|
STATIC = auto()
|
||||||
|
DYNAMIC = auto()
|
||||||
|
TEXT = auto()
|
||||||
|
SIZE = auto()
|
||||||
|
STYLE = auto()
|
||||||
|
DEBUG = auto()
|
||||||
|
FULL = VALUE | STATIC | DYNAMIC | TEXT | SIZE | STYLE | DEBUG
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected usage:
|
||||||
|
|
||||||
|
| Operation | Dirty flags |
|
||||||
|
|---|---|
|
||||||
|
| value update | `VALUE | DYNAMIC | TEXT` |
|
||||||
|
| animation tick | `DYNAMIC | TEXT` |
|
||||||
|
| threshold update | `STATIC | DYNAMIC` |
|
||||||
|
| marker update | `STATIC | DYNAMIC` |
|
||||||
|
| label/unit/precision update | `TEXT` |
|
||||||
|
| min/max update | `STATIC | DYNAMIC | TEXT` |
|
||||||
|
| resize | `SIZE | STATIC | DYNAMIC | TEXT` |
|
||||||
|
| style/theme update | `STYLE | STATIC | DYNAMIC | TEXT` |
|
||||||
|
|
||||||
|
## Frame and update lifecycle
|
||||||
|
|
||||||
|
For each gauge update:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. Public API validates value/config.
|
||||||
|
2. State target value/config is updated.
|
||||||
|
3. Dirty flags are marked.
|
||||||
|
4. Renderer is scheduled if needed.
|
||||||
|
5. Renderer measures size.
|
||||||
|
6. Renderer advances animation/smoothing.
|
||||||
|
7. Renderer redraws static layers only if needed.
|
||||||
|
8. Renderer redraws dynamic/text layers as needed.
|
||||||
|
9. Dirty flags are cleared.
|
||||||
|
10. Renderer continues scheduling frames only while animation/smoothing is active.
|
||||||
|
```
|
||||||
|
|
||||||
|
## High-rate telemetry strategy
|
||||||
|
|
||||||
|
The first release should not promise background-thread-safe UI calls. Instead, examples should show
|
||||||
|
the correct Dear PyGui pattern:
|
||||||
|
|
||||||
|
```text
|
||||||
|
producer thread writes latest raw value to a lock-protected variable or queue
|
||||||
|
GUI frame callback reads the newest value
|
||||||
|
GUI frame callback calls dpgg.set_value(...)
|
||||||
|
gauge renderer coalesces/animates/smooths visual changes
|
||||||
|
```
|
||||||
|
|
||||||
|
This preserves Dear PyGui thread rules while still covering worst-case telemetry rates.
|
||||||
|
|
||||||
|
## Initial setup instructions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir dpg-gauges
|
||||||
|
cd dpg-gauges
|
||||||
|
git init
|
||||||
|
uv init --package dpg-gauges
|
||||||
|
uv add dearpygui
|
||||||
|
uv add --dev pytest ruff pyright
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended `pyproject.toml` additions:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
target-version = "py311"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "I", "UP", "B", "SIM"]
|
||||||
|
|
||||||
|
[tool.pyright]
|
||||||
|
typeCheckingMode = "basic"
|
||||||
|
```
|
||||||
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`.
|
||||||
18
codex/README.md
Normal file
18
codex/README.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Codex Build Notes
|
||||||
|
|
||||||
|
This folder contains internal build instructions, implementation architecture notes, and the
|
||||||
|
rolling agent log for `dpg-gauges`.
|
||||||
|
|
||||||
|
These files are not the final user-facing package documentation:
|
||||||
|
|
||||||
|
- `AGENTS.md`: rolling implementation log and current build status
|
||||||
|
- `ARCHITECTURE.md`: internal architecture, module responsibilities, and invariants
|
||||||
|
- `FEATURES.md`: public feature and API contract
|
||||||
|
- `STEPS.md`: ordered implementation plan
|
||||||
|
|
||||||
|
User-facing documentation should live in:
|
||||||
|
|
||||||
|
- `../README.md`
|
||||||
|
- `../docs/GETTING_STARTED.md`
|
||||||
|
- `../docs/EXAMPLES.md`
|
||||||
|
- `../docs/API_REFERENCE.md`
|
||||||
503
codex/STEPS.md
Normal file
503
codex/STEPS.md
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
# dpg-gauges Build Steps
|
||||||
|
|
||||||
|
There is no Step 0. Initial setup is listed separately, then implementation starts at Step 1.
|
||||||
|
|
||||||
|
## Workflow rules
|
||||||
|
|
||||||
|
1. Use `uv` for all Python package and dependency management.
|
||||||
|
2. Always read `codex/FEATURES.md`, `codex/ARCHITECTURE.md`, and `codex/AGENTS.md` before making code changes.
|
||||||
|
3. Keep `codex/AGENTS.md` as a rolling log of what has been done, what is broken, and what comes next.
|
||||||
|
4. Update `README.md` and `docs/` whenever public behaviour or examples change.
|
||||||
|
5. After every step, update `codex/AGENTS.md`, run relevant checks, and commit if working in git.
|
||||||
|
6. Do not casually change public API once introduced.
|
||||||
|
7. Dear PyGui calls must happen on the GUI thread only.
|
||||||
|
8. Runtime gauge updates are GUI-thread-only unless explicitly documented otherwise.
|
||||||
|
9. Prefer stable, boring implementation over clever drawing tricks.
|
||||||
|
10. Keep each step shippable and tested before moving to the next.
|
||||||
|
|
||||||
|
## Initial setup instructions
|
||||||
|
|
||||||
|
Run these before Step 1 when starting from a clean repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir dpg-gauges
|
||||||
|
cd dpg-gauges
|
||||||
|
git init
|
||||||
|
uv init --package dpg-gauges
|
||||||
|
uv add dearpygui
|
||||||
|
uv add --dev pytest ruff pyright
|
||||||
|
```
|
||||||
|
|
||||||
|
Create this structure:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/dpg_gauges/
|
||||||
|
__init__.py
|
||||||
|
api.py
|
||||||
|
widget.py
|
||||||
|
state.py
|
||||||
|
renderer.py
|
||||||
|
draw_layers.py
|
||||||
|
sizing.py
|
||||||
|
animation.py
|
||||||
|
smoothing.py
|
||||||
|
scales.py
|
||||||
|
themes.py
|
||||||
|
gauges.py
|
||||||
|
diagnostics.py
|
||||||
|
types.py
|
||||||
|
exceptions.py
|
||||||
|
examples/
|
||||||
|
tests/
|
||||||
|
docs/
|
||||||
|
codex/FEATURES.md
|
||||||
|
codex/ARCHITECTURE.md
|
||||||
|
codex/STEPS.md
|
||||||
|
codex/AGENTS.md
|
||||||
|
README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended `pyproject.toml` additions:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
target-version = "py311"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "I", "UP", "B", "SIM"]
|
||||||
|
|
||||||
|
[tool.pyright]
|
||||||
|
typeCheckingMode = "basic"
|
||||||
|
```
|
||||||
|
|
||||||
|
Initial checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python -c "import dpg_gauges; print(dpg_gauges.__name__)"
|
||||||
|
uv run ruff check .
|
||||||
|
uv run ruff format .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 1 - Public API contract and pure core
|
||||||
|
|
||||||
|
Goal: lock the public API surface and implement pure non-DPG components first.
|
||||||
|
|
||||||
|
Tasks:
|
||||||
|
|
||||||
|
1. Define public exports in `__init__.py`.
|
||||||
|
|
||||||
|
Required API names:
|
||||||
|
|
||||||
|
```python
|
||||||
|
configure
|
||||||
|
|
||||||
|
GaugeTheme
|
||||||
|
GaugeStyle
|
||||||
|
AnimationConfig
|
||||||
|
SmoothingConfig
|
||||||
|
ThresholdBand
|
||||||
|
GaugeMarker
|
||||||
|
StatusState
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add exceptions in `exceptions.py`.
|
||||||
|
3. Add shared dataclasses and types in `types.py`.
|
||||||
|
4. Implement configuration normalization dataclasses in `gauges.py`.
|
||||||
|
5. Implement pure scale helpers in `scales.py`.
|
||||||
|
6. Implement lightweight theme/style presets in `themes.py`.
|
||||||
|
7. Stub GUI-dependent public functions so imports succeed.
|
||||||
|
8. Add tests for imports, dataclass construction, clamp/normalize math, tick generation, threshold validation, and formatting.
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest
|
||||||
|
uv run ruff check .
|
||||||
|
uv run ruff format --check .
|
||||||
|
uv run pyright
|
||||||
|
```
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- public API imports cleanly
|
||||||
|
- pure gauge config can be created and validated
|
||||||
|
- value clamping and formatting are tested
|
||||||
|
- no Dear PyGui context is required for Step 1 tests
|
||||||
|
- API names are fixed before renderer work begins
|
||||||
|
|
||||||
|
Commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "step 1: lock public api and pure core"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2 - State, sizing, animation, and smoothing model
|
||||||
|
|
||||||
|
Goal: build the logical model before rendering final visuals.
|
||||||
|
|
||||||
|
Tasks:
|
||||||
|
|
||||||
|
1. Implement `DpgGaugesConfig` and `configure(...)`.
|
||||||
|
2. Implement `GaugeState` and global gauge registry.
|
||||||
|
3. Implement `DirtyFlags`.
|
||||||
|
4. Implement sizing helpers:
|
||||||
|
- requested size vs measured size
|
||||||
|
- last non-zero size preservation
|
||||||
|
- square gauge rect
|
||||||
|
- stretch gauge rect
|
||||||
|
5. Implement animation interpolation in `animation.py`.
|
||||||
|
6. Implement smoothing helpers in `smoothing.py`.
|
||||||
|
7. Implement runtime state functions for `set_value`, `get_value`, `update_gauge`, `set_thresholds`, `set_markers`, `set_show`, and `delete_gauge` without final drawing complexity.
|
||||||
|
8. Add tests for registry behaviour, value/clamped/displayed separation, dirty flags, sizing transitions, animation interpolation, smoothing progression, and invalid values.
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest
|
||||||
|
uv run ruff check .
|
||||||
|
uv run ruff format --check .
|
||||||
|
uv run pyright
|
||||||
|
```
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- values clamp to min/max
|
||||||
|
- non-finite values do not crash pure state helpers
|
||||||
|
- dirty flags distinguish value, static, dynamic, text, size, and style changes
|
||||||
|
- smoothing and animation are optional and testable without Dear PyGui
|
||||||
|
- hidden/zero-size state does not erase last non-zero size
|
||||||
|
|
||||||
|
Commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "step 2: add gauge state sizing and animation model"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 3 - Widget shell and renderer foundation
|
||||||
|
|
||||||
|
Goal: create stable Dear PyGui gauge shells and a renderer that can draw placeholders without flicker.
|
||||||
|
|
||||||
|
Tasks:
|
||||||
|
|
||||||
|
1. Implement gauge creation context managers in `widget.py`.
|
||||||
|
2. Create internal group/child/drawlist structures for gauge widgets.
|
||||||
|
3. Register each gauge before yielding from the context manager.
|
||||||
|
4. Implement `GaugeRenderer` foundation:
|
||||||
|
- measure size
|
||||||
|
- schedule frame callbacks where animation is active
|
||||||
|
- draw placeholder background/frame/value text
|
||||||
|
- expose debug state
|
||||||
|
5. Implement `draw_layers.py` bookkeeping.
|
||||||
|
6. Implement `gauge_panel` and `gauge_grid` as lightweight layout helpers.
|
||||||
|
7. Add examples:
|
||||||
|
- `examples/basic_digital.py`
|
||||||
|
- `examples/basic_analog.py`
|
||||||
|
- `examples/sizing.py`
|
||||||
|
- `examples/dashboard.py`
|
||||||
|
8. Add Dear PyGui context smoke tests where feasible.
|
||||||
|
|
||||||
|
Manual checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python examples/basic_digital.py
|
||||||
|
uv run python examples/basic_analog.py
|
||||||
|
uv run python examples/sizing.py
|
||||||
|
uv run python examples/dashboard.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Automated checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest
|
||||||
|
uv run ruff check .
|
||||||
|
uv run ruff format --check .
|
||||||
|
uv run pyright
|
||||||
|
```
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- gauge shells appear inside Dear PyGui windows
|
||||||
|
- widgets respect basic width/height options
|
||||||
|
- hidden/show-later layout does not permanently collapse sizes
|
||||||
|
- debug state reports useful state
|
||||||
|
- no flicker through empty drawlists on simple value changes
|
||||||
|
|
||||||
|
Commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "step 3: add widget shell and renderer foundation"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 4 - Digital, bar, level, and status gauges
|
||||||
|
|
||||||
|
Goal: implement the simplest high-value gauges first.
|
||||||
|
|
||||||
|
Tasks:
|
||||||
|
|
||||||
|
1. Implement `digital_gauge` rendering.
|
||||||
|
2. Implement `bar_gauge` rendering with horizontal/vertical orientation.
|
||||||
|
3. Implement `level_gauge` rendering.
|
||||||
|
4. Implement `status_light` rendering.
|
||||||
|
5. Implement threshold and marker drawing for bar/level where applicable.
|
||||||
|
6. Implement optional tooltips and callbacks.
|
||||||
|
7. Add examples:
|
||||||
|
- `examples/basic_digital.py`
|
||||||
|
- `examples/basic_bar.py`
|
||||||
|
- `examples/basic_level.py`
|
||||||
|
- `examples/basic_status.py`
|
||||||
|
- `examples/thresholds_markers.py`
|
||||||
|
8. Add tests for pure geometry and runtime update state.
|
||||||
|
|
||||||
|
Manual checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python examples/basic_digital.py
|
||||||
|
uv run python examples/basic_bar.py
|
||||||
|
uv run python examples/basic_level.py
|
||||||
|
uv run python examples/basic_status.py
|
||||||
|
uv run python examples/thresholds_markers.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Automated checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest
|
||||||
|
uv run ruff check .
|
||||||
|
uv run ruff format --check .
|
||||||
|
uv run pyright
|
||||||
|
```
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- digital values format correctly
|
||||||
|
- bar/level fill fraction is correct and clamped
|
||||||
|
- status states display configured colors/text
|
||||||
|
- thresholds and markers render visually without events
|
||||||
|
- stretchable gauges behave in resizable layouts
|
||||||
|
|
||||||
|
Commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "step 4: add digital bar level and status gauges"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 5 - Analog, segmented, compass, thermometer, battery, and multi-value gauges
|
||||||
|
|
||||||
|
Goal: complete the first-release gauge catalogue.
|
||||||
|
|
||||||
|
Tasks:
|
||||||
|
|
||||||
|
1. Implement `analog_gauge`:
|
||||||
|
- arcs
|
||||||
|
- ticks
|
||||||
|
- tick labels
|
||||||
|
- needle
|
||||||
|
- redline
|
||||||
|
- markers
|
||||||
|
2. Implement `segmented_gauge`.
|
||||||
|
3. Implement `compass_gauge`.
|
||||||
|
4. Implement `thermometer_gauge`.
|
||||||
|
5. Implement `battery_gauge`.
|
||||||
|
6. Implement `multi_value_gauge`.
|
||||||
|
7. Ensure square gauges maintain aspect ratio by default.
|
||||||
|
8. Add examples:
|
||||||
|
- `examples/basic_segmented.py`
|
||||||
|
- `examples/basic_compass.py`
|
||||||
|
- `examples/basic_thermometer.py`
|
||||||
|
- `examples/basic_battery.py`
|
||||||
|
- `examples/basic_multi_value.py`
|
||||||
|
9. Add tests for value-to-angle math, tick generation, compass wraparound, segment activation, battery clamp, and multi-value config validation.
|
||||||
|
|
||||||
|
Manual checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python examples/basic_analog.py
|
||||||
|
uv run python examples/basic_segmented.py
|
||||||
|
uv run python examples/basic_compass.py
|
||||||
|
uv run python examples/basic_thermometer.py
|
||||||
|
uv run python examples/basic_battery.py
|
||||||
|
uv run python examples/basic_multi_value.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Automated checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest
|
||||||
|
uv run ruff check .
|
||||||
|
uv run ruff format --check .
|
||||||
|
uv run pyright
|
||||||
|
```
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- all MVP gauge types render
|
||||||
|
- analog redline and markers render correctly
|
||||||
|
- compass wraps heading values sensibly
|
||||||
|
- segmented gauges support dense shift-light style displays
|
||||||
|
- square gauges do not distort in common layouts
|
||||||
|
|
||||||
|
Commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "step 5: complete first gauge catalogue"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 6 - High-rate updates, animation, and stress examples
|
||||||
|
|
||||||
|
Goal: prove gauges remain readable and stable under fast telemetry.
|
||||||
|
|
||||||
|
Tasks:
|
||||||
|
|
||||||
|
1. Integrate animation into renderer scheduling.
|
||||||
|
2. Integrate optional smoothing into value rendering.
|
||||||
|
3. Ensure repeated GUI-thread `set_value` calls coalesce naturally to the newest target.
|
||||||
|
4. Add examples:
|
||||||
|
- `examples/animations.py`
|
||||||
|
- `examples/live_high_rate.py`
|
||||||
|
- `examples/live_worst_case.py`
|
||||||
|
5. High-rate examples should use a producer thread for data generation and GUI-thread frame callbacks for Dear PyGui updates.
|
||||||
|
6. Add debug fields for animation/smoothing state.
|
||||||
|
7. Add tests for animation convergence, smoothing reset behaviour, and latest-target wins.
|
||||||
|
|
||||||
|
Manual stress checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python examples/animations.py
|
||||||
|
uv run python examples/live_high_rate.py
|
||||||
|
uv run python examples/live_worst_case.py
|
||||||
|
```
|
||||||
|
|
||||||
|
While examples run:
|
||||||
|
|
||||||
|
- drive updates above 60 Hz where possible
|
||||||
|
- verify gauges do not flicker
|
||||||
|
- verify values remain human-readable with smoothing enabled
|
||||||
|
- verify animation can be disabled for immediate response
|
||||||
|
- verify UI remains responsive
|
||||||
|
|
||||||
|
Automated checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest
|
||||||
|
uv run ruff check .
|
||||||
|
uv run ruff format --check .
|
||||||
|
uv run pyright
|
||||||
|
```
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- high-rate examples remain stable
|
||||||
|
- smoothing and animation are configurable per gauge
|
||||||
|
- no unbounded update queue exists
|
||||||
|
- renderer only schedules continuous frames while needed
|
||||||
|
|
||||||
|
Commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "step 6: add animation and high rate update stress tests"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 7 - Documentation, hardening, and beta release
|
||||||
|
|
||||||
|
Goal: make the package usable as a dependency in DataFlux and public projects.
|
||||||
|
|
||||||
|
Tasks:
|
||||||
|
|
||||||
|
1. Write `README.md` with:
|
||||||
|
- install
|
||||||
|
- minimal analog/digital examples
|
||||||
|
- dashboard example
|
||||||
|
- GUI-thread update contract
|
||||||
|
- animation/smoothing explanation
|
||||||
|
- threshold/marker examples
|
||||||
|
2. Write docs:
|
||||||
|
- `docs/GETTING_STARTED.md`
|
||||||
|
- `docs/EXAMPLES.md`
|
||||||
|
- `docs/API_REFERENCE.md`
|
||||||
|
3. Add docstrings for all public functions.
|
||||||
|
4. Review public API exports.
|
||||||
|
5. Add hardening tests for:
|
||||||
|
- unknown gauge
|
||||||
|
- duplicate tag
|
||||||
|
- invalid range
|
||||||
|
- invalid threshold
|
||||||
|
- invalid marker
|
||||||
|
- non-finite value
|
||||||
|
- deleted gauge updates
|
||||||
|
- hidden layout sizing
|
||||||
|
- animation disabled/enabled
|
||||||
|
6. Run all examples manually.
|
||||||
|
7. Test local editable install from another project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv add --editable ../dpg-gauges
|
||||||
|
uv run python -c "import dpg_gauges as dpgg; print(dpgg.__name__)"
|
||||||
|
```
|
||||||
|
|
||||||
|
8. Bump version.
|
||||||
|
|
||||||
|
Suggested initial beta:
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.1.0b1
|
||||||
|
```
|
||||||
|
|
||||||
|
Final checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
uv run pytest
|
||||||
|
uv run ruff check .
|
||||||
|
uv run ruff format --check .
|
||||||
|
uv run pyright
|
||||||
|
```
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- all examples run
|
||||||
|
- local editable dependency works
|
||||||
|
- docs cover all public gauge types
|
||||||
|
- high-rate examples are stable
|
||||||
|
- README clearly documents Dear PyGui GUI-thread expectations
|
||||||
|
- `codex/AGENTS.md` accurately describes status
|
||||||
|
|
||||||
|
Commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "step 7: harden docs and prepare beta"
|
||||||
|
git tag v0.1.0b1
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user