rename table to gird, diagrams for layout

This commit is contained in:
Will McGugan
2022-09-06 10:16:52 +01:00
parent 6d6e385313
commit e8a4f2e806
17 changed files with 138 additions and 86 deletions

View File

@@ -1,7 +1,7 @@
Screen { Screen {
layout: table; layout: grid;
table-size: 2; grid-size: 2;
table-gutter: 2; grid-gutter: 2;
padding: 2; padding: 2;
} }
#question { #question {

View File

@@ -1,56 +1,76 @@
# Layout # Layout
TODO: Explanation of layout In textual the *layout* defines how widgets will be arranged (or *layed out*) on the screen. Textual supports a number of layouts which can be set either via a widgets `styles` object or via CSS.
## Vertical layout ## Vertical
A vertical layout will place new widgets below previous widgets, starting from the top of the screen.
<div class="excalidraw"> <div class="excalidraw">
--8<-- "docs/images/layout_vertical.excalidraw.svg" --8<-- "docs/images/layout/vertical.excalidraw.svg"
</div> </div>
TODO: Explanation of vertical layout TODO: Explanation of vertical layout
## Horizontal layout ## Horizontal
A horizontal layout will place the first widget at the top left of the screen, and new widgets will be place directly to the right of the previous widget.
<div class="excalidraw"> <div class="excalidraw">
--8<-- "docs/images/layout_horizontal.excalidraw.svg" --8<-- "docs/images/layout/horizontal.excalidraw.svg"
</div> </div>
TODO: Explantion of horizontal layout TODO: Explantion of horizontal layout
## Center layout ## Center
A center widget will place the widget directly in the center of the screen. New widgets will also be placed in the center of the screen, overlapping previous widgets.
There probably isn't a practical use for such overlapping widgets. In practice this layout is probably only useful where you have a single child widget.
<div class="excalidraw"> <div class="excalidraw">
--8<-- "docs/images/layout_center.excalidraw.svg" --8<-- "docs/images/layout/center.excalidraw.svg"
</div> </div>
TODO: Explanation of center layout TODO: Explanation of center layout
## Table layout ## Grid
A grid layout arranges widgets within a grid composed of columns and rows. Widgets can span multiple rows or columns to create more complex layouts.
<div class="excalidraw"> <div class="excalidraw">
--8<-- "docs/images/layout_table.excalidraw.svg" --8<-- "docs/images/layout/grid.excalidraw.svg"
</div> </div>
TODO: Explanation of table layout TODO: Explanation of grid layout
## Dock ## Docking
Widgets may be *docked*. Docking a widget removes it from the layout and fixes it position, aligned to either the top, right, bottom, or left edges of the screen. Docked widgets will not scroll, making them ideal for fixed headers / footers / sidebars.
<div class="excalidraw">
--8<-- "docs/images/layout/dock.excalidraw.svg"
</div>
TODO: Diagram TODO: Diagram
TODO: Explanation of dock TODO: Explanation of dock
## Offsets ## Offsets
Widgets have a relative offset which is added to the widget's location, after its location has been determined via its layout.
<div class="excalidraw">
--8<-- "docs/images/layout/offset.excalidraw.svg"
</div>
TODO: Diagram TODO: Diagram
TODO: Offsets TODO: Offsets
## Box Model
TBC

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 55 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -3,11 +3,11 @@ Screen {
} }
#calculator { #calculator {
layout: table; layout: grid;
table-size: 4; grid-size: 4;
table-gutter: 1 2; grid-gutter: 1 2;
table-columns: 1fr; grid-columns: 1fr;
table-rows: 2fr 1fr 1fr 1fr 1fr 1fr; grid-rows: 2fr 1fr 1fr 1fr 1fr 1fr;
margin: 1 2; margin: 1 2;
min-height:25; min-height:25;
min-width: 26; min-width: 26;

View File

@@ -3,11 +3,11 @@ Screen {
} }
#calculator { #calculator {
layout: table; layout: grid;
table-size: 4; grid-size: 4;
table-gutter: 1 2; grid-gutter: 1 2;
table-columns: 1fr; grid-columns: 1fr;
table-rows: 2fr 1fr 1fr 1fr 1fr 1fr; grid-rows: 2fr 1fr 1fr 1fr 1fr 1fr;
margin: 1 2; margin: 1 2;
min-height:25; min-height:25;
min-width: 26; min-width: 26;

View File

@@ -1,8 +1,8 @@
Screen { Screen {
layout: table; layout: grid;
table-columns: 2fr 1fr 1fr; grid-columns: 2fr 1fr 1fr;
table-rows: 1fr 1fr; grid-rows: 1fr 1fr;
table-gutter: 1 2; grid-gutter: 1 2;
} }
Static { Static {

View File

@@ -847,7 +847,7 @@ class StylesBuilder:
self.error(name, token, scrollbar_size_single_axis_help_text(name)) self.error(name, token, scrollbar_size_single_axis_help_text(name))
self.styles._rules["scrollbar_size_horizontal"] = value self.styles._rules["scrollbar_size_horizontal"] = value
def _process_table_rows_or_columns(self, name: str, tokens: list[Token]) -> None: def _process_grid_rows_or_columns(self, name: str, tokens: list[Token]) -> None:
scalars: list[Scalar] = [] scalars: list[Scalar] = []
for token in tokens: for token in tokens:
if token.name == "number": if token.name == "number":
@@ -867,8 +867,8 @@ class StylesBuilder:
) )
self.styles._rules[name.replace("-", "_")] = scalars self.styles._rules[name.replace("-", "_")] = scalars
process_table_rows = _process_table_rows_or_columns process_grid_rows = _process_grid_rows_or_columns
process_table_columns = _process_table_rows_or_columns process_grid_columns = _process_grid_rows_or_columns
def _process_integer(self, name: str, tokens: list[Token]) -> None: def _process_integer(self, name: str, tokens: list[Token]) -> None:
if not tokens: if not tokens:
@@ -884,14 +884,14 @@ class StylesBuilder:
self.error(name, token, integer_help_text(name)) self.error(name, token, integer_help_text(name))
self.styles._rules[name.replace("-", "_")] = value self.styles._rules[name.replace("-", "_")] = value
process_table_gutter_horizontal = _process_integer process_grid_gutter_horizontal = _process_integer
process_table_gutter_vertical = _process_integer process_grid_gutter_vertical = _process_integer
process_column_span = _process_integer process_column_span = _process_integer
process_row_span = _process_integer process_row_span = _process_integer
process_table_size_columns = _process_integer process_grid_size_columns = _process_integer
process_table_size_rows = _process_integer process_grid_size_rows = _process_integer
def process_table_gutter(self, name: str, tokens: list[Token]) -> None: def process_grid_gutter(self, name: str, tokens: list[Token]) -> None:
if not tokens: if not tokens:
return return
if len(tokens) == 1: if len(tokens) == 1:
@@ -899,25 +899,25 @@ class StylesBuilder:
if token.name != "number": if token.name != "number":
self.error(name, token, integer_help_text(name)) self.error(name, token, integer_help_text(name))
value = max(0, int(token.value)) value = max(0, int(token.value))
self.styles._rules["table_gutter_horizontal"] = value self.styles._rules["grid_gutter_horizontal"] = value
self.styles._rules["table_gutter_vertical"] = value self.styles._rules["grid_gutter_vertical"] = value
elif len(tokens) == 2: elif len(tokens) == 2:
token = tokens[0] token = tokens[0]
if token.name != "number": if token.name != "number":
self.error(name, token, integer_help_text(name)) self.error(name, token, integer_help_text(name))
value = max(0, int(token.value)) value = max(0, int(token.value))
self.styles._rules["table_gutter_horizontal"] = value self.styles._rules["grid_gutter_horizontal"] = value
token = tokens[1] token = tokens[1]
if token.name != "number": if token.name != "number":
self.error(name, token, integer_help_text(name)) self.error(name, token, integer_help_text(name))
value = max(0, int(token.value)) value = max(0, int(token.value))
self.styles._rules["table_gutter_vertical"] = value self.styles._rules["grid_gutter_vertical"] = value
else: else:
self.error(name, tokens[0], "expected two integers here") self.error(name, tokens[0], "expected two integers here")
def process_table_size(self, name: str, tokens: list[Token]) -> None: def process_grid_size(self, name: str, tokens: list[Token]) -> None:
if not tokens: if not tokens:
return return
if len(tokens) == 1: if len(tokens) == 1:
@@ -925,20 +925,20 @@ class StylesBuilder:
if token.name != "number": if token.name != "number":
self.error(name, token, integer_help_text(name)) self.error(name, token, integer_help_text(name))
value = max(0, int(token.value)) value = max(0, int(token.value))
self.styles._rules["table_size_columns"] = value self.styles._rules["grid_size_columns"] = value
self.styles._rules["table_size_rows"] = 0 self.styles._rules["grid_size_rows"] = 0
elif len(tokens) == 2: elif len(tokens) == 2:
token = tokens[0] token = tokens[0]
if token.name != "number": if token.name != "number":
self.error(name, token, integer_help_text(name)) self.error(name, token, integer_help_text(name))
value = max(0, int(token.value)) value = max(0, int(token.value))
self.styles._rules["table_size_columns"] = value self.styles._rules["grid_size_columns"] = value
token = tokens[1] token = tokens[1]
if token.name != "number": if token.name != "number":
self.error(name, token, integer_help_text(name)) self.error(name, token, integer_help_text(name))
value = max(0, int(token.value)) value = max(0, int(token.value))
self.styles._rules["table_size_rows"] = value self.styles._rules["grid_size_rows"] = value
else: else:
self.error(name, tokens[0], "expected two integers here") self.error(name, tokens[0], "expected two integers here")

