more functionality via CSS

This commit is contained in:
Will McGugan
2022-08-29 15:00:10 +01:00
parent 10ffbc0566
commit e2306decee
6 changed files with 133 additions and 54 deletions

View File

@@ -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 {

View File

@@ -29,7 +29,6 @@ class CalculatorApp(App):
Button("=", variant="warning"),
id="calculator",
)
self.dark = True
app = CalculatorApp(css_path="calculator.css")

View File

@@ -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.

View File

@@ -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)

View File

@@ -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}

View File

@@ -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