commit 44e4cbd2460ce0802944447d8741a00504295fad Author: Hector van der Aa Date: Thu Jul 2 12:40:20 2026 +0200 Build instructions diff --git a/codex/AGENTS.md b/codex/AGENTS.md new file mode 100644 index 0000000..668df4e --- /dev/null +++ b/codex/AGENTS.md @@ -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. diff --git a/codex/ARCHITECTURE.md b/codex/ARCHITECTURE.md new file mode 100644 index 0000000..395f6fb --- /dev/null +++ b/codex/ARCHITECTURE.md @@ -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" +``` diff --git a/codex/FEATURES.md b/codex/FEATURES.md new file mode 100644 index 0000000..066a44a --- /dev/null +++ b/codex/FEATURES.md @@ -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`. diff --git a/codex/README.md b/codex/README.md new file mode 100644 index 0000000..19ccdb1 --- /dev/null +++ b/codex/README.md @@ -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` diff --git a/codex/STEPS.md b/codex/STEPS.md new file mode 100644 index 0000000..347b102 --- /dev/null +++ b/codex/STEPS.md @@ -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 +```