This commit is contained in:
Will McGugan
2021-11-19 12:15:29 +00:00
parent 09dd1b9a5f
commit b47b0ac734
10 changed files with 55 additions and 51 deletions

View File

@@ -7,7 +7,7 @@ class BasicApp(App):
css = """ css = """
App > View { App > DockView {
layout: dock; layout: dock;
docks: sidebar=left | widgets=top; docks: sidebar=left | widgets=top;
} }

View File

@@ -6,7 +6,6 @@ from weakref import ref
import rich.repr import rich.repr
if TYPE_CHECKING: if TYPE_CHECKING:
from .widget import Widget
from .dom import DOMNode from .dom import DOMNode
@@ -20,8 +19,8 @@ class NodeList:
""" """
def __init__(self) -> None: def __init__(self) -> None:
self._widget_refs: list[ref[DOMNode]] = [] self._node_refs: list[ref[DOMNode]] = []
self.__widgets: list[DOMNode] | None = [] self.__nodes: list[DOMNode] | None = []
def __rich_repr__(self) -> rich.repr.Result: def __rich_repr__(self) -> rich.repr.Result:
yield self._widgets yield self._widgets
@@ -29,38 +28,38 @@ class NodeList:
def __len__(self) -> int: def __len__(self) -> int:
return len(self._widgets) return len(self._widgets)
def __contains__(self, widget: Widget) -> bool: def __contains__(self, widget: DOMNode) -> bool:
return widget in self._widgets return widget in self._widgets
@property @property
def _widgets(self) -> list[DOMNode]: def _widgets(self) -> list[DOMNode]:
if self.__widgets is None: if self.__nodes is None:
self.__widgets = list( self.__nodes = list(
filter(None, [widget_ref() for widget_ref in self._widget_refs]) filter(None, [widget_ref() for widget_ref in self._node_refs])
) )
return self.__widgets return self.__nodes
def _prune(self) -> None: def _prune(self) -> None:
"""Remove expired references.""" """Remove expired references."""
self._widget_refs[:] = filter( self._node_refs[:] = filter(
None, None,
[ [
None if widget_ref() is None else widget_ref None if widget_ref() is None else widget_ref
for widget_ref in self._widget_refs for widget_ref in self._node_refs
], ],
) )
def _append(self, widget: DOMNode) -> None: def _append(self, widget: DOMNode) -> None:
if widget not in self._widgets: if widget not in self._widgets:
self._widget_refs.append(ref(widget)) self._node_refs.append(ref(widget))
self.__widgets = None self.__nodes = None
def _clear(self) -> None: def _clear(self) -> None:
del self._widget_refs[:] del self._node_refs[:]
self.__widgets = None self.__nodes = None
def __iter__(self) -> Iterator[DOMNode]: def __iter__(self) -> Iterator[DOMNode]:
for widget_ref in self._widget_refs: for widget_ref in self._node_refs:
widget = widget_ref() widget = widget_ref()
if widget is not None: if widget is not None:
yield widget yield widget

View File

