Compare commits
7 Commits
v1.0.2
...
58ffcb8fb9
| Author | SHA1 | Date | |
|---|---|---|---|
| 58ffcb8fb9 | |||
| 0a7b4f0172 | |||
| 5bd819d812 | |||
| 44403bea2b | |||
| accd1d1dee | |||
| fe6670af9e | |||
| 63e79ebf8d |
@@ -37,12 +37,18 @@ dpg.destroy_context()
|
|||||||
```python
|
```python
|
||||||
with dpg.window(label="Engine"):
|
with dpg.window(label="Engine"):
|
||||||
with dpgg.gauge_panel(label="Powertrain", width=-1, height=260):
|
with dpgg.gauge_panel(label="Powertrain", width=-1, height=260):
|
||||||
with dpgg.gauge_grid(columns=3):
|
with dpgg.gauge_grid(columns=3, min_column_width=180):
|
||||||
dpgg.analog_gauge(tag="rpm", label="RPM", max_value=8000, redline_start=6500)
|
dpgg.analog_gauge(tag="rpm", label="RPM", max_value=8000, redline_start=6500)
|
||||||
dpgg.bar_gauge(tag="boost", label="Boost", max_value=30, unit="psi")
|
dpgg.bar_gauge(tag="boost", label="Boost", max_value=30, unit="psi")
|
||||||
dpgg.status_light(tag="ecu", label="ECU", state=True)
|
dpgg.status_light(tag="ecu", label="ECU", state=True)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`gauge_grid` is a wrapping, table-backed layout. An integer `columns` value is a maximum; if the
|
||||||
|
parent is too narrow for `columns * min_column_width`, the grid uses fewer columns and continues on
|
||||||
|
the next row instead of creating a horizontal scrollbar. Use `columns="auto"` for autobreak mode,
|
||||||
|
where the grid derives the column count from the available width. Gauges with `width=0` or
|
||||||
|
`width=-1` fill their grid cell.
|
||||||
|
|
||||||
Gauges also accept Dear PyGui's usual `parent` argument for adding a gauge to an
|
Gauges also accept Dear PyGui's usual `parent` argument for adding a gauge to an
|
||||||
existing container after that container has already been created:
|
existing container after that container has already been created:
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,14 @@ Creation functions are context managers and accept common options such as `tag`,
|
|||||||
## Layout Helpers
|
## Layout Helpers
|
||||||
|
|
||||||
- `gauge_panel(**config)`: child-window panel for grouped gauges.
|
- `gauge_panel(**config)`: child-window panel for grouped gauges.
|
||||||
- `gauge_grid(**config)`: lightweight horizontal gauge group.
|
- `gauge_grid(columns=1, min_column_width=180, fit_gauges=True, cell_padding=12, **config)`:
|
||||||
|
table-backed
|
||||||
|
wrapping gauge grid. Pass an integer `columns` to set the maximum column count; if the parent is
|
||||||
|
narrower than `columns * min_column_width`, the grid uses fewer columns and wraps gauges onto
|
||||||
|
later rows to avoid horizontal scrolling. Pass `columns="auto"` for autobreak mode, where the
|
||||||
|
column count is derived entirely from the available width and `min_column_width`. Gauges without
|
||||||
|
an explicit positive width fill their grid cell. Increase `cell_padding` if a custom Dear PyGui
|
||||||
|
theme uses wider table padding or borders.
|
||||||
|
|
||||||
## Runtime API
|
## Runtime API
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Run examples with `uv run python examples/<name>.py`.
|
|||||||
- `thresholds_markers.py`
|
- `thresholds_markers.py`
|
||||||
- `animations.py`
|
- `animations.py`
|
||||||
- `dashboard.py`
|
- `dashboard.py`
|
||||||
|
- `layout_grid.py`
|
||||||
- `sizing.py`
|
- `sizing.py`
|
||||||
- `live_high_rate.py`
|
- `live_high_rate.py`
|
||||||
- `live_worst_case.py`
|
- `live_worst_case.py`
|
||||||
|
|||||||
65
examples/layout_grid.py
Normal file
65
examples/layout_grid.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# pyright: reportGeneralTypeIssues=false
|
||||||
|
|
||||||
|
import dearpygui.dearpygui as dpg
|
||||||
|
|
||||||
|
import dpg_gauges as dpgg
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
dpg.create_context()
|
||||||
|
try:
|
||||||
|
with dpg.window(label="Grid layout", width=820, height=620):
|
||||||
|
dpg.add_text("Max columns: columns=3 wraps onto new rows after every third gauge")
|
||||||
|
with dpgg.gauge_panel(label="Wide panel", width=-1, height=270), dpgg.gauge_grid(
|
||||||
|
columns=3, min_column_width=190
|
||||||
|
):
|
||||||
|
dpgg.analog_gauge(
|
||||||
|
tag="grid_rpm", label="RPM", value=4200, max_value=8000, height=190
|
||||||
|
)
|
||||||
|
dpgg.digital_gauge(
|
||||||
|
tag="grid_speed", label="Speed", value=88, unit="km/h", height=90
|
||||||
|
)
|
||||||
|
dpgg.bar_gauge(
|
||||||
|
tag="grid_boost", label="Boost", value=12.5, max_value=30, unit="psi"
|
||||||
|
)
|
||||||
|
dpgg.status_light(tag="grid_ecu", label="ECU", state=True, height=70)
|
||||||
|
dpgg.battery_gauge(tag="grid_battery", label="Battery", value=74, height=160)
|
||||||
|
|
||||||
|
dpg.add_spacer(height=8)
|
||||||
|
dpg.add_text("Autobreak: columns='auto' chooses columns from available width")
|
||||||
|
with (
|
||||||
|
dpgg.gauge_panel(label="Autobreak narrow panel", width=430, height=285),
|
||||||
|
dpgg.gauge_grid(columns="auto", min_column_width=180),
|
||||||
|
):
|
||||||
|
for index, value in enumerate((18, 42, 67, 91, 55), start=1):
|
||||||
|
dpgg.digital_gauge(
|
||||||
|
tag=f"narrow_{index}",
|
||||||
|
label=f"Metric {index}",
|
||||||
|
value=value,
|
||||||
|
unit="%",
|
||||||
|
height=72,
|
||||||
|
)
|
||||||
|
|
||||||
|
dpg.add_spacer(height=8)
|
||||||
|
dpg.add_text("Panels can also occupy grid cells")
|
||||||
|
with dpgg.gauge_grid(columns=2, min_column_width=240):
|
||||||
|
with dpgg.gauge_panel(label="Left cell", height=120):
|
||||||
|
dpgg.segmented_gauge(tag="left_segments", label="Load", value=70, height=80)
|
||||||
|
with dpgg.gauge_panel(label="Right cell", height=120):
|
||||||
|
dpgg.multi_value_gauge(
|
||||||
|
tag="right_values",
|
||||||
|
label="Sensors",
|
||||||
|
values={"oil": 82, "fuel": 64, "air": 31},
|
||||||
|
height=92,
|
||||||
|
)
|
||||||
|
|
||||||
|
dpg.create_viewport(title="dpg-gauges grid layout", width=860, height=660)
|
||||||
|
dpg.setup_dearpygui()
|
||||||
|
dpg.show_viewport()
|
||||||
|
dpg.start_dearpygui()
|
||||||
|
finally:
|
||||||
|
dpg.destroy_context()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "dpg-gauges"
|
name = "dpg-gauges"
|
||||||
version = "1.0.2"
|
version = "1.1.0"
|
||||||
description = "Dear PyGui gauge widgets for dashboards and telemetry displays"
|
description = "Dear PyGui gauge widgets for dashboards and telemetry displays"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from .types import (
|
|||||||
ThresholdBand,
|
ThresholdBand,
|
||||||
)
|
)
|
||||||
|
|
||||||
__version__ = "1.0.2"
|
__version__ = "1.1.0"
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"configure",
|
"configure",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Mapping, Sequence
|
from collections.abc import Mapping, Sequence
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, replace
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import Any, Generic, Literal, TypeVar, cast
|
from typing import Any, Generic, Literal, TypeVar, cast
|
||||||
|
|
||||||
@@ -42,6 +42,8 @@ ConfigT = TypeVar("ConfigT", bound=GaugeConfig)
|
|||||||
_dpg_context_active = False
|
_dpg_context_active = False
|
||||||
_original_create_context = dpg.create_context
|
_original_create_context = dpg.create_context
|
||||||
_original_destroy_context = dpg.destroy_context
|
_original_destroy_context = dpg.destroy_context
|
||||||
|
_active_grids: list[GridLayoutContext] = []
|
||||||
|
_layout_suspend_depth = 0
|
||||||
|
|
||||||
|
|
||||||
def _install_context_tracking() -> None:
|
def _install_context_tracking() -> None:
|
||||||
@@ -73,10 +75,14 @@ _install_context_tracking()
|
|||||||
@dataclass
|
@dataclass
|
||||||
class GaugeContext(Generic[ConfigT]):
|
class GaugeContext(Generic[ConfigT]):
|
||||||
state: GaugeState
|
state: GaugeState
|
||||||
|
_suspended_layout: bool = False
|
||||||
|
|
||||||
def __enter__(self) -> ConfigT:
|
def __enter__(self) -> ConfigT:
|
||||||
|
global _layout_suspend_depth
|
||||||
if _dpg_context_active and dpg.does_item_exist(self.state.container_tag):
|
if _dpg_context_active and dpg.does_item_exist(self.state.container_tag):
|
||||||
dpg.push_container_stack(self.state.container_tag)
|
dpg.push_container_stack(self.state.container_tag)
|
||||||
|
_layout_suspend_depth += 1
|
||||||
|
self._suspended_layout = True
|
||||||
return cast(ConfigT, self.state.config)
|
return cast(ConfigT, self.state.config)
|
||||||
|
|
||||||
def __exit__(
|
def __exit__(
|
||||||
@@ -85,17 +91,27 @@ class GaugeContext(Generic[ConfigT]):
|
|||||||
exc: BaseException | None,
|
exc: BaseException | None,
|
||||||
traceback: TracebackType | None,
|
traceback: TracebackType | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
global _layout_suspend_depth
|
||||||
if _dpg_context_active and dpg.does_item_exist(self.state.container_tag):
|
if _dpg_context_active and dpg.does_item_exist(self.state.container_tag):
|
||||||
dpg.pop_container_stack()
|
dpg.pop_container_stack()
|
||||||
|
if self._suspended_layout:
|
||||||
|
_layout_suspend_depth = max(_layout_suspend_depth - 1, 0)
|
||||||
|
self._suspended_layout = False
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LayoutContext:
|
class LayoutContext:
|
||||||
tag: Tag | None
|
tag: Tag | None
|
||||||
|
suspend_layout: bool = True
|
||||||
|
_suspended_layout: bool = False
|
||||||
|
|
||||||
def __enter__(self) -> Tag | None:
|
def __enter__(self) -> Tag | None:
|
||||||
|
global _layout_suspend_depth
|
||||||
if self.tag is not None and _dpg_context_active and dpg.does_item_exist(self.tag):
|
if self.tag is not None and _dpg_context_active and dpg.does_item_exist(self.tag):
|
||||||
dpg.push_container_stack(self.tag)
|
dpg.push_container_stack(self.tag)
|
||||||
|
if self.suspend_layout:
|
||||||
|
_layout_suspend_depth += 1
|
||||||
|
self._suspended_layout = True
|
||||||
return self.tag
|
return self.tag
|
||||||
|
|
||||||
def __exit__(
|
def __exit__(
|
||||||
@@ -104,14 +120,70 @@ class LayoutContext:
|
|||||||
exc: BaseException | None,
|
exc: BaseException | None,
|
||||||
traceback: TracebackType | None,
|
traceback: TracebackType | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
global _layout_suspend_depth
|
||||||
if self.tag is not None and _dpg_context_active and dpg.does_item_exist(self.tag):
|
if self.tag is not None and _dpg_context_active and dpg.does_item_exist(self.tag):
|
||||||
dpg.pop_container_stack()
|
dpg.pop_container_stack()
|
||||||
|
if self._suspended_layout:
|
||||||
|
_layout_suspend_depth = max(_layout_suspend_depth - 1, 0)
|
||||||
|
self._suspended_layout = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GridLayoutContext:
|
||||||
|
tag: Tag | None
|
||||||
|
columns: int
|
||||||
|
min_column_width: int
|
||||||
|
fit_gauges: bool
|
||||||
|
cell_padding: int = 12
|
||||||
|
row_height: int = 0
|
||||||
|
available_width: int = 0
|
||||||
|
_next_index: int = 0
|
||||||
|
_current_row: Tag | None = None
|
||||||
|
_suspend_depth: int = 0
|
||||||
|
|
||||||
|
def __enter__(self) -> Tag | None:
|
||||||
|
self._suspend_depth = _layout_suspend_depth
|
||||||
|
if self.tag is not None and _dpg_context_active and dpg.does_item_exist(self.tag):
|
||||||
|
_active_grids.append(self)
|
||||||
|
return self.tag
|
||||||
|
|
||||||
|
def __exit__(
|
||||||
|
self,
|
||||||
|
exc_type: type[BaseException] | None,
|
||||||
|
exc: BaseException | None,
|
||||||
|
traceback: TracebackType | None,
|
||||||
|
) -> None:
|
||||||
|
if _active_grids and _active_grids[-1] is self:
|
||||||
|
_active_grids.pop()
|
||||||
|
elif self in _active_grids:
|
||||||
|
_active_grids.remove(self)
|
||||||
|
|
||||||
|
def next_cell(self) -> tuple[Tag | None, int | None]:
|
||||||
|
if self.tag is None or not _dpg_context_active or not dpg.does_item_exist(self.tag):
|
||||||
|
return None, None
|
||||||
|
if self._next_index % self.columns == 0:
|
||||||
|
self._current_row = dpg.add_table_row(parent=self.tag, height=self.row_height)
|
||||||
|
cell = dpg.add_table_cell(parent=self._current_row or self.tag)
|
||||||
|
self._next_index += 1
|
||||||
|
return cell, self.cell_width()
|
||||||
|
|
||||||
|
def cell_width(self) -> int | None:
|
||||||
|
if self.available_width <= 0:
|
||||||
|
return None
|
||||||
|
return max(int(self.available_width / self.columns) - self.cell_padding, 1)
|
||||||
|
|
||||||
|
|
||||||
def create_gauge_context(gauge_type: str, config: ConfigT) -> GaugeContext[ConfigT]:
|
def create_gauge_context(gauge_type: str, config: ConfigT) -> GaugeContext[ConfigT]:
|
||||||
if not _dpg_context_active:
|
if not _dpg_context_active:
|
||||||
return GaugeContext(register_gauge(gauge_type, config))
|
return GaugeContext(register_gauge(gauge_type, config))
|
||||||
|
|
||||||
|
layout_parent, layout_width = _next_layout_cell(config.parent)
|
||||||
|
if layout_parent is not None:
|
||||||
|
updates: dict[str, Any] = {"parent": layout_parent}
|
||||||
|
if layout_width is not None and _current_grid_fits_gauges() and config.width <= 0:
|
||||||
|
updates["width"] = layout_width
|
||||||
|
config = replace(config, **updates)
|
||||||
|
|
||||||
tag = config.tag
|
tag = config.tag
|
||||||
container_tag = tag if tag is not None else None
|
container_tag = tag if tag is not None else None
|
||||||
drawlist_tag = f"{tag}__drawlist" if tag is not None else None
|
drawlist_tag = f"{tag}__drawlist" if tag is not None else None
|
||||||
@@ -151,6 +223,55 @@ def create_gauge_context(gauge_type: str, config: ConfigT) -> GaugeContext[Confi
|
|||||||
return GaugeContext(state)
|
return GaugeContext(state)
|
||||||
|
|
||||||
|
|
||||||
|
def _next_layout_cell(parent: Tag | None) -> tuple[Tag | None, int | None]:
|
||||||
|
grid = _current_grid()
|
||||||
|
if parent is not None or grid is None:
|
||||||
|
return None, None
|
||||||
|
return grid.next_cell()
|
||||||
|
|
||||||
|
|
||||||
|
def _current_grid() -> GridLayoutContext | None:
|
||||||
|
if not _active_grids:
|
||||||
|
return None
|
||||||
|
grid = _active_grids[-1]
|
||||||
|
if grid._suspend_depth != _layout_suspend_depth:
|
||||||
|
return None
|
||||||
|
return grid
|
||||||
|
|
||||||
|
|
||||||
|
def _current_grid_fits_gauges() -> bool:
|
||||||
|
grid = _current_grid()
|
||||||
|
return grid.fit_gauges if grid is not None else False
|
||||||
|
|
||||||
|
|
||||||
|
def _available_layout_width(parent: Tag | None, width: int) -> int:
|
||||||
|
if width > 0:
|
||||||
|
return width
|
||||||
|
if parent is not None and dpg.does_item_exist(parent):
|
||||||
|
return max(dpg.get_item_width(parent) or 0, 0)
|
||||||
|
try:
|
||||||
|
container = dpg.top_container_stack()
|
||||||
|
except Exception:
|
||||||
|
return 0
|
||||||
|
if container is not None and dpg.does_item_exist(container):
|
||||||
|
return max(dpg.get_item_width(container) or 0, 0)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _resolved_columns(
|
||||||
|
columns: int | Literal["auto"], min_column_width: int, available_width: int
|
||||||
|
) -> int:
|
||||||
|
min_column_width = max(int(min_column_width), 0)
|
||||||
|
if columns == "auto":
|
||||||
|
if available_width > 0 and min_column_width > 0:
|
||||||
|
return max(1, available_width // min_column_width)
|
||||||
|
return 1
|
||||||
|
columns = max(int(columns), 1)
|
||||||
|
if available_width > 0 and min_column_width > 0:
|
||||||
|
columns = min(columns, max(1, available_width // min_column_width))
|
||||||
|
return columns
|
||||||
|
|
||||||
|
|
||||||
def gauge_panel(
|
def gauge_panel(
|
||||||
*,
|
*,
|
||||||
tag: Tag | None = None,
|
tag: Tag | None = None,
|
||||||
@@ -168,6 +289,11 @@ def gauge_panel(
|
|||||||
"""Create a lightweight child-window container for related gauges."""
|
"""Create a lightweight child-window container for related gauges."""
|
||||||
if not _dpg_context_active:
|
if not _dpg_context_active:
|
||||||
return LayoutContext(None)
|
return LayoutContext(None)
|
||||||
|
layout_parent, layout_width = _next_layout_cell(parent)
|
||||||
|
if layout_parent is not None:
|
||||||
|
parent = layout_parent
|
||||||
|
if width <= 0 and layout_width is not None:
|
||||||
|
width = layout_width
|
||||||
container = dpg.add_child_window(
|
container = dpg.add_child_window(
|
||||||
tag=tag or 0,
|
tag=tag or 0,
|
||||||
parent=parent or 0,
|
parent=parent or 0,
|
||||||
@@ -186,28 +312,50 @@ def gauge_panel(
|
|||||||
|
|
||||||
def gauge_grid(
|
def gauge_grid(
|
||||||
*,
|
*,
|
||||||
columns: int = 1,
|
columns: int | Literal["auto"] = 1,
|
||||||
|
min_column_width: int = 180,
|
||||||
|
fit_gauges: bool = True,
|
||||||
|
cell_padding: int = 12,
|
||||||
|
row_height: int = 0,
|
||||||
tag: Tag | None = None,
|
tag: Tag | None = None,
|
||||||
parent: Tag | None = None,
|
parent: Tag | None = None,
|
||||||
width: int = 0,
|
width: int = 0,
|
||||||
height: int = 0,
|
height: int = 0,
|
||||||
show: bool = True,
|
show: bool = True,
|
||||||
**config: Any,
|
**config: Any,
|
||||||
) -> LayoutContext:
|
) -> GridLayoutContext:
|
||||||
"""Create a lightweight horizontal gauge layout container."""
|
"""Create a lightweight wrapping gauge layout container."""
|
||||||
if not _dpg_context_active:
|
if not _dpg_context_active:
|
||||||
return LayoutContext(None)
|
return GridLayoutContext(None, 1, min_column_width, fit_gauges, cell_padding)
|
||||||
columns = max(int(columns), 1)
|
layout_parent, layout_width = _next_layout_cell(parent)
|
||||||
container = dpg.add_group(
|
if layout_parent is not None:
|
||||||
|
parent = layout_parent
|
||||||
|
if width <= 0 and layout_width is not None:
|
||||||
|
width = layout_width
|
||||||
|
available_width = _available_layout_width(parent, width)
|
||||||
|
columns = _resolved_columns(columns, min_column_width, available_width)
|
||||||
|
container = dpg.add_table(
|
||||||
tag=tag or 0,
|
tag=tag or 0,
|
||||||
parent=parent or 0,
|
parent=parent or 0,
|
||||||
width=width,
|
width=width,
|
||||||
height=height,
|
height=height,
|
||||||
show=show,
|
show=show,
|
||||||
horizontal=columns > 1,
|
header_row=False,
|
||||||
|
policy=dpg.mvTable_SizingStretchSame,
|
||||||
|
scrollX=False,
|
||||||
**config,
|
**config,
|
||||||
)
|
)
|
||||||
return LayoutContext(container)
|
for _ in range(columns):
|
||||||
|
dpg.add_table_column(parent=container, width_stretch=True, init_width_or_weight=1.0)
|
||||||
|
return GridLayoutContext(
|
||||||
|
container,
|
||||||
|
columns,
|
||||||
|
min_column_width,
|
||||||
|
fit_gauges,
|
||||||
|
cell_padding,
|
||||||
|
row_height,
|
||||||
|
available_width,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def analog_gauge(
|
def analog_gauge(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# pyright: reportGeneralTypeIssues=false
|
# pyright: reportGeneralTypeIssues=false
|
||||||
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import dearpygui.dearpygui as dpg
|
import dearpygui.dearpygui as dpg
|
||||||
import pytest
|
import pytest
|
||||||
@@ -9,6 +10,10 @@ import dpg_gauges as dpgg
|
|||||||
from dpg_gauges.state import clear_registry, get_gauge_state
|
from dpg_gauges.state import clear_registry, get_gauge_state
|
||||||
|
|
||||||
|
|
||||||
|
def _children(item: int | str) -> dict[int, list[int | str]]:
|
||||||
|
return cast(dict[int, list[int | str]], dpg.get_item_children(item))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def dpg_context() -> Generator[None]:
|
def dpg_context() -> Generator[None]:
|
||||||
clear_registry()
|
clear_registry()
|
||||||
@@ -76,3 +81,56 @@ def test_panel_and_grid_are_lightweight_containers(dpg_context: None) -> None:
|
|||||||
assert dpg.does_item_exist("engine")
|
assert dpg.does_item_exist("engine")
|
||||||
assert dpg.does_item_exist("engine_grid")
|
assert dpg.does_item_exist("engine_grid")
|
||||||
assert get_gauge_state("temp").renderer is not None
|
assert get_gauge_state("temp").renderer is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_grid_columns_wrap_gauges_into_rows(dpg_context: None) -> None:
|
||||||
|
with dpg.window(label="Dashboard", width=720), dpgg.gauge_grid(
|
||||||
|
tag="wrap_grid", columns=3, min_column_width=1
|
||||||
|
):
|
||||||
|
for index in range(5):
|
||||||
|
dpgg.digital_gauge(tag=f"metric_{index}", value=index, height=60)
|
||||||
|
|
||||||
|
children = _children("wrap_grid")
|
||||||
|
assert len(children[0]) == 3
|
||||||
|
assert len(children[1]) == 2
|
||||||
|
assert [len(_children(row)[1]) for row in children[1]] == [3, 2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_grid_auto_reduces_columns_to_parent_width(dpg_context: None) -> None:
|
||||||
|
with dpg.window(label="Narrow", width=380), dpgg.gauge_grid(
|
||||||
|
tag="narrow_grid", columns=3, min_column_width=180
|
||||||
|
):
|
||||||
|
for index in range(5):
|
||||||
|
dpgg.digital_gauge(tag=f"narrow_metric_{index}", value=index, height=60)
|
||||||
|
|
||||||
|
children = _children("narrow_grid")
|
||||||
|
assert len(children[0]) == 2
|
||||||
|
assert len(children[1]) == 3
|
||||||
|
assert [len(_children(row)[1]) for row in children[1]] == [2, 2, 1]
|
||||||
|
assert 0 < get_gauge_state("narrow_metric_0").config.width < 190
|
||||||
|
|
||||||
|
|
||||||
|
def test_grid_auto_columns_uses_available_width(dpg_context: None) -> None:
|
||||||
|
with dpg.window(label="Auto", width=640), dpgg.gauge_grid(
|
||||||
|
tag="auto_grid", columns="auto", min_column_width=180
|
||||||
|
):
|
||||||
|
for index in range(7):
|
||||||
|
dpgg.digital_gauge(tag=f"auto_metric_{index}", value=index, height=60)
|
||||||
|
|
||||||
|
children = _children("auto_grid")
|
||||||
|
assert len(children[0]) == 3
|
||||||
|
assert len(children[1]) == 3
|
||||||
|
assert [len(_children(row)[1]) for row in children[1]] == [3, 3, 1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_grid_routes_panels_without_stealing_panel_children(dpg_context: None) -> None:
|
||||||
|
with dpg.window(label="Nested", width=520), dpgg.gauge_grid(tag="nested_grid", columns=2):
|
||||||
|
with dpgg.gauge_panel(tag="nested_panel", height=120):
|
||||||
|
dpgg.digital_gauge(tag="inside_panel", value=1, height=60)
|
||||||
|
dpgg.digital_gauge(tag="beside_panel", value=2, height=60)
|
||||||
|
|
||||||
|
panel_cell = cast(int | str, dpg.get_item_parent("nested_panel"))
|
||||||
|
assert dpg.get_item_type(panel_cell) == "mvAppItemType::mvTableCell"
|
||||||
|
assert dpg.get_item_parent("inside_panel") == "nested_panel"
|
||||||
|
beside_cell = cast(int | str, dpg.get_item_parent("beside_panel"))
|
||||||
|
assert dpg.get_item_type(beside_cell) == "mvAppItemType::mvTableCell"
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ def dpg_context() -> Generator[None]:
|
|||||||
|
|
||||||
|
|
||||||
def test_public_exports_and_version() -> None:
|
def test_public_exports_and_version() -> None:
|
||||||
assert dpgg.__version__ == "1.0.2"
|
assert dpgg.__version__ == "1.1.0"
|
||||||
assert files("dpg_gauges").joinpath("py.typed").is_file()
|
assert files("dpg_gauges").joinpath("py.typed").is_file()
|
||||||
for name in dpgg.__all__:
|
for name in dpgg.__all__:
|
||||||
assert hasattr(dpgg, name)
|
assert hasattr(dpgg, name)
|
||||||
|
|||||||
Reference in New Issue
Block a user