# 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`.