mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
fixed e2e
This commit is contained in:
@@ -14,30 +14,45 @@
|
|||||||
|
|
||||||
App > Screen {
|
App > Screen {
|
||||||
|
|
||||||
background: $surface;
|
|
||||||
color: $text-surface;
|
|
||||||
layers: sidebar;
|
|
||||||
|
|
||||||
color: $text-background;
|
|
||||||
background: $background;
|
background: $background;
|
||||||
|
color: $text;
|
||||||
|
layers: base sidebar;
|
||||||
layout: vertical;
|
layout: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tree-container {
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 20;
|
||||||
|
margin: 1 2;
|
||||||
|
background: $surface;
|
||||||
|
padding: 1 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryTree {
|
||||||
|
padding: 0 1;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DataTable {
|
DataTable {
|
||||||
/*border:heavy red;*/
|
/*border:heavy red;*/
|
||||||
/* tint: 10% green; */
|
/* tint: 10% green; */
|
||||||
/* text-opacity: 50%; */
|
/* text-opacity: 50%; */
|
||||||
padding: 1;
|
padding: 1;
|
||||||
margin: 1 2;
|
margin: 1 2;
|
||||||
height: 12;
|
height: 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar {
|
#sidebar {
|
||||||
color: $text-panel;
|
|
||||||
background: $panel;
|
background: $panel;
|
||||||
|
color: $text;
|
||||||
dock: left;
|
dock: left;
|
||||||
width: 30;
|
width: 30;
|
||||||
|
margin-bottom: 1;
|
||||||
offset-x: -100%;
|
offset-x: -100%;
|
||||||
|
|
||||||
transition: offset 500ms in_out_cubic;
|
transition: offset 500ms in_out_cubic;
|
||||||
@@ -51,7 +66,7 @@ DataTable {
|
|||||||
#sidebar .title {
|
#sidebar .title {
|
||||||
height: 1;
|
height: 1;
|
||||||
background: $primary-background-darken-1;
|
background: $primary-background-darken-1;
|
||||||
color: $text-primary-background-darken-1;
|
color: $text;
|
||||||
border-right: wide $background;
|
border-right: wide $background;
|
||||||
content-align: center middle;
|
content-align: center middle;
|
||||||
}
|
}
|
||||||
@@ -59,35 +74,29 @@ DataTable {
|
|||||||
#sidebar .user {
|
#sidebar .user {
|
||||||
height: 8;
|
height: 8;
|
||||||
background: $panel-darken-1;
|
background: $panel-darken-1;
|
||||||
color: $text-panel-darken-1;
|
color: $text;
|
||||||
border-right: wide $background;
|
border-right: wide $background;
|
||||||
content-align: center middle;
|
content-align: center middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar .content {
|
#sidebar .content {
|
||||||
background: $panel-darken-2;
|
background: $panel-darken-2;
|
||||||
color: $text-surface;
|
color: $text;
|
||||||
border-right: wide $background;
|
border-right: wide $background;
|
||||||
content-align: center middle;
|
content-align: center middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
#header {
|
|
||||||
color: $text-secondary-background;
|
|
||||||
background: $secondary-background;
|
|
||||||
height: 1;
|
|
||||||
content-align: center middle;
|
|
||||||
|
|
||||||
dock: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Tweet {
|
Tweet {
|
||||||
height:12;
|
height:12;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin: 0 2;
|
||||||
|
|
||||||
|
margin:0 2;
|
||||||
background: $panel;
|
background: $panel;
|
||||||
color: $text-panel;
|
color: $text;
|
||||||
layout: vertical;
|
layout: vertical;
|
||||||
/* border: outer $primary; */
|
/* border: outer $primary; */
|
||||||
padding: 1;
|
padding: 1;
|
||||||
@@ -96,14 +105,15 @@ Tweet {
|
|||||||
/* scrollbar-gutter: stable; */
|
/* scrollbar-gutter: stable; */
|
||||||
align-horizontal: center;
|
align-horizontal: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.scrollable {
|
.scrollable {
|
||||||
|
overflow-x: auto;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
margin: 1 2;
|
margin: 1 2;
|
||||||
height: 20;
|
height: 24;
|
||||||
align-horizontal: center;
|
align-horizontal: center;
|
||||||
layout: vertical;
|
layout: vertical;
|
||||||
}
|
}
|
||||||
@@ -117,13 +127,13 @@ Tweet {
|
|||||||
TweetHeader {
|
TweetHeader {
|
||||||
height:1;
|
height:1;
|
||||||
background: $accent;
|
background: $accent;
|
||||||
color: $text-accent
|
color: $text;
|
||||||
}
|
}
|
||||||
|
|
||||||
TweetBody {
|
TweetBody {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: $panel;
|
background: $panel;
|
||||||
color: $text-panel;
|
color: $text;
|
||||||
height: auto;
|
height: auto;
|
||||||
padding: 0 1 0 0;
|
padding: 0 1 0 0;
|
||||||
}
|
}
|
||||||
@@ -134,7 +144,7 @@ Tweet.scroll-horizontal TweetBody {
|
|||||||
|
|
||||||
.button {
|
.button {
|
||||||
background: $accent;
|
background: $accent;
|
||||||
color: $text-accent;
|
color: $text;
|
||||||
width:20;
|
width:20;
|
||||||
height: 3;
|
height: 3;
|
||||||
/* border-top: hidden $accent-darken-3; */
|
/* border-top: hidden $accent-darken-3; */
|
||||||
@@ -150,7 +160,7 @@ Tweet.scroll-horizontal TweetBody {
|
|||||||
|
|
||||||
.button:hover {
|
.button:hover {
|
||||||
background: $accent-lighten-1;
|
background: $accent-lighten-1;
|
||||||
color: $text-accent-lighten-1;
|
color: $text;
|
||||||
width: 20;
|
width: 20;
|
||||||
height: 3;
|
height: 3;
|
||||||
border: tall $accent-darken-1;
|
border: tall $accent-darken-1;
|
||||||
@@ -162,7 +172,7 @@ Tweet.scroll-horizontal TweetBody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#footer {
|
#footer {
|
||||||
color: $text-accent;
|
color: $text;
|
||||||
background: $accent;
|
background: $accent;
|
||||||
height: 1;
|
height: 1;
|
||||||
|
|
||||||
@@ -185,7 +195,7 @@ OptionItem {
|
|||||||
|
|
||||||
OptionItem:hover {
|
OptionItem:hover {
|
||||||
height: 3;
|
height: 3;
|
||||||
color: $text-primary;
|
color: $text;
|
||||||
background: $primary-darken-1;
|
background: $primary-darken-1;
|
||||||
/* border-top: hkey $accent2-darken-3;
|
/* border-top: hkey $accent2-darken-3;
|
||||||
border-bottom: hkey $accent2-darken-3; */
|
border-bottom: hkey $accent2-darken-3; */
|
||||||
@@ -197,7 +207,7 @@ Error {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height:3;
|
height:3;
|
||||||
background: $error;
|
background: $error;
|
||||||
color: $text-error;
|
color: $text;
|
||||||
border-top: tall $error-darken-2;
|
border-top: tall $error-darken-2;
|
||||||
border-bottom: tall $error-darken-2;
|
border-bottom: tall $error-darken-2;
|
||||||
|
|
||||||
@@ -210,7 +220,7 @@ Warning {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height:3;
|
height:3;
|
||||||
background: $warning;
|
background: $warning;
|
||||||
color: $text-warning-fade-1;
|
color: $text;
|
||||||
border-top: tall $warning-darken-2;
|
border-top: tall $warning-darken-2;
|
||||||
border-bottom: tall $warning-darken-2;
|
border-bottom: tall $warning-darken-2;
|
||||||
|
|
||||||
@@ -224,7 +234,7 @@ Success {
|
|||||||
height:auto;
|
height:auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: $success;
|
background: $success;
|
||||||
color: $text-success-fade-1;
|
color: $text;
|
||||||
|
|
||||||
border-top: hkey $success-darken-2;
|
border-top: hkey $success-darken-2;
|
||||||
border-bottom: hkey $success-darken-2;
|
border-bottom: hkey $success-darken-2;
|
||||||
|
|||||||
@@ -6,42 +6,55 @@ from rich.text import Text
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.reactive import Reactive
|
from textual.reactive import Reactive
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import Static, DataTable
|
from textual.widgets import Static, DataTable, DirectoryTree, Header, Footer
|
||||||
|
from textual.layout import Container
|
||||||
|
|
||||||
CODE = '''
|
CODE = '''
|
||||||
class Offset(NamedTuple):
|
from __future__ import annotations
|
||||||
"""A point defined by x and y coordinates."""
|
|
||||||
|
|
||||||
x: int = 0
|
from typing import Iterable, TypeVar
|
||||||
y: int = 0
|
|
||||||
|
|
||||||
@property
|
T = TypeVar("T")
|
||||||
def is_origin(self) -> bool:
|
|
||||||
"""Check if the point is at the origin (0, 0)"""
|
|
||||||
return self == (0, 0)
|
|
||||||
|
|
||||||
def __bool__(self) -> bool:
|
|
||||||
return self != (0, 0)
|
|
||||||
|
|
||||||
def __add__(self, other: object) -> Offset:
|
def loop_first(values: Iterable[T]) -> Iterable[tuple[bool, T]]:
|
||||||
if isinstance(other, tuple):
|
"""Iterate and generate a tuple with a flag for first value."""
|
||||||
_x, _y = self
|
iter_values = iter(values)
|
||||||
x, y = other
|
try:
|
||||||
return Offset(_x + x, _y + y)
|
value = next(iter_values)
|
||||||
return NotImplemented
|
except StopIteration:
|
||||||
|
return
|
||||||
|
yield True, value
|
||||||
|
for value in iter_values:
|
||||||
|
yield False, value
|
||||||
|
|
||||||
def __sub__(self, other: object) -> Offset:
|
|
||||||
if isinstance(other, tuple):
|
|
||||||
_x, _y = self
|
|
||||||
x, y = other
|
|
||||||
return Offset(_x - x, _y - y)
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
def __mul__(self, other: object) -> Offset:
|
def loop_last(values: Iterable[T]) -> Iterable[tuple[bool, T]]:
|
||||||
if isinstance(other, (float, int)):
|
"""Iterate and generate a tuple with a flag for last value."""
|
||||||
x, y = self
|
iter_values = iter(values)
|
||||||
return Offset(int(x * other), int(y * other))
|
try:
|
||||||
return NotImplemented
|
previous_value = next(iter_values)
|
||||||
|
except StopIteration:
|
||||||
|
return
|
||||||
|
for value in iter_values:
|
||||||
|
yield False, previous_value
|
||||||
|
previous_value = value
|
||||||
|
yield True, previous_value
|
||||||
|
|
||||||
|
|
||||||
|
def loop_first_last(values: Iterable[T]) -> Iterable[tuple[bool, bool, T]]:
|
||||||
|
"""Iterate and generate a tuple with a flag for first and last value."""
|
||||||
|
iter_values = iter(values)
|
||||||
|
try:
|
||||||
|
previous_value = next(iter_values)
|
||||||
|
except StopIteration:
|
||||||
|
return
|
||||||
|
first = True
|
||||||
|
for value in iter_values:
|
||||||
|
yield first, False, previous_value
|
||||||
|
first = False
|
||||||
|
previous_value = value
|
||||||
|
yield first, True, previous_value
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@@ -96,25 +109,28 @@ class BasicApp(App, css_path="basic.css"):
|
|||||||
|
|
||||||
def on_load(self):
|
def on_load(self):
|
||||||
"""Bind keys here."""
|
"""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")
|
||||||
|
self.bind("q", "quit", description="Quit")
|
||||||
|
self.bind("f", "query_test", description="Query test")
|
||||||
|
|
||||||
|
def compose(self):
|
||||||
|
yield Header()
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
|
||||||
table = DataTable()
|
table = DataTable()
|
||||||
self.scroll_to_target = Tweet(TweetBody())
|
self.scroll_to_target = Tweet(TweetBody())
|
||||||
|
|
||||||
yield Static(
|
yield Container(
|
||||||
Text.from_markup(
|
|
||||||
"[b]This is a [u]Textual[/u] app, running in the terminal"
|
|
||||||
),
|
|
||||||
id="header",
|
|
||||||
)
|
|
||||||
yield from (
|
|
||||||
Tweet(TweetBody()),
|
Tweet(TweetBody()),
|
||||||
Widget(
|
Widget(
|
||||||
Static(Syntax(CODE, "python"), classes="code"),
|
Static(
|
||||||
|
Syntax(CODE, "python", line_numbers=True, indent_guides=True),
|
||||||
|
classes="code",
|
||||||
|
),
|
||||||
classes="scrollable",
|
classes="scrollable",
|
||||||
),
|
),
|
||||||
table,
|
table,
|
||||||
|
Widget(DirectoryTree("~/"), id="tree-container"),
|
||||||
Error(),
|
Error(),
|
||||||
Tweet(TweetBody(), classes="scrollbar-size-custom"),
|
Tweet(TweetBody(), classes="scrollbar-size-custom"),
|
||||||
Warning(),
|
Warning(),
|
||||||
@@ -126,7 +142,6 @@ class BasicApp(App, css_path="basic.css"):
|
|||||||
Tweet(TweetBody(), classes="scroll-horizontal"),
|
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||||
Tweet(TweetBody(), classes="scroll-horizontal"),
|
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||||
)
|
)
|
||||||
yield Widget(id="footer")
|
|
||||||
yield Widget(
|
yield Widget(
|
||||||
Widget(classes="title"),
|
Widget(classes="title"),
|
||||||
Widget(classes="user"),
|
Widget(classes="user"),
|
||||||
@@ -136,6 +151,7 @@ class BasicApp(App, css_path="basic.css"):
|
|||||||
Widget(classes="content"),
|
Widget(classes="content"),
|
||||||
id="sidebar",
|
id="sidebar",
|
||||||
)
|
)
|
||||||
|
yield Footer()
|
||||||
|
|
||||||
table.add_column("Foo", width=20)
|
table.add_column("Foo", width=20)
|
||||||
table.add_column("Bar", width=20)
|
table.add_column("Bar", width=20)
|
||||||
@@ -147,12 +163,32 @@ class BasicApp(App, css_path="basic.css"):
|
|||||||
for n in range(100):
|
for n in range(100):
|
||||||
table.add_row(*[f"Cell ([b]{n}[/b], {col})" for col in range(6)])
|
table.add_row(*[f"Cell ([b]{n}[/b], {col})" for col in range(6)])
|
||||||
|
|
||||||
|
def on_mount(self):
|
||||||
|
self.sub_title = "Widget demo"
|
||||||
|
|
||||||
async def on_key(self, event) -> None:
|
async def on_key(self, event) -> None:
|
||||||
await self.dispatch_key(event)
|
await self.dispatch_key(event)
|
||||||
|
|
||||||
def key_d(self):
|
def action_toggle_dark(self):
|
||||||
self.dark = not self.dark
|
self.dark = not self.dark
|
||||||
|
|
||||||
|
def action_query_test(self):
|
||||||
|
query = self.query("Tweet")
|
||||||
|
self.log(query)
|
||||||
|
self.log(query.nodes)
|
||||||
|
self.log(query)
|
||||||
|
self.log(query.nodes)
|
||||||
|
|
||||||
|
query.set_styles("outline: outer red;")
|
||||||
|
|
||||||
|
query = query.exclude(".scroll-horizontal")
|
||||||
|
self.log(query)
|
||||||
|
self.log(query.nodes)
|
||||||
|
|
||||||
|
# query = query.filter(".rubbish")
|
||||||
|
# self.log(query)
|
||||||
|
# self.log(query.first())
|
||||||
|
|
||||||
async def key_q(self):
|
async def key_q(self):
|
||||||
await self.shutdown()
|
await self.shutdown()
|
||||||
|
|
||||||
@@ -177,7 +213,7 @@ class BasicApp(App, css_path="basic.css"):
|
|||||||
app = BasicApp()
|
app = BasicApp()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(quit_after=2)
|
app.run()
|
||||||
|
|
||||||
# from textual.geometry import Region
|
# from textual.geometry import Region
|
||||||
# from textual.color import Color
|
# from textual.color import Color
|
||||||
|
|||||||
Reference in New Issue
Block a user