mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
grid layout
This commit is contained in:
@@ -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"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user