mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
key lines
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
from textual.app import App
|
from textual.app import App
|
||||||
from textual.widgets import Placeholder
|
from textual.widget import Widget
|
||||||
|
|
||||||
|
|
||||||
class BasicApp(App):
|
class BasicApp(App):
|
||||||
@@ -9,35 +9,51 @@ class BasicApp(App):
|
|||||||
|
|
||||||
App > DockView {
|
App > DockView {
|
||||||
layout: dock;
|
layout: dock;
|
||||||
docks: sidebar=left/1 widgets=top;
|
docks: side=left/1 header=top footer=bottom;
|
||||||
layers: base panels;
|
layers: base panels;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar {
|
#sidebar {
|
||||||
dock-group: sidebar;
|
text: bold #09312e on #3CAEA3;
|
||||||
width: 40;
|
/* dock-group: header; */
|
||||||
|
width: 30;
|
||||||
|
height: 1fr;
|
||||||
layer: panels;
|
layer: panels;
|
||||||
|
border-right: vkey #09312e;
|
||||||
}
|
}
|
||||||
|
|
||||||
#widget1 {
|
#header {
|
||||||
text: on blue;
|
text: on #173f5f;
|
||||||
dock-group: widgets;
|
dock-group: header;
|
||||||
height: 1fr;
|
height: 3;
|
||||||
|
border: hkey white;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#widget2 {
|
#footer {
|
||||||
text: on red;
|
dock-group: header;
|
||||||
dock-group: widgets;
|
height: 3;
|
||||||
height: 1fr;
|
border-top: hkey #0f2b41;
|
||||||
|
text: #3a3009 on #f6d55c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
dock-group: header;
|
||||||
|
text: on #20639B;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def on_mount(self) -> None:
|
async def on_mount(self) -> None:
|
||||||
"""Build layout here."""
|
"""Build layout here."""
|
||||||
await self.view.mount(
|
await self.view.mount(
|
||||||
sidebar=Placeholder(), widget1=Placeholder(), widget2=Placeholder()
|
header=Widget(),
|
||||||
|
content=Widget(),
|
||||||
|
footer=Widget(),
|
||||||
|
sidebar=Widget(),
|
||||||
)
|
)
|
||||||
|
self.panic(self.view.styles)
|
||||||
|
|
||||||
|
|
||||||
BasicApp.run(log="textual.log")
|
BasicApp.run(log="textual.log")
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ BORDER_STYLES: dict[str, tuple[str, str, str]] = {
|
|||||||
"heavy": ("┏━┓", "┃ ┃", "┗━┛"),
|
"heavy": ("┏━┓", "┃ ┃", "┗━┛"),
|
||||||
"inner": ("▗▄▖", "▐ ▌", "▝▀▘"),
|
"inner": ("▗▄▖", "▐ ▌", "▝▀▘"),
|
||||||
"outer": ("▛▀▜", "▌ ▐", "▙▄▟"),
|
"outer": ("▛▀▜", "▌ ▐", "▙▄▟"),
|
||||||
|
"hkey": ("▔▔▔", " ", "▁▁▁"),
|
||||||
|
"vkey": ("▏ ▕", "▏ ▕", "▏ ▕"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -98,9 +100,9 @@ class Border:
|
|||||||
render_options = options.update_dimensions(width, height)
|
render_options = options.update_dimensions(width, height)
|
||||||
|
|
||||||
lines = console.render_lines(self.renderable, render_options)
|
lines = console.render_lines(self.renderable, render_options)
|
||||||
if len(lines) <= 2:
|
# if len(lines) <= 2:
|
||||||
yield SegmentLines(lines, new_lines=True)
|
# yield SegmentLines(lines, new_lines=True)
|
||||||
return
|
# return
|
||||||
if self.outline:
|
if self.outline:
|
||||||
self._crop_renderable(lines, options.max_width)
|
self._crop_renderable(lines, options.max_width)
|
||||||
|
|
||||||
|
|||||||
@@ -305,7 +305,6 @@ 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()
|
||||||
@@ -322,16 +321,14 @@ class App(DOMNode):
|
|||||||
# 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()
|
||||||
|
|
||||||
await self.post_message(events.Mount(sender=self))
|
|
||||||
|
|
||||||
view = DockView()
|
|
||||||
await self.mount(self, view)
|
|
||||||
await self.push_view(view)
|
|
||||||
|
|
||||||
driver = self._driver = self.driver_class(self.console, self)
|
driver = self._driver = self.driver_class(self.console, self)
|
||||||
|
|
||||||
driver.start_application_mode()
|
driver.start_application_mode()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
mount_event = events.Mount(sender=self)
|
||||||
|
await self.dispatch_message(mount_event)
|
||||||
|
await mount_event.wait()
|
||||||
|
|
||||||
self.title = self._title
|
self.title = self._title
|
||||||
self.refresh()
|
self.refresh()
|
||||||
await self.animator.start()
|
await self.animator.start()
|
||||||
@@ -339,8 +336,6 @@ class App(DOMNode):
|
|||||||
log("PROCESS END")
|
log("PROCESS END")
|
||||||
await self.animator.stop()
|
await self.animator.stop()
|
||||||
await self.close_all()
|
await self.close_all()
|
||||||
except Exception:
|
|
||||||
self.panic()
|
|
||||||
finally:
|
finally:
|
||||||
driver.stop_application_mode()
|
driver.stop_application_mode()
|
||||||
except:
|
except:
|
||||||
@@ -376,13 +371,20 @@ class App(DOMNode):
|
|||||||
name_widgets: Iterable[tuple[str | None, Widget]]
|
name_widgets: Iterable[tuple[str | None, Widget]]
|
||||||
name_widgets = [*((None, widget) for widget in anon_widgets), *widgets.items()]
|
name_widgets = [*((None, widget) for widget in anon_widgets), *widgets.items()]
|
||||||
apply_stylesheet = self.stylesheet.apply
|
apply_stylesheet = self.stylesheet.apply
|
||||||
|
widget_events = []
|
||||||
for widget_id, widget in name_widgets:
|
for widget_id, widget in name_widgets:
|
||||||
if widget not in self.registry:
|
if widget not in self.registry:
|
||||||
if widget_id is not None:
|
if widget_id is not None:
|
||||||
widget.id = widget_id
|
widget.id = widget_id
|
||||||
self._register(parent, widget)
|
self._register(parent, widget)
|
||||||
apply_stylesheet(widget)
|
apply_stylesheet(widget)
|
||||||
widget.post_message_no_wait(events.Mount(sender=parent))
|
mount_event = events.Mount(sender=parent)
|
||||||
|
widget_events.append((widget, mount_event))
|
||||||
|
# await widget.post_message(mount_event)
|
||||||
|
# await mount_event.wait()
|
||||||
|
for widget, event in widget_events:
|
||||||
|
widget.post_message_no_wait(event)
|
||||||
|
# await event.wait()
|
||||||
|
|
||||||
def is_mounted(self, widget: Widget) -> bool:
|
def is_mounted(self, widget: Widget) -> bool:
|
||||||
return widget in self.registry
|
return widget in self.registry
|
||||||
@@ -471,7 +473,13 @@ class App(DOMNode):
|
|||||||
async def on_event(self, event: events.Event) -> None:
|
async def on_event(self, event: events.Event) -> None:
|
||||||
# Handle input events that haven't been forwarded
|
# Handle input events that haven't been forwarded
|
||||||
# If the event has been forwaded it may have bubbled up back to the App
|
# If the event has been forwaded it may have bubbled up back to the App
|
||||||
if isinstance(event, events.InputEvent) and not event.is_forwarded:
|
if isinstance(event, events.Mount):
|
||||||
|
view = DockView()
|
||||||
|
await self.mount(self, view)
|
||||||
|
await self.push_view(view)
|
||||||
|
await super().on_event(event)
|
||||||
|
|
||||||
|
elif isinstance(event, events.InputEvent) and not event.is_forwarded:
|
||||||
if isinstance(event, events.MouseEvent):
|
if isinstance(event, events.MouseEvent):
|
||||||
# Record current mouse position on App
|
# Record current mouse position on App
|
||||||
self.mouse_position = Offset(event.x, event.y)
|
self.mouse_position = Offset(event.x, event.y)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import rich.repr
|
|||||||
from rich.color import Color
|
from rich.color import Color
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
|
|
||||||
from .scalar import Scalar, ScalarParseError
|
from .scalar import get_symbols, UNIT_SYMBOL, Unit, Scalar, ScalarParseError
|
||||||
from ..geometry import Offset, Spacing, SpacingDimensions
|
from ..geometry import Offset, Spacing, SpacingDimensions
|
||||||
from .constants import NULL_SPACING, VALID_EDGE
|
from .constants import NULL_SPACING, VALID_EDGE
|
||||||
from .errors import StyleTypeError, StyleValueError
|
from .errors import StyleTypeError, StyleValueError
|
||||||
@@ -14,15 +14,16 @@ from ._error_tools import friendly_list
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .styles import Styles
|
from .styles import Styles
|
||||||
from .styles import DockSpecification
|
from .styles import DockGroup
|
||||||
|
|
||||||
|
|
||||||
class ScalarProperty:
|
class ScalarProperty:
|
||||||
def __init__(self, units: set[str]) -> None:
|
def __init__(self, units: set[Unit] | None = None) -> None:
|
||||||
self.units = units
|
self.units: set[Unit] = units or {*UNIT_SYMBOL}
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
|
self.name = name
|
||||||
self.internal_name = f"_rule_{name}"
|
self.internal_name = f"_rule_{name}"
|
||||||
|
|
||||||
def __get__(
|
def __get__(
|
||||||
@@ -38,7 +39,7 @@ class ScalarProperty:
|
|||||||
if value is None:
|
if value is None:
|
||||||
new_value = None
|
new_value = None
|
||||||
elif isinstance(value, float):
|
elif isinstance(value, float):
|
||||||
new_value = Scalar(value, "")
|
new_value = Scalar(value, Unit.CELLS)
|
||||||
elif isinstance(value, Scalar):
|
elif isinstance(value, Scalar):
|
||||||
new_value = value
|
new_value = value
|
||||||
elif isinstance(value, str):
|
elif isinstance(value, str):
|
||||||
@@ -49,7 +50,9 @@ class ScalarProperty:
|
|||||||
else:
|
else:
|
||||||
raise StyleValueError("expected float, Scalar, or None")
|
raise StyleValueError("expected float, Scalar, or None")
|
||||||
if new_value is not None and new_value.unit not in self.units:
|
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)}")
|
raise StyleValueError(
|
||||||
|
f"{self.name} units must be one of {friendly_list(get_symbols(self.units))}"
|
||||||
|
)
|
||||||
setattr(obj, self.internal_name, new_value)
|
setattr(obj, self.internal_name, new_value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -224,12 +227,12 @@ class SpacingProperty:
|
|||||||
class DocksProperty:
|
class DocksProperty:
|
||||||
def __get__(
|
def __get__(
|
||||||
self, obj: Styles, objtype: type[Styles] | None = None
|
self, obj: Styles, objtype: type[Styles] | None = None
|
||||||
) -> tuple[DockSpecification, ...]:
|
) -> tuple[DockGroup, ...]:
|
||||||
return obj._rule_docks or ()
|
return obj._rule_docks or ()
|
||||||
|
|
||||||
def __set__(
|
def __set__(
|
||||||
self, obj: Styles, docks: Iterable[DockSpecification] | None
|
self, obj: Styles, docks: Iterable[DockGroup] | None
|
||||||
) -> Iterable[DockSpecification] | None:
|
) -> Iterable[DockGroup] | None:
|
||||||
if docks is None:
|
if docks is None:
|
||||||
obj._rule_docks = None
|
obj._rule_docks = None
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from ._error_tools import friendly_list
|
|||||||
from ..geometry import Offset, Spacing, SpacingDimensions
|
from ..geometry import Offset, Spacing, SpacingDimensions
|
||||||
from .model import Declaration
|
from .model import Declaration
|
||||||
from .scalar import Scalar
|
from .scalar import Scalar
|
||||||
from .styles import DockSpecification, Styles
|
from .styles import DockGroup, Styles
|
||||||
from .types import Edge, Display, Visibility
|
from .types import Edge, Display, Visibility
|
||||||
from .tokenize import Token
|
from .tokenize import Token
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ class StylesBuilder:
|
|||||||
style_tokens: list[str] = []
|
style_tokens: list[str] = []
|
||||||
append = style_tokens.append
|
append = style_tokens.append
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
_, _, location, token_name, value = token
|
token_name, value, _, _, _ = token
|
||||||
if token_name == "token":
|
if token_name == "token":
|
||||||
if value in VALID_BORDER:
|
if value in VALID_BORDER:
|
||||||
border_type = value
|
border_type = value
|
||||||
@@ -157,7 +157,7 @@ class StylesBuilder:
|
|||||||
|
|
||||||
def _process_border(self, edge: str, name: str, tokens: list[Token]) -> None:
|
def _process_border(self, edge: str, name: str, tokens: list[Token]) -> None:
|
||||||
border = self._parse_border("border", tokens)
|
border = self._parse_border("border", tokens)
|
||||||
setattr(self.styles, f"_border_{edge}", border)
|
setattr(self.styles, f"_rule_border_{edge}", border)
|
||||||
|
|
||||||
def process_border(self, name: str, tokens: list[Token]) -> None:
|
def process_border(self, name: str, tokens: list[Token]) -> None:
|
||||||
border = self._parse_border("border", tokens)
|
border = self._parse_border("border", tokens)
|
||||||
@@ -179,7 +179,7 @@ class StylesBuilder:
|
|||||||
|
|
||||||
def _process_outline(self, edge: str, name: str, tokens: list[Token]) -> None:
|
def _process_outline(self, edge: str, name: str, tokens: list[Token]) -> None:
|
||||||
border = self._parse_border("outline", tokens)
|
border = self._parse_border("outline", tokens)
|
||||||
setattr(self.styles, f"_outline_{edge}", border)
|
setattr(self.styles, f"_rule_outline_{edge}", border)
|
||||||
|
|
||||||
def process_outline(self, name: str, tokens: list[Token]) -> None:
|
def process_outline(self, name: str, tokens: list[Token]) -> None:
|
||||||
border = self._parse_border("outline", tokens)
|
border = self._parse_border("outline", tokens)
|
||||||
@@ -289,7 +289,7 @@ class StylesBuilder:
|
|||||||
self.styles._rule_dock_group = tokens[0].value if tokens else ""
|
self.styles._rule_dock_group = tokens[0].value if tokens else ""
|
||||||
|
|
||||||
def process_docks(self, name: str, tokens: list[Token]) -> None:
|
def process_docks(self, name: str, tokens: list[Token]) -> None:
|
||||||
docks: list[DockSpecification] = []
|
docks: list[DockGroup] = []
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
if token.name == "key_value":
|
if token.name == "key_value":
|
||||||
key, edge_name = token.value.split("=")
|
key, edge_name = token.value.split("=")
|
||||||
@@ -308,7 +308,7 @@ class StylesBuilder:
|
|||||||
token,
|
token,
|
||||||
f"edge must be one of 'top', 'right', 'bottom', or 'left'; found {edge_name!r}",
|
f"edge must be one of 'top', 'right', 'bottom', or 'left'; found {edge_name!r}",
|
||||||
)
|
)
|
||||||
docks.append(DockSpecification(key.strip(), cast(Edge, edge_name), z))
|
docks.append(DockGroup(key.strip(), cast(Edge, edge_name), z))
|
||||||
elif token.name == "bar":
|
elif token.name == "bar":
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -10,13 +10,15 @@ from ..geometry import Spacing
|
|||||||
VALID_VISIBILITY: Final = {"visible", "hidden"}
|
VALID_VISIBILITY: Final = {"visible", "hidden"}
|
||||||
VALID_DISPLAY: Final = {"block", "none"}
|
VALID_DISPLAY: Final = {"block", "none"}
|
||||||
VALID_BORDER: Final = {
|
VALID_BORDER: Final = {
|
||||||
"rounded",
|
"none" "round",
|
||||||
"solid",
|
"solid",
|
||||||
"double",
|
"double",
|
||||||
"dashed",
|
"dashed",
|
||||||
"heavy",
|
"heavy",
|
||||||
"inner",
|
"inner",
|
||||||
"outer",
|
"outer",
|
||||||
|
"hkey",
|
||||||
|
"vkey",
|
||||||
}
|
}
|
||||||
VALID_EDGE: Final = {"top", "right", "bottom", "left"}
|
VALID_EDGE: Final = {"top", "right", "bottom", "left"}
|
||||||
VALID_LAYOUT: Final = {"dock", "vertical", "grid"}
|
VALID_LAYOUT: Final = {"dock", "vertical", "grid"}
|
||||||
|
|||||||
@@ -1,10 +1,46 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from enum import Enum, unique
|
||||||
import re
|
import re
|
||||||
from typing import NamedTuple
|
from typing import Iterable, NamedTuple
|
||||||
|
|
||||||
|
|
||||||
_MATCH_SCALAR = re.compile(r"^(\d+\.?\d*)(fr|%)?$").match
|
@unique
|
||||||
|
class Unit(Enum):
|
||||||
|
CELLS = 1
|
||||||
|
FRACTION = 2
|
||||||
|
PERCENT = 3
|
||||||
|
WIDTH = 4
|
||||||
|
HEIGHT = 5
|
||||||
|
VIEW_WIDTH = 6
|
||||||
|
VIEW_HEIGHT = 7
|
||||||
|
|
||||||
|
|
||||||
|
UNIT_SYMBOL = {
|
||||||
|
Unit.CELLS: "",
|
||||||
|
Unit.FRACTION: "fr",
|
||||||
|
Unit.PERCENT: "%",
|
||||||
|
Unit.WIDTH: "w",
|
||||||
|
Unit.HEIGHT: "h",
|
||||||
|
Unit.VIEW_WIDTH: "vw",
|
||||||
|
Unit.VIEW_HEIGHT: "vh",
|
||||||
|
}
|
||||||
|
|
||||||
|
SYMBOL_UNIT = {v: k for k, v in UNIT_SYMBOL.items()}
|
||||||
|
|
||||||
|
_MATCH_SCALAR = re.compile(r"^(\d+\.?\d*)(fr|%|w|h|vw|vh)?$").match
|
||||||
|
|
||||||
|
|
||||||
|
def get_symbols(units: Iterable[Unit]) -> list[str]:
|
||||||
|
"""Get symbols for an iterable of units.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
units (Iterable[Unit]): A number of units.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[str]: List of symbols.
|
||||||
|
"""
|
||||||
|
return [UNIT_SYMBOL[unit] for unit in units]
|
||||||
|
|
||||||
|
|
||||||
class ScalarParseError(Exception):
|
class ScalarParseError(Exception):
|
||||||
@@ -15,36 +51,25 @@ class Scalar(NamedTuple):
|
|||||||
"""A numeric value and a unit."""
|
"""A numeric value and a unit."""
|
||||||
|
|
||||||
value: float
|
value: float
|
||||||
unit: str
|
unit: Unit
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
value, unit = self
|
value, _unit = self
|
||||||
return f"{int(value) if value.is_integer() else value}{unit}"
|
return f"{int(value) if value.is_integer() else value}{self.symbol}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cells(self) -> int | None:
|
def cells(self) -> int | None:
|
||||||
value, unit = self
|
value, unit = self
|
||||||
if unit:
|
return int(value) if unit == Unit.CELLS else None
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return int(value)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fraction(self) -> int | None:
|
def fraction(self) -> int | None:
|
||||||
value, unit = self
|
value, unit = self
|
||||||
if unit == "fr":
|
return int(value) if unit == Unit.FRACTION else None
|
||||||
return int(value)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def resolve_size(self, total: int, total_fraction: int) -> int:
|
@property
|
||||||
value, unit = self
|
def symbol(self) -> str:
|
||||||
if unit == "":
|
return UNIT_SYMBOL[self.unit]
|
||||||
return int(value)
|
|
||||||
elif unit == "%":
|
|
||||||
return int(total * value / 100.0)
|
|
||||||
else: # if unit == "fr":
|
|
||||||
return int((value / total_fraction) * total)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, token: str) -> Scalar:
|
def parse(cls, token: str) -> Scalar:
|
||||||
@@ -62,11 +87,14 @@ class Scalar(NamedTuple):
|
|||||||
match = _MATCH_SCALAR(token)
|
match = _MATCH_SCALAR(token)
|
||||||
if match is None:
|
if match is None:
|
||||||
raise ScalarParseError(f"{token!r} is not a valid scalar")
|
raise ScalarParseError(f"{token!r} is not a valid scalar")
|
||||||
value, unit = match.groups()
|
value, unit_name = match.groups()
|
||||||
scalar = cls(float(value), unit or "")
|
scalar = cls(float(value), SYMBOL_UNIT[unit_name or ""])
|
||||||
return scalar
|
return scalar
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
print(Scalar.parse("3.14"))
|
print(Scalar.parse("3.14fr"))
|
||||||
|
s = Scalar.parse("23")
|
||||||
|
print(repr(s))
|
||||||
|
print(repr(s.cells))
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ else:
|
|||||||
from typing_extensions import Literal
|
from typing_extensions import Literal
|
||||||
|
|
||||||
|
|
||||||
class DockSpecification(NamedTuple):
|
class DockGroup(NamedTuple):
|
||||||
name: str
|
name: str
|
||||||
edge: Edge
|
edge: Edge
|
||||||
z: int
|
z: int
|
||||||
@@ -83,7 +83,7 @@ class Styles:
|
|||||||
_rule_layout: str | None = None
|
_rule_layout: str | None = None
|
||||||
|
|
||||||
_rule_dock_group: str | None = None
|
_rule_dock_group: str | None = None
|
||||||
_rule_docks: tuple[DockSpecification, ...] | None = None
|
_rule_docks: tuple[DockGroup, ...] | None = None
|
||||||
|
|
||||||
_rule_layers: tuple[str, ...] | None = None
|
_rule_layers: tuple[str, ...] | None = None
|
||||||
_rule_layer: str | None = None
|
_rule_layer: str | None = None
|
||||||
@@ -115,10 +115,10 @@ class Styles:
|
|||||||
outline_bottom = BoxProperty()
|
outline_bottom = BoxProperty()
|
||||||
outline_left = BoxProperty()
|
outline_left = BoxProperty()
|
||||||
|
|
||||||
width = ScalarProperty({"", "fr"})
|
width = ScalarProperty()
|
||||||
height = ScalarProperty({"", "fr"})
|
height = ScalarProperty()
|
||||||
min_width = ScalarProperty({"", "fr"})
|
min_width = ScalarProperty()
|
||||||
min_height = ScalarProperty({"", "fr"})
|
min_height = ScalarProperty()
|
||||||
|
|
||||||
dock_group = DockGroupProperty()
|
dock_group = DockGroupProperty()
|
||||||
docks = DocksProperty()
|
docks = DocksProperty()
|
||||||
@@ -251,8 +251,8 @@ class Styles:
|
|||||||
append_declaration(
|
append_declaration(
|
||||||
"docks",
|
"docks",
|
||||||
" ".join(
|
" ".join(
|
||||||
(f"{key}={value}/{z}" if z else f"{key}={value}")
|
(f"{name}={edge}/{z}" if z else f"{name}={edge}")
|
||||||
for key, value, z in self._rule_docks
|
for name, edge, z in self._rule_docks
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if self._rule_layers is not None:
|
if self._rule_layers is not None:
|
||||||
|
|||||||
@@ -134,7 +134,6 @@ 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__":
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ expect_declaration_content = Expect(
|
|||||||
comment_start=r"\/\*",
|
comment_start=r"\/\*",
|
||||||
percentage=r"\d+\%",
|
percentage=r"\d+\%",
|
||||||
scalar=r"\d+\.?\d*(?:fr|%)?",
|
scalar=r"\d+\.?\d*(?:fr|%)?",
|
||||||
color=r"\#[0-9a-f]{6}|color\([0-9]{1,3}\)|rgb\(\d{1,3}\,\s?\d{1,3}\,\s?\d{1,3}\)",
|
color=r"\#[0-9a-fA-F]{6}|color\([0-9]{1,3}\)|rgb\(\d{1,3}\,\s?\d{1,3}\,\s?\d{1,3}\)",
|
||||||
key_value=r"[a-zA-Z_-][a-zA-Z0-9_-]*=[0-9a-zA-Z_\-\/]+",
|
key_value=r"[a-zA-Z_-][a-zA-Z0-9_-]*=[0-9a-zA-Z_\-\/]+",
|
||||||
token="[a-zA-Z_-]+",
|
token="[a-zA-Z_-]+",
|
||||||
string=r"\".*?\"",
|
string=r"\".*?\"",
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ class DockLayout(Layout):
|
|||||||
self, view: View, size: Size, scroll: Offset
|
self, view: View, size: Size, scroll: Offset
|
||||||
) -> Iterable[WidgetPlacement]:
|
) -> Iterable[WidgetPlacement]:
|
||||||
|
|
||||||
map: LayoutMap = LayoutMap(size)
|
|
||||||
width, height = size
|
width, height = size
|
||||||
layout_region = Region(0, 0, width, height)
|
layout_region = Region(0, 0, width, height)
|
||||||
layers: dict[int, Region] = defaultdict(lambda: layout_region)
|
layers: dict[int, Region] = defaultdict(lambda: layout_region)
|
||||||
@@ -170,5 +169,3 @@ class DockLayout(Layout):
|
|||||||
region = Region(x, y, width - total, height)
|
region = Region(x, y, width - total, height)
|
||||||
|
|
||||||
layers[z] = region
|
layers[z] = region
|
||||||
|
|
||||||
return map
|
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ class Message:
|
|||||||
cls.bubble = bubble
|
cls.bubble = bubble
|
||||||
cls.verbosity = verbosity
|
cls.verbosity = verbosity
|
||||||
|
|
||||||
|
def set_done(self) -> None:
|
||||||
|
self._done_event.set()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _done_event(self) -> Event:
|
def _done_event(self) -> Event:
|
||||||
if self.__done_event is None:
|
if self.__done_event is None:
|
||||||
|
|||||||
@@ -225,14 +225,11 @@ class MessagePump:
|
|||||||
|
|
||||||
async def dispatch_message(self, message: Message) -> bool | None:
|
async def dispatch_message(self, message: Message) -> bool | None:
|
||||||
_rich_traceback_guard = True
|
_rich_traceback_guard = True
|
||||||
try:
|
if isinstance(message, events.Event):
|
||||||
if isinstance(message, events.Event):
|
if not isinstance(message, events.Null):
|
||||||
if not isinstance(message, events.Null):
|
await self.on_event(message)
|
||||||
await self.on_event(message)
|
else:
|
||||||
else:
|
return await self.on_message(message)
|
||||||
return await self.on_message(message)
|
|
||||||
finally:
|
|
||||||
message._done_event.set()
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _get_dispatch_methods(
|
def _get_dispatch_methods(
|
||||||
@@ -248,9 +245,12 @@ class MessagePump:
|
|||||||
async def on_event(self, event: events.Event) -> None:
|
async def on_event(self, event: events.Event) -> None:
|
||||||
_rich_traceback_guard = True
|
_rich_traceback_guard = True
|
||||||
|
|
||||||
for method in self._get_dispatch_methods(f"on_{event.name}", event):
|
try:
|
||||||
log(event, ">>>", self, verbosity=event.verbosity)
|
for method in self._get_dispatch_methods(f"on_{event.name}", event):
|
||||||
await invoke(method, event)
|
log(event, ">>>", self, verbosity=event.verbosity)
|
||||||
|
await invoke(method, event)
|
||||||
|
finally:
|
||||||
|
event.set_done()
|
||||||
|
|
||||||
if event.bubble and self._parent and not event._stop_propagation:
|
if event.bubble and self._parent and not event._stop_propagation:
|
||||||
if event.sender == self._parent:
|
if event.sender == self._parent:
|
||||||
@@ -264,9 +264,12 @@ class MessagePump:
|
|||||||
method_name = f"handle_{message.name}"
|
method_name = f"handle_{message.name}"
|
||||||
|
|
||||||
method = getattr(self, method_name, None)
|
method = getattr(self, method_name, None)
|
||||||
if method is not None:
|
try:
|
||||||
log(message, ">>>", self, verbosity=message.verbosity)
|
if method is not None:
|
||||||
await invoke(method, message)
|
log(message, ">>>", self, verbosity=message.verbosity)
|
||||||
|
await invoke(method, message)
|
||||||
|
finally:
|
||||||
|
message.set_done()
|
||||||
|
|
||||||
if message.bubble and self._parent and not message._stop_propagation:
|
if message.bubble and self._parent and not message._stop_propagation:
|
||||||
if message.sender == self._parent:
|
if message.sender == self._parent:
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ class View(Widget):
|
|||||||
if cached_size == size and cached_scroll == scroll:
|
if cached_size == size and cached_scroll == scroll:
|
||||||
return arrangement
|
return arrangement
|
||||||
arrangement = list(self._layout.arrange(self, size, scroll))
|
arrangement = list(self._layout.arrange(self, size, scroll))
|
||||||
|
self.log(arrangement)
|
||||||
self._cached_arrangement = (size, scroll, arrangement)
|
self._cached_arrangement = (size, scroll, arrangement)
|
||||||
return arrangement
|
return arrangement
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from rich.padding import Padding
|
|||||||
from rich.pretty import Pretty
|
from rich.pretty import Pretty
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
from rich.styled import Styled
|
from rich.styled import Styled
|
||||||
from rich.text import TextType
|
from rich.text import Text, TextType
|
||||||
|
|
||||||
from . import events
|
from . import events
|
||||||
from . import errors
|
from . import errors
|
||||||
@@ -135,24 +135,28 @@ class Widget(DOMNode):
|
|||||||
Returns:
|
Returns:
|
||||||
RenderableType: A new renderable.
|
RenderableType: A new renderable.
|
||||||
"""
|
"""
|
||||||
renderable = Styled(self.render(), self.styles.text)
|
|
||||||
|
renderable = self.render()
|
||||||
|
styles = self.styles
|
||||||
if self.padding is not None:
|
if self.padding is not None:
|
||||||
renderable = Padding(renderable, self.padding)
|
renderable = Padding(renderable, self.padding)
|
||||||
if self.border not in ("", "none"):
|
|
||||||
_border_style = self.console.get_style(self.border_style)
|
if styles.has_border:
|
||||||
renderable = Border(
|
renderable = Border(renderable, styles.border)
|
||||||
renderable,
|
|
||||||
(
|
# _border_style = self.console.get_style(self.border_style)
|
||||||
("heavy", _border_style),
|
# renderable = Border(
|
||||||
("heavy", _border_style),
|
# renderable,
|
||||||
("heavy", _border_style),
|
# (
|
||||||
("heavy", _border_style),
|
# ("heavy", _border_style),
|
||||||
),
|
# ("heavy", _border_style),
|
||||||
)
|
# ("heavy", _border_style),
|
||||||
|
# ("heavy", _border_style),
|
||||||
|
# ),
|
||||||
|
# )
|
||||||
if self.margin is not None:
|
if self.margin is not None:
|
||||||
renderable = Padding(renderable, self.margin)
|
renderable = Padding(renderable, self.margin)
|
||||||
if self.style:
|
renderable = Styled(renderable, styles.text)
|
||||||
renderable = Styled(renderable, self.style)
|
|
||||||
return renderable
|
return renderable
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -269,9 +273,7 @@ class Widget(DOMNode):
|
|||||||
Returns:
|
Returns:
|
||||||
RenderableType: Any renderable
|
RenderableType: Any renderable
|
||||||
"""
|
"""
|
||||||
return Panel(
|
return Align.center(Text(f"#{self.id}"), vertical="middle")
|
||||||
Align.center(Pretty(self), vertical="middle"), title=self.__class__.__name__
|
|
||||||
)
|
|
||||||
|
|
||||||
async def action(self, action: str, *params) -> None:
|
async def action(self, action: str, *params) -> None:
|
||||||
await self.app.action(action, self)
|
await self.app.action(action, self)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import rich.repr
|
|||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from .. import events
|
from .. import events
|
||||||
from ..geometry import Offset
|
|
||||||
from ..widget import Reactive, Widget
|
from ..widget import Reactive, Widget
|
||||||
|
|
||||||
log = getLogger("rich")
|
log = getLogger("rich")
|
||||||
|
|||||||
Reference in New Issue
Block a user