View File

@@ -32,7 +32,7 @@ VALID_BORDER: Final[set[EdgeType]] = {
"wide", "wide",
} }
VALID_EDGE: Final = {"top", "right", "bottom", "left"} VALID_EDGE: Final = {"top", "right", "bottom", "left"}
VALID_LAYOUT: Final = {"vertical", "horizontal", "center", "table"} VALID_LAYOUT: Final = {"vertical", "horizontal", "center", "grid"}
VALID_BOX_SIZING: Final = {"border-box", "content-box"} VALID_BOX_SIZING: Final = {"border-box", "content-box"}
VALID_OVERFLOW: Final = {"scroll", "hidden", "auto"} VALID_OVERFLOW: Final = {"scroll", "hidden", "auto"}

View File

@@ -146,12 +146,12 @@ class RulesMap(TypedDict, total=False):
content_align_horizontal: AlignHorizontal content_align_horizontal: AlignHorizontal
content_align_vertical: AlignVertical content_align_vertical: AlignVertical
table_size_rows: int grid_size_rows: int
table_size_columns: int grid_size_columns: int
table_gutter_horizontal: int grid_gutter_horizontal: int
table_gutter_vertical: int grid_gutter_vertical: int
table_rows: tuple[Scalar, ...] grid_rows: tuple[Scalar, ...]
table_columns: tuple[Scalar, ...] grid_columns: tuple[Scalar, ...]
row_span: int row_span: int
column_span: int column_span: int
@@ -267,13 +267,13 @@ class StylesBase(ABC):
content_align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top") content_align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top")
content_align = AlignProperty() content_align = AlignProperty()
table_rows = ScalarListProperty() grid_rows = ScalarListProperty()
table_columns = ScalarListProperty() grid_columns = ScalarListProperty()
table_size_columns = IntegerProperty(default=1, layout=True) grid_size_columns = IntegerProperty(default=1, layout=True)
table_size_rows = IntegerProperty(default=0, layout=True) grid_size_rows = IntegerProperty(default=0, layout=True)
table_gutter_horizontal = IntegerProperty(default=0, layout=True) grid_gutter_horizontal = IntegerProperty(default=0, layout=True)
table_gutter_vertical = IntegerProperty(default=0, layout=True) grid_gutter_vertical = IntegerProperty(default=0, layout=True)
row_span = IntegerProperty(default=1, layout=True) row_span = IntegerProperty(default=1, layout=True)
column_span = IntegerProperty(default=1, layout=True) column_span = IntegerProperty(default=1, layout=True)
@@ -805,26 +805,26 @@ class Styles(StylesBase):
) )
elif has_rule("content_align_vertical"): elif has_rule("content_align_vertical"):
append_declaration("content-align-vertical", self.content_align_vertical) append_declaration("content-align-vertical", self.content_align_vertical)
elif has_rule("table_columns"): elif has_rule("grid_columns"):
append_declaration( append_declaration(
"table-columns", "grid-columns",
" ".join(str(scalar) for scalar in self.table_columns or ()), " ".join(str(scalar) for scalar in self.grid_columns or ()),
) )
elif has_rule("table_rows"): elif has_rule("grid_rows"):
append_declaration( append_declaration(
"table-rows", "grid-rows",
" ".join(str(scalar) for scalar in self.table_rows or ()), " ".join(str(scalar) for scalar in self.grid_rows or ()),
) )
elif has_rule("table_size_columns"): elif has_rule("grid_size_columns"):
append_declaration("table-size-columns", str(self.table_size_columns)) append_declaration("grid-size-columns", str(self.grid_size_columns))
elif has_rule("table_size_rows"): elif has_rule("grid_size_rows"):
append_declaration("table-size-rows", str(self.table_size_rows)) append_declaration("grid-size-rows", str(self.grid_size_rows))
elif has_rule("table_gutter_horizontal"): elif has_rule("grid_gutter_horizontal"):
append_declaration( append_declaration(
"table-gutter-horizontal", str(self.table_gutter_horizontal) "grid-gutter-horizontal", str(self.grid_gutter_horizontal)
) )
elif has_rule("table_gutter_vertical"): elif has_rule("grid_gutter_vertical"):
append_declaration("table-gutter-vertical", str(self.table_gutter_vertical)) append_declaration("grid-gutter-vertical", str(self.grid_gutter_vertical))
elif has_rule("row_span"): elif has_rule("row_span"):
append_declaration("row-span", str(self.row_span)) append_declaration("row-span", str(self.row_span))
elif has_rule("column_span"): elif has_rule("column_span"):

