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; docks: side=left/1;
} }
Widget:hover {
outline: solid green;
}
#sidebar { #sidebar {
text: #09312e on #3caea3; text: #09312e on #3caea3;
dock: side; dock: side;

View File

@@ -239,6 +239,9 @@ class App(DOMNode):
self.stylesheet.update(self) self.stylesheet.update(self)
self.view.refresh(layout=True) 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: def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None:
self.register(self.view, *anon_widgets, **widgets) self.register(self.view, *anon_widgets, **widgets)
self.view.refresh() self.view.refresh()
@@ -622,6 +625,11 @@ class App(DOMNode):
self.view.query(selector).toggle_class(class_name) self.view.query(selector).toggle_class(class_name)
self.view.refresh(layout=True) 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__": if __name__ == "__main__":
import asyncio import asyncio

View File

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

View File

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

View File

@@ -66,7 +66,7 @@ class Load(Event, bubble=False):
class Idle(Event, bubble=False): class Idle(Event, bubble=False):
"""Sent when there are no more items in the message queue. """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. through the usual message queue.
""" """

View File

@@ -41,3 +41,12 @@ class CursorMove(Message):
def __init__(self, sender: MessagePump, line: int) -> None: def __init__(self, sender: MessagePump, line: int) -> None:
self.line = line self.line = line
super().__init__(sender) 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 __future__ import annotations
from logging import getLogger from logging import PercentStyle, getLogger
from typing import ( from typing import (
Any, Any,
Awaitable, Awaitable,
@@ -77,6 +77,7 @@ class Widget(DOMNode):
self._layout_required = False self._layout_required = False
self._animate: BoundAnimator | None = None self._animate: BoundAnimator | None = None
self._reactive_watches: dict[str, Callable] = {} self._reactive_watches: dict[str, Callable] = {}
self._mouse_over: bool = False
self.render_cache: RenderCache | None = None self.render_cache: RenderCache | None = None
self.highlight_style: Style | None = None self.highlight_style: Style | None = None
@@ -92,11 +93,19 @@ class Widget(DOMNode):
yield "name", self.name yield "name", self.name
if self.classes: if self.classes:
yield "classes", 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: def __rich__(self) -> RenderableType:
renderable = self.render_styled() renderable = self.render_styled()
return renderable return renderable
def get_pseudo_classes(self) -> Iterable[str]:
if self._mouse_over:
yield "hover"
def get_child_by_id(self, id: str) -> Widget: def get_child_by_id(self, id: str) -> Widget:
"""Get a child with a given id. """Get a child with a given id.
@@ -212,6 +221,7 @@ class Widget(DOMNode):
return gutter return gutter
def on_style_change(self) -> None: def on_style_change(self) -> None:
self.log("style_Change", self)
self.clear_render_cache() self.clear_render_cache()
def _update_size(self, size: Size) -> None: def _update_size(self, size: Size) -> None:
@@ -359,3 +369,11 @@ class Widget(DOMNode):
async def on_click(self, event: events.Click) -> None: async def on_click(self, event: events.Click) -> None:
await self.broker_event("click", event) 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()