@@ -3,7 +3,7 @@ import os
import asyncio import asyncio
from typing import Any, Callable, ClassVar, Type, TypeVar from typing import Any, Callable, ClassVar, Iterable, Type, TypeVar
import warnings import warnings
from rich.control import Control from rich.control import Control
@@ -301,6 +301,7 @@ class App(DOMNode):
self.stylesheet.read(self.css_file) self.stylesheet.read(self.css_file)
if self.css is not None: if self.css is not None:
self.stylesheet.parse(self.css, path=f"<{self.__class__.__name__}>") self.stylesheet.parse(self.css, path=f"<{self.__class__.__name__}>")
print(self.stylesheet.css)
except StylesheetParseError as error: except StylesheetParseError as error:
self.panic(error) self.panic(error)
self._print_error_renderables() self._print_error_renderables()
@@ -313,8 +314,10 @@ class App(DOMNode):
try: try:
load_event = events.Load(sender=self) load_event = events.Load(sender=self)
await self.dispatch_message(load_event) await self.dispatch_message(load_event)
view = DockView()
await self.mount(view)
await self.push_view(view)
await self.post_message(events.Mount(self)) await self.post_message(events.Mount(self))
await self.push_view(DockView())
# Wait for the load event to be processed, so we don't go in to application mode beforehand # Wait for the load event to be processed, so we don't go in to application mode beforehand
await load_event.wait() await load_event.wait()
@@ -342,15 +345,27 @@ class App(DOMNode):
if self.log_file is not None: if self.log_file is not None:
self.log_file.close() self.log_file.close()
def register(self, child: MessagePump, parent: MessagePump) -> bool: def register(self, child: DOMNode, parent: DOMNode) -> bool:
if child not in self.registry: if child not in self.registry:
self.registry.add(child) self.registry.add(child)
child.set_parent(parent) child.set_parent(parent)
child.start_messages() child.start_messages()
child.post_message_no_wait(events.Mount(sender=parent)) child.post_message_no_wait(events.Mount(sender=parent))
parent.children._append(child)
return True return True
return False return False
async def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None:
name_widgets: Iterable[tuple[str | None, Widget]]
name_widgets = [*((None, widget) for widget in anon_widgets), *widgets.items()]
apply_stylesheet = self.stylesheet.apply
for widget_id, widget in name_widgets:
if widget_id is not None:
widget.id = widget_id
self.register(widget, self)
apply_stylesheet(widget)
def is_mounted(self, widget: Widget) -> bool: def is_mounted(self, widget: Widget) -> bool:
return widget in self.registry return widget in self.registry

View File

@@ -10,19 +10,19 @@ from .match import match
from .parse import parse_selectors from .parse import parse_selectors
if TYPE_CHECKING: if TYPE_CHECKING:
from ..dom import DOMNode from ..widget import Widget
@rich.repr.auto(angular=True) @rich.repr.auto(angular=True)
class DOMQuery: class DOMQuery:
def __init__( def __init__(
self, self,
node: DOMNode | None = None, node: Widget | None = None,
selector: str | None = None, selector: str | None = None,
nodes: Iterable[DOMNode] | None = None, nodes: Iterable[Widget] | None = None,
) -> None: ) -> None:
self._nodes: list[DOMNode] self._nodes: list[Widget] = []
if nodes is not None: if nodes is not None:
self._nodes = list(nodes) self._nodes = list(nodes)
elif node is not None: elif node is not None:
@@ -34,7 +34,7 @@ class DOMQuery:
selector_set = parse_selectors(selector) selector_set = parse_selectors(selector)
self._nodes = [_node for _node in self._nodes if match(selector_set, _node)] self._nodes = [_node for _node in self._nodes if match(selector_set, _node)]
def __iter__(self) -> Iterator[DOMNode]: def __iter__(self) -> Iterator[Widget]:
return iter(self._nodes) return iter(self._nodes)
def __rich_repr__(self) -> rich.repr.Result: def __rich_repr__(self) -> rich.repr.Result:
@@ -45,3 +45,6 @@ class DOMQuery:
query = DOMQuery() query = DOMQuery()
query._nodes = [_node for _node in self._nodes if match(selector_set, _node)] query._nodes = [_node for _node in self._nodes if match(selector_set, _node)]
return query return query
def first(self) -> Widget:
return self._nodes[0]

View File

@@ -21,6 +21,7 @@ from .model import RuleSet
from .parse import parse from .parse import parse
from .types import Specificity3, Specificity4 from .types import Specificity3, Specificity4
from ..dom import DOMNode from ..dom import DOMNode
from .. import log
class StylesheetParseError(Exception): class StylesheetParseError(Exception):
@@ -133,6 +134,7 @@ class Stylesheet:
for name, specificity_rules in rule_attributes.items() for name, specificity_rules in rule_attributes.items()
] ]
node.styles.apply_rules(node_rules) node.styles.apply_rules(node_rules)
log(node, node_rules)
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import Iterable, Iterator, TYPE_CHECKING from typing import cast, Iterable, Iterator, TYPE_CHECKING
from rich.highlighter import ReprHighlighter from rich.highlighter import ReprHighlighter
import rich.repr import rich.repr
@@ -14,6 +14,7 @@ from ._node_list import NodeList
if TYPE_CHECKING: if TYPE_CHECKING:
from .css.query import DOMQuery from .css.query import DOMQuery
from .widget import Widget
@rich.repr.auto @rich.repr.auto

View File

