added grid layout

This commit is contained in:
Will McGugan
2021-07-08 21:43:49 +01:00
parent 9c150152df
commit 111f596911
5 changed files with 202 additions and 4 deletions

36
examples/grid.py Normal file
View File

@@ -0,0 +1,36 @@
from textual.app import App
from textual import events
from textual.view import View
from textual.widgets import Placeholder
from textual.layouts.grid import GridLayout
class GridTest(App):
async def on_load(self, event: events.Load) -> None:
await self.bind("q,ctrl+c", "quit", "Quit")
async def on_startup(self, event: events.Startup) -> None:
layout = GridLayout()
view = await self.push_view(View(layout=layout))
layout.add_column(fraction=1, name="left", minimum_size=20)
layout.add_column(size=30, name="center")
layout.add_column(fraction=1, name="right")
layout.add_row(fraction=1, name="top")
layout.add_row(fraction=2, name="middle")
layout.add_row(fraction=1, name="bottom")
layout.add_area("area1", "left", "top")
layout.add_area("area2", "center", "middle")
layout.add_area("area3", ("left-start", "right-end"), "bottom")
layout.add_area("area4", "right", ("top-start", "middle-end"))
await view.mount(layout.add_widget(Placeholder(name="area1"), "area1"))
await view.mount(layout.add_widget(Placeholder(name="area2"), "area2"))
await view.mount(layout.add_widget(Placeholder(name="area3"), "area3"))
await view.mount(layout.add_widget(Placeholder(name="area4"), "area4"))
GridTest.run(title="Grid Test")

View File

@@ -25,7 +25,7 @@ from ._context import active_app
from ._event_broker import extract_handler_actions, NoHandler
from .keys import Binding
from .driver import Driver
from .layouts.dock import DockLayout, Dock, DockEdge, DockOptions
from .layouts.dock import DockLayout, Dock
from ._linux_driver import LinuxDriver
from .message_pump import MessagePump
from .message import Message

View File

@@ -4,13 +4,12 @@ import sys
from collections import defaultdict
from dataclasses import dataclass
import logging
from typing import TYPE_CHECKING, Mapping, Sequence
from typing import TYPE_CHECKING, Sequence
from rich._ratio import ratio_resolve
from ..geometry import Region, Point
from ..layout import Layout, OrderedRegion
from .._types import Lines
if sys.version_info >= (3, 8):
from typing import Literal

161
src/textual/layouts/grid.py Normal file
View File

@@ -0,0 +1,161 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import NamedTuple
from rich._ratio import ratio_resolve
from ..geometry import Point, Region
from ..layout import Layout, OrderedRegion
from ..widget import Widget
@dataclass
class GridOptions:
size: int | None = None
fraction: int = 1
minimum_size: int = 1
name: str | None = None
@property
def ratio(self) -> int:
return self.fraction
class GridArea(NamedTuple):
col_start: str
col_end: str
row_start: str
row_end: str
class GridLayout(Layout):
def __init__(self) -> None:
self.columns: list[GridOptions] = []
self.rows: list[GridOptions] = []
self.areas: dict[str, GridArea] = {}
self.widgets: dict[Widget, str | None] = {}
super().__init__()
def add_column(
self,
*,
size: int | None = None,
fraction: int = 1,
minimum_size: int = 1,
name: str | None = None,
) -> None:
options = GridOptions(
size=size, fraction=fraction, minimum_size=minimum_size, name=name
)
self.columns.append(options)
def add_row(
self,
*,
size: int | None = None,
fraction: int = 1,
minimum_size: int = 1,
name: str | None = None,
) -> None:
options = GridOptions(
size=size, fraction=fraction, minimum_size=minimum_size, name=name
)
self.rows.append(options)
def add_area(
self, name: str, columns: str | tuple[str, str], rows: str | tuple[str, str]
):
if isinstance(columns, str):
column_start = f"{columns}-start"
column_end = f"{columns}-end"
else:
column_start, column_end = columns
if isinstance(rows, str):
row_start = f"{rows}-start"
row_end = f"{rows}-end"
else:
row_start, row_end = rows
area = GridArea(column_start, column_end, row_start, row_end)
self.areas[name] = area
def add_widget(self, widget: Widget, area: str | None = None) -> Widget:
self.widgets[widget] = area
return widget
def generate_map(
self, width: int, height: int, offset: Point = Point(0, 0)
) -> dict[Widget, OrderedRegion]:
def resolve(size, edges) -> list[int]:
sizes = ratio_resolve(size, edges)
total = 0
for _size in sizes:
total += _size
yield total
columns = [
("", 0),
*(
(column.name, column_width)
for column, column_width in zip(
self.columns, resolve(width, self.columns)
)
),
]
column_tracks = {}
for (_, track1), (name2, track2) in zip(columns, columns[1:]):
column_tracks[f"{name2}-end"] = track2
column_tracks[f"{name2}-start"] = track1
rows = [
("", 0),
*(
(row.name, column_height)
for row, column_height in zip(self.rows, resolve(height, self.rows))
),
]
row_tracks = {}
for (_, track1), (name2, track2) in zip(rows, rows[1:]):
row_tracks[f"{name2}-end"] = track2
row_tracks[f"{name2}-start"] = track1
order = 1
map = {}
for widget, area in self.widgets.items():
if not area:
continue
column_start, column_end, row_start, row_end = self.areas[area]
x1 = column_tracks[column_start]
x2 = column_tracks[column_end]
y1 = row_tracks[row_start]
y2 = row_tracks[row_end]
map[widget] = OrderedRegion(Region.from_corners(x1, y1, x2, y2), (0, order))
order += 1
return map
if __name__ == "__main__":
layout = GridLayout()
layout.add_column(size=20, name="left")
layout.add_column(fraction=2, name="middle")
layout.add_column(fraction=1, name="right")
layout.add_row(fraction=1, name="top")
layout.add_row(fraction=2, name="bottom")
layout.add_area("center", "middle", "top")
from ..widgets import Static
layout.add_widget(Static("foo"), "center")
from rich import print
print(layout.widgets)
map = layout.generate_map(100, 80)
print(map)

View File

@@ -27,7 +27,9 @@ class Placeholder(Widget, can_focus=True):
def render(self) -> RenderableType:
return Panel(
Align.center(Pretty(self), vertical="middle"),
Align.center(
Pretty(self, no_wrap=True, overflow="ellipsis"), vertical="middle"
),
title=self.__class__.__name__,
border_style="green" if self.mouse_over else "blue",
box=box.HEAVY if self.has_focus else box.ROUNDED,