Compare commits
5 Commits
dev
...
a8a6e1db19
| Author | SHA1 | Date | |
|---|---|---|---|
| a8a6e1db19 | |||
| 58ffcb8fb9 | |||
| accd1d1dee | |||
| fe6670af9e | |||
| 63e79ebf8d |
@@ -1,62 +0,0 @@
|
|||||||
# pyright: reportGeneralTypeIssues=false
|
|
||||||
|
|
||||||
import dearpygui.dearpygui as dpg
|
|
||||||
|
|
||||||
import dpg_gauges as dpgg
|
|
||||||
|
|
||||||
GRID = "connector_grid"
|
|
||||||
ACTIVE_TAGS: list[str] = []
|
|
||||||
CYCLE = 0
|
|
||||||
|
|
||||||
|
|
||||||
def connect_connector() -> None:
|
|
||||||
global CYCLE
|
|
||||||
if ACTIVE_TAGS:
|
|
||||||
return
|
|
||||||
for index in range(5):
|
|
||||||
tag = f"connector_{CYCLE}_{index}"
|
|
||||||
ACTIVE_TAGS.append(tag)
|
|
||||||
dpgg.digital_gauge(
|
|
||||||
tag=tag,
|
|
||||||
parent=GRID,
|
|
||||||
label=f"Conn {CYCLE}.{index}",
|
|
||||||
value=(CYCLE * 13 + index * 17) % 100,
|
|
||||||
unit="%",
|
|
||||||
height=72,
|
|
||||||
)
|
|
||||||
CYCLE += 1
|
|
||||||
|
|
||||||
|
|
||||||
def disconnect_connector() -> None:
|
|
||||||
while ACTIVE_TAGS:
|
|
||||||
dpgg.delete_gauge(ACTIVE_TAGS.pop())
|
|
||||||
|
|
||||||
|
|
||||||
def reconnect_connector() -> None:
|
|
||||||
disconnect_connector()
|
|
||||||
connect_connector()
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
dpg.create_context()
|
|
||||||
try:
|
|
||||||
with dpg.window(label="Reconnect grid test", width=860, height=420):
|
|
||||||
dpg.add_text("Reconnect should always start at the first grid cell.")
|
|
||||||
with dpgg.gauge_panel(label="Connector gauges", width=820, height=230):
|
|
||||||
dpgg.gauge_grid(tag=GRID, columns="auto", min_column_width=180)
|
|
||||||
with dpg.group(horizontal=True):
|
|
||||||
dpg.add_button(label="Connect", callback=connect_connector)
|
|
||||||
dpg.add_button(label="Disconnect", callback=disconnect_connector)
|
|
||||||
dpg.add_button(label="Reconnect", callback=reconnect_connector)
|
|
||||||
|
|
||||||
dpg.set_frame_callback(2, connect_connector)
|
|
||||||
dpg.create_viewport(title="dpg-gauges reconnect grid", width=900, height=460)
|
|
||||||
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.1.2"
|
version = "1.1.1"
|
||||||
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.1.2"
|
__version__ = "1.1.1"
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"configure",
|
"configure",
|
||||||
|
|||||||
@@ -62,12 +62,9 @@ class GaugeRenderer:
|
|||||||
if state.animation_state.active or state.smoothing_state.active:
|
if state.animation_state.active or state.smoothing_state.active:
|
||||||
self.schedule_frame()
|
self.schedule_frame()
|
||||||
|
|
||||||
def delete(self) -> Tag | None:
|
def delete(self) -> None:
|
||||||
if not dpg.does_item_exist(self.state.container_tag):
|
if dpg.does_item_exist(self.state.container_tag):
|
||||||
return None
|
dpg.delete_item(self.state.container_tag)
|
||||||
parent = dpg.get_item_parent(self.state.container_tag)
|
|
||||||
dpg.delete_item(self.state.container_tag)
|
|
||||||
return cast(Tag | None, parent)
|
|
||||||
|
|
||||||
def schedule_frame(self) -> None:
|
def schedule_frame(self) -> None:
|
||||||
if self._scheduled:
|
if self._scheduled:
|
||||||
|
|||||||
@@ -301,14 +301,9 @@ def set_runtime_show(tag: Tag, show: bool) -> None:
|
|||||||
|
|
||||||
def delete_runtime_gauge(tag: Tag) -> None:
|
def delete_runtime_gauge(tag: Tag) -> None:
|
||||||
state = get_gauge_state(tag)
|
state = get_gauge_state(tag)
|
||||||
layout_parent: Tag | None = None
|
|
||||||
if state.renderer is not None:
|
if state.renderer is not None:
|
||||||
layout_parent = state.renderer.delete()
|
state.renderer.delete()
|
||||||
del _registry[tag]
|
del _registry[tag]
|
||||||
if layout_parent is not None:
|
|
||||||
from .widget import _cleanup_grid_after_item_delete
|
|
||||||
|
|
||||||
_cleanup_grid_after_item_delete(layout_parent)
|
|
||||||
|
|
||||||
|
|
||||||
def render_runtime_gauge(state: GaugeState) -> None:
|
def render_runtime_gauge(state: GaugeState) -> None:
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ _original_create_context = dpg.create_context
|
|||||||
_original_destroy_context = dpg.destroy_context
|
_original_destroy_context = dpg.destroy_context
|
||||||
_active_grids: list[GridLayoutContext] = []
|
_active_grids: list[GridLayoutContext] = []
|
||||||
_grid_layouts: dict[Tag, GridLayoutContext] = {}
|
_grid_layouts: dict[Tag, GridLayoutContext] = {}
|
||||||
_pending_grid_cleanups: set[Tag] = set()
|
|
||||||
_layout_suspend_depth = 0
|
_layout_suspend_depth = 0
|
||||||
_grid_refresh_scheduled = False
|
_grid_refresh_scheduled = False
|
||||||
|
|
||||||
@@ -67,7 +66,6 @@ def _install_context_tracking() -> None:
|
|||||||
_dpg_context_active = False
|
_dpg_context_active = False
|
||||||
_active_grids.clear()
|
_active_grids.clear()
|
||||||
_grid_layouts.clear()
|
_grid_layouts.clear()
|
||||||
_pending_grid_cleanups.clear()
|
|
||||||
_grid_refresh_scheduled = False
|
_grid_refresh_scheduled = False
|
||||||
|
|
||||||
create_context.__dpg_gauges_wrapped__ = True # type: ignore[attr-defined]
|
create_context.__dpg_gauges_wrapped__ = True # type: ignore[attr-defined]
|
||||||
@@ -173,13 +171,6 @@ class GridLayoutContext:
|
|||||||
if self.tag is None or not _dpg_context_active or not dpg.does_item_exist(self.tag):
|
if self.tag is None or not _dpg_context_active or not dpg.does_item_exist(self.tag):
|
||||||
return None, None
|
return None, None
|
||||||
self.refresh_columns()
|
self.refresh_columns()
|
||||||
empty_cell = _first_empty_table_cell(self.tag)
|
|
||||||
if empty_cell is not None:
|
|
||||||
self._next_index = _table_cell_count(self.tag)
|
|
||||||
self._current_row, self._current_row_cells = _last_row_state(
|
|
||||||
self.tag, self.columns
|
|
||||||
)
|
|
||||||
return empty_cell, self.cell_width()
|
|
||||||
if self._current_row is None or self._current_row_cells >= self.columns:
|
if self._current_row is None or self._current_row_cells >= self.columns:
|
||||||
self._current_row = dpg.add_table_row(parent=self.tag, height=self.row_height)
|
self._current_row = dpg.add_table_row(parent=self.tag, height=self.row_height)
|
||||||
self._current_row_cells = 0
|
self._current_row_cells = 0
|
||||||
@@ -346,9 +337,6 @@ def _reflow_table_cells(table: Tag, columns: int, row_height: int = 0) -> None:
|
|||||||
cell_contents = _table_cell_contents(table)
|
cell_contents = _table_cell_contents(table)
|
||||||
old_rows = _table_rows(table)
|
old_rows = _table_rows(table)
|
||||||
if not cell_contents:
|
if not cell_contents:
|
||||||
for row in old_rows:
|
|
||||||
if dpg.does_item_exist(row):
|
|
||||||
dpg.delete_item(row)
|
|
||||||
_set_table_column_count(table, columns)
|
_set_table_column_count(table, columns)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -388,22 +376,10 @@ def _table_cell_contents(table: Tag) -> list[list[Tag]]:
|
|||||||
row_children = cast(dict[int, list[Tag]], dpg.get_item_children(row) or {})
|
row_children = cast(dict[int, list[Tag]], dpg.get_item_children(row) or {})
|
||||||
for cell in row_children.get(1, []):
|
for cell in row_children.get(1, []):
|
||||||
cell_children = cast(dict[int, list[Tag]], dpg.get_item_children(cell) or {})
|
cell_children = cast(dict[int, list[Tag]], dpg.get_item_children(cell) or {})
|
||||||
contents = [child for child in cell_children.get(1, []) if dpg.does_item_exist(child)]
|
cells.append(list(cell_children.get(1, [])))
|
||||||
if contents:
|
|
||||||
cells.append(contents)
|
|
||||||
return cells
|
return cells
|
||||||
|
|
||||||
|
|
||||||
def _first_empty_table_cell(table: Tag) -> Tag | None:
|
|
||||||
for row in _table_rows(table):
|
|
||||||
row_children = cast(dict[int, list[Tag]], dpg.get_item_children(row) or {})
|
|
||||||
for cell in row_children.get(1, []):
|
|
||||||
cell_children = cast(dict[int, list[Tag]], dpg.get_item_children(cell) or {})
|
|
||||||
if not any(dpg.does_item_exist(child) for child in cell_children.get(1, [])):
|
|
||||||
return cell
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _table_cell_count(table: Tag) -> int:
|
def _table_cell_count(table: Tag) -> int:
|
||||||
count = 0
|
count = 0
|
||||||
for row in _table_rows(table):
|
for row in _table_rows(table):
|
||||||
@@ -421,35 +397,6 @@ def _last_row_state(table: Tag, columns: int) -> tuple[Tag | None, int]:
|
|||||||
return row, min(len(row_children.get(1, [])), columns)
|
return row, min(len(row_children.get(1, [])), columns)
|
||||||
|
|
||||||
|
|
||||||
def _cleanup_grid_after_item_delete(parent: Tag) -> None:
|
|
||||||
if not _dpg_context_active or not dpg.does_item_exist(parent):
|
|
||||||
return
|
|
||||||
row = dpg.get_item_parent(parent)
|
|
||||||
if row is None or not dpg.does_item_exist(row):
|
|
||||||
return
|
|
||||||
table = dpg.get_item_parent(row)
|
|
||||||
if table is None or table not in _grid_layouts or not dpg.does_item_exist(table):
|
|
||||||
return
|
|
||||||
_pending_grid_cleanups.add(table)
|
|
||||||
_schedule_grid_refresh()
|
|
||||||
|
|
||||||
|
|
||||||
def _compact_grid(grid: GridLayoutContext) -> None:
|
|
||||||
table = grid.tag
|
|
||||||
if table is None or not dpg.does_item_exist(table):
|
|
||||||
return
|
|
||||||
_reflow_table_cells(table, grid.columns, grid.row_height)
|
|
||||||
grid._next_index = _table_cell_count(table)
|
|
||||||
grid._current_row, grid._current_row_cells = _last_row_state(table, grid.columns)
|
|
||||||
|
|
||||||
|
|
||||||
def _cleanup_grid_now(table: Tag) -> None:
|
|
||||||
if table not in _grid_layouts or not dpg.does_item_exist(table):
|
|
||||||
return
|
|
||||||
grid = _grid_layouts[table]
|
|
||||||
_compact_grid(grid)
|
|
||||||
|
|
||||||
|
|
||||||
def _schedule_grid_refresh() -> None:
|
def _schedule_grid_refresh() -> None:
|
||||||
global _grid_refresh_scheduled
|
global _grid_refresh_scheduled
|
||||||
if _grid_refresh_scheduled or not _dpg_context_active:
|
if _grid_refresh_scheduled or not _dpg_context_active:
|
||||||
@@ -469,11 +416,7 @@ def _refresh_grid_layouts() -> None:
|
|||||||
for tag, grid in list(_grid_layouts.items()):
|
for tag, grid in list(_grid_layouts.items()):
|
||||||
if tag is None or not dpg.does_item_exist(tag):
|
if tag is None or not dpg.does_item_exist(tag):
|
||||||
del _grid_layouts[tag]
|
del _grid_layouts[tag]
|
||||||
_pending_grid_cleanups.discard(tag)
|
|
||||||
continue
|
continue
|
||||||
if tag in _pending_grid_cleanups:
|
|
||||||
_pending_grid_cleanups.discard(tag)
|
|
||||||
_compact_grid(grid)
|
|
||||||
grid.refresh_columns()
|
grid.refresh_columns()
|
||||||
if _grid_layouts:
|
if _grid_layouts:
|
||||||
_schedule_grid_refresh()
|
_schedule_grid_refresh()
|
||||||
|
|||||||
@@ -14,17 +14,6 @@ def _children(item: int | str) -> dict[int, list[int | str]]:
|
|||||||
return cast(dict[int, list[int | str]], dpg.get_item_children(item))
|
return cast(dict[int, list[int | str]], dpg.get_item_children(item))
|
||||||
|
|
||||||
|
|
||||||
def _occupied_cell_counts(table: int | str) -> list[int]:
|
|
||||||
counts: list[int] = []
|
|
||||||
for row in _children(table)[1]:
|
|
||||||
occupied = 0
|
|
||||||
for cell in _children(row)[1]:
|
|
||||||
if any(dpg.does_item_exist(child) for child in _children(cell)[1]):
|
|
||||||
occupied += 1
|
|
||||||
counts.append(occupied)
|
|
||||||
return counts
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def dpg_context() -> Generator[None]:
|
def dpg_context() -> Generator[None]:
|
||||||
clear_registry()
|
clear_registry()
|
||||||
@@ -223,44 +212,3 @@ def test_auto_grid_reflows_existing_gauges_when_panel_narrows(dpg_context: None)
|
|||||||
for index in range(6):
|
for index in range(6):
|
||||||
cell = cast(int | str, dpg.get_item_parent(f"resize_metric_{index}"))
|
cell = cast(int | str, dpg.get_item_parent(f"resize_metric_{index}"))
|
||||||
assert dpg.get_item_type(cell) == "mvAppItemType::mvTableCell"
|
assert dpg.get_item_type(cell) == "mvAppItemType::mvTableCell"
|
||||||
|
|
||||||
|
|
||||||
def test_grid_compacts_after_runtime_gauge_delete(dpg_context: None) -> None:
|
|
||||||
from dpg_gauges.widget import _cleanup_grid_now
|
|
||||||
|
|
||||||
with dpg.window(label="Reconnect", width=800):
|
|
||||||
dpgg.gauge_grid(tag="connector_grid", columns=3, min_column_width=1)
|
|
||||||
|
|
||||||
for cycle in range(3):
|
|
||||||
tags = [f"connector_{cycle}_{index}" for index in range(3)]
|
|
||||||
for index, tag in enumerate(tags):
|
|
||||||
dpgg.digital_gauge(tag=tag, parent="connector_grid", value=index, height=60)
|
|
||||||
|
|
||||||
children = _children("connector_grid")
|
|
||||||
assert [len(_children(row)[1]) for row in children[1]] == [3]
|
|
||||||
assert _occupied_cell_counts("connector_grid") == [3]
|
|
||||||
|
|
||||||
for tag in tags:
|
|
||||||
dpgg.delete_gauge(tag)
|
|
||||||
|
|
||||||
_cleanup_grid_now("connector_grid")
|
|
||||||
children = _children("connector_grid")
|
|
||||||
assert children[1] == []
|
|
||||||
|
|
||||||
|
|
||||||
def test_grid_reuses_deleted_cell_before_appending(dpg_context: None) -> None:
|
|
||||||
with dpg.window(label="Partial", width=800):
|
|
||||||
dpgg.gauge_grid(tag="partial_grid", columns=3, min_column_width=1)
|
|
||||||
|
|
||||||
for index in range(3):
|
|
||||||
dpgg.digital_gauge(tag=f"partial_{index}", parent="partial_grid", value=index, height=60)
|
|
||||||
|
|
||||||
dpgg.delete_gauge("partial_1")
|
|
||||||
|
|
||||||
assert _occupied_cell_counts("partial_grid") == [2]
|
|
||||||
|
|
||||||
dpgg.digital_gauge(tag="partial_new", parent="partial_grid", value=99, height=60)
|
|
||||||
|
|
||||||
children = _children("partial_grid")
|
|
||||||
assert [len(_children(row)[1]) for row in children[1]] == [3]
|
|
||||||
assert _occupied_cell_counts("partial_grid") == [3]
|
|
||||||
|
|||||||
@@ -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.1.2"
|
assert dpgg.__version__ == "1.1.1"
|
||||||
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