mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
more functionality via CSS
This commit is contained in:
@@ -1,22 +1,24 @@
|
||||
#calculator {
|
||||
layout: table;
|
||||
table-columns: 1fr 1fr 1fr 1fr;
|
||||
table-rows: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
table-size: 4;
|
||||
table-gutter: 1 2;
|
||||
margin: 1;
|
||||
table-columns: 1fr;
|
||||
table-rows: 1fr;
|
||||
margin: 1 2;
|
||||
min-height:23;
|
||||
}
|
||||
|
||||
.display {
|
||||
column-span: 4;
|
||||
content-align: center middle;
|
||||
height: 100%;
|
||||
background: $panel-darken-2;
|
||||
}
|
||||
|
||||
Button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.display {
|
||||
column-span: 4;
|
||||
content-align: right middle;
|
||||
padding: 0 1;
|
||||
height: 100%;
|
||||
background: $panel-darken-2;
|
||||
}
|
||||
|
||||
.special {
|
||||
|
||||
@@ -29,7 +29,6 @@ class CalculatorApp(App):
|
||||
Button("=", variant="warning"),
|
||||
id="calculator",
|
||||
)
|
||||
self.dark = True
|
||||
|
||||
|
||||
app = CalculatorApp(css_path="calculator.css")
|
||||
|
||||
@@ -868,6 +868,8 @@ class StylesBuilder:
|
||||
process_table_gutter_vertical = _process_integer
|
||||
process_column_span = _process_integer
|
||||
process_row_span = _process_integer
|
||||
process_table_size_columns = _process_integer
|
||||
process_table_size_rows = _process_integer
|
||||
|
||||
def process_table_gutter(self, name: str, tokens: list[Token]) -> None:
|
||||
if not tokens:
|
||||
@@ -895,6 +897,32 @@ class StylesBuilder:
|
||||
else:
|
||||
self.error(name, tokens[0], "expected two integers here")
|
||||
|
||||
def process_table_size(self, name: str, tokens: list[Token]) -> None:
|
||||
if not tokens:
|
||||
return
|
||||
if len(tokens) == 1:
|
||||
token = tokens[0]
|
||||
if token.name != "number":
|
||||
self.error(name, token, integer_help_text(name))
|
||||
value = max(0, int(token.value))
|
||||
self.styles._rules["table_size_columns"] = value
|
||||
self.styles._rules["table_size_rows"] = 0
|
||||
|
||||
elif len(tokens) == 2:
|
||||
token = tokens[0]
|
||||
if token.name != "number":
|
||||
self.error(name, token, integer_help_text(name))
|
||||
value = max(0, int(token.value))
|
||||
self.styles._rules["table_size_columns"] = value
|
||||
token = tokens[1]
|
||||
if token.name != "number":
|
||||
self.error(name, token, integer_help_text(name))
|
||||
value = max(0, int(token.value))
|
||||
self.styles._rules["table_size_rows"] = value
|
||||
|
||||
else:
|
||||
self.error(name, tokens[0], "expected two integers here")
|
||||
|
||||
def _get_suggested_property_name_for_rule(self, rule_name: str) -> str | None:
|
||||
"""
|
||||
Returns a valid CSS property "Python" name, or None if no close matches could be found.
|
||||
|
||||
@@ -143,6 +143,8 @@ class RulesMap(TypedDict, total=False):
|
||||
content_align_horizontal: AlignHorizontal
|
||||
content_align_vertical: AlignVertical
|
||||
|
||||
table_size_rows: int
|
||||
table_size_columns: int
|
||||
table_gutter_horizontal: int
|
||||
table_gutter_vertical: int
|
||||
table_rows: tuple[Scalar, ...]
|
||||
@@ -261,6 +263,8 @@ class StylesBase(ABC):
|
||||
table_rows = ScalarListProperty()
|
||||
table_columns = ScalarListProperty()
|
||||
|
||||
table_size_columns = IntegerProperty(default=1, layout=True)
|
||||
table_size_rows = IntegerProperty(default=0, layout=True)
|
||||
table_gutter_horizontal = IntegerProperty(default=0, layout=True)
|
||||
table_gutter_vertical = IntegerProperty(default=0, layout=True)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class TableLayout(Layout):
|
||||
"""Used to layout Widgets vertically on screen, from top to bottom."""
|
||||
"""Used to layout Widgets in to a table."""
|
||||
|
||||
name = "table"
|
||||
|
||||
@@ -25,72 +25,117 @@ class TableLayout(Layout):
|
||||
column_scalars = styles.table_columns or [Scalar.parse("1fr")]
|
||||
gutter_horizontal = styles.table_gutter_horizontal
|
||||
gutter_vertical = styles.table_gutter_vertical
|
||||
table_size_columns = max(1, styles.table_size_columns)
|
||||
table_size_rows = styles.table_size_rows
|
||||
viewport = parent.screen.size
|
||||
|
||||
def cell_coords(column_count: int, row_count: int) -> Iterable[tuple[int, int]]:
|
||||
"""Iterate over table coordinates.
|
||||
def cell_coords(column_count: int) -> Iterable[tuple[int, int]]:
|
||||
"""Iterate over table coordinates ad infinitum.
|
||||
|
||||
Args:
|
||||
column_count (int): Number of columns
|
||||
row_count (int): Number of row
|
||||
|
||||
"""
|
||||
for row in range(row_count):
|
||||
row = 0
|
||||
while True:
|
||||
for column in range(column_count):
|
||||
yield (column, row)
|
||||
row += 1
|
||||
|
||||
def widget_coords(
|
||||
col_start: int, row_start: int, columns: int, rows: int
|
||||
column_start: int, row_start: int, columns: int, rows: int
|
||||
) -> set[tuple[int, int]]:
|
||||
"""Get coords occupied by a cell."""
|
||||
"""Get coords occupied by a cell.
|
||||
|
||||
Args:
|
||||
column_start (int): Start column.
|
||||
row_start (int): Start_row.
|
||||
columns (int): Number of columns.
|
||||
rows (int): Number of rows.
|
||||
|
||||
Returns:
|
||||
set[tuple[int, int]]: Set of coords.
|
||||
"""
|
||||
return {
|
||||
(column, row)
|
||||
for column in range(col_start, col_start + columns)
|
||||
for column in range(column_start, column_start + columns)
|
||||
for row in range(row_start, row_start + rows)
|
||||
}
|
||||
|
||||
def repeat_scalars(scalars: Iterable[Scalar], count: int) -> list[Scalar]:
|
||||
"""Repeat an iterable of scalars as many times as required to return
|
||||
a list of `count` values.
|
||||
|
||||
Args:
|
||||
scalars (Iterable[T]): Iterable of values.
|
||||
count (int): Number of values to return.
|
||||
|
||||
Returns:
|
||||
list[T]: A list of values.
|
||||
"""
|
||||
limited_values = list(scalars)[:]
|
||||
while len(limited_values) < count:
|
||||
limited_values.extend(scalars)
|
||||
return limited_values[:count]
|
||||
|
||||
cell_map: dict[tuple[int, int], tuple[Widget, bool]] = {}
|
||||
cell_size_map: dict[Widget, tuple[int, int, int, int]] = {}
|
||||
|
||||
column_count = len(column_scalars)
|
||||
row_count = len(row_scalars)
|
||||
next_coord = iter(cell_coords(column_count, row_count)).__next__
|
||||
column_count = table_size_columns
|
||||
next_coord = iter(cell_coords(column_count)).__next__
|
||||
cell_coord = (0, 0)
|
||||
try:
|
||||
for child in children:
|
||||
child_styles = child.styles
|
||||
column_span = child_styles.column_span or 1
|
||||
row_span = child_styles.row_span or 1
|
||||
while True:
|
||||
column, row = cell_coord
|
||||
coords = widget_coords(column, row, column_span, row_span)
|
||||
if cell_map.keys().isdisjoint(coords):
|
||||
for coord in coords:
|
||||
cell_map[coord] = (child, coord == cell_coord)
|
||||
cell_size_map[child] = (
|
||||
column,
|
||||
row,
|
||||
column_span - 1,
|
||||
row_span - 1,
|
||||
)
|
||||
break
|
||||
else:
|
||||
cell_coord = next_coord()
|
||||
continue
|
||||
cell_coord = next_coord()
|
||||
except StopIteration:
|
||||
pass
|
||||
column = row = 0
|
||||
|
||||
columns = resolve(column_scalars, size.width, gutter_vertical, size, viewport)
|
||||
rows = resolve(row_scalars, size.height, gutter_horizontal, size, viewport)
|
||||
for child in children:
|
||||
child_styles = child.styles
|
||||
column_span = child_styles.column_span or 1
|
||||
row_span = child_styles.row_span or 1
|
||||
while True:
|
||||
column, row = cell_coord
|
||||
coords = widget_coords(column, row, column_span, row_span)
|
||||
if cell_map.keys().isdisjoint(coords):
|
||||
for coord in coords:
|
||||
cell_map[coord] = (child, coord == cell_coord)
|
||||
cell_size_map[child] = (
|
||||
column,
|
||||
row,
|
||||
column_span - 1,
|
||||
row_span - 1,
|
||||
)
|
||||
break
|
||||
else:
|
||||
cell_coord = next_coord()
|
||||
continue
|
||||
cell_coord = next_coord()
|
||||
|
||||
columns = resolve(
|
||||
repeat_scalars(column_scalars, table_size_columns),
|
||||
size.width,
|
||||
gutter_vertical,
|
||||
size,
|
||||
viewport,
|
||||
)
|
||||
rows = resolve(
|
||||
repeat_scalars(
|
||||
row_scalars, table_size_rows if table_size_rows else row + 1
|
||||
),
|
||||
size.height,
|
||||
gutter_horizontal,
|
||||
size,
|
||||
viewport,
|
||||
)
|
||||
|
||||
placements: list[WidgetPlacement] = []
|
||||
add_placement = placements.append
|
||||
fraction_unit = Fraction(1)
|
||||
max_column = column_count - 1
|
||||
max_row = row_count - 1
|
||||
widgets: list[Widget] = []
|
||||
add_widget = widgets.append
|
||||
max_column = len(columns) - 1
|
||||
max_row = len(rows) - 1
|
||||
for widget, (column, row, column_span, row_span) in cell_size_map.items():
|
||||
x = columns[column][0]
|
||||
if row > max_row:
|
||||
break
|
||||
y = rows[row][0]
|
||||
x2, cell_width = columns[min(max_column, column + column_span)]
|
||||
y2, cell_height = rows[min(max_row, row + row_span)]
|
||||
@@ -104,5 +149,6 @@ class TableLayout(Layout):
|
||||
)
|
||||
region = Region(x, y, int(width), int(height)).shrink(margin)
|
||||
add_placement(WidgetPlacement(region, widget))
|
||||
add_widget(widget)
|
||||
|
||||
return placements, set(cell_size_map.keys())
|
||||
return placements, {*widgets}
|
||||
|
||||
@@ -189,7 +189,7 @@ class Widget(DOMNode):
|
||||
|
||||
@property
|
||||
def _allow_scroll(self) -> bool:
|
||||
"""Check if both axes may be scrolled.
|
||||
"""Check if both axis may be scrolled.
|
||||
|
||||
Returns:
|
||||
bool: True if horizontal and vertical scrolling is enabled.
|
||||
@@ -668,7 +668,7 @@ class Widget(DOMNode):
|
||||
|
||||
@property
|
||||
def _focus_sort_key(self) -> tuple[int, int]:
|
||||
"""Key function to sort widgets in to focus order."""
|
||||
"""Key function to sort widgets in to tfocus order."""
|
||||
x, y, _, _ = self.virtual_region
|
||||
top, _, _, left = self.styles.margin
|
||||
return y - top, x - left
|
||||
|
||||
Reference in New Issue
Block a user