@@ -15,7 +15,6 @@ from rich.segment import Segment, SegmentLines
from rich.style import Style from rich.style import Style
from . import log, panic from . import log, panic
from .dom import DOMNode
from ._loop import loop_last from ._loop import loop_last
from .layout_map import LayoutMap from .layout_map import LayoutMap
from ._profile import timer from ._profile import timer
@@ -150,7 +149,7 @@ class Layout(ABC):
) )
@abstractmethod @abstractmethod
def get_widgets(self, view: View) -> Iterable[DOMNode]: def get_widgets(self, view: View) -> Iterable[Widget]:
... ...
@abstractmethod @abstractmethod

View File

@@ -5,11 +5,14 @@ from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from typing import Iterable, TYPE_CHECKING, Sequence from typing import Iterable, TYPE_CHECKING, Sequence
from ..app import active_app
from ..dom import DOMNode from ..dom import DOMNode
from .._layout_resolve import layout_resolve from .._layout_resolve import layout_resolve
from ..geometry import Offset, Region, Size from ..geometry import Offset, Region, Size
from ..layout import Layout, WidgetPlacement from ..layout import Layout, WidgetPlacement
from ..layout_map import LayoutMap from ..layout_map import LayoutMap
from ..widget import Widget
if sys.version_info >= (3, 8): if sys.version_info >= (3, 8):
from typing import Literal from typing import Literal
@@ -35,7 +38,7 @@ class DockOptions:
@dataclass @dataclass
class Dock: class Dock:
edge: str edge: str
widgets: Sequence[DOMNode] widgets: Sequence[Widget]
z: int = 0 z: int = 0
@@ -45,8 +48,9 @@ class DockLayout(Layout):
self._docks: list[Dock] | None = None self._docks: list[Dock] | None = None
def get_docks(self, view: View) -> list[Dock]: def get_docks(self, view: View) -> list[Dock]:
groups: dict[str, list[DOMNode]] = defaultdict(list) groups: dict[str, list[Widget]] = defaultdict(list)
for child in view.children: for child in view.children:
assert isinstance(child, Widget)
groups[child.styles.dock_group].append(child) groups[child.styles.dock_group].append(child)
docks: list[Dock] = [] docks: list[Dock] = []
append_dock = docks.append append_dock = docks.append

View File

@@ -138,15 +138,7 @@ class View(Widget):
self.app.refresh() self.app.refresh()
async def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None: async def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None:
await self.app.mount(*anon_widgets, **widgets)
name_widgets: Iterable[tuple[str | None, Widget]]
name_widgets = [*((None, widget) for widget in anon_widgets), *widgets.items()]
apply_stylesheet = self.app.stylesheet.apply
for widget_id, widget in name_widgets:
if widget_id is not None:
widget.id = widget_id
apply_stylesheet(widget)
self._add_child(widget)
self.refresh() self.refresh()
async def refresh_layout(self) -> None: async def refresh_layout(self) -> None:

View File

@@ -8,7 +8,6 @@ from typing import (
Callable, Callable,
ClassVar, ClassVar,
NamedTuple, NamedTuple,
NewType,
cast, cast,
) )
import rich.repr import rich.repr
@@ -116,25 +115,15 @@ class Widget(DOMNode):
renderable = self.render_styled() renderable = self.render_styled()
return renderable return renderable
def _add_child(self, widget: Widget) -> Widget:
"""Add a child widget.
Args:
widget (Widget): Widget
"""
self.app.register(widget, self)
self.children._append(widget)
return widget
def get_child(self, name: str | None = None, id: str | None = None) -> Widget: def get_child(self, name: str | None = None, id: str | None = None) -> Widget:
if name is not None: if name is not None:
for widget in self.children: for widget in self.children:
if widget.name == name: if widget.name == name:
return widget return cast(Widget, widget)
if id is not None: if id is not None:
for widget in self.children: for widget in self.children:
if widget.id == id: if widget.id == id:
return widget return cast(Widget, widget)
raise errors.MissingWidget(f"Widget named {name!r} was not found in {self}") raise errors.MissingWidget(f"Widget named {name!r} was not found in {self}")
def watch(self, attribute_name, callback: Callable[[Any], Awaitable[None]]) -> None: def watch(self, attribute_name, callback: Callable[[Any], Awaitable[None]]) -> None: