grid layout

This commit is contained in:
Will McGugan
2021-07-10 20:49:22 +01:00
parent b7b129b6ef
commit 2257cbe97a
3 changed files with 87 additions and 30 deletions

View File

@@ -1,9 +1,10 @@
from rich.align import Align
from rich.text import Text from rich.text import Text
from textual.app import App from textual.app import App
from textual import events from textual import events
from textual.view import View from textual.view import View
from textual.widgets import Button, Placeholder from textual.widgets import Button, Placeholder, Static
from textual.layouts.grid import GridLayout from textual.layouts.grid import GridLayout
try: try:
@@ -20,32 +21,45 @@ class GridTest(App):
async def on_startup(self, event: events.Startup) -> None: async def on_startup(self, event: events.Startup) -> None:
layout = GridLayout(gap=1, gutter=1, align=("center", "center")) layout = GridLayout(gap=(2, 1), gutter=1, align=("center", "center"))
await self.push_view(View(layout=layout)) await self.push_view(View(layout=layout))
layout.add_column("col", max_size=20, repeat=4) layout.add_column("col", max_size=20, repeat=4)
layout.add_row("numbers", min_size=3, max_size=10) layout.add_row("numbers", min_size=3, max_size=6)
layout.add_row("row", max_size=10, repeat=4) layout.add_row("row", max_size=10, repeat=5)
layout.add_areas( layout.add_areas(
numbers="col1-start|col4-end,numbers", numbers="col1-start|col4-end,numbers",
zero="col1-start|col2-end,row4", zero="col1-start|col2-end,row5",
dot="col3,row4", dot="col3,row4",
equals="col4,row4", equals="col4,row4",
) )
def make_text(text: str) -> Text:
return Text(font.renderText(text).rstrip("\n"), style="bold")
def make_button(text: str) -> Button: def make_button(text: str) -> Button:
label = Text(font.renderText(text).rstrip("\n"), style="bold") label = make_text(text)
return Button(label) return Button(label, style="white on rgb(51,51,51)")
buttons = { buttons = {
name: make_button(name) name: make_button(name)
for name in "AC,+/-,%,/,7,8,9,X,4,5,6,-,1,2,3,+,.,=".split(",") for name in "AC,+/-,%,/,7,8,9,X,4,5,6,-,1,2,3,+,.,=".split(",")
} }
buttons["AC"].style = "#000000 on rgb(165,165,165)"
buttons["+/-"].style = "#000000 on rgb(165,165,165)"
buttons["%"].style = "#000000 on rgb(165,165,165)"
buttons["/"].style = "#ffffff on rgb(255,159,7)"
buttons["X"].style = "#ffffff on rgb(255,159,7)"
buttons["-"].style = "#ffffff on rgb(255,159,7)"
buttons["+"].style = "#ffffff on rgb(255,159,7)"
buttons["="].style = "#ffffff on rgb(255,159,7)"
zero_text = make_text("0")
layout.place( layout.place(
*buttons.values(), *buttons.values(),
numbers=Placeholder(name="numbers"), numbers=Static(Align.right(zero_text)),
zero=make_button("0"), zero=make_button("0"),
) )

View File

@@ -31,10 +31,15 @@ class NoWidget(Exception):
pass pass
@rich.repr.auto
class OrderedRegion(NamedTuple): class OrderedRegion(NamedTuple):
region: Region region: Region
order: tuple[int, int] order: tuple[int, int]
def __rich_repr__(self) -> rich.repr.RichReprResult:
yield "region", self.region
yield "order", self.order
class ReflowResult(NamedTuple): class ReflowResult(NamedTuple):
"""The result of a reflow operation. Describes the chances to widgets.""" """The result of a reflow operation. Describes the chances to widgets."""
@@ -284,7 +289,7 @@ class Layout(ABC):
clip_x, clip_y, clip_x2, clip_y2 = clip.corners clip_x, clip_y, clip_x2, clip_y2 = clip.corners
divide = Segment.divide divide = Segment.divide
back = Style.parse("on blue") back = Style.parse("black")
# Maps each cut on to a list of segments # Maps each cut on to a list of segments
cuts = self.cuts cuts = self.cuts

View File

@@ -251,26 +251,33 @@ class GridLayout(Layout):
def resolve_tracks( def resolve_tracks(
grid: list[GridOptions], size: int, gap: int, repeat: bool grid: list[GridOptions], size: int, gap: int, repeat: bool
) -> tuple[dict[str, list[tuple[int, int]]], int]: ) -> tuple[list[str], dict[str, tuple[int, int]], int, int]:
spans = ( spans = [
(options.name, span) (options.name, span)
for options, span in zip(cycle(grid), resolve(size, grid, gap, repeat)) for options, span in zip(cycle(grid), resolve(size, grid, gap, repeat))
) ]
names = [name for name, _span in spans]
max_size = 0 max_size = 0
tracks: dict[str, list[tuple[int, int]]] = defaultdict(list) tracks: dict[str, tuple[int, int]] = {}
counts = defaultdict(int)
for index, (name, (start, end)) in enumerate(spans): for index, (name, (start, end)) in enumerate(spans):
max_size = max(max_size, end) max_size = max(max_size, end)
tracks[f"{name}-start"].append((index, start)) counts[name] += 1
tracks[f"{name}-end"].append((index, end)) count = counts[name]
return tracks, max_size tracks[f"{name}-start"] = (index, start)
tracks[f"{name}-end"] = (index, end)
if repeat:
tracks[f"{name}-{count}-start"] = (index, start)
tracks[f"{name}-{count}-end"] = (index, end)
return names, tracks, len(spans), max_size
container = Dimensions( container = Dimensions(
width - self.column_gutter * 2, height - self.row_gutter * 2 width - self.column_gutter * 2, height - self.row_gutter * 2
) )
column_tracks, column_size = resolve_tracks( column_names, column_tracks, column_count, column_size = resolve_tracks(
self.columns, container.width, self.column_gap, self.column_repeat self.columns, container.width, self.column_gap, self.column_repeat
) )
row_tracks, row_size = resolve_tracks( row_names, row_tracks, row_count, row_size = resolve_tracks(
self.rows, container.height, self.row_gap, self.row_repeat self.rows, container.height, self.row_gap, self.row_repeat
) )
grid_size = Dimensions(column_size, row_size) grid_size = Dimensions(column_size, row_size)
@@ -282,8 +289,7 @@ class GridLayout(Layout):
) )
free_slots = { free_slots = {
(col, row) (col, row) for col, row in product(range(column_count), range(row_count))
for col, row in product(range(len(self.columns)), range(len(self.rows)))
} }
map = {} map = {}
@@ -293,15 +299,16 @@ class GridLayout(Layout):
for widget, area in widget_areas: for widget, area in widget_areas:
column_start, column_end, row_start, row_end = self.areas[area] column_start, column_end, row_start, row_end = self.areas[area]
try: try:
col1, x1 = column_tracks[column_start][0] col1, x1 = column_tracks[column_start]
col2, x2 = column_tracks[column_end][0] col2, x2 = column_tracks[column_end]
row1, y1 = row_tracks[row_start][0] row1, y1 = row_tracks[row_start]
row2, y2 = row_tracks[row_end][0] row2, y2 = row_tracks[row_end]
except (KeyError, IndexError): except (KeyError, IndexError):
continue continue
free_slots -= { free_slots -= {
(col, row) for col, row in product(range(col1, col2), range(row1, row2)) (col, row)
for col, row in product(range(col1, col2 + 1), range(row1, row2 + 1))
} }
region = self._align( region = self._align(
@@ -317,10 +324,36 @@ class GridLayout(Layout):
# Widgets with no area assigned. # Widgets with no area assigned.
auto_widgets = [widget for widget, area in self.widgets.items() if area is None] auto_widgets = [widget for widget, area in self.widgets.items() if area is None]
# for (widget, area) in widget_areas: grid_refs = sorted(
# if widget in map: product(range(column_count), range(row_count)), key=(lambda i: (i[1], i[0]))
# col1, col2, row1, row2 = area )
# for col in range() iter_grid = iter(grid_refs)
for widget in auto_widgets:
try:
while True:
col, row = next(iter_grid)
if (col, row) in free_slots:
break
except StopIteration:
break
col_name = column_names[col]
row_name = row_names[row]
col1, x1 = column_tracks[f"{col_name}-start"]
col2, x2 = column_tracks[f"{col_name}-end"]
row1, y1 = row_tracks[f"{row_name}-start"]
row2, y2 = row_tracks[f"{row_name}-end"]
region = self._align(
from_corners(x1, y1, x2, y2),
grid_size,
container,
self.column_align,
self.row_align,
)
map[widget] = OrderedRegion(region + gutter, (0, order))
order += 1
return map return map
@@ -334,7 +367,12 @@ if __name__ == "__main__":
layout.add_row(fraction=1, name="top") layout.add_row(fraction=1, name="top")
layout.add_row(fraction=2, name="bottom") layout.add_row(fraction=2, name="bottom")
layout.add_area("center", "middle", "top") layout.add_areas(center="a-start|b-end,top")
# layout.set_repeat(True)
from ..widgets import Placeholder
layout.place(center=Placeholder())
from rich import print from rich import print