Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b1826d9c3 |
91
examples/autobreak_runtime.py
Normal file
91
examples/autobreak_runtime.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# pyright: reportGeneralTypeIssues=false
|
||||||
|
|
||||||
|
import dearpygui.dearpygui as dpg
|
||||||
|
|
||||||
|
import dpg_gauges as dpgg
|
||||||
|
|
||||||
|
COUNTERS: dict[str, int] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def add_runtime_gauges(grid: str, prefix: str, count: int = 6) -> None:
|
||||||
|
start = COUNTERS.get(grid, 0)
|
||||||
|
for offset in range(count):
|
||||||
|
index = start + offset
|
||||||
|
value = (index * 17 + 23) % 101
|
||||||
|
dpgg.digital_gauge(
|
||||||
|
tag=f"{prefix}_{index}",
|
||||||
|
parent=grid,
|
||||||
|
label=f"{prefix} {index + 1}",
|
||||||
|
value=value,
|
||||||
|
unit="%",
|
||||||
|
height=72,
|
||||||
|
)
|
||||||
|
COUNTERS[grid] = start + count
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
dpg.create_context()
|
||||||
|
try:
|
||||||
|
with dpg.window(label="Runtime autobreak tests", width=1220, height=900):
|
||||||
|
dpg.add_text("Fixed panel, runtime gauges: expected 5 columns at width=1000")
|
||||||
|
with dpgg.gauge_panel(label="Fixed 1000 x 230", width=1000, height=230):
|
||||||
|
dpgg.gauge_grid(tag="fixed_runtime_grid", columns="auto", min_column_width=180)
|
||||||
|
dpg.add_button(
|
||||||
|
label="Add fixed gauges",
|
||||||
|
callback=lambda: add_runtime_gauges("fixed_runtime_grid", "fixed"),
|
||||||
|
)
|
||||||
|
|
||||||
|
dpg.add_spacer(height=10)
|
||||||
|
dpg.add_text("Autosize panel with configured size: should match fixed panel")
|
||||||
|
with dpgg.gauge_panel(
|
||||||
|
label="Autosize configured 1000 x 230",
|
||||||
|
width=1000,
|
||||||
|
height=230,
|
||||||
|
autosize_x=True,
|
||||||
|
autosize_y=True,
|
||||||
|
):
|
||||||
|
dpgg.gauge_grid(tag="autosize_runtime_grid", columns="auto", min_column_width=180)
|
||||||
|
dpg.add_button(
|
||||||
|
label="Add autosize gauges",
|
||||||
|
callback=lambda: add_runtime_gauges("autosize_runtime_grid", "auto"),
|
||||||
|
)
|
||||||
|
|
||||||
|
dpg.add_spacer(height=10)
|
||||||
|
dpg.add_text("Autosize panel without width: grid falls back to the owning window width")
|
||||||
|
with dpgg.gauge_panel(label="Autosize inherited width", height=230, autosize_x=True):
|
||||||
|
dpgg.gauge_grid(tag="inherited_runtime_grid", columns="auto", min_column_width=180)
|
||||||
|
dpg.add_button(
|
||||||
|
label="Add inherited-width gauges",
|
||||||
|
callback=lambda: add_runtime_gauges("inherited_runtime_grid", "inherited"),
|
||||||
|
)
|
||||||
|
|
||||||
|
dpg.add_spacer(height=10)
|
||||||
|
dpg.add_text("Narrow autosize panel: expected 2 columns at width=430")
|
||||||
|
with dpgg.gauge_panel(
|
||||||
|
label="Narrow autosize 430 x 210",
|
||||||
|
width=430,
|
||||||
|
height=210,
|
||||||
|
autosize_x=True,
|
||||||
|
autosize_y=True,
|
||||||
|
):
|
||||||
|
dpgg.gauge_grid(tag="narrow_runtime_grid", columns="auto", min_column_width=180)
|
||||||
|
dpg.add_button(
|
||||||
|
label="Add narrow gauges",
|
||||||
|
callback=lambda: add_runtime_gauges("narrow_runtime_grid", "narrow"),
|
||||||
|
)
|
||||||
|
|
||||||
|
dpg.set_frame_callback(2, lambda: add_runtime_gauges("fixed_runtime_grid", "fixed"))
|
||||||
|
dpg.set_frame_callback(3, lambda: add_runtime_gauges("autosize_runtime_grid", "auto"))
|
||||||
|
dpg.set_frame_callback(4, lambda: add_runtime_gauges("inherited_runtime_grid", "inherited"))
|
||||||
|
dpg.set_frame_callback(5, lambda: add_runtime_gauges("narrow_runtime_grid", "narrow"))
|
||||||
|
|
||||||
|
dpg.create_viewport(title="dpg-gauges runtime autobreak", width=1260, height=940)
|
||||||
|
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.0"
|
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.0"
|
__version__ = "1.1.1"
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"configure",
|
"configure",
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ _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] = []
|
_active_grids: list[GridLayoutContext] = []
|
||||||
|
_grid_layouts: dict[Tag, GridLayoutContext] = {}
|
||||||
_layout_suspend_depth = 0
|
_layout_suspend_depth = 0
|
||||||
|
_grid_refresh_scheduled = False
|
||||||
|
|
||||||
|
|
||||||
def _install_context_tracking() -> None:
|
def _install_context_tracking() -> None:
|
||||||
@@ -57,11 +59,14 @@ def _install_context_tracking() -> None:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def destroy_context(*args: Any, **kwargs: Any) -> Any:
|
def destroy_context(*args: Any, **kwargs: Any) -> Any:
|
||||||
global _dpg_context_active
|
global _dpg_context_active, _grid_refresh_scheduled
|
||||||
try:
|
try:
|
||||||
return _original_destroy_context(*args, **kwargs)
|
return _original_destroy_context(*args, **kwargs)
|
||||||
finally:
|
finally:
|
||||||
_dpg_context_active = False
|
_dpg_context_active = False
|
||||||
|
_active_grids.clear()
|
||||||
|
_grid_layouts.clear()
|
||||||
|
_grid_refresh_scheduled = False
|
||||||
|
|
||||||
create_context.__dpg_gauges_wrapped__ = True # type: ignore[attr-defined]
|
create_context.__dpg_gauges_wrapped__ = True # type: ignore[attr-defined]
|
||||||
destroy_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
|
@dataclass
|
||||||
class GridLayoutContext:
|
class GridLayoutContext:
|
||||||
tag: Tag | None
|
tag: Tag | None
|
||||||
|
parent: Tag | None
|
||||||
|
width: int
|
||||||
|
requested_columns: int | Literal["auto"]
|
||||||
columns: int
|
columns: int
|
||||||
min_column_width: int
|
min_column_width: int
|
||||||
fit_gauges: bool
|
fit_gauges: bool
|
||||||
@@ -139,6 +147,7 @@ class GridLayoutContext:
|
|||||||
available_width: int = 0
|
available_width: int = 0
|
||||||
_next_index: int = 0
|
_next_index: int = 0
|
||||||
_current_row: Tag | None = None
|
_current_row: Tag | None = None
|
||||||
|
_current_row_cells: int = 0
|
||||||
_suspend_depth: int = 0
|
_suspend_depth: int = 0
|
||||||
|
|
||||||
def __enter__(self) -> Tag | None:
|
def __enter__(self) -> Tag | None:
|
||||||
@@ -161,10 +170,13 @@ class GridLayoutContext:
|
|||||||
def next_cell(self) -> tuple[Tag | None, int | None]:
|
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):
|
if self.tag is None or not _dpg_context_active or not dpg.does_item_exist(self.tag):
|
||||||
return None, None
|
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 = 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)
|
cell = dpg.add_table_cell(parent=self._current_row or self.tag)
|
||||||
self._next_index += 1
|
self._next_index += 1
|
||||||
|
self._current_row_cells += 1
|
||||||
return cell, self.cell_width()
|
return cell, self.cell_width()
|
||||||
|
|
||||||
def cell_width(self) -> int | None:
|
def cell_width(self) -> int | None:
|
||||||
@@ -172,15 +184,35 @@ class GridLayoutContext:
|
|||||||
return None
|
return None
|
||||||
return max(int(self.available_width / self.columns) - self.cell_padding, 1)
|
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]:
|
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)
|
layout_parent, layout_width, layout_grid = _next_layout_cell(config.parent)
|
||||||
if layout_parent is not None:
|
if layout_parent is not None:
|
||||||
updates: dict[str, Any] = {"parent": layout_parent}
|
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
|
updates["width"] = layout_width
|
||||||
config = replace(config, **updates)
|
config = replace(config, **updates)
|
||||||
|
|
||||||
@@ -223,11 +255,21 @@ 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]:
|
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()
|
grid = _current_grid()
|
||||||
if parent is not None or grid is None:
|
if grid is None:
|
||||||
return None, None
|
return None, None, None
|
||||||
return grid.next_cell()
|
cell, width = grid.next_cell()
|
||||||
|
return cell, width, grid
|
||||||
|
|
||||||
|
|
||||||
def _current_grid() -> GridLayoutContext | None:
|
def _current_grid() -> GridLayoutContext | None:
|
||||||
@@ -239,25 +281,147 @@ def _current_grid() -> GridLayoutContext | None:
|
|||||||
return grid
|
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:
|
def _available_layout_width(parent: Tag | None, width: int) -> int:
|
||||||
if width > 0:
|
if width > 0:
|
||||||
return width
|
return width
|
||||||
if parent is not None and dpg.does_item_exist(parent):
|
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:
|
try:
|
||||||
container = dpg.top_container_stack()
|
container = dpg.top_container_stack()
|
||||||
except Exception:
|
except Exception:
|
||||||
return 0
|
return 0
|
||||||
if container is not None and dpg.does_item_exist(container):
|
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
|
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(
|
def _resolved_columns(
|
||||||
columns: int | Literal["auto"], min_column_width: int, available_width: int
|
columns: int | Literal["auto"], min_column_width: int, available_width: int
|
||||||
) -> int:
|
) -> int:
|
||||||
@@ -289,7 +453,7 @@ 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)
|
layout_parent, layout_width, _layout_grid = _next_layout_cell(parent)
|
||||||
if layout_parent is not None:
|
if layout_parent is not None:
|
||||||
parent = layout_parent
|
parent = layout_parent
|
||||||
if width <= 0 and layout_width is not None:
|
if width <= 0 and layout_width is not None:
|
||||||
@@ -326,14 +490,24 @@ def gauge_grid(
|
|||||||
) -> GridLayoutContext:
|
) -> GridLayoutContext:
|
||||||
"""Create a lightweight wrapping gauge layout container."""
|
"""Create a lightweight wrapping gauge layout container."""
|
||||||
if not _dpg_context_active:
|
if not _dpg_context_active:
|
||||||
return GridLayoutContext(None, 1, min_column_width, fit_gauges, cell_padding)
|
return GridLayoutContext(
|
||||||
layout_parent, layout_width = _next_layout_cell(parent)
|
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:
|
if layout_parent is not None:
|
||||||
parent = layout_parent
|
parent = layout_parent
|
||||||
if width <= 0 and layout_width is not None:
|
if width <= 0 and layout_width is not None:
|
||||||
width = layout_width
|
width = layout_width
|
||||||
available_width = _available_layout_width(parent, 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(
|
container = dpg.add_table(
|
||||||
tag=tag or 0,
|
tag=tag or 0,
|
||||||
parent=parent or 0,
|
parent=parent or 0,
|
||||||
@@ -345,17 +519,24 @@ def gauge_grid(
|
|||||||
scrollX=False,
|
scrollX=False,
|
||||||
**config,
|
**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)
|
dpg.add_table_column(parent=container, width_stretch=True, init_width_or_weight=1.0)
|
||||||
return GridLayoutContext(
|
resolved_parent = cast(Tag | None, dpg.get_item_parent(container))
|
||||||
container,
|
context = GridLayoutContext(
|
||||||
columns,
|
tag=container,
|
||||||
min_column_width,
|
parent=resolved_parent,
|
||||||
fit_gauges,
|
width=width,
|
||||||
cell_padding,
|
requested_columns=requested_columns,
|
||||||
row_height,
|
columns=resolved_columns,
|
||||||
available_width,
|
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(
|
def analog_gauge(
|
||||||
|
|||||||
@@ -134,3 +134,81 @@ def test_grid_routes_panels_without_stealing_panel_children(dpg_context: None) -
|
|||||||
assert dpg.get_item_parent("inside_panel") == "nested_panel"
|
assert dpg.get_item_parent("inside_panel") == "nested_panel"
|
||||||
beside_cell = cast(int | str, dpg.get_item_parent("beside_panel"))
|
beside_cell = cast(int | str, dpg.get_item_parent("beside_panel"))
|
||||||
assert dpg.get_item_type(beside_cell) == "mvAppItemType::mvTableCell"
|
assert dpg.get_item_type(beside_cell) == "mvAppItemType::mvTableCell"
|
||||||
|
|
||||||
|
|
||||||
|
def test_grid_accepts_gauges_added_after_context(dpg_context: None) -> None:
|
||||||
|
with dpg.window(label="Late", width=520), dpgg.gauge_grid(tag="late_grid", columns=2):
|
||||||
|
dpgg.digital_gauge(tag="late_initial", value=1, height=60)
|
||||||
|
|
||||||
|
dpgg.digital_gauge(tag="late_second", parent="late_grid", value=2, height=60)
|
||||||
|
dpgg.digital_gauge(tag="late_third", parent="late_grid", value=3, height=60)
|
||||||
|
|
||||||
|
children = _children("late_grid")
|
||||||
|
assert [len(_children(row)[1]) for row in children[1]] == [2, 1]
|
||||||
|
|
||||||
|
for tag in ("late_initial", "late_second", "late_third"):
|
||||||
|
cell = cast(int | str, dpg.get_item_parent(tag))
|
||||||
|
assert dpg.get_item_type(cell) == "mvAppItemType::mvTableCell"
|
||||||
|
|
||||||
|
|
||||||
|
def test_auto_grid_in_autosize_panel_wraps_late_gauges(dpg_context: None) -> None:
|
||||||
|
with (
|
||||||
|
dpg.window(label="Autosize", width=1200),
|
||||||
|
dpgg.gauge_panel(
|
||||||
|
tag="autosize_panel", width=1000, height=600, autosize_x=True, autosize_y=True
|
||||||
|
),
|
||||||
|
):
|
||||||
|
dpgg.gauge_grid(tag="autosize_grid", columns="auto", min_column_width=180)
|
||||||
|
|
||||||
|
for index in range(7):
|
||||||
|
dpgg.digital_gauge(
|
||||||
|
tag=f"autosize_metric_{index}", parent="autosize_grid", value=index, height=60
|
||||||
|
)
|
||||||
|
|
||||||
|
children = _children("autosize_grid")
|
||||||
|
assert len(children[0]) == 5
|
||||||
|
assert [len(_children(row)[1]) for row in children[1]] == [5, 2]
|
||||||
|
assert 0 < get_gauge_state("autosize_metric_0").config.width < 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_auto_grid_refreshes_when_autosize_width_becomes_known(dpg_context: None) -> None:
|
||||||
|
with (
|
||||||
|
dpg.window(label="Autosize"),
|
||||||
|
dpgg.gauge_panel(tag="late_width_panel", autosize_x=True, autosize_y=True),
|
||||||
|
):
|
||||||
|
grid = dpgg.gauge_grid(tag="late_width_grid", columns="auto", min_column_width=180)
|
||||||
|
|
||||||
|
assert grid.columns == 1
|
||||||
|
|
||||||
|
dpg.configure_item("late_width_panel", width=1000)
|
||||||
|
for index in range(6):
|
||||||
|
dpgg.digital_gauge(tag=f"late_width_metric_{index}", parent="late_width_grid", value=index)
|
||||||
|
|
||||||
|
children = _children("late_width_grid")
|
||||||
|
assert len(children[0]) == 5
|
||||||
|
assert [len(_children(row)[1]) for row in children[1]] == [5, 1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_auto_grid_reflows_existing_gauges_when_panel_narrows(dpg_context: None) -> None:
|
||||||
|
with dpg.window(label="Resize"), dpgg.gauge_panel(
|
||||||
|
tag="resize_panel", width=1000, height=300
|
||||||
|
):
|
||||||
|
grid = dpgg.gauge_grid(tag="resize_grid", columns="auto", min_column_width=180)
|
||||||
|
with grid:
|
||||||
|
for index in range(6):
|
||||||
|
dpgg.digital_gauge(tag=f"resize_metric_{index}", value=index, height=60)
|
||||||
|
|
||||||
|
assert grid.columns == 5
|
||||||
|
children = _children("resize_grid")
|
||||||
|
assert [len(_children(row)[1]) for row in children[1]] == [5, 1]
|
||||||
|
|
||||||
|
dpg.configure_item("resize_panel", width=430)
|
||||||
|
grid.refresh_columns()
|
||||||
|
|
||||||
|
assert grid.columns == 2
|
||||||
|
children = _children("resize_grid")
|
||||||
|
assert len(children[0]) == 2
|
||||||
|
assert [len(_children(row)[1]) for row in children[1]] == [2, 2, 2]
|
||||||
|
for index in range(6):
|
||||||
|
cell = cast(int | str, dpg.get_item_parent(f"resize_metric_{index}"))
|
||||||
|
assert dpg.get_item_type(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.1.0"
|
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