psuedo classes

This commit is contained in:
Will McGugan
2022-01-10 11:31:06 +00:00
parent 72d7b5915e
commit 3ffc5826c2
7 changed files with 60 additions and 16 deletions

View File

@@ -4,6 +4,10 @@ App > View {
docks: side=left/1;
}
Widget:hover {
outline: solid green;
}
#sidebar {
text: #09312e on #3caea3;
dock: side;

View File

@@ -239,6 +239,9 @@ class App(DOMNode):
self.stylesheet.update(self)
self.view.refresh(layout=True)
def update_styles(self) -> None:
self.post_message_no_wait(messages.RefreshStyles(self))
def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None:
self.register(self.view, *anon_widgets, **widgets)
self.view.refresh()
@@ -622,6 +625,11 @@ class App(DOMNode):
self.view.query(selector).toggle_class(class_name)
self.view.refresh(layout=True)
async def handle_refresh_styles(self, message: messages.RefreshStyles) -> None:
self.reset_styles()
self.stylesheet.update(self)
self.view.refresh(layout=True)
if __name__ == "__main__":
import asyncio

View File

@@ -45,15 +45,15 @@ class Selector:
@property
def css(self) -> str:
psuedo_suffix = "".join(f":{name}" for name in self.pseudo_classes)
pseudo_suffix = "".join(f":{name}" for name in self.pseudo_classes)
if self.type == SelectorType.UNIVERSAL:
return "*"
elif self.type == SelectorType.TYPE:
return f"{self.name}{psuedo_suffix}"
return f"{self.name}{pseudo_suffix}"
elif self.type == SelectorType.CLASS:
return f".{self.name}{psuedo_suffix}"
return f".{self.name}{pseudo_suffix}"
else:
return f"#{self.name}{psuedo_suffix}"
return f"#{self.name}{pseudo_suffix}"
def __post_init__(self) -> None:
self._name_lower = self.name.lower()
@@ -73,21 +73,21 @@ class Selector:
def _check_type(self, node: DOMNode) -> bool:
if node.css_type != self._name_lower:
return False
if self.pseudo_classes and not node.has_psuedo_class(*self.pseudo_classes):
if self.pseudo_classes and not node.has_pseudo_class(*self.pseudo_classes):
return False
return True
def _check_class(self, node: DOMNode) -> bool:
if not node.has_class(self._name_lower):
return False
if self.pseudo_classes and not node.has_psuedo_class(*self.pseudo_classes):
if self.pseudo_classes and not node.has_pseudo_class(*self.pseudo_classes):
return False
return True
def _check_id(self, node: DOMNode) -> bool:
if not node.id == self._name_lower:
return False
if self.pseudo_classes and not node.has_psuedo_class(*self.pseudo_classes):
if self.pseudo_classes and not node.has_pseudo_class(*self.pseudo_classes):
return False
return True

View File

@@ -99,9 +99,11 @@ class DOMNode(MessagePump):
return frozenset(self._classes)
@property
def psuedo_classes(self) -> set[str]:
"""Get a set of all psuedo classes"""
return set()
def pseudo_classes(self) -> frozenset[str]:
"""Get a set of all pseudo classes"""
pseudo_classes = frozenset({*self.get_pseudo_classes()})
self.log(pseudo_classes)
return pseudo_classes
@property
def css_type(self) -> str:
@@ -186,6 +188,9 @@ class DOMNode(MessagePump):
add_children(tree, self)
return tree
def get_pseudo_classes(self) -> Iterable[str]:
return ()
def reset_styles(self) -> None:
from .widget import Widget
@@ -255,7 +260,7 @@ class DOMNode(MessagePump):
self._classes.symmetric_difference_update(class_names)
self.app.stylesheet.update(self.app)
def has_psuedo_class(self, *class_names: str) -> bool:
"""Check for psuedo class (such as hover, focus etc)"""
has_psuedo_classes = self.psuedo_classes.issuperset(class_names)
return has_psuedo_classes
def has_pseudo_class(self, *class_names: str) -> bool:
"""Check for pseudo class (such as hover, focus etc)"""
has_pseudo_classes = self.pseudo_classes.issuperset(class_names)
return has_pseudo_classes

View File

@@ -66,7 +66,7 @@ class Load(Event, bubble=False):
class Idle(Event, bubble=False):
"""Sent when there are no more items in the message queue.
This is a psuedo-event in that it is created by the Textual system and doesn't go
This is a pseudo-event in that it is created by the Textual system and doesn't go
through the usual message queue.
"""

View File

@@ -41,3 +41,12 @@ class CursorMove(Message):
def __init__(self, sender: MessagePump, line: int) -> None:
self.line = line
super().__init__(sender)
@rich.repr.auto
class RefreshStyles(Message):
def __init__(self, sender: MessagePump) -> None:
super().__init__(sender)
def can_replace(self, message: Message) -> bool:
return isinstance(message, RefreshStyles)

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from logging import getLogger
from logging import PercentStyle, getLogger
from typing import (
Any,
Awaitable,
@@ -77,6 +77,7 @@ class Widget(DOMNode):
self._layout_required = False
self._animate: BoundAnimator | None = None
self._reactive_watches: dict[str, Callable] = {}
self._mouse_over: bool = False
self.render_cache: RenderCache | None = None
self.highlight_style: Style | None = None
@@ -92,11 +93,19 @@ class Widget(DOMNode):
yield "name", self.name
if self.classes:
yield "classes", self.classes
pseudo_classes = self.pseudo_classes
if pseudo_classes:
yield "pseudo_classes", pseudo_classes
yield "outline", self.styles.outline
def __rich__(self) -> RenderableType:
renderable = self.render_styled()
return renderable
def get_pseudo_classes(self) -> Iterable[str]:
if self._mouse_over:
yield "hover"
def get_child_by_id(self, id: str) -> Widget:
"""Get a child with a given id.
@@ -212,6 +221,7 @@ class Widget(DOMNode):
return gutter
def on_style_change(self) -> None:
self.log("style_Change", self)
self.clear_render_cache()
def _update_size(self, size: Size) -> None:
@@ -359,3 +369,11 @@ class Widget(DOMNode):
async def on_click(self, event: events.Click) -> None:
await self.broker_event("click", event)
async def on_enter(self, event: events.Enter) -> None:
self._mouse_over = True
self.app.update_styles()
async def on_leave(self, event: events.Leave) -> None:
self._mouse_over = False
self.app.update_styles()