step 2: add gauge state sizing and animation model

This commit is contained in:
2026-07-02 13:54:58 +02:00
parent 44e4cbd246
commit 34fd60c34e
27 changed files with 1732 additions and 5 deletions

150
src/dpg_gauges/gauges.py Normal file
View File

@@ -0,0 +1,150 @@
"""Pure gauge configuration dataclasses."""
from __future__ import annotations
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, replace
from typing import Any, Literal
from .scales import validate_markers, validate_range, validate_thresholds
from .themes import GaugeStyle, GaugeTheme, get_theme, merge_style
from .types import (
AnimationConfig,
Callback,
GaugeMarker,
GaugeValue,
SmoothingConfig,
StatusState,
Tag,
ThresholdBand,
ValueFormatter,
)
def _normalize_animation(value: AnimationConfig | bool | None) -> AnimationConfig | None:
if isinstance(value, bool):
return AnimationConfig(enabled=value)
return value
def _normalize_smoothing(value: SmoothingConfig | bool | None) -> SmoothingConfig | None:
if isinstance(value, bool):
return SmoothingConfig(enabled=value)
return value
@dataclass(frozen=True)
class GaugeConfig:
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] = ()
def __post_init__(self) -> None:
validate_range(self.min_value, self.max_value)
validate_thresholds(self.thresholds, self.min_value, self.max_value)
validate_markers(self.markers, self.min_value, self.max_value)
if self.precision < 0:
msg = "precision must be non-negative"
raise ValueError(msg)
object.__setattr__(self, "theme", get_theme(self.theme))
object.__setattr__(self, "style", self.style or get_theme(self.theme).style)
object.__setattr__(self, "animation", _normalize_animation(self.animation))
object.__setattr__(self, "smoothing", _normalize_smoothing(self.smoothing))
object.__setattr__(self, "thresholds", tuple(self.thresholds))
object.__setattr__(self, "markers", tuple(self.markers))
def with_style_overrides(self, **overrides: Any) -> GaugeConfig:
return replace(self, style=merge_style(self.style, **overrides))
@dataclass(frozen=True)
class AnalogGaugeConfig(GaugeConfig):
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
@dataclass(frozen=True)
class DigitalGaugeConfig(GaugeConfig):
pass
@dataclass(frozen=True)
class BarGaugeConfig(GaugeConfig):
orientation: Literal["horizontal", "vertical"] = "horizontal"
fill_mode: Literal["clamp", "centered"] = "clamp"
corner_radius: float = 4.0
show_scale: bool = True
stretch: bool = True
@dataclass(frozen=True)
class LevelGaugeConfig(BarGaugeConfig):
orientation: Literal["horizontal", "vertical"] = "vertical"
@dataclass(frozen=True)
class StatusLightConfig(GaugeConfig):
state: str | bool | int = False
states: Mapping[Any, StatusState] | None = None
blink: bool = False
pulse: bool = False
@dataclass(frozen=True)
class SegmentedGaugeConfig(BarGaugeConfig):
segments: int = 10
@dataclass(frozen=True)
class CompassGaugeConfig(AnalogGaugeConfig):
min_value: float = 0.0
max_value: float = 360.0
start_angle: float = 90.0
end_angle: float = -270.0
@dataclass(frozen=True)
class ThermometerGaugeConfig(LevelGaugeConfig):
pass
@dataclass(frozen=True)
class BatteryGaugeConfig(LevelGaugeConfig):
charging: bool | None = None
@dataclass(frozen=True)
class MultiValueGaugeConfig(GaugeConfig):
values: Mapping[str, GaugeValue] | None = None