step 3: add widget shell and renderer foundation
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
"""Dear PyGui widget shell creation helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from types import TracebackType
|
||||
from typing import Any, Generic, TypeVar, cast
|
||||
|
||||
import dearpygui.dearpygui as dpg
|
||||
|
||||
from .gauges import (
|
||||
AnalogGaugeConfig,
|
||||
BarGaugeConfig,
|
||||
BatteryGaugeConfig,
|
||||
CompassGaugeConfig,
|
||||
DigitalGaugeConfig,
|
||||
GaugeConfig,
|
||||
LevelGaugeConfig,
|
||||
MultiValueGaugeConfig,
|
||||
SegmentedGaugeConfig,
|
||||
StatusLightConfig,
|
||||
ThermometerGaugeConfig,
|
||||
)
|
||||
from .renderer import GaugeRenderer
|
||||
from .state import GaugeState, register_gauge
|
||||
from .types import Tag
|
||||
|
||||
ConfigT = TypeVar("ConfigT", bound=GaugeConfig)
|
||||
|
||||
_dpg_context_active = False
|
||||
_original_create_context = dpg.create_context
|
||||
_original_destroy_context = dpg.destroy_context
|
||||
|
||||
|
||||
def _install_context_tracking() -> None:
|
||||
if getattr(dpg.create_context, "__dpg_gauges_wrapped__", False):
|
||||
return
|
||||
|
||||
def create_context(*args: Any, **kwargs: Any) -> Any:
|
||||
global _dpg_context_active
|
||||
result = _original_create_context(*args, **kwargs)
|
||||
_dpg_context_active = True
|
||||
return result
|
||||
|
||||
def destroy_context(*args: Any, **kwargs: Any) -> Any:
|
||||
global _dpg_context_active
|
||||
try:
|
||||
return _original_destroy_context(*args, **kwargs)
|
||||
finally:
|
||||
_dpg_context_active = False
|
||||
|
||||
create_context.__dpg_gauges_wrapped__ = True # type: ignore[attr-defined]
|
||||
destroy_context.__dpg_gauges_wrapped__ = True # type: ignore[attr-defined]
|
||||
dpg.create_context = create_context
|
||||
dpg.destroy_context = destroy_context
|
||||
|
||||
|
||||
_install_context_tracking()
|
||||
|
||||
|
||||
@dataclass
|
||||
class GaugeContext(Generic[ConfigT]):
|
||||
state: GaugeState
|
||||
|
||||
def __enter__(self) -> ConfigT:
|
||||
if _dpg_context_active and dpg.does_item_exist(self.state.container_tag):
|
||||
dpg.push_container_stack(self.state.container_tag)
|
||||
return cast(ConfigT, self.state.config)
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
exc: BaseException | None,
|
||||
traceback: TracebackType | None,
|
||||
) -> None:
|
||||
if _dpg_context_active and dpg.does_item_exist(self.state.container_tag):
|
||||
dpg.pop_container_stack()
|
||||
|
||||
|
||||
@dataclass
|
||||
class LayoutContext:
|
||||
tag: Tag | None
|
||||
|
||||
def __enter__(self) -> Tag | None:
|
||||
if self.tag is not None and _dpg_context_active and dpg.does_item_exist(self.tag):
|
||||
dpg.push_container_stack(self.tag)
|
||||
return self.tag
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
exc: BaseException | None,
|
||||
traceback: TracebackType | None,
|
||||
) -> None:
|
||||
if self.tag is not None and _dpg_context_active and dpg.does_item_exist(self.tag):
|
||||
dpg.pop_container_stack()
|
||||
|
||||
|
||||
def create_gauge_context(gauge_type: str, config: ConfigT) -> GaugeContext[ConfigT]:
|
||||
if not _dpg_context_active:
|
||||
return GaugeContext(register_gauge(gauge_type, config))
|
||||
|
||||
tag = config.tag
|
||||
container_tag = tag if tag is not None else None
|
||||
drawlist_tag = f"{tag}__drawlist" if tag is not None else None
|
||||
width = config.width
|
||||
height = config.height or _default_height(gauge_type)
|
||||
if height == -1:
|
||||
height = _default_height(gauge_type)
|
||||
|
||||
container = dpg.add_group(
|
||||
tag=container_tag or 0,
|
||||
width=width,
|
||||
height=height,
|
||||
show=config.show,
|
||||
user_data=config.user_data,
|
||||
)
|
||||
drawlist = dpg.add_drawlist(
|
||||
max(width, 1) if width > 0 else _default_width(gauge_type),
|
||||
max(height, 1),
|
||||
tag=drawlist_tag or 0,
|
||||
parent=container,
|
||||
show=config.show,
|
||||
)
|
||||
_add_tooltip(container, config.tooltip)
|
||||
state = register_gauge(
|
||||
gauge_type,
|
||||
config,
|
||||
container_tag=container,
|
||||
drawlist_tag=drawlist,
|
||||
text_tags={},
|
||||
)
|
||||
renderer = GaugeRenderer(state)
|
||||
state.renderer = renderer
|
||||
renderer.render()
|
||||
return GaugeContext(state)
|
||||
|
||||
|
||||
def gauge_panel(**config: Any) -> LayoutContext:
|
||||
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),
|
||||
**config,
|
||||
)
|
||||
_add_tooltip(container, tooltip)
|
||||
return LayoutContext(container)
|
||||
|
||||
|
||||
def gauge_grid(**config: Any) -> LayoutContext:
|
||||
if not _dpg_context_active:
|
||||
return LayoutContext(None)
|
||||
columns = max(int(config.pop("columns", 1)), 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),
|
||||
horizontal=columns > 1,
|
||||
**config,
|
||||
)
|
||||
return LayoutContext(container)
|
||||
|
||||
|
||||
def analog_gauge(**config: Any) -> GaugeContext[AnalogGaugeConfig]:
|
||||
return create_gauge_context("analog", AnalogGaugeConfig(**config))
|
||||
|
||||
|
||||
def digital_gauge(**config: Any) -> GaugeContext[DigitalGaugeConfig]:
|
||||
return create_gauge_context("digital", DigitalGaugeConfig(**config))
|
||||
|
||||
|
||||
def bar_gauge(**config: Any) -> GaugeContext[BarGaugeConfig]:
|
||||
return create_gauge_context("bar", BarGaugeConfig(**config))
|
||||
|
||||
|
||||
def level_gauge(**config: Any) -> GaugeContext[LevelGaugeConfig]:
|
||||
return create_gauge_context("level", LevelGaugeConfig(**config))
|
||||
|
||||
|
||||
def status_light(**config: Any) -> GaugeContext[StatusLightConfig]:
|
||||
return create_gauge_context("status_light", StatusLightConfig(**config))
|
||||
|
||||
|
||||
def segmented_gauge(**config: Any) -> GaugeContext[SegmentedGaugeConfig]:
|
||||
return create_gauge_context("segmented", SegmentedGaugeConfig(**config))
|
||||
|
||||
|
||||
def compass_gauge(**config: Any) -> GaugeContext[CompassGaugeConfig]:
|
||||
return create_gauge_context("compass", CompassGaugeConfig(**config))
|
||||
|
||||
|
||||
def thermometer_gauge(**config: Any) -> GaugeContext[ThermometerGaugeConfig]:
|
||||
return create_gauge_context("thermometer", ThermometerGaugeConfig(**config))
|
||||
|
||||
|
||||
def battery_gauge(**config: Any) -> GaugeContext[BatteryGaugeConfig]:
|
||||
return create_gauge_context("battery", BatteryGaugeConfig(**config))
|
||||
|
||||
|
||||
def multi_value_gauge(**config: Any) -> GaugeContext[MultiValueGaugeConfig]:
|
||||
return create_gauge_context("multi_value", MultiValueGaugeConfig(**config))
|
||||
|
||||
|
||||
def _default_width(gauge_type: str) -> int:
|
||||
return 220 if gauge_type in {"analog", "compass"} else 180
|
||||
|
||||
|
||||
def _default_height(gauge_type: str) -> int:
|
||||
return 220 if gauge_type in {"analog", "compass"} else 80
|
||||
|
||||
|
||||
def _add_tooltip(parent: Tag, tooltip: str | None) -> None:
|
||||
if not tooltip:
|
||||
return
|
||||
tooltip_tag = dpg.add_tooltip(parent)
|
||||
dpg.add_text(tooltip, parent=tooltip_tag)
|
||||
|
||||
Reference in New Issue
Block a user