Build instructions

This commit is contained in:
2026-07-02 12:40:20 +02:00
commit 44e4cbd246
5 changed files with 1380 additions and 0 deletions

41
codex/AGENTS.md Normal file
View 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
View 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
View 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
View 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
View 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
```