rename table to gird, diagrams for layout
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
16
docs/images/layout/dock.excalidraw.svg
Normal file
|
After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
16
docs/images/layout/offset.excalidraw.svg
Normal file
|
After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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"}
|
||||||
|
|||||||
@@ -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"):
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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]]:
|
||||||