This commit is contained in:
Will McGugan
2021-11-17 16:59:39 +00:00
parent 6d4292e84f
commit 09dd1b9a5f
8 changed files with 112 additions and 41 deletions

View File

@@ -289,6 +289,7 @@ class App(DOMNode):
def _print_error_renderables(self) -> None:
for renderable in self._exit_renderables:
self.error_console.print(renderable)
self._exit_renderables.clear()
async def process_messages(self) -> None:
active_app.set(self)
@@ -300,7 +301,6 @@ 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()
@@ -310,20 +310,18 @@ class App(DOMNode):
self._print_error_renderables()
return
load_event = events.Load(sender=self)
await self.dispatch_message(load_event)
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()
driver = self._driver = self.driver_class(self.console, self)
try:
load_event = events.Load(sender=self)
await self.dispatch_message(load_event)
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()
driver = self._driver = self.driver_class(self.console, self)
driver.start_application_mode()
except Exception:
self.console.print_exception()
else:
try:
self.title = self._title
self.refresh()
@@ -332,15 +330,17 @@ class App(DOMNode):
log("PROCESS END")
await self.animator.stop()
await self.close_all()
except Exception:
self.panic()
finally:
driver.stop_application_mode()
if self._exit_renderables:
self._print_error_renderables()
if self.log_file is not None:
self.log_file.close()
except:
self.panic()
finally:
if self._exit_renderables:
self._print_error_renderables()
if self.log_file is not None:
self.log_file.close()
def register(self, child: MessagePump, parent: MessagePump) -> bool:
if child not in self.registry:

View File

@@ -17,6 +17,10 @@ if TYPE_CHECKING:
class ScalarProperty:
def __init__(self, units: set[str]) -> None:
self.units = units
super().__init__()
def __set_name__(self, owner: Styles, name: str) -> None:
self.internal_name = f"_rule_{name}"
@@ -27,19 +31,23 @@ class ScalarProperty:
def __set__(
self, obj: Styles, value: float | Scalar | str | None
) -> float | Scalar | str | None:
new_value: Scalar | None = None
if value is None:
setattr(obj, self.internal_name, None)
new_value = None
elif isinstance(value, float):
setattr(obj, self.internal_name, Scalar(value, "cells"))
new_value = Scalar(value, "")
elif isinstance(value, Scalar):
setattr(obj, self.internal_name, value)
new_value = value
elif isinstance(value, str):
try:
setattr(obj, self.internal_name, Scalar.parse(value))
new_value = Scalar.parse(value)
except ScalarParseError:
raise StyleValueError("unable to parse scalar from {value!r}")
else:
raise StyleValueError("expected float, Scalar, or None")
if new_value is not None and new_value.unit not in self.units:
raise StyleValueError(f"units must be one of {friendly_list(self.units)}")
setattr(obj, self.internal_name, new_value)
return value

View File

@@ -6,7 +6,7 @@ import rich.repr
from rich.color import ANSI_COLOR_NAMES, Color
from rich.style import Style
from .constants import VALID_BORDER, VALID_DISPLAY, VALID_VISIBILITY
from .constants import VALID_BORDER, VALID_EDGE, VALID_DISPLAY, VALID_VISIBILITY
from .errors import DeclarationError, StyleValueError
from ._error_tools import friendly_list
from ..geometry import Offset, Spacing, SpacingDimensions
@@ -75,7 +75,7 @@ class StylesBuilder:
if not tokens:
return
if len(tokens) == 1:
setattr(self.styles, f"_rule_{name}", Scalar.parse(tokens[0].value))
setattr(self.styles, name, Scalar.parse(tokens[0].value))
else:
self.error(name, tokens[0], "a single scalar is expected")
@@ -294,8 +294,15 @@ class StylesBuilder:
if token.name == "token":
docks.append((token.value, ""))
elif token.name == "key_value":
key, value = token.value.split("=")
docks.append((key.strip(), value.strip()))
key, group_name = token.value.split("=")
group_name = group_name.strip().lower()
if group_name not in VALID_EDGE:
self.error(
name,
token,
f"edge must be one of 'top', 'right', 'bottom', or 'left'; found {group_name!r}",
)
docks.append((key.strip(), group_name))
elif token.name == "bar":
pass
else:

View File

@@ -21,6 +21,31 @@ class Scalar(NamedTuple):
value, unit = self
return f"{int(value) if value.is_integer() else value}{unit}"
@property
def cells(self) -> int | None:
value, unit = self
if unit:
return None
else:
return int(value)
@property
def fraction(self) -> int | None:
value, unit = self
if unit == "fr":
return int(value)
else:
return None
def resolve_size(self, total: int, total_fraction: int) -> int:
value, unit = self
if unit == "":
return int(value)
elif unit == "%":
return int(total * value / 100.0)
else: # if unit == "fr":
return int((value / total_fraction) * total)
@classmethod
def parse(cls, token: str) -> Scalar:
"""Parse a string in to a Scalar

View File

@@ -103,10 +103,10 @@ class Styles:
outline_bottom = BoxProperty()
outline_left = BoxProperty()
width = ScalarProperty()
height = ScalarProperty()
min_width = ScalarProperty()
min_height = ScalarProperty()
width = ScalarProperty({"", "fr"})
height = ScalarProperty({"", "fr"})
min_width = ScalarProperty({"", "fr"})
min_height = ScalarProperty({"", "fr"})
dock_group = DockGroupProperty()
docks = DocksProperty()

View File

@@ -79,6 +79,10 @@ class DOMNode(MessagePump):
append(node)
return result[::-1]
@property
def visible(self) -> bool:
return self.styles.display != "none"
@property
def tree(self) -> Tree:
highlighter = ReprHighlighter()

View File

@@ -15,6 +15,7 @@ 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
@@ -149,7 +150,7 @@ class Layout(ABC):
)
@abstractmethod
def get_widgets(self) -> Iterable[Widget]:
def get_widgets(self, view: View) -> Iterable[DOMNode]:
...
@abstractmethod
@@ -168,7 +169,7 @@ class Layout(ABC):
"""
async def mount_all(self, view: "View") -> None:
await view.mount(*self.get_widgets())
await view.mount(*self.get_widgets(view))
@property
def map(self) -> LayoutMap | None:

View File

@@ -5,8 +5,7 @@ from collections import defaultdict
from dataclasses import dataclass
from typing import Iterable, TYPE_CHECKING, Sequence
from rich.console import Console
from ..dom import DOMNode
from .._layout_resolve import layout_resolve
from ..geometry import Offset, Region, Size
from ..layout import Layout, WidgetPlacement
@@ -35,14 +34,28 @@ class DockOptions:
@dataclass
class Dock:
edge: DockEdge
widgets: Sequence[Widget]
edge: str
widgets: Sequence[DOMNode]
z: int = 0
class DockLayout(Layout):
def get_widgets(self) -> Iterable[Widget]:
for dock in self.docks:
def __init__(self) -> None:
super().__init__()
self._docks: list[Dock] | None = None
def get_docks(self, view: View) -> list[Dock]:
groups: dict[str, list[DOMNode]] = defaultdict(list)
for child in view.children:
groups[child.styles.dock_group].append(child)
docks: list[Dock] = []
append_dock = docks.append
for name, edge in view.styles.docks:
append_dock(Dock(edge, groups[name], 0))
return docks
def get_widgets(self, view: View) -> Iterable[DOMNode]:
for dock in self.get_docks(view):
yield from dock.widgets
def arrange(
@@ -54,10 +67,23 @@ class DockLayout(Layout):
layout_region = Region(0, 0, width, height)
layers: dict[int, Region] = defaultdict(lambda: layout_region)
for index, dock in enumerate(self.docks):
docks = self.get_docks(view)
for index, dock in enumerate(docks):
dock_options = [
DockOptions(
widget.layout_size, widget.layout_fraction, widget.layout_min_size
(
DockOptions(
widget.styles.width.cells,
widget.styles.width.fraction or 1,
widget.styles.min_width.cells or 1,
)
if dock.edge in ("left", "right")
else DockOptions(
widget.styles.height.cells,
widget.styles.height.fraction or 1,
widget.styles.min_height.cells or 1,
)
)
for widget in dock.widgets
]