View File

@@ -3,13 +3,13 @@ from __future__ import annotations
from .._layout import Layout from .._layout import Layout
from .center import CenterLayout from .center import CenterLayout
from .horizontal import HorizontalLayout from .horizontal import HorizontalLayout
from .table import TableLayout from .grid import GridLayout
from .vertical import VerticalLayout from .vertical import VerticalLayout
LAYOUT_MAP: dict[str, type[Layout]] = { LAYOUT_MAP: dict[str, type[Layout]] = {
"center": CenterLayout, "center": CenterLayout,
"horizontal": HorizontalLayout, "horizontal": HorizontalLayout,
"table": TableLayout, "grid": GridLayout,
"vertical": VerticalLayout, "vertical": VerticalLayout,
} }

View File

@@ -12,21 +12,21 @@ if TYPE_CHECKING:
from ..widget import Widget from ..widget import Widget
class TableLayout(Layout): class GridLayout(Layout):
"""Used to layout Widgets in to a table.""" """Used to layout Widgets in to a grid."""
name = "table" name = "grid"
def arrange( def arrange(
self, parent: Widget, children: list[Widget], size: Size self, parent: Widget, children: list[Widget], size: Size
) -> ArrangeResult: ) -> ArrangeResult:
styles = parent.styles styles = parent.styles
row_scalars = styles.table_rows or [Scalar.parse("1fr")] row_scalars = styles.grid_rows or [Scalar.parse("1fr")]
column_scalars = styles.table_columns or [Scalar.parse("1fr")] column_scalars = styles.grid_columns or [Scalar.parse("1fr")]
gutter_horizontal = styles.table_gutter_horizontal gutter_horizontal = styles.grid_gutter_horizontal
gutter_vertical = styles.table_gutter_vertical gutter_vertical = styles.grid_gutter_vertical
table_size_columns = max(1, styles.table_size_columns) table_size_columns = max(1, styles.grid_size_columns)
table_size_rows = styles.table_size_rows table_size_rows = styles.grid_size_rows
viewport = parent.screen.size viewport = parent.screen.size
def cell_coords(column_count: int) -> Iterable[tuple[int, int]]: def cell_coords(column_count: int) -> Iterable[tuple[int, int]]: