mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
dock
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user