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 = """
App > View {
App > DockView {
layout: dock;
docks: sidebar=left | widgets=top;
}

View file

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

View file

@ -3,7 +3,7 @@ import os
import asyncio
from typing import Any, Callable, ClassVar, Type, TypeVar
from typing import Any, Callable, ClassVar, Iterable, Type, TypeVar
import warnings
from rich.control import Control
@ -301,6 +301,7 @@ class App(DOMNode):
self.stylesheet.read(self.css_file)
if self.css is not None:
self.stylesheet.parse(self.css, path=f"<{self.__class__.__name__}>")
print(self.stylesheet.css)
except StylesheetParseError as error:
self.panic(error)
self._print_error_renderables()
@ -313,8 +314,10 @@ class App(DOMNode):
try:
load_event = events.Load(sender=self)
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.push_view(DockView())
# Wait for the load event to be processed, so we don't go in to application mode beforehand
await load_event.wait()
@ -342,15 +345,27 @@ class App(DOMNode):
if self.log_file is not None:
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:
self.registry.add(child)
child.set_parent(parent)
child.start_messages()
child.post_message_no_wait(events.Mount(sender=parent))
parent.children._append(child)
return True
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:
return widget in self.registry

View file

@ -10,19 +10,19 @@ from .match import match
from .parse import parse_selectors
if TYPE_CHECKING:
from ..dom import DOMNode
from ..widget import Widget
@rich.repr.auto(angular=True)
class DOMQuery:
def __init__(
self,
node: DOMNode | None = None,
node: Widget | None = None,
selector: str | None = None,
nodes: Iterable[DOMNode] | None = None,
nodes: Iterable[Widget] | None = None,
) -> None:
self._nodes: list[DOMNode]
self._nodes: list[Widget] = []
if nodes is not None:
self._nodes = list(nodes)
elif node is not None:
@ -34,7 +34,7 @@ class DOMQuery:
selector_set = parse_selectors(selector)
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)
def __rich_repr__(self) -> rich.repr.Result:
@ -45,3 +45,6 @@ class DOMQuery:
query = DOMQuery()
query._nodes = [_node for _node in self._nodes if match(selector_set, _node)]
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 .types import Specificity3, Specificity4
from ..dom import DOMNode
from .. import log
class StylesheetParseError(Exception):
@ -133,6 +134,7 @@ class Stylesheet:
for name, specificity_rules in rule_attributes.items()
]
node.styles.apply_rules(node_rules)
log(node, node_rules)
if __name__ == "__main__":

View file

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

View file

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

View file

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

View file

@ -138,15 +138,7 @@ class View(Widget):
self.app.refresh()
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.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)
await self.app.mount(*anon_widgets, **widgets)
self.refresh()
async def refresh_layout(self) -> None:

View file

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