From b7b129b6efc0a29e10fe0f371e3037021d518d3d Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 10 Jul 2021 17:36:23 +0100 Subject: [PATCH] auto widget placement --- examples/calculator.py | 23 ++++++++++++++-- src/textual/layouts/grid.py | 55 +++++++++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/examples/calculator.py b/examples/calculator.py index 67b31a71e..06f264cfe 100644 --- a/examples/calculator.py +++ b/examples/calculator.py @@ -1,9 +1,18 @@ +from rich.text import Text + from textual.app import App from textual import events from textual.view import View from textual.widgets import Button, Placeholder from textual.layouts.grid import GridLayout +try: + from pyfiglet import Figlet +except ImportError: + print("Please install pyfiglet to run this example") + +font = Figlet(font="small") + class GridTest(App): async def on_load(self, event: events.Load) -> None: @@ -25,11 +34,19 @@ class GridTest(App): equals="col4,row4", ) + def make_button(text: str) -> Button: + label = Text(font.renderText(text).rstrip("\n"), style="bold") + return Button(label) + + buttons = { + name: make_button(name) + for name in "AC,+/-,%,/,7,8,9,X,4,5,6,-,1,2,3,+,.,=".split(",") + } + layout.place( + *buttons.values(), numbers=Placeholder(name="numbers"), - zero=Button("0"), - dot=Button("."), - equals=Button("="), + zero=make_button("0"), ) diff --git a/src/textual/layouts/grid.py b/src/textual/layouts/grid.py index a592a8495..def5e3aea 100644 --- a/src/textual/layouts/grid.py +++ b/src/textual/layouts/grid.py @@ -2,7 +2,7 @@ from __future__ import annotations from collections import defaultdict from dataclasses import dataclass -from itertools import cycle +from itertools import cycle, product import sys from typing import Iterable, NamedTuple @@ -23,11 +23,11 @@ GridAlign = Literal["start", "end", "center", "stretch"] @dataclass class GridOptions: + name: str size: int | None = None fraction: int = 1 min_size: int = 1 max_size: int | None = None - name: str | None = None class GridArea(NamedTuple): @@ -76,7 +76,7 @@ class GridLayout(Layout): def add_column( self, - name: str | None = None, + name: str, *, size: int | None = None, fraction: int = 1, @@ -93,17 +93,17 @@ class GridLayout(Layout): for name in names: append( GridOptions( + name, size=size, fraction=fraction, min_size=min_size, max_size=max_size, - name=name, ) ) def add_row( self, - name: str | None = None, + name: str, *, size: int | None = None, fraction: int = 1, @@ -120,11 +120,11 @@ class GridLayout(Layout): for name in names: append( GridOptions( + name, size=size, fraction=fraction, min_size=min_size, max_size=max_size, - name=name, ) ) @@ -222,6 +222,17 @@ class GridLayout(Layout): def generate_map( self, width: int, height: int, offset: Point = Point(0, 0) ) -> dict[Widget, OrderedRegion]: + """[summary] + + Args: + width (int): [description] + height (int): [description] + offset (Point, optional): [description]. Defaults to Point(0, 0). + + Returns: + dict[Widget, OrderedRegion]: [description] + """ + def resolve( size: int, edges: list[GridOptions], gap: int, repeat: bool ) -> Iterable[tuple[int, int]]: @@ -240,17 +251,17 @@ class GridLayout(Layout): def resolve_tracks( grid: list[GridOptions], size: int, gap: int, repeat: bool - ) -> tuple[dict[str, list[int]], int]: + ) -> tuple[dict[str, list[tuple[int, int]]], int]: spans = ( (options.name, span) for options, span in zip(cycle(grid), resolve(size, grid, gap, repeat)) ) max_size = 0 - tracks: dict[str, list[int]] = defaultdict(list) - for name, (start, end) in spans: + tracks: dict[str, list[tuple[int, int]]] = defaultdict(list) + for index, (name, (start, end)) in enumerate(spans): max_size = max(max_size, end) - tracks[f"{name}-start"].append(start) - tracks[f"{name}-end"].append(end) + tracks[f"{name}-start"].append((index, start)) + tracks[f"{name}-end"].append((index, end)) return tracks, max_size container = Dimensions( @@ -270,6 +281,11 @@ class GridLayout(Layout): if area and widget.visible ) + free_slots = { + (col, row) + for col, row in product(range(len(self.columns)), range(len(self.rows))) + } + map = {} order = 1 from_corners = Region.from_corners @@ -277,13 +293,17 @@ class GridLayout(Layout): for widget, area in widget_areas: column_start, column_end, row_start, row_end = self.areas[area] try: - x1 = column_tracks[column_start][0] - x2 = column_tracks[column_end][0] - y1 = row_tracks[row_start][0] - y2 = row_tracks[row_end][0] + col1, x1 = column_tracks[column_start][0] + col2, x2 = column_tracks[column_end][0] + row1, y1 = row_tracks[row_start][0] + row2, y2 = row_tracks[row_end][0] except (KeyError, IndexError): continue + free_slots -= { + (col, row) for col, row in product(range(col1, col2), range(row1, row2)) + } + region = self._align( from_corners(x1, y1, x2, y2), grid_size, @@ -297,6 +317,11 @@ class GridLayout(Layout): # Widgets with no area assigned. auto_widgets = [widget for widget, area in self.widgets.items() if area is None] + # for (widget, area) in widget_areas: + # if widget in map: + # col1, col2, row1, row2 = area + # for col in range() + return map