Clarify grid autobreak behavior

This commit is contained in:
2026-07-07 23:06:53 +01:00
parent 44403bea2b
commit 5bd819d812
5 changed files with 52 additions and 17 deletions

View File

@@ -43,9 +43,11 @@ with dpg.window(label="Engine"):
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. `columns` is a maximum; if the parent is too `gauge_grid` is a wrapping, table-backed layout. An integer `columns` value is a maximum; if the
narrow for `columns * min_column_width`, the grid uses fewer columns and continues on the next row parent is too narrow for `columns * min_column_width`, the grid uses fewer columns and continues on
instead of creating a horizontal scrollbar. Gauges with `width=0` or `width=-1` fill their grid cell. 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:

View File

@@ -24,10 +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(columns=1, min_column_width=180, fit_gauges=True, **config)`: table-backed - `gauge_grid(columns=1, min_column_width=180, fit_gauges=True, cell_padding=12, **config)`:
wrapping gauge grid. `columns` is the maximum number of columns. If the parent is narrower than table-backed
`columns * min_column_width`, the grid uses fewer columns and wraps gauges onto later rows to wrapping gauge grid. Pass an integer `columns` to set the maximum column count; if the parent is
avoid horizontal scrolling. Gauges without an explicit positive width fill their grid cell. 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

View File

@@ -9,7 +9,7 @@ def main() -> None:
dpg.create_context() dpg.create_context()
try: try:
with dpg.window(label="Grid layout", width=820, height=620): with dpg.window(label="Grid layout", width=820, height=620):
dpg.add_text("Three-column grid: fixed column cap, automatic rows") 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( with dpgg.gauge_panel(label="Wide panel", width=-1, height=270), dpgg.gauge_grid(
columns=3, min_column_width=190 columns=3, min_column_width=190
): ):
@@ -26,9 +26,10 @@ def main() -> None:
dpgg.battery_gauge(tag="grid_battery", label="Battery", value=74, height=160) dpgg.battery_gauge(tag="grid_battery", label="Battery", value=74, height=160)
dpg.add_spacer(height=8) dpg.add_spacer(height=8)
dpg.add_text("Narrow panel: columns=3 auto-reduces to avoid horizontal scrolling") dpg.add_text("Autobreak: columns='auto' chooses columns from available width")
with dpgg.gauge_panel(label="Narrow panel", width=390, height=250), dpgg.gauge_grid( with (
columns=3, min_column_width=180 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): for index, value in enumerate((18, 42, 67, 91, 55), start=1):
dpgg.digital_gauge( dpgg.digital_gauge(

View File

@@ -134,6 +134,7 @@ class GridLayoutContext:
columns: int columns: int
min_column_width: int min_column_width: int
fit_gauges: bool fit_gauges: bool
cell_padding: int = 12
row_height: int = 0 row_height: int = 0
available_width: int = 0 available_width: int = 0
_next_index: int = 0 _next_index: int = 0
@@ -169,7 +170,7 @@ class GridLayoutContext:
def cell_width(self) -> int | None: def cell_width(self) -> int | None:
if self.available_width <= 0: if self.available_width <= 0:
return None return None
return max(int(self.available_width / self.columns), 1) 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]:
@@ -257,9 +258,15 @@ def _available_layout_width(parent: Tag | None, width: int) -> int:
return 0 return 0
def _resolved_columns(columns: int, min_column_width: int, available_width: int) -> int: def _resolved_columns(
columns = max(int(columns), 1) columns: int | Literal["auto"], min_column_width: int, available_width: int
) -> int:
min_column_width = max(int(min_column_width), 0) 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: if available_width > 0 and min_column_width > 0:
columns = min(columns, max(1, available_width // min_column_width)) columns = min(columns, max(1, available_width // min_column_width))
return columns return columns
@@ -305,9 +312,10 @@ def gauge_panel(
def gauge_grid( def gauge_grid(
*, *,
columns: int = 1, columns: int | Literal["auto"] = 1,
min_column_width: int = 180, min_column_width: int = 180,
fit_gauges: bool = True, fit_gauges: bool = True,
cell_padding: int = 12,
row_height: int = 0, row_height: int = 0,
tag: Tag | None = None, tag: Tag | None = None,
parent: Tag | None = None, parent: Tag | None = None,
@@ -318,7 +326,7 @@ 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) return GridLayoutContext(None, 1, min_column_width, fit_gauges, cell_padding)
layout_parent, layout_width = _next_layout_cell(parent) layout_parent, layout_width = _next_layout_cell(parent)
if layout_parent is not None: if layout_parent is not None:
parent = layout_parent parent = layout_parent
@@ -340,7 +348,13 @@ def gauge_grid(
for _ in range(columns): for _ in range(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( return GridLayoutContext(
container, columns, min_column_width, fit_gauges, row_height, available_width container,
columns,
min_column_width,
fit_gauges,
cell_padding,
row_height,
available_width,
) )

View File

@@ -107,6 +107,20 @@ def test_grid_auto_reduces_columns_to_parent_width(dpg_context: None) -> None:
assert len(children[0]) == 2 assert len(children[0]) == 2
assert len(children[1]) == 3 assert len(children[1]) == 3
assert [len(_children(row)[1]) for row in children[1]] == [2, 2, 1] 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: def test_grid_routes_panels_without_stealing_panel_children(dpg_context: None) -> None: