[css] Add "scrollbar-size" CSS properties - first step

This commit is contained in:
Olivier Philippon
2022-05-18 15:26:51 +01:00
parent 2b485cd4cc
commit ee30b54828
6 changed files with 142 additions and 21 deletions

View File

@@ -107,14 +107,7 @@ class BasicApp(App, css_path="basic.css"):
), ),
), ),
content=Widget( content=Widget(
Tweet( Tweet(TweetBody(), classes="scrollbar-size-test"),
TweetBody(),
# Widget(
# Widget(classes={"button"}),
# Widget(classes={"button"}),
# classes={"horizontal"},
# ),
),
Widget( Widget(
Static(Syntax(CODE, "python"), classes="code"), Static(Syntax(CODE, "python"), classes="code"),
classes="scrollable", classes="scrollable",

View File

@@ -356,6 +356,7 @@ class Compositor:
container_size, container_size,
container_size, container_size,
) )
t = 1
# Add the container widget, which will render a background # Add the container widget, which will render a background
map[widget] = MapGeometry( map[widget] = MapGeometry(

View File

@@ -784,6 +784,56 @@ class StylesBuilder:
else: else:
self.styles._rules[name.replace("-", "_")] = value self.styles._rules[name.replace("-", "_")] = value
def process_scrollbar_size(self, name: str, tokens: list[Token]) -> None:
def offset_error(name: str, token: Token) -> None:
# TODO: handle help text
self.error(name, token, offset_property_help_text(context="css"))
if not tokens:
return
if len(tokens) != 2:
offset_error(name, tokens[0])
else:
token1, token2 = tokens
if token1.name not in ("scalar", "number"):
offset_error(name, token1)
if token2.name not in ("scalar", "number"):
offset_error(name, token2)
vertical = Scalar.parse(token1.value, Unit.HEIGHT)
horizontal = Scalar.parse(token2.value, Unit.WIDTH)
self.styles._rules["scrollbar_size_vertical"] = vertical
self.styles._rules["scrollbar_size_horizontal"] = horizontal
def process_scrollbar_size_vertical(self, name: str, tokens: list[Token]) -> None:
if not tokens:
return
if len(tokens) != 1:
# TODO: handle help text
self.error(name, tokens[0], offset_single_axis_help_text(name))
else:
token = tokens[0]
if token.name not in ("scalar", "number"):
self.error(name, token, offset_single_axis_help_text(name))
self.styles._rules["scrollbar_size_vertical"] = Scalar.parse(
token.value, Unit.HEIGHT
)
def process_scrollbar_size_horizontal(self, name: str, tokens: list[Token]) -> None:
if not tokens:
return
if len(tokens) != 1:
# TODO: handle help text
self.error(name, tokens[0], offset_single_axis_help_text(name))
else:
token = tokens[0]
if token.name not in ("scalar", "number"):
self.error(name, token, offset_single_axis_help_text(name))
self.styles._rules["scrollbar_size_horizontal"] = Scalar.parse(
token.value, Unit.WIDTH
)
def _get_suggested_property_name_for_rule(self, rule_name: str) -> str | None: 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. Returns a valid CSS property "Python" name, or None if no close matches could be found.

View File

@@ -129,6 +129,9 @@ class RulesMap(TypedDict, total=False):
scrollbar_gutter: ScrollbarGutter scrollbar_gutter: ScrollbarGutter
scrollbar_size_vertical: Scalar
scrollbar_size_horizontal: Scalar
align_horizontal: AlignHorizontal align_horizontal: AlignHorizontal
align_vertical: AlignVertical align_vertical: AlignVertical
@@ -228,6 +231,13 @@ class StylesBase(ABC):
scrollbar_gutter = StringEnumProperty(VALID_SCROLLBAR_GUTTER, "auto") scrollbar_gutter = StringEnumProperty(VALID_SCROLLBAR_GUTTER, "auto")
scrollbar_size_vertical = ScalarProperty(
units={Unit.CELLS}, percent_unit=Unit.WIDTH, allow_auto=False
)
scrollbar_size_horizontal = ScalarProperty(
units={Unit.CELLS}, percent_unit=Unit.HEIGHT, allow_auto=False
)
align_horizontal = StringEnumProperty(VALID_ALIGN_HORIZONTAL, "left") align_horizontal = StringEnumProperty(VALID_ALIGN_HORIZONTAL, "left")
align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top") align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top")
@@ -664,6 +674,16 @@ class Styles(StylesBase):
append_declaration("overflow-y", self.overflow_y) append_declaration("overflow-y", self.overflow_y)
if has_rule("scrollbar-gutter"): if has_rule("scrollbar-gutter"):
append_declaration("scrollbar-gutter", self.scrollbar_gutter) append_declaration("scrollbar-gutter", self.scrollbar_gutter)
if has_rule("scrollbar-size"):
append_declaration("scrollbar-size", self.scrollbar_size)
if has_rule("scrollbar-size-vertical"):
append_declaration(
"scrollbar-size-vertical", str(self.scrollbar_size_vertical)
)
if has_rule("scrollbar-size-horizontal"):
append_declaration(
"scrollbar-size-horizontal", str(self.scrollbar_size_horizontal)
)
if has_rule("box-sizing"): if has_rule("box-sizing"):
append_declaration("box-sizing", self.box_sizing) append_declaration("box-sizing", self.box_sizing)

View File

@@ -188,8 +188,11 @@ class ScrollBarRender:
@rich.repr.auto @rich.repr.auto
class ScrollBar(Widget): class ScrollBar(Widget):
def __init__(self, vertical: bool = True, name: str | None = None) -> None: def __init__(
self, vertical: bool = True, name: str | None = None, *, thickness: int = 1
) -> None:
self.vertical = vertical self.vertical = vertical
self.thickness = thickness
self.grabbed_position: float = 0 self.grabbed_position: float = 0
super().__init__(name=name) super().__init__(name=name)
@@ -204,6 +207,8 @@ class ScrollBar(Widget):
yield "window_virtual_size", self.window_virtual_size yield "window_virtual_size", self.window_virtual_size
yield "window_size", self.window_size yield "window_size", self.window_size
yield "position", self.position yield "position", self.position
if self.thickness > 1:
yield "thickness", self.thickness
def render(self, style: Style) -> RenderableType: def render(self, style: Style) -> RenderableType:
styles = self.parent.styles styles = self.parent.styles
@@ -223,6 +228,7 @@ class ScrollBar(Widget):
virtual_size=self.window_virtual_size, virtual_size=self.window_virtual_size,
window_size=self.window_size, window_size=self.window_size,
position=self.position, position=self.position,
thickness=self.thickness,
vertical=self.vertical, vertical=self.vertical,
style=scrollbar_style, style=scrollbar_style,
) )
@@ -283,8 +289,12 @@ if __name__ == "__main__":
from rich.console import Console from rich.console import Console
console = Console() console = Console()
bar = ScrollBarRender()
console.print( thickness = 2
ScrollBarRender(position=15.3, window_size=100, thickness=5, vertical=True) console.print(f"Bars thickness: {thickness}")
)
console.print("Vertical bar:")
console.print(ScrollBarRender.render_bar(thickness=thickness))
console.print("Horizontal bar:")
console.print(ScrollBarRender.render_bar(vertical=False, thickness=thickness))

View File

@@ -288,8 +288,13 @@ class Widget(DOMNode):
if self._vertical_scrollbar is not None: if self._vertical_scrollbar is not None:
return self._vertical_scrollbar return self._vertical_scrollbar
thickness = (
1
if self.styles.scrollbar_size_vertical is None
else int(self.styles.scrollbar_size_vertical.value)
)
self._vertical_scrollbar = scroll_bar = ScrollBar( self._vertical_scrollbar = scroll_bar = ScrollBar(
vertical=True, name="vertical" vertical=True, name="vertical", thickness=thickness
) )
self.app.start_widget(self, scroll_bar) self.app.start_widget(self, scroll_bar)
return scroll_bar return scroll_bar
@@ -305,8 +310,13 @@ class Widget(DOMNode):
if self._horizontal_scrollbar is not None: if self._horizontal_scrollbar is not None:
return self._horizontal_scrollbar return self._horizontal_scrollbar
thickness = (
1
if self.styles.scrollbar_size_horizontal is None
else int(self.styles.scrollbar_size_horizontal.value)
)
self._horizontal_scrollbar = scroll_bar = ScrollBar( self._horizontal_scrollbar = scroll_bar = ScrollBar(
vertical=False, name="horizontal" vertical=False, name="horizontal", thickness=thickness
) )
self.app.start_widget(self, scroll_bar) self.app.start_widget(self, scroll_bar)
@@ -320,6 +330,9 @@ class Widget(DOMNode):
styles = self.styles styles = self.styles
overflow_x = styles.overflow_x overflow_x = styles.overflow_x
overflow_y = styles.overflow_y overflow_y = styles.overflow_y
scrollbar_size_horizontal = styles.scrollbar_size_horizontal
scrollbar_size_vertical = styles.scrollbar_size_vertical
overflow_y = styles.overflow_y
width, height = self.container_size width, height = self.container_size
show_horizontal = self.show_horizontal_scrollbar show_horizontal = self.show_horizontal_scrollbar
@@ -329,6 +342,8 @@ class Widget(DOMNode):
show_horizontal = True show_horizontal = True
elif overflow_x == "auto": elif overflow_x == "auto":
show_horizontal = self.virtual_size.width > width show_horizontal = self.virtual_size.width > width
if scrollbar_size_horizontal == 0:
show_horizontal = False
show_vertical = self.show_vertical_scrollbar show_vertical = self.show_vertical_scrollbar
if overflow_y == "hidden": if overflow_y == "hidden":
@@ -337,6 +352,8 @@ class Widget(DOMNode):
show_vertical = True show_vertical = True
elif overflow_y == "auto": elif overflow_y == "auto":
show_vertical = self.virtual_size.height > height show_vertical = self.virtual_size.height > height
if scrollbar_size_vertical == 0:
show_vertical = False
self.show_horizontal_scrollbar = show_horizontal self.show_horizontal_scrollbar = show_horizontal
self.show_vertical_scrollbar = show_vertical self.show_vertical_scrollbar = show_vertical
@@ -577,12 +594,19 @@ class Widget(DOMNode):
if self.styles.scrollbar_gutter == "stable": if self.styles.scrollbar_gutter == "stable":
# Let's _always_ reserve some space, whether the scrollbar is actually displayed or not: # Let's _always_ reserve some space, whether the scrollbar is actually displayed or not:
show_vertical_scrollbar = True show_vertical_scrollbar = True
(
vertical_scrollbar_thickness,
horizontal_scrollbar_thickness,
) = self._get_scrollbar_thicknesses()
if show_horizontal_scrollbar and show_vertical_scrollbar: if show_horizontal_scrollbar and show_vertical_scrollbar:
(region, _, _, _) = region.split(-1, -1) (region, _, _, _) = region.split(
-horizontal_scrollbar_thickness, -vertical_scrollbar_thickness
)
elif show_vertical_scrollbar: elif show_vertical_scrollbar:
region, _ = region.split_vertical(-1) region, _ = region.split_vertical(-vertical_scrollbar_thickness)
elif show_horizontal_scrollbar: elif show_horizontal_scrollbar:
region, _ = region.split_horizontal(-1) region, _ = region.split_horizontal(-horizontal_scrollbar_thickness)
return region return region
def _arrange_scrollbars(self, size: Size) -> Iterable[tuple[Widget, Region]]: def _arrange_scrollbars(self, size: Size) -> Iterable[tuple[Widget, Region]]:
@@ -600,26 +624,49 @@ class Widget(DOMNode):
region = size.region region = size.region
show_vertical_scrollbar, show_horizontal_scrollbar = self.scrollbars_enabled show_vertical_scrollbar, show_horizontal_scrollbar = self.scrollbars_enabled
(
vertical_scrollbar_thickness,
horizontal_scrollbar_thickness,
) = self._get_scrollbar_thicknesses()
if show_horizontal_scrollbar and show_vertical_scrollbar: if show_horizontal_scrollbar and show_vertical_scrollbar:
( (
region, region,
vertical_scrollbar_region, vertical_scrollbar_region,
horizontal_scrollbar_region, horizontal_scrollbar_region,
_, _,
) = region.split(-1, -1) ) = region.split(
-horizontal_scrollbar_thickness, -vertical_scrollbar_thickness
)
if vertical_scrollbar_region: if vertical_scrollbar_region:
yield self.vertical_scrollbar, vertical_scrollbar_region yield self.vertical_scrollbar, vertical_scrollbar_region
if horizontal_scrollbar_region: if horizontal_scrollbar_region:
yield self.horizontal_scrollbar, horizontal_scrollbar_region yield self.horizontal_scrollbar, horizontal_scrollbar_region
elif show_vertical_scrollbar: elif show_vertical_scrollbar:
region, scrollbar_region = region.split_vertical(-1) region, scrollbar_region = region.split_vertical(
-vertical_scrollbar_thickness
)
if scrollbar_region: if scrollbar_region:
yield self.vertical_scrollbar, scrollbar_region yield self.vertical_scrollbar, scrollbar_region
elif show_horizontal_scrollbar: elif show_horizontal_scrollbar:
region, scrollbar_region = region.split_horizontal(-1) region, scrollbar_region = region.split_horizontal(
-horizontal_scrollbar_thickness
)
if scrollbar_region: if scrollbar_region:
yield self.horizontal_scrollbar, scrollbar_region yield self.horizontal_scrollbar, scrollbar_region
def _get_scrollbar_thicknesses(self) -> tuple[int, int]:
"""
Returns:
tuple[int, int]: first integer is the thickness of the vertical scrollbar,
2nd integer is the thickness of the horizontal scrollbar
"""
vertical_scrollbar_size = horizontal_scrollbar_size = 1
if self.styles.scrollbar_size_vertical is not None:
vertical_scrollbar_size = int(self.styles.scrollbar_size_vertical.value)
if self.styles.scrollbar_size_horizontal is not None:
horizontal_scrollbar_size = int(self.styles.scrollbar_size_horizontal.value)
return vertical_scrollbar_size, horizontal_scrollbar_size
def get_pseudo_classes(self) -> Iterable[str]: def get_pseudo_classes(self) -> Iterable[str]:
"""Pseudo classes for a widget""" """Pseudo classes for a widget"""
if self.mouse_over: if self.mouse_over: