mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
color scheme, compositor fix
This commit is contained in:
@@ -1,44 +1,62 @@
|
||||
/* CSS file for basic.py */
|
||||
|
||||
$primary: #20639b;
|
||||
|
||||
App > Screen {
|
||||
layout: dock;
|
||||
docks: side=left/1;
|
||||
background: $primary;
|
||||
background: $background;
|
||||
color: $on-background;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
color: #09312e;
|
||||
background: #3caea3;
|
||||
color: $on-primary;
|
||||
background: $primary;
|
||||
dock: side;
|
||||
width: 30;
|
||||
offset-x: -100%;
|
||||
|
||||
layout: dock;
|
||||
transition: offset 500ms in_out_cubic;
|
||||
border-right: outer #09312e;
|
||||
}
|
||||
|
||||
#sidebar.-active {
|
||||
offset-x: 0;
|
||||
}
|
||||
|
||||
#sidebar .title {
|
||||
height: 1;
|
||||
background: $secondary-darken1;
|
||||
color: $on-secondary-darken1;
|
||||
border-right: vkey $secondary-darken2;
|
||||
}
|
||||
|
||||
#sidebar .user {
|
||||
height: 8;
|
||||
background: $secondary;
|
||||
color: $on-secondary;
|
||||
border-right: vkey $secondary-darken2;
|
||||
}
|
||||
|
||||
#sidebar .content {
|
||||
background: $secondary-lighten1;
|
||||
color: $on-secondary-lighten1;
|
||||
border-right: vkey $secondary-darken2;
|
||||
}
|
||||
|
||||
#header {
|
||||
color: white;
|
||||
background: #173f5f;
|
||||
color: $on-primary;
|
||||
background: $primary;
|
||||
height: 3;
|
||||
border: hkey white;
|
||||
border: hkey $primary-darken2;
|
||||
}
|
||||
|
||||
#content {
|
||||
color: white;
|
||||
background: $primary;
|
||||
color: $on-background;
|
||||
background: $background;
|
||||
border-bottom: hkey #0f2b41;
|
||||
}
|
||||
|
||||
#footer {
|
||||
color: #3a3009;
|
||||
background: #f6d55c;
|
||||
color: $on-accent1;
|
||||
background: $accent1;
|
||||
|
||||
height: 3;
|
||||
height: 1;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,21 @@ class BasicApp(App):
|
||||
header=Widget(),
|
||||
content=Widget(),
|
||||
footer=Widget(),
|
||||
sidebar=Widget(),
|
||||
sidebar=Widget(
|
||||
Widget(classes={"title"}),
|
||||
Widget(classes={"user"}),
|
||||
Widget(classes={"content"}),
|
||||
),
|
||||
)
|
||||
|
||||
async def on_key(self, event) -> None:
|
||||
await self.dispatch_key(event)
|
||||
|
||||
def key_d(self):
|
||||
self.dark = not self.dark
|
||||
|
||||
def key_x(self):
|
||||
self.panic(self.tree)
|
||||
|
||||
|
||||
BasicApp.run(css_file="basic.css", watch_css=True, log="textual.log")
|
||||
|
||||
@@ -4,14 +4,14 @@ Screen {
|
||||
|
||||
#borders {
|
||||
layout: vertical;
|
||||
text-background: #212121;
|
||||
background: #212121;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
Lorem.border {
|
||||
height: 12;
|
||||
margin: 2 4;
|
||||
text-background: #303f9f;
|
||||
background: #303f9f;
|
||||
}
|
||||
|
||||
Lorem.round {
|
||||
|
||||
@@ -205,6 +205,8 @@ class Compositor:
|
||||
else ORIGIN
|
||||
)
|
||||
|
||||
# region += layout_offset
|
||||
|
||||
# Container region is minus border
|
||||
container_region = region.shrink(widget.styles.gutter)
|
||||
|
||||
@@ -233,8 +235,13 @@ class Compositor:
|
||||
if sub_widget is not None:
|
||||
add_widget(
|
||||
sub_widget,
|
||||
sub_region + child_region.origin - scroll_offset,
|
||||
sub_widget.z + (z,),
|
||||
(
|
||||
sub_region
|
||||
+ child_region.origin
|
||||
- scroll_offset
|
||||
+ layout_offset
|
||||
),
|
||||
(z,) + sub_widget.z,
|
||||
sub_clip,
|
||||
)
|
||||
|
||||
|
||||
@@ -19,15 +19,13 @@ class NodeList:
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._node_refs: list[ref[DOMNode]] = []
|
||||
self.__nodes: list[DOMNode] | None = []
|
||||
self._nodes: list[DOMNode] = []
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
self._prune()
|
||||
return bool(self._node_refs)
|
||||
return bool(self._nodes)
|
||||
|
||||
def __length_hint__(self) -> int:
|
||||
return len(self._node_refs)
|
||||
return len(self._nodes)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield self._nodes
|
||||
@@ -38,32 +36,12 @@ class NodeList:
|
||||
def __contains__(self, widget: DOMNode) -> bool:
|
||||
return widget in self._nodes
|
||||
|
||||
@property
|
||||
def _nodes(self) -> list[DOMNode]:
|
||||
if self.__nodes is None:
|
||||
self.__nodes = list(
|
||||
filter(None, [widget_ref() for widget_ref in self._node_refs])
|
||||
)
|
||||
return self.__nodes
|
||||
|
||||
def _prune(self) -> None:
|
||||
"""Remove expired references."""
|
||||
self._node_refs[:] = filter(
|
||||
None,
|
||||
[
|
||||
None if widget_ref() is None else widget_ref
|
||||
for widget_ref in self._node_refs
|
||||
],
|
||||
)
|
||||
|
||||
def _append(self, widget: DOMNode) -> None:
|
||||
if widget not in self._nodes:
|
||||
self._node_refs.append(ref(widget))
|
||||
self.__nodes = None
|
||||
self._nodes.append(widget)
|
||||
|
||||
def _clear(self) -> None:
|
||||
del self._node_refs[:]
|
||||
self.__nodes = None
|
||||
del self._nodes[:]
|
||||
|
||||
def __iter__(self) -> Iterator[DOMNode]:
|
||||
return iter(self._nodes)
|
||||
@@ -77,6 +55,5 @@ class NodeList:
|
||||
...
|
||||
|
||||
def __getitem__(self, index: int | slice) -> DOMNode | list[DOMNode]:
|
||||
self._prune()
|
||||
assert self._nodes is not None
|
||||
return self._nodes[index]
|
||||
|
||||
@@ -12,10 +12,12 @@ import rich.repr
|
||||
from rich.console import Console, RenderableType
|
||||
from rich.control import Control
|
||||
from rich.measure import Measurement
|
||||
from rich.segment import Segments
|
||||
from rich.screen import Screen as ScreenRenderable
|
||||
from rich.traceback import Traceback
|
||||
|
||||
from . import actions
|
||||
|
||||
from . import events
|
||||
from . import log
|
||||
from . import messages
|
||||
@@ -26,6 +28,7 @@ from ._event_broker import extract_handler_actions, NoHandler
|
||||
from ._profile import timer
|
||||
from .binding import Bindings, NoBinding
|
||||
from .css.stylesheet import Stylesheet, StylesheetParseError, StylesheetError
|
||||
from .design import ColorSystem
|
||||
from .dom import DOMNode
|
||||
from .driver import Driver
|
||||
from .file_monitor import FileMonitor
|
||||
@@ -33,7 +36,6 @@ from .geometry import Offset, Region, Size
|
||||
from .layouts.dock import Dock
|
||||
from .message_pump import MessagePump
|
||||
from .reactive import Reactive
|
||||
from .renderables.gradient import VerticalGradient
|
||||
from .screen import Screen
|
||||
from .widget import Widget
|
||||
|
||||
@@ -110,7 +112,17 @@ class App(DOMNode):
|
||||
self.bindings.bind("ctrl+c", "quit", show=False, allow_forward=False)
|
||||
self._refresh_required = False
|
||||
|
||||
self.stylesheet = Stylesheet()
|
||||
self.design = ColorSystem(
|
||||
accent1="#ffa726",
|
||||
secondary="#00695c",
|
||||
warning="#ffa000",
|
||||
error="#C62828",
|
||||
success="#558B2F",
|
||||
primary="#1976D2",
|
||||
accent3="#512DA8",
|
||||
)
|
||||
|
||||
self.stylesheet = Stylesheet(variables=self.get_css_variables())
|
||||
|
||||
self.css_file = css_file
|
||||
self.css_monitor = (
|
||||
@@ -128,6 +140,16 @@ class App(DOMNode):
|
||||
title: Reactive[str] = Reactive("Textual")
|
||||
sub_title: Reactive[str] = Reactive("")
|
||||
background: Reactive[str] = Reactive("black")
|
||||
dark = Reactive(True)
|
||||
|
||||
def get_css_variables(self) -> dict[str, str]:
|
||||
variables = self.design.generate(self.dark)
|
||||
return variables
|
||||
|
||||
def watch_dark(self, dark: bool) -> None:
|
||||
self.log(dark=dark)
|
||||
self.screen.dark = dark
|
||||
self.refresh_css()
|
||||
|
||||
def get_driver_class(self) -> Type[Driver]:
|
||||
"""Get a driver class for this platform.
|
||||
@@ -137,6 +159,7 @@ class App(DOMNode):
|
||||
Returns:
|
||||
Driver: A Driver class which manages input and display.
|
||||
"""
|
||||
driver_class: Type[Driver]
|
||||
if WINDOWS:
|
||||
from .drivers.windows_driver import WindowsDriver
|
||||
|
||||
@@ -248,7 +271,7 @@ class App(DOMNode):
|
||||
async def _on_css_change(self) -> None:
|
||||
|
||||
if self.css_file is not None:
|
||||
stylesheet = Stylesheet()
|
||||
stylesheet = Stylesheet(variables=self.get_css_variables())
|
||||
try:
|
||||
self.log("loading", self.css_file)
|
||||
stylesheet.read(self.css_file)
|
||||
@@ -367,16 +390,18 @@ class App(DOMNode):
|
||||
"""
|
||||
|
||||
if not renderables:
|
||||
|
||||
renderables = (
|
||||
Traceback(
|
||||
show_locals=True,
|
||||
width=None,
|
||||
locals_max_length=5,
|
||||
suppress=[rich],
|
||||
show_locals=True, width=None, locals_max_length=5, suppress=[rich]
|
||||
),
|
||||
)
|
||||
self._exit_renderables.extend(renderables)
|
||||
|
||||
prerendered = [
|
||||
Segments(self.console.render(renderable, self.console.options))
|
||||
for renderable in renderables
|
||||
]
|
||||
|
||||
self._exit_renderables.extend(prerendered)
|
||||
self.close_messages_no_wait()
|
||||
|
||||
def _print_error_renderables(self) -> None:
|
||||
@@ -458,24 +483,30 @@ class App(DOMNode):
|
||||
Args:
|
||||
parent (Widget): Parent Widget
|
||||
"""
|
||||
self.log("app.register", parent, anon_widgets)
|
||||
if not anon_widgets and not widgets:
|
||||
raise AppError(
|
||||
"Nothing to mount, did you forget parent as first positional arg?"
|
||||
)
|
||||
name_widgets: Iterable[tuple[str | None, Widget]]
|
||||
name_widgets = [*((None, widget) for widget in anon_widgets), *widgets.items()]
|
||||
self.log("name_widgets", name_widgets, bool(name_widgets))
|
||||
apply_stylesheet = self.stylesheet.apply
|
||||
|
||||
# Register children
|
||||
for widget_id, widget in name_widgets:
|
||||
if widget.children:
|
||||
self.register(widget, *widget.children)
|
||||
# for widget_id, widget in name_widgets:
|
||||
# if widget.children:
|
||||
# for child in widget.children:
|
||||
# self.register(child, *child.children)
|
||||
|
||||
for widget_id, widget in name_widgets:
|
||||
self.log(widget_id=widget_id, widget=widget, _in=widget in self.registry)
|
||||
if widget not in self.registry:
|
||||
if widget_id is not None:
|
||||
widget.id = widget_id
|
||||
self._register_child(parent, child=widget)
|
||||
self._register_child(parent, widget)
|
||||
if widget.children:
|
||||
self.register(widget, *widget.children)
|
||||
apply_stylesheet(widget)
|
||||
|
||||
for _widget_id, widget in name_widgets:
|
||||
@@ -531,6 +562,17 @@ class App(DOMNode):
|
||||
except Exception:
|
||||
self.panic()
|
||||
|
||||
def refresh_css(self, animate: bool = True) -> None:
|
||||
"""Refresh CSS.
|
||||
|
||||
Args:
|
||||
animate (bool, optional): Also execute CSS animations. Defaults to True.
|
||||
"""
|
||||
# TODO: This doesn't update variables
|
||||
self.app.stylesheet.set_variables(self.get_css_variables())
|
||||
self.app.stylesheet.update(self.app, animate=animate)
|
||||
self.refresh(layout=True)
|
||||
|
||||
def display(self, renderable: RenderableType) -> None:
|
||||
if not self._running:
|
||||
return
|
||||
|
||||
@@ -32,8 +32,8 @@ from ..geometry import Spacing, SpacingDimensions, clamp
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..layout import Layout
|
||||
from .styles import Styles, StylesBase
|
||||
from .styles import DockGroup
|
||||
from .styles import DockGroup, Styles, StylesBase
|
||||
|
||||
|
||||
from .types import EdgeType
|
||||
|
||||
@@ -378,7 +378,11 @@ class DocksProperty:
|
||||
Returns:
|
||||
tuple[DockGroup, ...]: A ``tuple`` containing the defined docks.
|
||||
"""
|
||||
return obj.get_rule("docks", ())
|
||||
if obj.has_rule("docks"):
|
||||
return obj.get_rule("docks")
|
||||
from .styles import DockGroup
|
||||
|
||||
return (DockGroup("_default", "top", 1),)
|
||||
|
||||
def __set__(self, obj: StylesBase, docks: Iterable[DockGroup] | None):
|
||||
"""Set the Docks property
|
||||
|
||||
@@ -16,7 +16,7 @@ from .model import (
|
||||
SelectorType,
|
||||
)
|
||||
from .styles import Styles
|
||||
from .tokenize import tokenize, tokenize_declarations, Token
|
||||
from .tokenize import tokenize, tokenize_declarations, Token, tokenize_values
|
||||
from .tokenizer import EOFError, ReferencedBy
|
||||
|
||||
SELECTOR_MAP: dict[str, tuple[SelectorType, tuple[int, int, int]]] = {
|
||||
@@ -212,13 +212,13 @@ def _unresolved(
|
||||
|
||||
|
||||
def substitute_references(
|
||||
tokens: Iterator[Token], css_variables: dict[str, list[Token]] | None = None
|
||||
tokens: Iterable[Token], css_variables: dict[str, list[Token]] | None = None
|
||||
) -> Iterable[Token]:
|
||||
"""Replace variable references with values by substituting variable reference
|
||||
tokens with the tokens representing their values.
|
||||
|
||||
Args:
|
||||
tokens (Iterator[Token]): Iterator of Tokens which may contain tokens
|
||||
tokens (Iterable[Token]): Iterator of Tokens which may contain tokens
|
||||
with the name "variable_ref".
|
||||
|
||||
Returns:
|
||||
@@ -230,8 +230,10 @@ def substitute_references(
|
||||
"""
|
||||
variables: dict[str, list[Token]] = css_variables.copy() if css_variables else {}
|
||||
|
||||
iter_tokens = iter(tokens)
|
||||
|
||||
while tokens:
|
||||
token = next(tokens, None)
|
||||
token = next(iter_tokens, None)
|
||||
if token is None:
|
||||
break
|
||||
if token.name == "variable_name":
|
||||
@@ -239,7 +241,7 @@ def substitute_references(
|
||||
yield token
|
||||
|
||||
while True:
|
||||
token = next(tokens, None)
|
||||
token = next(iter_tokens, None)
|
||||
# TODO: Mypy error looks legit
|
||||
if token.name == "whitespace":
|
||||
yield token
|
||||
@@ -281,7 +283,7 @@ def substitute_references(
|
||||
else:
|
||||
variables.setdefault(variable_name, []).append(token)
|
||||
yield token
|
||||
token = next(tokens, None)
|
||||
token = next(iter_tokens, None)
|
||||
elif token.name == "variable_ref":
|
||||
variable_name = token.value[1:] # Trim the $, so $x -> x
|
||||
if variable_name in variables:
|
||||
@@ -302,7 +304,9 @@ def substitute_references(
|
||||
yield token
|
||||
|
||||
|
||||
def parse(css: str, path: str) -> Iterable[RuleSet]:
|
||||
def parse(
|
||||
css: str, path: str, variables: dict[str, str] | None = None
|
||||
) -> Iterable[RuleSet]:
|
||||
"""Parse CSS by tokenizing it, performing variable substitution,
|
||||
and generating rule sets from it.
|
||||
|
||||
@@ -310,7 +314,8 @@ def parse(css: str, path: str) -> Iterable[RuleSet]:
|
||||
css (str): The input CSS
|
||||
path (str): Path to the CSS
|
||||
"""
|
||||
tokens = iter(substitute_references(tokenize(css, path)))
|
||||
variable_tokens = tokenize_values(variables or {})
|
||||
tokens = iter(substitute_references(tokenize(css, path), variable_tokens))
|
||||
while True:
|
||||
token = next(tokens, None)
|
||||
if token is None:
|
||||
|
||||
@@ -36,10 +36,14 @@ class StylesheetParseError(Exception):
|
||||
|
||||
|
||||
class StylesheetErrors:
|
||||
def __init__(self, stylesheet: "Stylesheet") -> None:
|
||||
def __init__(
|
||||
self, stylesheet: "Stylesheet", variables: dict[str, str] | None = None
|
||||
) -> None:
|
||||
self.stylesheet = stylesheet
|
||||
self.variables: dict[str, object] = {}
|
||||
self.variables: dict[str, str] = {}
|
||||
self._css_variables: dict[str, list[Token]] = {}
|
||||
if variables:
|
||||
self.set_variables(variables)
|
||||
|
||||
@classmethod
|
||||
def _get_snippet(cls, code: str, line_no: int) -> Panel:
|
||||
@@ -54,7 +58,7 @@ class StylesheetErrors:
|
||||
)
|
||||
return Panel(syntax, border_style="red")
|
||||
|
||||
def add_variables(self, **variable_map: object) -> None:
|
||||
def set_variables(self, variable_map: dict[str, str]) -> None:
|
||||
"""Pre-populate CSS variables."""
|
||||
self.variables.update(variable_map)
|
||||
self._css_variables = tokenize_values(self.variables)
|
||||
@@ -95,8 +99,9 @@ class StylesheetErrors:
|
||||
|
||||
@rich.repr.auto
|
||||
class Stylesheet:
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, *, variables: dict[str, str] | None = None) -> None:
|
||||
self.rules: list[RuleSet] = []
|
||||
self.variables = variables or {}
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield self.rules
|
||||
@@ -114,6 +119,9 @@ class Stylesheet:
|
||||
def error_renderable(self) -> StylesheetErrors:
|
||||
return StylesheetErrors(self)
|
||||
|
||||
def set_variables(self, variables: dict[str, str]) -> None:
|
||||
self.variables = variables
|
||||
|
||||
def read(self, filename: str) -> None:
|
||||
filename = os.path.expanduser(filename)
|
||||
try:
|
||||
@@ -123,14 +131,14 @@ class Stylesheet:
|
||||
except Exception as error:
|
||||
raise StylesheetError(f"unable to read {filename!r}; {error}")
|
||||
try:
|
||||
rules = list(parse(css, path))
|
||||
rules = list(parse(css, path, variables=self.variables))
|
||||
except Exception as error:
|
||||
raise StylesheetError(f"failed to parse {filename!r}; {error}")
|
||||
self.rules.extend(rules)
|
||||
|
||||
def parse(self, css: str, *, path: str = "") -> None:
|
||||
try:
|
||||
rules = list(parse(css, path))
|
||||
rules = list(parse(css, path, variables=self.variables))
|
||||
except Exception as error:
|
||||
raise StylesheetError(f"failed to parse css; {error}")
|
||||
self.rules.extend(rules)
|
||||
|
||||
@@ -31,7 +31,13 @@ class ColorProperty:
|
||||
|
||||
|
||||
class ColorSystem:
|
||||
"""Defines a standard set of colors and variations for building a UI."""
|
||||
"""Defines a standard set of colors and variations for building a UI.
|
||||
|
||||
Primary is the main theme color
|
||||
Secondary is a second theme color
|
||||
|
||||
|
||||
"""
|
||||
|
||||
COLOR_NAMES = [
|
||||
"primary",
|
||||
@@ -97,7 +103,7 @@ class ColorSystem:
|
||||
def generate(
|
||||
self,
|
||||
dark: bool = False,
|
||||
luminosity_spread: float = 0.2,
|
||||
luminosity_spread: float = 0.15,
|
||||
text_alpha: float = 0.9,
|
||||
) -> dict[str, str]:
|
||||
"""Generate a mapping of color name on to a CSS color.
|
||||
@@ -129,6 +135,12 @@ class ColorSystem:
|
||||
colors: dict[str, str] = {}
|
||||
|
||||
def luminosity_range(spread) -> Iterable[tuple[str, float]]:
|
||||
"""Get the range of shades from darken2 to lighten2.
|
||||
|
||||
Returns:
|
||||
Iterable of tuples (<SHADE SUFFIX, LUMINOSITY DELTA>)
|
||||
|
||||
"""
|
||||
luminosity_step = spread / 2
|
||||
for n in range(-2, +3):
|
||||
if n < 0:
|
||||
@@ -139,6 +151,7 @@ class ColorSystem:
|
||||
label = ""
|
||||
yield (f"{label}{abs(n) if n else ''}"), n * luminosity_step
|
||||
|
||||
# Color names and color
|
||||
COLORS = [
|
||||
("primary", primary),
|
||||
("secondary", secondary),
|
||||
@@ -152,6 +165,7 @@ class ColorSystem:
|
||||
("accent3", accent3),
|
||||
]
|
||||
|
||||
# Colors names that have a dark varient
|
||||
DARK_SHADES = {"primary", "secondary"}
|
||||
|
||||
for name, color in COLORS:
|
||||
@@ -203,8 +217,8 @@ class ColorSystem:
|
||||
|
||||
if __name__ == "__main__":
|
||||
color_system = ColorSystem(
|
||||
primary="#4caf50",
|
||||
secondary="#ffa000",
|
||||
primary="#1b5e20",
|
||||
secondary="#263238",
|
||||
warning="#ffa000",
|
||||
error="#C62828",
|
||||
success="#558B2F",
|
||||
|
||||
@@ -19,26 +19,6 @@ class WidgetPlacement(NamedTuple):
|
||||
widget: Widget | None = None # A widget of None means empty space
|
||||
order: int = 0
|
||||
|
||||
def apply_margin(self) -> "WidgetPlacement":
|
||||
"""Apply any margin present in the styles of the widget by shrinking the
|
||||
region appropriately.
|
||||
|
||||
Returns:
|
||||
WidgetPlacement: Returns ``self`` if no ``margin`` styles are present in
|
||||
the widget. Otherwise, returns a copy of self with a region shrunk to
|
||||
account for margin.
|
||||
"""
|
||||
region, widget, order = self
|
||||
if widget is not None:
|
||||
styles = widget.styles
|
||||
if styles.margin:
|
||||
return WidgetPlacement(
|
||||
region=region.shrink(styles.margin),
|
||||
widget=widget,
|
||||
order=order,
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
class Layout(ABC):
|
||||
"""Responsible for arranging Widgets in a view and rendering them."""
|
||||
|
||||
@@ -9,6 +9,7 @@ from . import events, messages, errors
|
||||
|
||||
from .geometry import Offset, Region
|
||||
from ._compositor import Compositor
|
||||
from .reactive import Reactive
|
||||
from .widget import Widget
|
||||
from .renderables.gradient import VerticalGradient
|
||||
|
||||
@@ -24,11 +25,16 @@ class Screen(Widget):
|
||||
|
||||
"""
|
||||
|
||||
dark = Reactive(False)
|
||||
|
||||
def __init__(self, name: str | None = None, id: str | None = None) -> None:
|
||||
super().__init__(name=name, id=id)
|
||||
self._compositor = Compositor()
|
||||
self._dirty_widgets: list[Widget] = []
|
||||
|
||||
def watch_dark(self, dark: bool) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def is_transparent(self) -> bool:
|
||||
return False
|
||||
|
||||
@@ -67,7 +67,7 @@ class Widget(DOMNode):
|
||||
can_focus: bool = False
|
||||
|
||||
DEFAULT_STYLES = """
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -421,7 +421,10 @@ class Widget(DOMNode):
|
||||
|
||||
@property
|
||||
def region(self) -> Region:
|
||||
return self.screen._compositor.get_widget_region(self)
|
||||
try:
|
||||
return self.screen._compositor.get_widget_region(self)
|
||||
except errors.NoWidget:
|
||||
return Region()
|
||||
|
||||
@property
|
||||
def scroll_offset(self) -> Offset:
|
||||
|
||||
Reference in New Issue
Block a user