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