mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
ws
This commit is contained in:
31
examples/basic.py
Normal file
31
examples/basic.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Placeholder
|
||||
|
||||
|
||||
class BasicApp(App):
|
||||
"""Demonstrates smooth animation. Press 'b' to see it in action."""
|
||||
|
||||
css = """
|
||||
|
||||
App > View {
|
||||
layout: dock
|
||||
}
|
||||
|
||||
#widget1 {
|
||||
edge: top
|
||||
}
|
||||
|
||||
#widget2 {
|
||||
|
||||
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
async def on_mount(self) -> None:
|
||||
"""Build layout here."""
|
||||
|
||||
await self.view.mount(widget1=Placeholder(), widget2=Placeholder())
|
||||
|
||||
|
||||
SmoothApp.run(log="textual.log")
|
||||
@@ -1,6 +1,5 @@
|
||||
from rich.markdown import Markdown
|
||||
|
||||
from textual import events
|
||||
from textual.app import App
|
||||
from textual.widgets import Header, Footer, Placeholder, ScrollView
|
||||
|
||||
@@ -8,24 +7,38 @@ from textual.widgets import Header, Footer, Placeholder, ScrollView
|
||||
class MyApp(App):
|
||||
"""An example of a very simple Textual App"""
|
||||
|
||||
async def on_load(self, event: events.Load) -> None:
|
||||
stylesheet = """
|
||||
|
||||
App > View {
|
||||
layout: dock
|
||||
}
|
||||
|
||||
#body {
|
||||
padding: 1
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
edge left
|
||||
size: 40
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
async def on_load(self) -> None:
|
||||
"""Bind keys with the app loads (but before entering application mode)"""
|
||||
await self.bind("b", "view.toggle('sidebar')", "Toggle sidebar")
|
||||
await self.bind("q", "quit", "Quit")
|
||||
|
||||
async def on_mount(self, event: events.Mount) -> None:
|
||||
async def on_mount(self) -> None:
|
||||
"""Create and dock the widgets."""
|
||||
|
||||
# A scrollview to contain the markdown file
|
||||
body = ScrollView(gutter=1)
|
||||
|
||||
# Header / footer / dock
|
||||
await self.view.dock(Header(), edge="top")
|
||||
await self.view.dock(Footer(), edge="bottom")
|
||||
await self.view.dock(Placeholder(), edge="left", size=30, name="sidebar")
|
||||
|
||||
# Dock the body in the remaining space
|
||||
await self.view.dock(body, edge="right")
|
||||
body = ScrollView()
|
||||
await self.view.mount(
|
||||
Header(),
|
||||
Footer(),
|
||||
body=body,
|
||||
sidebar=Placeholder(),
|
||||
)
|
||||
|
||||
async def get_markdown(filename: str) -> None:
|
||||
with open(filename, "rt") as fh:
|
||||
@@ -35,4 +48,4 @@ class MyApp(App):
|
||||
await self.call_later(get_markdown, "richreadme.md")
|
||||
|
||||
|
||||
MyApp.run(title="Simple App", log="textual.log", css_file="theme.css")
|
||||
MyApp.run(title="Simple App", log="textual.log")
|
||||
|
||||
@@ -71,6 +71,7 @@ class App(MessagePump):
|
||||
log_verbosity: int = 1,
|
||||
title: str = "Textual Application",
|
||||
css_file: str | None = None,
|
||||
css: str | None = None,
|
||||
):
|
||||
"""The Textual Application base class
|
||||
|
||||
@@ -112,6 +113,7 @@ class App(MessagePump):
|
||||
self.stylesheet = Stylesheet()
|
||||
|
||||
self.css_file = css_file
|
||||
self.css = css
|
||||
|
||||
super().__init__()
|
||||
|
||||
@@ -299,7 +301,8 @@ class App(MessagePump):
|
||||
try:
|
||||
if self.css_file is not None:
|
||||
self.stylesheet.read(self.css_file)
|
||||
print(self.stylesheet.css)
|
||||
if self.css is not None:
|
||||
self.stylesheet.parse(self.css)
|
||||
except Exception:
|
||||
self.panic()
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
||||
from .styles import Styles
|
||||
|
||||
|
||||
class _BoxProperty:
|
||||
class BoxProperty:
|
||||
|
||||
DEFAULT = ("", Style())
|
||||
|
||||
@@ -67,7 +67,7 @@ class Edges(NamedTuple):
|
||||
yield "left", left
|
||||
|
||||
|
||||
class _BorderProperty:
|
||||
class BorderProperty:
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self._properties = (
|
||||
f"{name}_top",
|
||||
@@ -129,14 +129,21 @@ class _BorderProperty:
|
||||
raise StyleValueError("expected 1, 2, or 4 values")
|
||||
|
||||
|
||||
class _StyleProperty:
|
||||
class StyleProperty:
|
||||
|
||||
DEFAULT_STYLE = Style()
|
||||
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self._internal_name = f"_{name}"
|
||||
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Style:
|
||||
return getattr(obj, self._internal_name)
|
||||
return getattr(obj, self._internal_name) or self.DEFAULT_STYLE
|
||||
|
||||
def __set__(self, obj: Styles, style: Style | str | None) -> Style | str | None:
|
||||
if style is None:
|
||||
setattr(obj, self._internal_name, None)
|
||||
return None
|
||||
|
||||
def __set__(self, obj: Styles, style: Style | str) -> Style:
|
||||
if isinstance(style, str):
|
||||
_style = Style.parse(style)
|
||||
else:
|
||||
@@ -145,7 +152,7 @@ class _StyleProperty:
|
||||
return _style
|
||||
|
||||
|
||||
class _SpacingProperty:
|
||||
class SpacingProperty:
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self._internal_name = f"_{name}"
|
||||
|
||||
@@ -158,7 +165,7 @@ class _SpacingProperty:
|
||||
return spacing
|
||||
|
||||
|
||||
class _DocksProperty:
|
||||
class DocksProperty:
|
||||
def __get__(
|
||||
self, obj: Styles, objtype: type[Styles] | None = None
|
||||
) -> tuple[str, ...]:
|
||||
@@ -173,7 +180,7 @@ class _DocksProperty:
|
||||
return _docks
|
||||
|
||||
|
||||
class _DockGroupProperty:
|
||||
class DockGroupProperty:
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> str:
|
||||
return obj._dock_group or ""
|
||||
|
||||
@@ -182,7 +189,7 @@ class _DockGroupProperty:
|
||||
return spacing
|
||||
|
||||
|
||||
class _OffsetProperty:
|
||||
class OffsetProperty:
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self._internal_name = f"_{name}"
|
||||
|
||||
@@ -195,7 +202,7 @@ class _OffsetProperty:
|
||||
return offset
|
||||
|
||||
|
||||
class _DockEdgeProperty:
|
||||
class DockEdgeProperty:
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> str:
|
||||
return obj._dock_edge or ""
|
||||
|
||||
@@ -204,3 +211,37 @@ class _DockEdgeProperty:
|
||||
raise ValueError(f"dock edge must be one of {friendly_list(VALID_EDGE)}")
|
||||
obj._dock_edge = edge
|
||||
return edge
|
||||
|
||||
|
||||
class IntegerProperty:
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self._internal_name = f"_{name}"
|
||||
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> int:
|
||||
return getattr(obj, self._internal_name, 0)
|
||||
|
||||
def __set__(self, obj: Styles, value: int | None) -> int | None:
|
||||
setattr(obj, self._internal_name, value)
|
||||
return value
|
||||
|
||||
|
||||
class StringProperty:
|
||||
def __init__(self, valid_values: set[str], default: str) -> None:
|
||||
self._valid_values = valid_values
|
||||
self._default = default
|
||||
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self._name = name
|
||||
self._internal_name = f"_{name}"
|
||||
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> str:
|
||||
return getattr(obj, self._internal_name, self._default)
|
||||
|
||||
def __set__(self, obj: Styles, value: str | None = None) -> str | None:
|
||||
if value is not None:
|
||||
if value not in self._valid_values:
|
||||
raise StyleValueError(
|
||||
f"{self._name} must be one of {friendly_list(self._valid_values)}"
|
||||
)
|
||||
setattr(obj, self._internal_name, value)
|
||||
return value
|
||||
|
||||
@@ -16,14 +16,16 @@ from .constants import (
|
||||
)
|
||||
from ..geometry import NULL_OFFSET, Offset, Spacing
|
||||
from ._style_properties import (
|
||||
_BorderProperty,
|
||||
_BoxProperty,
|
||||
_DockEdgeProperty,
|
||||
_DocksProperty,
|
||||
_DockGroupProperty,
|
||||
_OffsetProperty,
|
||||
_SpacingProperty,
|
||||
_StyleProperty,
|
||||
BorderProperty,
|
||||
BoxProperty,
|
||||
DockEdgeProperty,
|
||||
DocksProperty,
|
||||
DockGroupProperty,
|
||||
IntegerProperty,
|
||||
OffsetProperty,
|
||||
SpacingProperty,
|
||||
StringProperty,
|
||||
StyleProperty,
|
||||
)
|
||||
from .types import Display, Visibility
|
||||
|
||||
@@ -33,10 +35,10 @@ class Styles:
|
||||
|
||||
_display: Display | None = None
|
||||
_visibility: Visibility | None = None
|
||||
_layout: str | None = None
|
||||
|
||||
_text: Style = Style()
|
||||
_text: Style | None = None
|
||||
|
||||
_layout: str = ""
|
||||
_padding: Spacing | None = None
|
||||
_margin: Spacing | None = None
|
||||
_offset: Offset | None = None
|
||||
@@ -51,71 +53,45 @@ class Styles:
|
||||
_outline_bottom: tuple[str, Style] | None = None
|
||||
_outline_left: tuple[str, Style] | None = None
|
||||
|
||||
_size: int | None = None
|
||||
_fraction: int | None = None
|
||||
_min_size: int | None = None
|
||||
|
||||
_dock_group: str | None = None
|
||||
_dock_edge: str | None = None
|
||||
_docks: tuple[str, ...] | None = None
|
||||
|
||||
important: set[str] = field(default_factory=set)
|
||||
|
||||
@property
|
||||
def display(self) -> Display:
|
||||
return self._display or "block"
|
||||
display = StringProperty(VALID_DISPLAY, "block")
|
||||
visibility = StringProperty(VALID_VISIBILITY, "visible")
|
||||
layout = StringProperty(VALID_LAYOUT, "dock")
|
||||
|
||||
@display.setter
|
||||
def display(self, display: Display) -> None:
|
||||
if display not in VALID_DISPLAY:
|
||||
raise StyleValueError(
|
||||
f"display must be one of {friendly_list(VALID_DISPLAY)}"
|
||||
)
|
||||
self._display = display
|
||||
text = StyleProperty()
|
||||
|
||||
@property
|
||||
def visibility(self) -> Visibility:
|
||||
return self._visibility or "visible"
|
||||
padding = SpacingProperty()
|
||||
margin = SpacingProperty()
|
||||
offset = OffsetProperty()
|
||||
|
||||
@visibility.setter
|
||||
def visibility(self, visibility: Visibility) -> None:
|
||||
if visibility not in VALID_VISIBILITY:
|
||||
raise StyleValueError(
|
||||
f"visibility must be one of {friendly_list(VALID_VISIBILITY)}"
|
||||
)
|
||||
self._visibility = visibility
|
||||
border = BorderProperty()
|
||||
border_top = BoxProperty()
|
||||
border_right = BoxProperty()
|
||||
border_bottom = BoxProperty()
|
||||
border_left = BoxProperty()
|
||||
|
||||
text = _StyleProperty()
|
||||
outline = BorderProperty()
|
||||
outline_top = BoxProperty()
|
||||
outline_right = BoxProperty()
|
||||
outline_bottom = BoxProperty()
|
||||
outline_left = BoxProperty()
|
||||
|
||||
@property
|
||||
def layout(self) -> str:
|
||||
return self._layout
|
||||
size = IntegerProperty()
|
||||
fraction = IntegerProperty()
|
||||
min_size = IntegerProperty()
|
||||
|
||||
@layout.setter
|
||||
def layout(self, layout: str) -> None:
|
||||
if layout not in VALID_LAYOUT:
|
||||
raise StyleValueError(
|
||||
f"layout must be one of {friendly_list(VALID_LAYOUT)}"
|
||||
)
|
||||
self._layout = layout
|
||||
|
||||
offset = _OffsetProperty()
|
||||
|
||||
padding = _SpacingProperty()
|
||||
margin = _SpacingProperty()
|
||||
|
||||
border = _BorderProperty()
|
||||
border_top = _BoxProperty()
|
||||
border_right = _BoxProperty()
|
||||
border_bottom = _BoxProperty()
|
||||
border_left = _BoxProperty()
|
||||
|
||||
outline = _BorderProperty()
|
||||
outline_top = _BoxProperty()
|
||||
outline_right = _BoxProperty()
|
||||
outline_bottom = _BoxProperty()
|
||||
outline_left = _BoxProperty()
|
||||
|
||||
dock_group = _DockGroupProperty()
|
||||
docks = _DocksProperty()
|
||||
|
||||
dock_edge = _DockEdgeProperty()
|
||||
dock_group = DockGroupProperty()
|
||||
docks = DocksProperty()
|
||||
dock_edge = DockEdgeProperty()
|
||||
|
||||
@property
|
||||
def has_border(self) -> bool:
|
||||
@@ -238,7 +214,6 @@ if __name__ == "__main__":
|
||||
styles.docks = "foo bar"
|
||||
styles.text = "italic blue"
|
||||
styles.dock_group = "bar"
|
||||
styles.dock_edge = "sdfsdf"
|
||||
|
||||
from rich import inspect, print
|
||||
|
||||
|
||||
@@ -41,10 +41,6 @@ class Dock:
|
||||
|
||||
|
||||
class DockLayout(Layout):
|
||||
def __init__(self, docks: list[Dock] = None) -> None:
|
||||
self.docks: list[Dock] = docks or []
|
||||
super().__init__()
|
||||
|
||||
def get_widgets(self) -> Iterable[Widget]:
|
||||
for dock in self.docks:
|
||||
yield from dock.widgets
|
||||
|
||||
@@ -41,7 +41,9 @@ class View(Widget):
|
||||
|
||||
layout_factory: ClassVar[Callable[[], Layout]]
|
||||
|
||||
def __init__(self, layout: Layout = None, name: str | None = None) -> None:
|
||||
def __init__(
|
||||
self, layout: Layout = None, name: str | None = None, id: str | None = None
|
||||
) -> None:
|
||||
self._layout: Layout = layout or self.layout_factory()
|
||||
|
||||
self.mouse_over: Widget | None = None
|
||||
@@ -55,7 +57,7 @@ class View(Widget):
|
||||
[],
|
||||
)
|
||||
|
||||
super().__init__(name=name)
|
||||
super().__init__(name=name, id=id)
|
||||
|
||||
def __init_subclass__(
|
||||
cls, layout: Callable[[], Layout] | None = None, **kwargs
|
||||
@@ -144,7 +146,7 @@ class View(Widget):
|
||||
for name, widget in name_widgets:
|
||||
if name is not None:
|
||||
widget.name = name
|
||||
self.add_child(widget)
|
||||
self._add_child(widget)
|
||||
|
||||
self.refresh()
|
||||
|
||||
|
||||
@@ -21,16 +21,21 @@ class DockView(View):
|
||||
async def dock(
|
||||
self,
|
||||
*widgets: Widget,
|
||||
name: str | None = None,
|
||||
id: str | None = None,
|
||||
edge: DockEdge = "top",
|
||||
z: int = 0,
|
||||
size: int | None | DoNotSet = do_not_set,
|
||||
name: str | None = None,
|
||||
) -> None:
|
||||
|
||||
dock = Dock(edge, widgets, z)
|
||||
assert isinstance(self._layout, DockLayout)
|
||||
self._layout.docks.append(dock)
|
||||
for widget in widgets:
|
||||
if id is not None:
|
||||
widget._id = id
|
||||
if name is not None:
|
||||
widget.name = name
|
||||
if size is not do_not_set:
|
||||
widget.layout_size = cast(Optional[int], size)
|
||||
if name is None:
|
||||
@@ -42,17 +47,18 @@ class DockView(View):
|
||||
async def dock_grid(
|
||||
self,
|
||||
*,
|
||||
name: str | None = None,
|
||||
id: str | None = None,
|
||||
edge: DockEdge = "top",
|
||||
z: int = 0,
|
||||
size: int | None | DoNotSet = do_not_set,
|
||||
name: str | None = None,
|
||||
gap: tuple[int, int] | int | None = None,
|
||||
gutter: tuple[int, int] | int | None = None,
|
||||
align: tuple[GridAlign, GridAlign] | None = None,
|
||||
) -> GridLayout:
|
||||
|
||||
grid = GridLayout(gap=gap, gutter=gutter, align=align)
|
||||
view = View(layout=grid, name=name)
|
||||
view = View(layout=grid, id=id, name=name)
|
||||
dock = Dock(edge, (view,), z)
|
||||
assert isinstance(self._layout, DockLayout)
|
||||
self._layout.docks.append(dock)
|
||||
|
||||
@@ -85,7 +85,6 @@ class Widget(MessagePump):
|
||||
|
||||
super().__init__()
|
||||
|
||||
id: str | None = None
|
||||
visible: Reactive[bool] = Reactive(True, layout=True)
|
||||
layout_size: Reactive[int | None] = Reactive(None, layout=True)
|
||||
layout_fraction: Reactive[int] = Reactive(1, layout=True)
|
||||
@@ -124,7 +123,7 @@ class Widget(MessagePump):
|
||||
renderable = self.render_styled()
|
||||
return renderable
|
||||
|
||||
def add_child(self, widget: Widget) -> Widget:
|
||||
def _add_child(self, widget: Widget) -> Widget:
|
||||
"""Add a child widget.
|
||||
|
||||
Args:
|
||||
@@ -134,12 +133,21 @@ class Widget(MessagePump):
|
||||
self.children._append(widget)
|
||||
return widget
|
||||
|
||||
def get_child(self, name: str | None = None) -> Widget:
|
||||
def get_child(self, name: str | None = None, id: str | None = None) -> Widget:
|
||||
if name is not None:
|
||||
for widget in self.children:
|
||||
if widget.name == name:
|
||||
return widget
|
||||
if id is not None:
|
||||
for widget in self.children:
|
||||
if widget.id == id:
|
||||
return widget
|
||||
raise errors.MissingWidget(f"Widget named {name!r} was not found in {self}")
|
||||
|
||||
@property
|
||||
def id(self) -> str | None:
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def class_names(self) -> frozenset[str]:
|
||||
return frozenset(self._class_names)
|
||||
@@ -188,6 +196,7 @@ class Widget(MessagePump):
|
||||
if self.padding is not None:
|
||||
renderable = Padding(renderable, self.padding)
|
||||
if self.border not in ("", "none"):
|
||||
1 / 0
|
||||
_border_style = self.console.get_style(self.border_style)
|
||||
renderable = Border(
|
||||
renderable,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from logging import getLogger
|
||||
|
||||
|
||||
@@ -35,15 +35,13 @@ class ScrollView(View):
|
||||
super().__init__(name=name, layout=layout)
|
||||
|
||||
self.fluid = fluid
|
||||
self.vscroll = self.add_child(ScrollBar(vertical=True))
|
||||
self.hscroll = self.add_child(ScrollBar(vertical=False))
|
||||
self.window = self.add_child(
|
||||
WindowView(
|
||||
self.vscroll = ScrollBar(vertical=True)
|
||||
self.hscroll = ScrollBar(vertical=False)
|
||||
self.window = WindowView(
|
||||
"" if contents is None else contents,
|
||||
auto_width=auto_width,
|
||||
gutter=gutter,
|
||||
)
|
||||
)
|
||||
|
||||
layout.add_column("main")
|
||||
layout.add_column("vscroll", size=1)
|
||||
|
||||
Reference in New Issue
Block a user