Release 1.1.1
This commit is contained in:
@@ -35,7 +35,7 @@ from .types import (
|
||||
ThresholdBand,
|
||||
)
|
||||
|
||||
__version__ = "1.1.0"
|
||||
__version__ = "1.1.1"
|
||||
|
||||
__all__ = [
|
||||
"configure",
|
||||
|
||||
@@ -43,7 +43,9 @@ _dpg_context_active = False
|
||||
_original_create_context = dpg.create_context
|
||||
_original_destroy_context = dpg.destroy_context
|
||||
_active_grids: list[GridLayoutContext] = []
|
||||
_grid_layouts: dict[Tag, GridLayoutContext] = {}
|
||||
_layout_suspend_depth = 0
|
||||
_grid_refresh_scheduled = False
|
||||
|
||||
|
||||
def _install_context_tracking() -> None:
|
||||
@@ -57,11 +59,14 @@ def _install_context_tracking() -> None:
|
||||
return result
|
||||
|
||||
def destroy_context(*args: Any, **kwargs: Any) -> Any:
|
||||
global _dpg_context_active
|
||||
global _dpg_context_active, _grid_refresh_scheduled
|
||||
try:
|
||||
return _original_destroy_context(*args, **kwargs)
|
||||
finally:
|
||||
_dpg_context_active = False
|
||||
_active_grids.clear()
|
||||
_grid_layouts.clear()
|
||||
_grid_refresh_scheduled = False
|
||||
|
||||
create_context.__dpg_gauges_wrapped__ = True # type: ignore[attr-defined]
|
||||
destroy_context.__dpg_gauges_wrapped__ = True # type: ignore[attr-defined]
|
||||
@@ -131,6 +136,9 @@ class LayoutContext:
|
||||
@dataclass
|
||||
class GridLayoutContext:
|
||||
tag: Tag | None
|
||||
parent: Tag | None
|
||||
width: int
|
||||
requested_columns: int | Literal["auto"]
|
||||
columns: int
|
||||
min_column_width: int
|
||||
fit_gauges: bool
|
||||
@@ -139,6 +147,7 @@ class GridLayoutContext:
|
||||
available_width: int = 0
|
||||
_next_index: int = 0
|
||||
_current_row: Tag | None = None
|
||||
_current_row_cells: int = 0
|
||||
_suspend_depth: int = 0
|
||||
|
||||
def __enter__(self) -> Tag | None:
|
||||
@@ -161,10 +170,13 @@ class GridLayoutContext:
|
||||
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.refresh_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_cells = 0
|
||||
cell = dpg.add_table_cell(parent=self._current_row or self.tag)
|
||||
self._next_index += 1
|
||||
self._current_row_cells += 1
|
||||
return cell, self.cell_width()
|
||||
|
||||
def cell_width(self) -> int | None:
|
||||
@@ -172,15 +184,35 @@ class GridLayoutContext:
|
||||
return None
|
||||
return max(int(self.available_width / self.columns) - self.cell_padding, 1)
|
||||
|
||||
def refresh_columns(self) -> None:
|
||||
if self.tag is None or not _dpg_context_active or not dpg.does_item_exist(self.tag):
|
||||
return
|
||||
available_width = _available_layout_width(self.parent, self.width)
|
||||
if available_width <= 0:
|
||||
return
|
||||
columns = _resolved_columns(self.requested_columns, self.min_column_width, available_width)
|
||||
if columns == self.columns and available_width == self.available_width:
|
||||
return
|
||||
_reflow_table_cells(self.tag, columns, self.row_height)
|
||||
self.columns = columns
|
||||
self.available_width = available_width
|
||||
self._next_index = _table_cell_count(self.tag)
|
||||
self._current_row, self._current_row_cells = _last_row_state(self.tag, self.columns)
|
||||
|
||||
|
||||
def create_gauge_context(gauge_type: str, config: ConfigT) -> GaugeContext[ConfigT]:
|
||||
if not _dpg_context_active:
|
||||
return GaugeContext(register_gauge(gauge_type, config))
|
||||
|
||||
layout_parent, layout_width = _next_layout_cell(config.parent)
|
||||
layout_parent, layout_width, layout_grid = _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:
|
||||
if (
|
||||
layout_width is not None
|
||||
and layout_grid is not None
|
||||
and layout_grid.fit_gauges
|
||||
and config.width <= 0
|
||||
):
|
||||
updates["width"] = layout_width
|
||||
config = replace(config, **updates)
|
||||
|
||||
@@ -223,11 +255,21 @@ def create_gauge_context(gauge_type: str, config: ConfigT) -> GaugeContext[Confi
|
||||
return GaugeContext(state)
|
||||
|
||||
|
||||
def _next_layout_cell(parent: Tag | None) -> tuple[Tag | None, int | None]:
|
||||
def _next_layout_cell(
|
||||
parent: Tag | None,
|
||||
) -> tuple[Tag | None, int | None, GridLayoutContext | None]:
|
||||
if parent is not None:
|
||||
grid = _grid_layouts.get(parent)
|
||||
if grid is not None:
|
||||
cell, width = grid.next_cell()
|
||||
return cell, width, grid
|
||||
return None, None, None
|
||||
|
||||
grid = _current_grid()
|
||||
if parent is not None or grid is None:
|
||||
return None, None
|
||||
return grid.next_cell()
|
||||
if grid is None:
|
||||
return None, None, None
|
||||
cell, width = grid.next_cell()
|
||||
return cell, width, grid
|
||||
|
||||
|
||||
def _current_grid() -> GridLayoutContext | None:
|
||||
@@ -239,25 +281,147 @@ def _current_grid() -> GridLayoutContext | 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)
|
||||
return _available_layout_width_for_item(parent)
|
||||
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 _available_layout_width_for_item(container)
|
||||
return 0
|
||||
|
||||
|
||||
def _available_layout_width_for_item(item: Tag) -> int:
|
||||
while item is not None and dpg.does_item_exist(item):
|
||||
width = _item_layout_width(item)
|
||||
if width > 0:
|
||||
return width
|
||||
parent = dpg.get_item_parent(item)
|
||||
if parent is None or parent == item:
|
||||
break
|
||||
item = parent
|
||||
return 0
|
||||
|
||||
|
||||
def _item_layout_width(item: Tag) -> int:
|
||||
width = max(dpg.get_item_width(item) or 0, 0)
|
||||
if width > 0:
|
||||
return width
|
||||
try:
|
||||
configured_width = dpg.get_item_configuration(item).get("width", 0)
|
||||
except Exception:
|
||||
return 0
|
||||
if isinstance(configured_width, int | float) and configured_width > 0:
|
||||
return int(configured_width)
|
||||
return 0
|
||||
|
||||
|
||||
def _set_table_column_count(table: Tag, columns: int) -> None:
|
||||
children = cast(dict[int, list[Tag]], dpg.get_item_children(table) or {})
|
||||
table_columns = list(children.get(0, []))
|
||||
current = len(table_columns)
|
||||
if columns > current:
|
||||
for _ in range(columns - current):
|
||||
dpg.add_table_column(parent=table, width_stretch=True, init_width_or_weight=1.0)
|
||||
elif columns < current:
|
||||
for column in reversed(table_columns[columns:]):
|
||||
dpg.delete_item(column)
|
||||
|
||||
|
||||
def _reflow_table_cells(table: Tag, columns: int, row_height: int = 0) -> None:
|
||||
columns = max(int(columns), 1)
|
||||
cell_contents = _table_cell_contents(table)
|
||||
old_rows = _table_rows(table)
|
||||
if not cell_contents:
|
||||
_set_table_column_count(table, columns)
|
||||
return
|
||||
|
||||
if columns > len(_table_columns(table)):
|
||||
_set_table_column_count(table, columns)
|
||||
|
||||
new_rows: list[Tag] = []
|
||||
for index, contents in enumerate(cell_contents):
|
||||
if index % columns == 0:
|
||||
new_rows.append(dpg.add_table_row(parent=table, height=row_height))
|
||||
cell = dpg.add_table_cell(parent=new_rows[-1])
|
||||
for child in contents:
|
||||
if dpg.does_item_exist(child):
|
||||
dpg.move_item(child, parent=cell)
|
||||
|
||||
for row in old_rows:
|
||||
if dpg.does_item_exist(row):
|
||||
dpg.delete_item(row)
|
||||
|
||||
if columns < len(_table_columns(table)):
|
||||
_set_table_column_count(table, columns)
|
||||
|
||||
|
||||
def _table_columns(table: Tag) -> list[Tag]:
|
||||
children = cast(dict[int, list[Tag]], dpg.get_item_children(table) or {})
|
||||
return list(children.get(0, []))
|
||||
|
||||
|
||||
def _table_rows(table: Tag) -> list[Tag]:
|
||||
children = cast(dict[int, list[Tag]], dpg.get_item_children(table) or {})
|
||||
return list(children.get(1, []))
|
||||
|
||||
|
||||
def _table_cell_contents(table: Tag) -> list[list[Tag]]:
|
||||
cells: list[list[Tag]] = []
|
||||
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 {})
|
||||
cells.append(list(cell_children.get(1, [])))
|
||||
return cells
|
||||
|
||||
|
||||
def _table_cell_count(table: Tag) -> int:
|
||||
count = 0
|
||||
for row in _table_rows(table):
|
||||
row_children = cast(dict[int, list[Tag]], dpg.get_item_children(row) or {})
|
||||
count += len(row_children.get(1, []))
|
||||
return count
|
||||
|
||||
|
||||
def _last_row_state(table: Tag, columns: int) -> tuple[Tag | None, int]:
|
||||
rows = _table_rows(table)
|
||||
if not rows:
|
||||
return None, 0
|
||||
row = rows[-1]
|
||||
row_children = cast(dict[int, list[Tag]], dpg.get_item_children(row) or {})
|
||||
return row, min(len(row_children.get(1, [])), columns)
|
||||
|
||||
|
||||
def _schedule_grid_refresh() -> None:
|
||||
global _grid_refresh_scheduled
|
||||
if _grid_refresh_scheduled or not _dpg_context_active:
|
||||
return
|
||||
try:
|
||||
dpg.set_frame_callback(dpg.get_frame_count() + 1, _refresh_grid_layouts)
|
||||
except Exception:
|
||||
return
|
||||
_grid_refresh_scheduled = True
|
||||
|
||||
|
||||
def _refresh_grid_layouts() -> None:
|
||||
global _grid_refresh_scheduled
|
||||
_grid_refresh_scheduled = False
|
||||
if not _dpg_context_active:
|
||||
return
|
||||
for tag, grid in list(_grid_layouts.items()):
|
||||
if tag is None or not dpg.does_item_exist(tag):
|
||||
del _grid_layouts[tag]
|
||||
continue
|
||||
grid.refresh_columns()
|
||||
if _grid_layouts:
|
||||
_schedule_grid_refresh()
|
||||
|
||||
|
||||
def _resolved_columns(
|
||||
columns: int | Literal["auto"], min_column_width: int, available_width: int
|
||||
) -> int:
|
||||
@@ -289,7 +453,7 @@ def gauge_panel(
|
||||
"""Create a lightweight child-window container for related gauges."""
|
||||
if not _dpg_context_active:
|
||||
return LayoutContext(None)
|
||||
layout_parent, layout_width = _next_layout_cell(parent)
|
||||
layout_parent, layout_width, _layout_grid = _next_layout_cell(parent)
|
||||
if layout_parent is not None:
|
||||
parent = layout_parent
|
||||
if width <= 0 and layout_width is not None:
|
||||
@@ -326,14 +490,24 @@ def gauge_grid(
|
||||
) -> GridLayoutContext:
|
||||
"""Create a lightweight wrapping gauge layout container."""
|
||||
if not _dpg_context_active:
|
||||
return GridLayoutContext(None, 1, min_column_width, fit_gauges, cell_padding)
|
||||
layout_parent, layout_width = _next_layout_cell(parent)
|
||||
return GridLayoutContext(
|
||||
tag=None,
|
||||
parent=None,
|
||||
width=width,
|
||||
requested_columns=columns,
|
||||
columns=1,
|
||||
min_column_width=min_column_width,
|
||||
fit_gauges=fit_gauges,
|
||||
cell_padding=cell_padding,
|
||||
)
|
||||
layout_parent, layout_width, _layout_grid = _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
|
||||
available_width = _available_layout_width(parent, width)
|
||||
columns = _resolved_columns(columns, min_column_width, available_width)
|
||||
requested_columns = columns
|
||||
resolved_columns = _resolved_columns(columns, min_column_width, available_width)
|
||||
container = dpg.add_table(
|
||||
tag=tag or 0,
|
||||
parent=parent or 0,
|
||||
@@ -345,17 +519,24 @@ def gauge_grid(
|
||||
scrollX=False,
|
||||
**config,
|
||||
)
|
||||
for _ in range(columns):
|
||||
for _ in range(resolved_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,
|
||||
resolved_parent = cast(Tag | None, dpg.get_item_parent(container))
|
||||
context = GridLayoutContext(
|
||||
tag=container,
|
||||
parent=resolved_parent,
|
||||
width=width,
|
||||
requested_columns=requested_columns,
|
||||
columns=resolved_columns,
|
||||
min_column_width=min_column_width,
|
||||
fit_gauges=fit_gauges,
|
||||
cell_padding=cell_padding,
|
||||
row_height=row_height,
|
||||
available_width=available_width,
|
||||
)
|
||||
_grid_layouts[container] = context
|
||||
_schedule_grid_refresh()
|
||||
return context
|
||||
|
||||
|
||||
def analog_gauge(
|
||||
|
||||
Reference in New Issue
Block a user