From 451557299e1074275d07df489d8df8aaf7687c31 Mon Sep 17 00:00:00 2001 From: Hector van der Aa Date: Thu, 2 Jul 2026 15:47:12 +0200 Subject: [PATCH] improve gauge function signatures --- src/dpg_gauges/widget.py | 463 +++++++++++++++++++++++++++++++++++---- 1 file changed, 423 insertions(+), 40 deletions(-) diff --git a/src/dpg_gauges/widget.py b/src/dpg_gauges/widget.py index 24e54f3..2fd9b20 100644 --- a/src/dpg_gauges/widget.py +++ b/src/dpg_gauges/widget.py @@ -2,9 +2,10 @@ from __future__ import annotations +from collections.abc import Mapping, Sequence from dataclasses import dataclass from types import TracebackType -from typing import Any, Generic, TypeVar, cast +from typing import Any, Generic, Literal, TypeVar, cast import dearpygui.dearpygui as dpg @@ -23,7 +24,18 @@ from .gauges import ( ) from .renderer import GaugeRenderer from .state import GaugeState, register_gauge -from .types import Tag +from .themes import GaugeStyle, GaugeTheme +from .types import ( + AnimationConfig, + Callback, + GaugeMarker, + GaugeValue, + SmoothingConfig, + StatusState, + Tag, + ThresholdBand, + ValueFormatter, +) ConfigT = TypeVar("ConfigT", bound=GaugeConfig) @@ -138,92 +150,463 @@ def create_gauge_context(gauge_type: str, config: ConfigT) -> GaugeContext[Confi return GaugeContext(state) -def gauge_panel(**config: Any) -> LayoutContext: +def gauge_panel( + *, + tag: Tag | None = None, + label: str | None = None, + width: int = 0, + height: int = 0, + show: bool = True, + border: bool = True, + autosize_x: bool = False, + autosize_y: bool = False, + tooltip: str | None = None, + **config: Any, +) -> LayoutContext: """Create a lightweight child-window container for related gauges.""" if not _dpg_context_active: return LayoutContext(None) - tag = config.pop("tag", 0) - label = config.pop("label", None) - tooltip = config.pop("tooltip", None) container = dpg.add_child_window( - tag=tag, - label=label, - width=config.pop("width", 0), - height=config.pop("height", 0), - show=config.pop("show", True), - border=config.pop("border", True), - autosize_x=config.pop("autosize_x", False), - autosize_y=config.pop("autosize_y", False), + tag=tag or 0, + label=label or "", + width=width, + height=height, + show=show, + border=border, + autosize_x=autosize_x, + autosize_y=autosize_y, **config, ) _add_tooltip(container, tooltip) return LayoutContext(container) -def gauge_grid(**config: Any) -> LayoutContext: +def gauge_grid( + *, + columns: int = 1, + tag: Tag | None = None, + width: int = 0, + height: int = 0, + show: bool = True, + **config: Any, +) -> LayoutContext: """Create a lightweight horizontal gauge layout container.""" if not _dpg_context_active: return LayoutContext(None) - columns = max(int(config.pop("columns", 1)), 1) + columns = max(int(columns), 1) container = dpg.add_group( - tag=config.pop("tag", 0), - width=config.pop("width", 0), - height=config.pop("height", 0), - show=config.pop("show", True), + tag=tag or 0, + width=width, + height=height, + show=show, horizontal=columns > 1, **config, ) return LayoutContext(container) -def analog_gauge(**config: Any) -> GaugeContext[AnalogGaugeConfig]: +def analog_gauge( + *, + tag: Tag | None = None, + label: str | None = None, + value: GaugeValue = 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: Callback | 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: ValueFormatter | None = None, + thresholds: Sequence[ThresholdBand] = (), + markers: Sequence[GaugeMarker] = (), + 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, + redline_start: float | None = None, + **config: Any, +) -> GaugeContext[AnalogGaugeConfig]: """Create an analog dial gauge with ticks, needle, thresholds, and markers.""" - return create_gauge_context("analog", AnalogGaugeConfig(**config)) + return create_gauge_context("analog", AnalogGaugeConfig(**_config_from_locals(locals()))) -def digital_gauge(**config: Any) -> GaugeContext[DigitalGaugeConfig]: +def digital_gauge( + *, + tag: Tag | None = None, + label: str | None = None, + value: GaugeValue = 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: Callback | 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: ValueFormatter | None = None, + thresholds: Sequence[ThresholdBand] = (), + markers: Sequence[GaugeMarker] = (), + **config: Any, +) -> GaugeContext[DigitalGaugeConfig]: """Create a large numeric readout gauge.""" - return create_gauge_context("digital", DigitalGaugeConfig(**config)) + return create_gauge_context("digital", DigitalGaugeConfig(**_config_from_locals(locals()))) -def bar_gauge(**config: Any) -> GaugeContext[BarGaugeConfig]: +def bar_gauge( + *, + tag: Tag | None = None, + label: str | None = None, + value: GaugeValue = 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: Callback | 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: ValueFormatter | None = None, + thresholds: Sequence[ThresholdBand] = (), + markers: Sequence[GaugeMarker] = (), + orientation: Literal["horizontal", "vertical"] = "horizontal", + fill_mode: Literal["clamp", "centered"] = "clamp", + corner_radius: float = 4.0, + show_scale: bool = True, + stretch: bool = True, + **config: Any, +) -> GaugeContext[BarGaugeConfig]: """Create a horizontal or vertical fill bar gauge.""" - return create_gauge_context("bar", BarGaugeConfig(**config)) + return create_gauge_context("bar", BarGaugeConfig(**_config_from_locals(locals()))) -def level_gauge(**config: Any) -> GaugeContext[LevelGaugeConfig]: +def level_gauge( + *, + tag: Tag | None = None, + label: str | None = None, + value: GaugeValue = 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: Callback | 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: ValueFormatter | None = None, + thresholds: Sequence[ThresholdBand] = (), + markers: Sequence[GaugeMarker] = (), + orientation: Literal["horizontal", "vertical"] = "vertical", + fill_mode: Literal["clamp", "centered"] = "clamp", + corner_radius: float = 4.0, + show_scale: bool = True, + stretch: bool = True, + **config: Any, +) -> GaugeContext[LevelGaugeConfig]: """Create a tank or level-style vertical gauge.""" - return create_gauge_context("level", LevelGaugeConfig(**config)) + return create_gauge_context("level", LevelGaugeConfig(**_config_from_locals(locals()))) -def status_light(**config: Any) -> GaugeContext[StatusLightConfig]: +def status_light( + *, + tag: Tag | None = None, + label: str | None = None, + value: GaugeValue = 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: Callback | 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: ValueFormatter | None = None, + thresholds: Sequence[ThresholdBand] = (), + markers: Sequence[GaugeMarker] = (), + state: str | bool | int = False, + states: Mapping[Any, StatusState] | None = None, + blink: bool = False, + pulse: bool = False, + **config: Any, +) -> GaugeContext[StatusLightConfig]: """Create an LED-style status indicator gauge.""" - return create_gauge_context("status_light", StatusLightConfig(**config)) + return create_gauge_context("status_light", StatusLightConfig(**_config_from_locals(locals()))) -def segmented_gauge(**config: Any) -> GaugeContext[SegmentedGaugeConfig]: +def segmented_gauge( + *, + tag: Tag | None = None, + label: str | None = None, + value: GaugeValue = 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: Callback | 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: ValueFormatter | None = None, + thresholds: Sequence[ThresholdBand] = (), + markers: Sequence[GaugeMarker] = (), + orientation: Literal["horizontal", "vertical"] = "horizontal", + fill_mode: Literal["clamp", "centered"] = "clamp", + corner_radius: float = 4.0, + show_scale: bool = True, + stretch: bool = True, + segments: int = 10, + **config: Any, +) -> GaugeContext[SegmentedGaugeConfig]: """Create a segmented strip gauge for shift-light or cell-style displays.""" - return create_gauge_context("segmented", SegmentedGaugeConfig(**config)) + return create_gauge_context("segmented", SegmentedGaugeConfig(**_config_from_locals(locals()))) -def compass_gauge(**config: Any) -> GaugeContext[CompassGaugeConfig]: +def compass_gauge( + *, + tag: Tag | None = None, + label: str | None = None, + value: GaugeValue = None, + min_value: float = 0.0, + max_value: float = 360.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: Callback | 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: ValueFormatter | None = None, + thresholds: Sequence[ThresholdBand] = (), + markers: Sequence[GaugeMarker] = (), + start_angle: float = 90.0, + end_angle: float = -270.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, + redline_start: float | None = None, + **config: Any, +) -> GaugeContext[CompassGaugeConfig]: """Create a compass gauge that wraps numeric headings to 0-360 degrees.""" - return create_gauge_context("compass", CompassGaugeConfig(**config)) + return create_gauge_context("compass", CompassGaugeConfig(**_config_from_locals(locals()))) -def thermometer_gauge(**config: Any) -> GaugeContext[ThermometerGaugeConfig]: +def thermometer_gauge( + *, + tag: Tag | None = None, + label: str | None = None, + value: GaugeValue = 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: Callback | 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: ValueFormatter | None = None, + thresholds: Sequence[ThresholdBand] = (), + markers: Sequence[GaugeMarker] = (), + orientation: Literal["horizontal", "vertical"] = "vertical", + fill_mode: Literal["clamp", "centered"] = "clamp", + corner_radius: float = 4.0, + show_scale: bool = True, + stretch: bool = True, + **config: Any, +) -> GaugeContext[ThermometerGaugeConfig]: """Create a thermometer-style temperature gauge.""" - return create_gauge_context("thermometer", ThermometerGaugeConfig(**config)) + return create_gauge_context( + "thermometer", ThermometerGaugeConfig(**_config_from_locals(locals())) + ) -def battery_gauge(**config: Any) -> GaugeContext[BatteryGaugeConfig]: +def battery_gauge( + *, + tag: Tag | None = None, + label: str | None = None, + value: GaugeValue = 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: Callback | 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: ValueFormatter | None = None, + thresholds: Sequence[ThresholdBand] = (), + markers: Sequence[GaugeMarker] = (), + orientation: Literal["horizontal", "vertical"] = "vertical", + fill_mode: Literal["clamp", "centered"] = "clamp", + corner_radius: float = 4.0, + show_scale: bool = True, + stretch: bool = True, + charging: bool | None = None, + **config: Any, +) -> GaugeContext[BatteryGaugeConfig]: """Create a battery-style percentage or value gauge.""" - return create_gauge_context("battery", BatteryGaugeConfig(**config)) + return create_gauge_context("battery", BatteryGaugeConfig(**_config_from_locals(locals()))) -def multi_value_gauge(**config: Any) -> GaugeContext[MultiValueGaugeConfig]: +def multi_value_gauge( + *, + tag: Tag | None = None, + label: str | None = None, + value: GaugeValue = 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: Callback | 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: ValueFormatter | None = None, + thresholds: Sequence[ThresholdBand] = (), + markers: Sequence[GaugeMarker] = (), + values: Mapping[str, GaugeValue] | None = None, + **config: Any, +) -> GaugeContext[MultiValueGaugeConfig]: """Create a compact grouped key/value readout gauge.""" - return create_gauge_context("multi_value", MultiValueGaugeConfig(**config)) + return create_gauge_context( + "multi_value", MultiValueGaugeConfig(**_config_from_locals(locals())) + ) + + +def _config_from_locals(values: dict[str, Any]) -> dict[str, Any]: + config = dict(values.pop("config")) + config.update(values) + return config def _default_width(gauge_type: str) -> int: