fix for footer

This commit is contained in:
Will McGugan
2022-08-11 16:45:31 +01:00
parent e8d0d7f3ef
commit bd82ece9cf
10 changed files with 117 additions and 36 deletions

View File

@@ -47,7 +47,7 @@ DataTable {
/* opacity: 50%; */
padding: 1;
margin: 1 2;
height: 12;
height: 24;
}
#sidebar {
@@ -55,6 +55,7 @@ DataTable {
background: $panel;
dock: left;
width: 30;
margin-bottom: 1;
offset-x: -100%;
transition: offset 500ms in_out_cubic;

View File

@@ -6,7 +6,7 @@ from rich.text import Text
from textual.app import App, ComposeResult
from textual.reactive import Reactive
from textual.widget import Widget
from textual.widgets import Static, DataTable, DirectoryTree
from textual.widgets import Static, DataTable, DirectoryTree, Footer
from textual.layout import Vertical
CODE = '''
@@ -109,7 +109,8 @@ class BasicApp(App, css_path="basic.css"):
def on_load(self):
"""Bind keys here."""
self.bind("s", "toggle_class('#sidebar', '-active')")
self.bind("s", "toggle_class('#sidebar', '-active')", description="Sidebar")
self.bind("d", "toggle_dark()", description="Dark mode")
def compose(self) -> ComposeResult:
table = DataTable()
@@ -142,17 +143,17 @@ class BasicApp(App, css_path="basic.css"):
Tweet(TweetBody(), classes="scroll-horizontal"),
Tweet(TweetBody(), classes="scroll-horizontal"),
Tweet(TweetBody(), classes="scroll-horizontal"),
Widget(
Widget(classes="title"),
Widget(classes="user"),
OptionItem(),
OptionItem(),
OptionItem(),
Widget(classes="content"),
id="sidebar",
),
)
yield Widget(id="footer")
yield Widget(
Widget(classes="title"),
Widget(classes="user"),
OptionItem(),
OptionItem(),
OptionItem(),
Widget(classes="content"),
id="sidebar",
)
yield Footer()
table.add_column("Foo", width=20)
table.add_column("Bar", width=20)
@@ -167,7 +168,7 @@ class BasicApp(App, css_path="basic.css"):
async def on_key(self, event) -> None:
await self.dispatch_key(event)
def key_d(self):
def action_toggle_dark(self):
self.dark = not self.dark
async def key_q(self):

13
sandbox/will/footer.py Normal file
View File

@@ -0,0 +1,13 @@
from textual.app import App
from textual.widgets import Footer
class FooterApp(App):
def on_mount(self):
self.dark = True
self.bind("b", "app.bell", description="Play the Bell")
self.bind("f1", "app.bell", description="Hello World")
self.bind("f2", "app.bell", description="Do something")
def compose(self):
yield Footer()

View File

@@ -12,7 +12,7 @@ from rich.style import Style
from .._animator import Animation, EasingFunction
from ..color import Color
from ..geometry import Offset, Size, Spacing
from ..geometry import Offset, Spacing
from ._style_properties import (
AlignProperty,
BorderProperty,
@@ -223,7 +223,7 @@ class StylesBase(ABC):
layers = NameListProperty()
transitions = TransitionsProperty()
rich_style = StyleProperty()
# rich_style = StyleProperty()
tint = ColorProperty("transparent")
scrollbar_color = ColorProperty("ansi_bright_magenta")
@@ -800,6 +800,12 @@ class RenderStyles(StylesBase):
"""Quick access to the inline styles."""
return self._inline_styles
@property
def rich_style(self) -> Style:
"""Get a Rich style for this Styles object."""
assert self.node is not None
return self.node.rich_style
def __rich_repr__(self) -> rich.repr.Result:
for rule_name in RULE_NAMES:
if self.has_rule(rule_name):

View File

@@ -23,7 +23,7 @@ from .parse import parse
from .styles import RulesMap, Styles
from .tokenize import tokenize_values, Token
from .tokenizer import TokenError
from .types import Specificity3, Specificity5
from .types import Specificity3, Specificity6
from ..dom import DOMNode
from .. import messages
@@ -325,7 +325,7 @@ class Stylesheet:
# We can use this to determine, for a given rule, whether we should apply it
# or not by examining the specificity. If we have two rules for the
# same attribute, then we can choose the most specific rule and use that.
rule_attributes: dict[str, list[tuple[Specificity5, object]]]
rule_attributes: dict[str, list[tuple[Specificity6, object]]]
rule_attributes = defaultdict(list)
_check_rule = self._check_rule
@@ -352,12 +352,12 @@ class Stylesheet:
self.replace_rules(node, node_rules, animate=animate)
node.component_styles.clear()
node._component_styles.clear()
for component in node.COMPONENT_CLASSES:
virtual_node = DOMNode(classes=component)
virtual_node.set_parent(node)
self.apply(virtual_node, animate=False)
node.component_styles[component] = virtual_node.styles
node._component_styles[component] = virtual_node.styles
@classmethod
def replace_rules(

View File

@@ -68,7 +68,7 @@ class DOMNode(MessagePump):
self._inline_styles: Styles = Styles(self)
self.styles = RenderStyles(self, self._css_styles, self._inline_styles)
# A mapping of class names to Styles set in COMPONENT_CLASSES
self.component_styles: dict[str, StylesBase] = {}
self._component_styles: dict[str, RenderStyles] = {}
super().__init__()
@@ -80,6 +80,23 @@ class DOMNode(MessagePump):
css_type_names.add(base.__name__.lower())
cls._css_type_names = frozenset(css_type_names)
def get_component_styles(self, name: str) -> RenderStyles:
"""Get a "component" styles object (must be defined in COMPONENT_CLASSES classvar).
Args:
name (str): Name of the component.
Raises:
KeyError: If the component class doesn't exist.
Returns:
RenderStyles: A Styles object.
"""
if name not in self._component_styles:
raise KeyError(f"No {name!r} key in COMPONENT_CLASSES")
styles = self._component_styles[name]
return styles
@property
def _node_bases(self) -> Iterator[Type[DOMNode]]:
"""Get the DOMNode bases classes (including self.__class__)

View File

@@ -938,15 +938,13 @@ class Widget(DOMNode):
def watch(self, attribute_name, callback: Callable[[Any], Awaitable[None]]) -> None:
watch(self, attribute_name, callback)
def _render_styled(self) -> RenderableType:
def post_render(self, renderable: RenderableType) -> RenderableType:
"""Applies style attributes to the default renderable.
Returns:
RenderableType: A new renderable.
"""
renderable = self.render()
if isinstance(renderable, str):
renderable = Text.from_markup(renderable)
@@ -1002,7 +1000,8 @@ class Widget(DOMNode):
def _render_content(self) -> None:
"""Render all lines."""
width, height = self.size
renderable = self._render_styled()
renderable = self.render()
renderable = self.post_render(renderable)
options = self.console.options.update_dimensions(width, height).update(
highlight=False
)

View File

@@ -349,9 +349,9 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
Lines: A list of segments per line.
"""
if hover:
style += self.component_styles["datatable--highlight"].node.rich_style
style += self.get_component_styles("datatable--highlight").rich_style
if cursor:
style += self.component_styles["datatable--cursor"].node.rich_style
style += self.get_component_styles("datatable--cursor").rich_style
cell_key = (row_index, column_index, style, cursor, hover)
if cell_key not in self._cell_render_cache:
style += Style.from_meta({"row": row_index, "column": column_index})
@@ -394,7 +394,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
render_cell = self._render_cell
if self.fixed_columns:
fixed_style = self.component_styles["datatable--fixed"].node.rich_style
fixed_style = self.get_component_styles("datatable--fixed").rich_style
fixed_style += Style.from_meta({"fixed": True})
fixed_row = [
render_cell(row_index, column.index, fixed_style, column.width)[line_no]
@@ -404,13 +404,13 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
fixed_row = []
if row_index == -1:
row_style = self.component_styles["datatable--header"].node.rich_style
row_style = self.get_component_styles("datatable--header").rich_style
else:
if self.zebra_stripes:
component_row_style = (
"datatable--odd-row" if row_index % 2 else "datatable--even-row"
)
row_style = self.component_styles[component_row_style].node.rich_style
row_style = self.get_component_styles(component_row_style).rich_style
else:
row_style = base_style

View File

@@ -12,10 +12,41 @@ from ..widget import Widget
@rich.repr.auto
class Footer(Widget):
CSS = """
Footer {
background: $accent;
color: $text-accent;
dock: bottom;
height: 1;
}
Footer > .footer--highlight {
background: $accent-darken-1;
color: $text-accent-darken-1;
}
Footer > .footer--highlight-key {
background: $secondary;
color: $text-secondary;
text-style: bold;
}
Footer > .footer--key {
text-style: bold;
background: $accent-darken-2;
color: $text-accent-darken-2;
}
"""
COMPONENT_CLASSES = {
"footer--description",
"footer--key",
"footer--highlight",
"footer--highlight-key",
}
def __init__(self) -> None:
self.keys: list[tuple[str, str]] = []
super().__init__()
self.layout_size = 1
self._key_text: Text | None = None
highlight_key: Reactive[str | None] = Reactive(None)
@@ -37,13 +68,19 @@ class Footer(Widget):
def make_key_text(self) -> Text:
"""Create text containing all the keys."""
base_style = self.rich_style
text = Text(
style="white on dark_green",
style=self.rich_style,
no_wrap=True,
overflow="ellipsis",
justify="left",
end="",
)
highlight_style = self.get_component_styles("footer--highlight").rich_style
highlight_key_style = self.get_component_styles(
"footer--highlight-key"
).rich_style
key_style = self.get_component_styles("footer--key").rich_style
for binding in self.app.bindings.shown_keys:
key_display = (
binding.key.upper()
@@ -52,13 +89,20 @@ class Footer(Widget):
)
hovered = self.highlight_key == binding.key
key_text = Text.assemble(
(f" {key_display} ", "reverse" if hovered else "default on default"),
f" {binding.description} ",
(f" {key_display} ", highlight_key_style if hovered else key_style),
(
f" {binding.description} ",
highlight_style if hovered else base_style,
),
meta={"@click": f"app.press('{binding.key}')", "key": binding.key},
)
text.append_text(key_text)
self.log(text)
return text
def post_render(self, renderable):
return renderable
def render(self) -> RenderableType:
if self._key_text is None:
self._key_text = self.make_key_text()

View File

@@ -267,7 +267,7 @@ class TreeControl(Generic[NodeDataType], Widget, can_focus=True):
return None
def render(self) -> RenderableType:
self._tree.guide_style = self.component_styles["tree--guides"].node.rich_style
self._tree.guide_style = self._component_styles["tree--guides"].node.rich_style
return self._tree
def render_node(self, node: TreeNode[NodeDataType]) -> RenderableType: